You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fourth slice. Upgrades injectDSD to project authored children into
<slot> positions during server-side rendering for light-DOM
WebComponents, with full parity to the client-side projection rules
and the shadow-DOM <slot> spec.
When a light-DOM component's render() output contains <slot> tags,
injectDSD now:
1. Finds the source HTML's matching closing tag for the custom
element by walking forward with depth tracking for nested
same-tag elements.
2. Extracts the authored inner HTML between the element's opening
and closing tags.
3. Partitions the authored HTML by each top-level child's slot=""
attribute. Text nodes, comment nodes, and elements without
slot="" all route to the default-slot bucket.
4. Walks the rendered template's <slot> tags in document order and
substitutes each with a framework-marked
<slot data-webjs-light data-projection="actual"|"fallback">
element carrying either the projected children or the slot's
authored fallback content. Multiple slots with the same name
follow the first-wins rule per spec.
5. Emits one edit spanning the entire opening-to-closing range of
the source element. Inner custom elements among authored
children are processed via the recursive injectDSD call on the
substituted output, not by the outer loop (a new sort + overlap
filter drops the duplicate inner edits that were enumerated
against the original html before substitution).
When a component's render() output has NO <slot> tags, the old SSR
shape is preserved unchanged: edit at the opening tag only, leave
authored children adjacent to the rendered template, closing tag
untouched. This keeps existing components that never used slots
behaving exactly as before.
Shadow DOM components are completely unaffected. Their native <slot>
elements live inside the DSD <template shadowrootmode="open"> block
and the browser handles projection from the host's light-DOM children
into the shadow tree natively. No framework substitution there.
New helpers in render-server.js:
isVoidElement(tag) tag is a void element (br, img...).
findClosingTagInString(html, ...) depth-tracked matching close tag.
extractSlotAttr(attrsRaw) pulls slot="..." value or null.
partitionAuthoredBySlot(html) groups authored inner HTML by slot.
appendStringToMap(map, k, v) concatenating map insert helper.
substituteSlotsInRender(...) walks <slot> tags, emits framework
marker variants with projection or
fallback content, first-wins per
name across the document.
End-to-end smoke (server-only):
<my-card>
<h2 slot="header">Title</h2>
<p>Body</p>
<span slot="footer">Foot</span>
</my-card>
renders to
<my-card><!--webjs-hydrate-->
<div class="card">
<header><slot data-webjs-light data-projection="actual" name="header">
<h2 slot="header">Title</h2>
</slot></header>
<main><slot data-webjs-light data-projection="actual">
<p>Body</p>
</slot></main>
<footer><slot data-webjs-light data-projection="actual" name="footer">
<span slot="footer">Foot</span>
</slot></footer>
</div>
</my-card>
Fallback content surfaces correctly when no children match; first-wins
holds across duplicate same-named slots; shadow DOM passthrough is
unchanged. 127 existing unit tests across component, render-client,
render-server, directives, registry, css, html, context, task,
suspense, repeat, testing, blog-smoke, json-negotiation, and
light-dom-ssr all pass.
What remains: the 62-case test suite (Task #14) and docs +
convention rule (Task #15).
0 commit comments