Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions V5_PLANNING.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,25 @@ Modernize Stencil after 10 years: shed tech debt, embrace modern tooling, simpli

## Tasks

### 🎰 Light DOM Slot System Modernization
**Status:** In Progress

Replace Stencil's ~15-year-old proprietary light DOM slot polyfill with a much simpler architecture using real `<slot>` elements as containers.

**Key changes:**
- Slot references: empty text nodes → real `<slot>` elements (UA stylesheet gives them `display:contents` globally)
- Content model: siblings after a text-node marker → children *inside* `<slot>` elements
- Fallback visibility: JS traversal (`updateFallbackSlotVisibility`) → pure CSS: `slot:not(:empty)+slot-fb{display:none}`
- `assignedNodes()` / `assignedElements()`: sibling traversal → `slot.childNodes` / `slot.children`
- DOM patches (`dom-extras.ts`): complex sibling-walking → `slot.appendChild` / `slot.prepend`
- SSR: comment marker soup → real `<slot>` elements with content already inside them

**Removes entirely:** `updateFallbackSlotVisibility`, `getSlotChildSiblings`, `checkSlotFallbackVisibility` flag, `isSlotFallback`/`isSlotReference` VNODE_FLAGS, `s-nt-` comment-node hack for unmatched text nodes, debug slot reference comment nodes

**What stays:** `s-ol` original-location markers (re-render put-back), DOM method patching on host (still needed for transparency, but much simpler implementations), content relocation (still physically moves nodes, now *into* `<slot>` instead of adjacent)

**Normalization:** JSX `<slot>fallback</slot>` is normalized at render time into two sibling vnodes: `<slot/>` + `<slot-fb>fallback</slot-fb>`, so the vdom and DOM always match 1:1.

### 🌍 `ssr-wasm` Output Target (Planned)

New output target that compiles the SSR script to a standalone `.wasm` binary, callable from any language with a WASM runtime (PHP via `ext-wasm`, Java via `wasmtime-java`, Ruby via `wasmtime-rb`, Go, Rust, etc.).
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/runtime/_test_/dom-extras.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ describe('dom-extras - patches for non-shadow dom methods and accessors', () =>
expect((specPage.root.childNodes[0].parentNode as HTMLElement).tagName).toBe('CMP-A');
expect((specPage.root.childNodes[1].parentNode as HTMLElement).tagName).toBe('CMP-A');
// @ts-ignore
expect((specPage.root.children[0].__parentNode as HTMLElement).tagName).toBe('DIV');
expect((specPage.root.children[0].__parentNode as HTMLElement).tagName).toBe('SLOT');
// @ts-ignore
expect((specPage.root.childNodes[0].__parentNode as HTMLElement).tagName).toBe('DIV');
expect((specPage.root.childNodes[0].__parentNode as HTMLElement).tagName).toBe('SLOT');
});
});
100 changes: 57 additions & 43 deletions packages/core/src/runtime/_test_/hydrate-no-encapsulation.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,10 @@ describe('hydrate no encapsulation', () => {
<cmp-b class="hydrated" c-id="1.0.0.0" s-id="2">
<!--r.2-->
<!--o.1.1-->
<!--s.2.0.0.0.-->
<!--t.1.1.1.0-->
light-dom
<slot c-id="2.0.0.0">
<!--t.1.1.1.0-->
light-dom
</slot>
<footer c-id="2.1.0.1"></footer>
</cmp-b>
</cmp-a>
Expand All @@ -214,8 +215,9 @@ describe('hydrate no encapsulation', () => {
<!--r.1-->
<cmp-b class="hydrated">
<!--r.2-->
<!--s.2.0.0.0.-->
light-dom
<slot>
light-dom
</slot>
<footer></footer>
</cmp-b>
</cmp-a>
Expand Down Expand Up @@ -255,9 +257,10 @@ describe('hydrate no encapsulation', () => {
<cmp-b class="hydrated" c-id="1.0.0.0" s-id="2">
<!--r.2-->
<!--o.1.1-->
<!--s.2.0.0.0.-->
<!--t.1.1.1.0-->
light-dom
<slot c-id="2.0.0.0">
<!--t.1.1.1.0-->
light-dom
</slot>
<footer c-id="2.1.0.1"></footer>
</cmp-b>
</cmp-a>
Expand All @@ -274,8 +277,9 @@ describe('hydrate no encapsulation', () => {
<!--r.1-->
<cmp-b class="hydrated">
<!--r.2-->
<!--s.2.0.0.0.-->
light-dom
<slot>
light-dom
</slot>
<footer></footer>
</cmp-b>
</cmp-a>
Expand Down Expand Up @@ -317,9 +321,10 @@ describe('hydrate no encapsulation', () => {
<!--r.2-->
<!--o.1.1-->
<header c-id="2.0.0.0"></header>
<!--s.2.1.0.1.-->
<!--t.1.1.1.0-->
light-dom
<slot c-id="2.1.0.1">
<!--t.1.1.1.0-->
light-dom
</slot>
</cmp-b>
</cmp-a>
`);
Expand All @@ -336,8 +341,9 @@ describe('hydrate no encapsulation', () => {
<cmp-b class="hydrated">
<!--r.2-->
<header></header>
<!--s.2.1.0.1.-->
light-dom
<slot>
light-dom
</slot>
</cmp-b>
</cmp-a>
`);
Expand Down Expand Up @@ -378,9 +384,10 @@ describe('hydrate no encapsulation', () => {
<!--r.2-->
<!--o.1.1-->
<header c-id="2.0.0.0"></header>
<!--s.2.1.0.1.-->
<!--t.1.1.1.0-->
light-dom
<slot c-id="2.1.0.1">
<!--t.1.1.1.0-->
light-dom
</slot>
<footer c-id="2.2.0.2"></footer>
</cmp-b>
</cmp-a>
Expand All @@ -398,8 +405,9 @@ describe('hydrate no encapsulation', () => {
<cmp-b class="hydrated">
<!--r.2-->
<header></header>
<!--s.2.1.0.1.-->
light-dom
<slot>
light-dom
</slot>
<footer></footer>
</cmp-b>
</cmp-a>
Expand Down Expand Up @@ -449,19 +457,22 @@ describe('hydrate no encapsulation', () => {
<!--o.1.3-->
<!--o.1.5-->
<header c-id="2.0.0.0"></header>
<!--s.2.1.0.1.top-->
<div slot="top" c-id="1.3.1.1">
<!--t.1.4.2.0-->
top light-dom
</div>
<!--s.2.2.0.2.-->
<!--t.1.5.1.2-->
middle light-dom
<!--s.2.3.0.3.bottom-->
<div slot="bottom" c-id="1.1.1.0">
<!--t.1.2.2.0-->
bottom light-dom
</div>
<slot name="top" c-id="2.1.0.1">
<div slot="top" c-id="1.3.1.1">
<!--t.1.4.2.0-->
top light-dom
</div>
</slot>
<slot c-id="2.2.0.2">
<!--t.1.5.1.2-->
middle light-dom
</slot>
<slot name="bottom" c-id="2.3.0.3">
<div slot="bottom" c-id="1.1.1.0">
<!--t.1.2.2.0-->
bottom light-dom
</div>
</slot>
<footer c-id="2.4.0.4"></footer>
</cmp-b>
</cmp-a>
Expand All @@ -479,16 +490,19 @@ describe('hydrate no encapsulation', () => {
<cmp-b class="hydrated">
<!--r.2-->
<header></header>
<!--s.2.1.0.1.top-->
<div slot="top">
top light-dom
</div>
<!--s.2.2.0.2.-->
middle light-dom
<!--s.2.3.0.3.bottom-->
<div slot="bottom">
bottom light-dom
</div>
<slot name="top">
<div slot="top">
top light-dom
</div>
</slot>
<slot>
middle light-dom
</slot>
<slot name="bottom">
<div slot="bottom">
bottom light-dom
</div>
</slot>
<footer></footer>
</cmp-b>
</cmp-a>
Expand Down
31 changes: 17 additions & 14 deletions packages/core/src/runtime/_test_/hydrate-scoped.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ describe('hydrate scoped', () => {
<!--r.1-->
<!--o.0.1.-->
<article c-id="1.0.0.0">
<!--s.1.1.1.0.-->
<!--t.0.1-->
88mph
<slot c-id="1.1.1.0">
<!--t.0.1-->
88mph
</slot>
</article>
</cmp-a>
`);
Expand Down Expand Up @@ -78,9 +79,10 @@ describe('hydrate scoped', () => {
<!--r.1-->
<!--o.0.1.c-->
<article c-id="1.0.0.0">
<!--s.1.1.1.0.-->
<!--t.0.1-->
88mph
<slot c-id="1.1.1.0">
<!--t.0.1-->
88mph
</slot>
</article>
</cmp-a>
`);
Expand All @@ -98,9 +100,10 @@ describe('hydrate scoped', () => {
expect(clientHydrated.root).toEqualHtml(`
<cmp-a class="hydrated sc-cmp-a-h">
<!--r.1-->
<article class="sc-cmp-a-s sc-cmp-a">
<!--s.1.1.1.0.-->
88mph
<article class="sc-cmp-a">
<slot class="sc-cmp-a-s sc-cmp-a">
88mph
</slot>
</article>
</cmp-a>
`);
Expand Down Expand Up @@ -177,9 +180,9 @@ describe('hydrate scoped', () => {
expect(serverHydrated.root).toEqualHtml(`
<cmp-a class="hydrated" s-id="1">
<!--r.1-->
<div class="wrapper" c-id="1.0.0.0">
<div class="wrapper" c-id="1.0.0.0">
<p class="hi" c-id="1.1.1.0">
<!--s.1.2.2.0.-->
<slot c-id="1.2.2.0"></slot>
</p>
</div>
</cmp-a>`);
Expand All @@ -189,14 +192,14 @@ describe('hydrate scoped', () => {
html: serverHydrated.root.outerHTML,
hydrateClientSide: true,
});
expect(clientHydrated.root.querySelector('p').className).toBe('hi sc-cmp-a-s sc-cmp-a');
expect(clientHydrated.root.querySelector('p').className).toBe('hi sc-cmp-a');

expect(clientHydrated.root).toEqualHtml(`
<cmp-a class="hydrated sc-cmp-a-h">
<!--r.1-->
<div class="wrapper sc-cmp-a">
<p class="hi sc-cmp-a-s sc-cmp-a">
<!--s.1.2.2.0.-->
<p class="hi sc-cmp-a">
<slot class="sc-cmp-a-s sc-cmp-a"></slot>
</p>
</div>
</cmp-a>`);
Expand Down
Loading