- Issue created by @jessebaker
Client side rendering of our JS components (based on Astro) involves the rendering of <astro-island>
and <astro-slot>
tags around their markup so that Astro knows what to hydrate.
These additional tags unfortunately cause a wide variety of issues with certain CSS selectors.
At some point non-interactive* JS components will be rendered server side and because they have no need to be hydrated, they won't have the wrapping <astro-*>
tags. Before that though, we can unwrap their content HTML with JS to ensure CSS works as users expect.
* interactive components will still need the astro tags to work correctly and will need a different solution.
@effulgentsia came up with the following solution which I agree is a good way to address the issue.
"Adding the following (code snippet) just before astro-hydration/dist/hydration.js seems to successfully remove the and wrappers. This currently does it for all islands, it doesn't yet limit itself to only non-reactive components. "
(() => {
// Remove an element (but not its children) from the DOM.
function _unwrap(element) {
// Drop the kids off with their grandparent.
while (element.firstChild) {
element.parentNode.insertBefore(element.firstChild, element);
}
// And leave.
element.remove();
}
// Decorate the implementation of the <astro-island> custom element with
// the behavior of unwrapping itself and its <astro-slot> elements after
// hydration is complete.
function withUnwrapping(AstroIsland) {
return class extends AstroIsland {
constructor() {
super();
this._hydrate = this.hydrate;
this._hydrated = false;
this.addEventListener('astro:hydrate', () => {this._hydrated = true});
this.hydrate = async () => {
await(this._hydrate());
// We check the flag to make sure hydration is really done, rather
// than having returned early to be retried following some other
// condition.
if (this._hydrated) {
// We unwrap here, rather than within the 'astro:hydrate' event
// listener, to ensure that all other 'astro:hydrate' event
// listeners have executed before unwrapping.
this.unwrap();
}
}
}
unwrap() {
const slots = this.getElementsByTagName('astro-slot');
for (const slot of slots) {
// Only unwrap our own slots, not ones of descendant islands.
if (slot.closest('astro-island') === this) {
_unwrap(slot);
}
}
_unwrap(this);
}
}
}
// Once a custom element constructor is registered, it can't be changed.
// Therefore, we have to intercept customElements.define() to change the
// constructor prior to it getting registered.
const proxy = new Proxy(customElements.define, {
apply(target, thisArg, args) {
let [tagName, constructor, ...rest] = args;
if (tagName === 'astro-island') {
constructor = withUnwrapping(constructor);
}
target.call(thisArg, tagName, constructor, ...rest);
}
})
customElements.define = proxy;
})();
In response to the above @balintbrews suggested
"I wonder if we should make use of the 'use client' directive, so code component authors can explicitly control when unwrapping shouldn’t happen. At this point, everything is client-side, so 'use client' isn’t very semantic, but this will make sense in the context of SSR, and it will be required for React Server Components anyway. Also, that’s how LLMs will generate code (when the prompt mentions SSR)."
Which I think should also be included in the solution.
Active
0.0
Page builder