Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support provide/inject between Vue's Custom Elements (defineCustomElement) and Vue Components (defineComponent) #4476

Open
gnuletik opened this issue Aug 30, 2021 · 8 comments

Comments

@gnuletik
Copy link

gnuletik commented Aug 30, 2021

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 : webcomponents-cg/community-protocols#2 so most questions are covered.

There may be other possibilities I did not think about.

Thanks for your work !

@yyx990803
Copy link
Member

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?

@gnuletik
Copy link
Author

gnuletik commented Sep 17, 2021

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!

@u12206050
Copy link

Still doesn't work in Vue 3 it seems?

@LinusBorg
Copy link
Member

LinusBorg commented Nov 1, 2022

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?

@gnuletik
Copy link
Author

gnuletik commented Nov 2, 2022

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.

@dfreier
Copy link

dfreier commented Nov 9, 2022

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.

@yyx990803 yyx990803 added the ✨ feature request New feature or request label Nov 11, 2022
@LinusBorg
Copy link
Member

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

@dfreier
Copy link

dfreier commented Nov 14, 2022

That seems to work fine: Playground

You're right! Sorry for the wrong accusations. 💐

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants