Support provide/inject between Vue's Custom Elements (defineCustomElement) and Vue Components (defineComponent)
What problem does this feature solve?
As stated in the docs (https://v3.vuejs.org/guide/web-components.html#provide-inject), the Provide/Inject API only works between Vue's Custom Elements.
It makes it harder to use injected libraries inside custom elements.
The use case is :
- A classic MVC application (Django/Rails-based) which serves data
- Multiple (async) web components where user interaction is required
- Shared libraries (i18n, toasts, etc..) for Vue Components (defineCustomElement and defineComponent)
- Shared internal components, not exposed as web components
What does the proposed API look like?
I've thought of several approaches to implement this.
1. Deep provide function
The provide function (https://github.com/vuejs/vue-next/blob/db1dc1c63097ed62a3f683a7a11c7e819d90bb73/packages/runtime-core/src/apiInject.ts#L58-L60) could search inside parent elements :
let inst = getCurrentInstance().parent
while (inst !== null) {
if (key in inst.provides) {
return inst.provides[key]
}
inst = inst.parent
}
However, this can be a performance issue when heavily using provide/inject.
It could be enabled when needed as a function parameter like this :
inject('some-instance', { deep: true })
but many libraries use provide
inside useLibrary()
-like functions. So, library developers would have to add the deep
parameter too.
To workaround this, this behavior may be manually enabled with a global configuration when required :
Vue.deepInject = true
2. Implement an event based provide/inject API
The provide/inject API could be reworked with CustomEvents.
There's a current discussion about implement it as a standard : https://github.com/webcomponents/community-protocols/issues/2 so most questions are covered.
There may be other possibilities I did not think about.
Thanks for your work !
To clarify, what is the case that is currently not working? Can you provide a code sample demonstrating a simplified case of what you intend to do?
Thanks for looking into this! Here are multiple use cases I encountered :
Use case: async component
AppContext.vue
<template>
<slot />
</template>
<script setup>
// it could be a library like vue-i18n
provide('something', 'test')
</script>
MyComponent.vue
<script setup>
// inject fails. it should not.
const val = inject('something')
</script>
main.ts
customElements.define(
'my-app-context',
defineCustomElement(AppContext)
)
customElements.define(
'my-component',
defineCustomElement(defineAsyncComponent(() => import('MyComponent.vue')))
)
index.html
<html>
<body>
<my-app-context>
<my-component></my-component>
</my-app-context>
<script src="main.ts"></script>
</body>
</html>
Use case: shared components
AppContext.vue
<template>
<slot />
</template>
<script setup>
// it could be a library like vue-i18n
provide('something', 'test')
</script>
UtilComponent.vue
<script setup>
// inject fails. it should not.
const val = inject('something')
</script>
MyComponent.vue
<template>
<UtilComponent />
</template>
main.ts
customElements.define(
'my-app-context',
defineCustomElement(AppContext)
)
customElements.define(
'my-component',
defineCustomElement(defineAsyncComponent(() => import('MyComponent.vue')))
)
index.html
<html>
<body>
<my-app-context>
<my-component></my-component>
</my-app-context>
<script src="main.ts"></script>
</body>
</html>
I made a reproduction of both usecases here : https://github.com/gnuletik/vue-inject-deep
Thanks!
Yes. This has no real prioity for us - the real-life usecase is still cloudy.
Personal Take: Your web components should not depend on a Vue-specific mechanism like provide/inject. Why turn your Vue Components into Web Components if you can only use them in other Vue apps because they depend on provide/inject?
Why turn your Vue Components into Web Components if you can only use them in other Vue apps because they depend on provide/inject?
My usecase was to integrate Vue into a MVC framework like Django, which already provides its own template system.
The web components allows you to load vue components and inject variables with <my-custom-com var="{{ django_var }}"></my-custom-comp>
.
Your web components should not depend on a Vue-specific mechanism like provide/inject
Using an external mechanism (implementing Vue provide/inject into the app) would work if you don't want to use the existing Vue ecosystem because you can't call the useXX()
function of libraries, which usually calls inject
.
I think the use cases described by @gnuletik are very realistic.
EDIT, that actually works ⬇️
The offical vue documentation is a bit confusing in my opinon:
Vue-defined custom element won't be able to inject properties provided by a non-custom-element Vue component.
This means the parent provider component is a regular vue component and custom element is a child component which cannot inject the provided value.
But it doesn't mention that it is also not possible to provide a value from a custom element down into a sub component of the custom element which is also a regular vue component but it lives in the same project as the parent custom element.
You can get provide/inject to work if you use all child components of a custom element also as custom elements. That means you never use the components: { MySubcomponent }
option, all sub components are global.
EDIT end ⬆️
Unfortunately there is still no solution to use provide/inject between custom elements if they were lazy loaded.
But it doesn't mention that it is also not possible to provide a value from a custom element down into a sub component of the custom element which is also a regular vue component but it lives in the same project as the parent custom element.
That seems to work fine: Playground
That seems to work fine: Playground
You're right! Sorry for the wrong accusations. 💐