6

I have project with nested custom elements. Now I need to vuex & vue router. How I can use this packages from root custom element and then use in all child custom elements?

Currently I tried only use vuex inside each component like this:

<script>
import store from './store';

export default {
  setup() {
    const state = store.state;

    return { state };
  },
};
</script>

Here is demo project with nested custom elements

Here is my main.js file code:

import { defineCustomElement } from "./defineCustomElementWithStyles";
import App from "./App.ce.vue";

customElements.define("app-root", defineCustomElement(App));

3 Answers 3

24

Vue plugins require the app instance, which is not defined for components created from defineCustomElement(). As a workaround, you can install the plugins on a temporary app instance, and then copy the resulting context over to the actual app, as seen in this utility (which we'll use later):

// defineCustomElementWithStyles.js
import { defineCustomElement as VueDefineCustomElement, h, createApp, getCurrentInstance } from 'vue'

export const defineCustomElement = (component, { plugins = [] } = {}) =>
  VueDefineCustomElement({
    render: () => h(component),
    setup() {
      const app = createApp()

      // install plugins
      plugins.forEach(app.use)

      const inst = getCurrentInstance()
      Object.assign(inst.appContext, app._context)
      Object.assign(inst.provides, app._context.provides)
    },
  })

Use the defineCustomElement() above instead of the one from vue:

// main.js
import { defineCustomElement } from './defineCustomElementWithStyles'
import App from './App.ce.vue'
import store from './store'
import router from './router'

customElements.define(
  'app-root',
  defineCustomElement(App, {
    plugins: [store, router],
  })
)

demo

Sign up to request clarification or add additional context in comments.

7 Comments

Has someone managed to type hint this properly with TS? I'm currently struggling with this.
same here, besides that it works fine. @acme, did you make it work?
How about using Vuetify? Is it possible to use vuetify inside a custom web component?
I've included vuetify as you explained, but the custom theme of vuetify, does not implement on vuetify components!
@amirtbi That's because Vuetify attaches styles directly to the root document instead of using Vue's style support. You can try the workarounds in the linked issue.
|
8

Just a little fix for tony19's solution:

export const defineCustomElementWrapped = (component, { plugins = [] } = {}) =>
  defineCustomElement({
    styles: component.styles, // <- this
    render: () => h(component),
    setup() {
      const app = createApp()

      // install plugins
      plugins.forEach(app.use)

      const inst = getCurrentInstance()
      Object.assign(inst.appContext, app._context)
      Object.assign(inst.provides, app._context.provides)
    },
  })

and then

customElements.define(
  'app-root',
  defineCustomElementWrapped(App, {
    plugins: [store, router],
  })
)

This will also include styles exported by components

(also I don't like shadowing original function names if you are changing their behaviors, so I renamed it)

Might be also required:

    styles: component.styles.map((style) => {
      return style
        .replace(":root", ":host") // <- rename :root values to :host
        .replace("body {", "#my-app {"); // <- in case of frameworks like bootstrap that styles directly body
    }),

1 Comment

I like your answer. To get it work, I had to import the entiry vue im my web component-<styles>. Yet I am struggeling to strip away all unnessary CSS from styles. do you have any idea to accomplish that?
1

Just a small adjustments to the great answers: when you need props, emits or styles, it might be easier to destructure the component instead of wrapping it:

const defineCustomElement = (component, { plugins = [] } = {}) =>
  VueDefineCustomElement({
    ...component, // <---- use all props from the original component (except setup)
    setup(...args) {
      const app = createApp({})
      plugins.forEach(app.use)
      const instance = getCurrentInstance()
      Object.assign(instance.appContext, app._context)
      Object.assign(instance.provides, app._context.provides)

      return component.setup?.(...args) // <---- run initial setup if exists
    },
  })

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.