Search code examples
javascriptvue.jsvue-componentvuetify.js

Pass props after adding Vuetify to a Vue web component


I'm developing a web component in vue. A lot of it has been made using vuetify component library and I'm currently in process of trying to export to to a web component. I've followed the steps described in this thread: How can Add libraries like Vuetify inside of a web component created by Vue 3?, which consist of overriding the standard 'defineCustomElement' method, however after following all the steps Im now unable to pass props to the exported component in the index.html file.

Some code snippets to give you an idea:

  1. Very Simplified version of my component using vuetify

  2. Binding vuetify files to the web-component (as described in the thread above) I've also declared the props separately within the method because otherwise I get an error followed by a whitescreen. However, even after the declaration, they are not passed onto the actual component.

Binding vuetify files to the web-component  

  1. Adding props to the custom component within the index.html file <custom-vuetify-element checkProp = "foo"></custom-vuetify-element>

What could be the reason for this issue? Is there anything within the override defineCustomElement method, that could be changed to make passing a props possible?

Any help would be greatly appreciated! Thanks in advance

Tried various methods. Unfortunately this specific issue isn't really mentioned anywhere else.


Solution

  • Instead of wrapping the passed-in component, you can try to use it directly:

    const defineCustomElement = (component, { plugins = [] } = {}) =>
      VueDefineCustomElement({
        ...component,
        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)
        },
      })
    

    see playground


    When you wrap the component in the overridden defineCustomElement() function, you need to pass on the props and emits, and then wire those to the component instance:

    const defineCustomElement = (component, { plugins = [] } = {}) =>
      VueDefineCustomElement({
        props: component.props, // <---- pass on component props
        emits: component.emits, // <---- pass on component emits
        setup(props, {emit}) {
          const app = createApp({})
          plugins.forEach(app.use)
          const instance = getCurrentInstance()
          Object.assign(instance.appContext, app._context)
          Object.assign(instance.provides, app._context.provides)
    
          const emitProps = turnEventNamesToProps(component.emits, emit) // <--- turn events into props (see below)
    
          return () => h(component, {...props, ...emitProps}) // <--- return render function from setup
        },
      })
    

    To pass the emits to the instance, you have to turn them into props. For example, if your component emits an event named myEvent, you need to pass in a prop onMyEvent with a handler that triggers the emit on the wrapper component.

    Here is an example that turns an array of event names into an object with prop names as keys and handler as values:

    const emitEventToProp = eventName => 'on' + eventName[0].toUpperCase() + eventName.slice(1)
    const turnEventNamesToProps = (eventNames, emitter) => {
      const buildHandler = (eventName) => (...args) => emitter(eventName, ...params)
      const entries = eventNames.map(eventName => [
        emitEventToProp(eventName), 
        buildHandler(eventName)
      ])
      return Object.fromEntries(entries)
    }
    

    Note that there seem to be restrictions to what event names Vue can listen to on web components. While web component can emit an update:modelValue event and will even emit an update:model-value along with it, putting @update:modelValue on the web component in a Vue template does not seem to register a listener.

    Here it is in a playground