sveltejs/svelte

context api not working with custom-elements

pascalvos opened this issue · 5 comments

Describe the bug

i tried to implement https://svelte.dev/docs/svelte#getcontext with custom elements after the svelte 4 release.
i thought it was fixed in #3422 and #8457 but it looks like its still doesnt work.

if i can help in any way let me know, because this currently blocks my implementation

if this cant be fixed it might be an idea to update https://svelte.dev/docs/custom-elements-api#caveats-and-limitations until it is fixed

Reproduction

reproduction of the actual code using svelte 4.0.5 https://stackblitz.com/edit/vitejs-vite-gk4rsr
in uc-button-group.svelte i commented out these lines

they use the svelte component directly and work without any problem :)

i made the reproduction on stackblitz because some issues with the repl i already reported in discord :)

Logs

No response

System Info

System:
    OS: macOS 13.3.1
    CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
    Memory: 1.39 GB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.16.0 - ~/.nvm/versions/node/v18.16.0/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 9.5.1 - ~/.nvm/versions/node/v18.16.0/bin/npm
  Browsers:
    Chrome: 114.0.5735.198
    Chrome Canary: 117.0.5895.0
    Safari: 16.4

Severity

blocking all usage of svelte

Context works for regular Svelte components used within a custom elements now (this is why the mentioned issue was closed), but it does not work across custom elements. There's no reliably way to make this happen, ultimately getContext is a Svelte feature, not a custom element feature. We should clarify this in the documentation.

@dummdidumm well i if im correct parent component gets mounted before the child ones when using slots right so this way there could be a message bus of some kind between them, not sure how vue for example did this is vue3 they also did something with there inject provide i think https://vuejs.org/guide/extras/web-components.html#building-custom-elements-with-vue im also on discord posted this also here https://discord.com/channels/457912077277855764/1130569409388367932 if you want a more active conversation thing is that its then not documented as good

i saw your pr, @dummdidumm looks nice the way you can extend now.
i do think it is a missed chance to allow the use of svelte context or try to make them work somehow
i know there not a part of the webcomponent spec, but svelte is a compiler driven framework and allows for these kind of abstraction to be more easly and some gaps or add some kind of abstraction same goes for #8963.
think there is a real big ask for custom components and being able to write them in a non class based syntax.

I was able to cleanly implement context for Svelte-based custom elements using svelte-retag. I found that while custom elements can be rendered in a seemingly unpredictable order (at least from the perspective of Svelte and svelte-retag), you can still control it. I found that the order of initialization was the core issue with implementing context for custom elements. See demo here (specifically the tabs, which are also nested to demonstrate scope): https://svelte-retag.vercel.app/

@pascalvos if you’d like, please give it a shot and let me know if you encounter any issues. 😊

More Technically: The reason I think this is able to be solved is because svelte-retag doesn’t attempt to render the Svelte component immediately in connectedCallback() (like the current implementation). Instead, it orchestrates rendering across all of its custom elements by deferring the rendering of the Svelte component until the next requestAnimationFrame(), at which point it then able to 1.) process each component in document order (from the top down) and 2.) then immediately prior to rendering of any particular slotted child component, fetches context from the already rendered parent. That way you don’t have child components attempting to access context from parents that hasn’t been fully established yet (this is what causes those undefined errors when using Svelte context in web components).

@dummdidumm do you have more details on your point about reliability? I want to make sure that I take care of any other issues beyond that (rendering/initialization order) I may not have thought of, since I’ll be using it with custom elements pretty heavily myself.

Importantly, this approach isn’t prone to the order upon which the custom elements.define(…) calls were performed. That was the main caveat that resulted in reliability issues for me, anyway. The reason was because the construct()s and connectedCallback()’s were being executed in the order of definition, and if the Svelte component was rendered immediately as a result of connectedCallback() (rather than deferring to be performed in document order), then child/slotted components end up initializing before the parents have had a chance to finish running. 😅

p.s. Just for fun: Another interesting caveat I found was the catch-22 of being able to support lit-style lowercased attributes (i.e. translating element myprop to the correct case-sensitive myProp svelte prop) also got in the way of initializing in the correct order and importantly only once.

Originally, I just took the naive approach of constructing the component and then inspecting its $$.props object, then later on performing translations from lowercase to the correct case. The issue then though is that you cannot initialize it only once.

The simple solution to that however is just to use a Proxy to dynamically return the correct values as props are accessed the first time around and then just cache that for subsequent instances of the component.