Closed effulgentsia closed 3 months ago
Additional info...
This FAILS (meaning, the slots DON'T pass as props and therefore aren't rendered):
<html>
<head><script src="test.js"></script></head>
<body><div id="wrapper">
<text-section>
<span slot="heading">Nice heading</span>
<span slot="content">Great content</span>
</text-section>
</div></body>
</html>
This also FAILS:
<html>
<head><script src="test.js"></script></head>
<body><div id="wrapper">
<script>
document.write(`
<text-section>
<span slot="heading">Nice heading</span>
<span slot="content">Great content</span>
</text-section>
`)
</script>
</div></body>
</html>
However, this PASSES (meaning, the slots DO get passed as props and are rendered):
<html>
<head><script src="test.js"></script></head>
<body>
<div id="wrapper"></div>
<script>
wrapper.innerHTML = `
<text-section>
<span slot="heading">Nice heading</span>
<span slot="content">Great content</span>
</text-section>
`
</script>
</body>
</html>
I've just run into this as well when I took defer
out of the script
tag in my head
section. To be fair, I don't need it at this point since all my custom elements are there when the page loads, but this is surprising behavior. Aside from slots, everything else seemed to work.
Edit: For completeness, I'm using { shadow: false }
when registering.
Edit2: The issue is the same for { shadow: true }
Looks like this is a documented issue with Custom Elements: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements
Note that in this case we must ensure that the script defining our custom element is executed after the DOM has been fully parsed, because connectedCallback() is called as soon as the expanding list is added to the DOM, and at that point its children have not been added yet, so the querySelectorAll() calls will not find any items. One way to ensure this is to add the defer attribute to the line that includes the script.
Looks like if the Custom Element is added to the DOM after the first pass then it works fine. The issue is when an element is already registered, then connectedCallback
is triggered immediately when the Custom Element tag is found, but before the children are parsed. This is why setting innerHTML
on an element that was already parsed works, but using document.write
to append a CE before the first DOM pass happens, fails.
Since we should all be using defer
in our script tags to avoid blocking the DOM anyway, this feels like a non-issue to me. Maybe it's worth documenting though? One suggestion I saw was to put a setTimeout(0)
in connectedCallback
to slightly defer until the element's children were parsed, but I'm not sure if that actually works.
Sorry for getting to this so late, but yeah, it's a bit of a fundamental issue (or feature, depending on how you need to use it I suppose) of custom elements.
If someone wanted to add a note somewhere, we'd probably accept it, but how you use your scripts generally falls outside the scope of the library here.
If I follow the example in https://preactjs.com/guide/v10/web-components/#passing-slots-as-props and bundle the jsx file into an iife (e.g., via
./node_modules/.bin/esbuild test-src.jsx --bundle --jsx-import-source=preact --jsx=automatic --minify --outfile=test.js
), then the example works as documented if my script runs after the element such as:However, the
heading
andcontent
props are undefined if my script runs before the element such as: