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

Web component props are set before constructor call #1735

Closed
pedro00dk opened this issue May 20, 2023 · 4 comments
Closed

Web component props are set before constructor call #1735

pedro00dk opened this issue May 20, 2023 · 4 comments
Labels
bug Something isn't working

Comments

@pedro00dk
Copy link

pedro00dk commented May 20, 2023

Describe the bug

Not sure this is a bug or expected behavior, but I did not find any documentation about it.

When rendering a web component in solid, the web component properties are set before its constructor call.
If the web component defines getter/setter functions with the same name as the props, these are somehow overwritten when the props are set during render and cause the setter not to work.

Your Example Website or App

https://playground.solidjs.com/anonymous/4adb86fa-535a-4ecd-8b10-7fd72b33341d

Steps to Reproduce the Bug or Issue

  1. Go to the playground link
  2. Type into the input field

Note: because customElements.define can't be called twice with same component, if you make changes to the code, you need to click the reload button.

Expected behavior

I expected the webcomponent below the input field to display the input value as it is being typed.
In devtools, I expected to see a 'constructor' log, followed by 'setter' logs when I type in the input.

However, I only see the 'constructor' log, typing in the input does not call the value setter function, hence, it does not generate the 'setter' logs.

Screenshots or Videos

No response

Platform

  • OS: Linux
  • Browser: Chrome
  • Version: 113

Additional context

I tried debugging this issue for a while and had some discoveries about the issue.

The first issue was that when trying to run the output code (by coping it into main.tsx), there is a _$template call that created the elements, when I set the isCE flag in the _$template call to false, the component starts working again.
Now inspecting solid's code a bit, when isCE is set to true, the function uses a cloneNode call to return a copy of the element, when false, it uses document.insertNode.

I latter noticed that the call to document.importNode triggers the webcomponent constructor call, making it happen before the prop setting.
After looking looking at the docs I found out document.importNode works like cloneNode, but it also sets the onwerDocument of the element.
The nodes of a template's content have a ownerDocument that is not the page's document, and that document also has null as its defaultView (the window, where customElements are stored).

In solid's template function, if I change the cloneNode by a importNode call, it starts working again.
But since I cant really do that I also tried overriding Node.prototype.cloneNode function with a call to importNode, and it works too.
However, I can't use that in the environment I'm working on, there are other teams in a microfrontends setup and I can't change properties of prototypes.

This hack is also coded in the playground example, just uncomment it and the webcomponent starts working.

@ryansolid
Copy link
Member

Oh it looks like the isCE condition is just reversed. cloneNode is a slightly faster so I only wanted to use importNode for when it was needed... ie webcomponents. I must have reversed the conditions during refactoring.

@ryansolid ryansolid added the bug Something isn't working label May 21, 2023
@DynamicArray
Copy link

Could this be related?

works just fine, but if I create a custom element like this document.createElement("my-custom-element"), getCurrentElement in onMount retrieves nothing.

@pedro00dk
Copy link
Author

Regarding customElements loading, I'm also using scoped-custom-elements.
In the setup adopted by my company, this is used so that different microfrontends can have different versions of our webcomponents library, otherwise we would get conflicts when the webcomponents library gets updated.
This allows us to attach custom elements to the shadowRoot instead of window.

We currently use react+webcomponents sucessfully because the way react renders elements.
React does not use document.createElement directly, it uses the element passed to the createRoot call and gets its ownerDocument.
So we replace the root element.ownerDocument with the shadow root that contains it, so that createElement, importNode, etc... are all scoped to the shadow root, not the root document.

It would be really nice to do something like that in solid as well.
The only thinkg needed for that would be replace the current document.importNode call in the template function with something like <get root somehow>.onwerDocument.importNode or <rootIsDocumentOrShadow.importNode>.
It seems simple to implement, although I'm not familiar with solid internals, so I'm not sure how easy would it be to get the root element.
Performance wise, it would be is just an extra prop access when isCE is set. The cloneElement path would be unchanged, so probably there is no impact.

@pedro00dk
Copy link
Author

I implemented a prototype for the issue I mentioned in the last message: #1740

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants