Skip to content

Commit 5a7ab24

Browse files
johnjenkinsJohn Jenkinschristian-bromann
authored
fix(ssr): stop stripping comment nodes (#6123)
* fix(ssr): stop stripping comment nodes * Update test/wdio/ssr-hydration/cmp.test.tsx Co-authored-by: Christian Bromann <[email protected]> --------- Co-authored-by: John Jenkins <[email protected]> Co-authored-by: Christian Bromann <[email protected]>
1 parent eb11d25 commit 5a7ab24

File tree

6 files changed

+162
-18
lines changed

6 files changed

+162
-18
lines changed

src/runtime/client-hydrate.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -220,13 +220,6 @@ export const initializeClientHydrate = (
220220
for (rnIdex; rnIdex < rnLen; rnIdex++) {
221221
shadowRoot.appendChild(shadowRootNodes[rnIdex] as any);
222222
}
223-
224-
// Tidy up left-over / unnecessary comments to stop frameworks complaining about DOM mismatches
225-
Array.from(hostElm.childNodes).forEach((node) => {
226-
if (node.nodeType === NODE_TYPE.CommentNode && typeof (node as d.RenderNode)['s-sn'] !== 'string') {
227-
node.parentNode.removeChild(node);
228-
}
229-
});
230223
}
231224

232225
hostRef.$hostElement$ = hostElm;

src/runtime/test/hydrate-shadow-child.spec.tsx

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ describe('hydrate, shadow child', () => {
454454
);
455455
}
456456
}
457-
// @ts-ignore
457+
458458
const serverHydrated = await newSpecPage({
459459
components: [CmpA, CmpB, CmpC],
460460
html: `
@@ -467,6 +467,7 @@ describe('hydrate, shadow child', () => {
467467
`,
468468
hydrateServerSide: true,
469469
});
470+
470471
expect(serverHydrated.root).toEqualHtml(`
471472
<cmp-a class="hydrated" s-id="1">
472473
<!--r.1-->
@@ -522,35 +523,78 @@ describe('hydrate, shadow child', () => {
522523
`);
523524
});
524525

525-
it('test shadow root innerHTML', async () => {
526+
it('preserves all nodes', async () => {
526527
@Component({
527528
tag: 'cmp-a',
528529
shadow: true,
529530
})
530531
class CmpA {
531532
render() {
532-
return <div>Shadow Content</div>;
533+
return <slot>Shadow Content</slot>;
533534
}
534535
}
535536

536-
const page = await newSpecPage({
537+
const serverHydrated = await newSpecPage({
537538
components: [CmpA],
538539
html: `
539540
<cmp-a>
540-
Light Content
541+
A text node
542+
<!-- a comment -->
543+
<div>An element</div>
544+
<!-- another comment -->
545+
Another text node
541546
</cmp-a>
542547
`,
548+
hydrateServerSide: true,
543549
});
544550

545-
expect(page.root).toEqualHtml(`
546-
<cmp-a>
551+
expect(serverHydrated.root).toEqualHtml(`
552+
<cmp-a class=\"hydrated\" s-id=\"1\">
553+
<!--r.1-->
554+
<!--o.0.1.-->
555+
<!--o.0.2.-->
556+
<!--o.0.4.-->
557+
<!--o.0.6.-->
558+
<!--o.0.7.-->
559+
<slot-fb c-id=\"1.0.0.0\" hidden=\"\" s-sn=\"\">
560+
<!--t.1.1.1.0-->
561+
Shadow Content
562+
</slot-fb>
563+
<!--t.0.1-->
564+
A text node
565+
<!--c.0.2-->
566+
<!-- a comment -->
567+
<div c-id=\"0.4\" s-sn=\"\">
568+
An element
569+
</div>
570+
<!--c.0.6-->
571+
<!-- another comment -->
572+
<!--t.0.7-->
573+
Another text node
574+
</cmp-a>
575+
`);
576+
577+
const clientHydrated = await newSpecPage({
578+
components: [CmpA],
579+
html: serverHydrated.root.outerHTML,
580+
hydrateClientSide: true,
581+
});
582+
583+
expect(clientHydrated.root).toEqualHtml(`
584+
<cmp-a class=\"hydrated\">
547585
<mock:shadow-root>
548-
<div>
586+
<slot>
549587
Shadow Content
550-
</div>
588+
</slot>
551589
</mock:shadow-root>
552-
Light Content
553-
</cmp-a>
590+
A text node
591+
<!-- a comment -->
592+
<div>
593+
An element
594+
</div>
595+
<!-- another comment -->
596+
Another text node
597+
</cmp-a>
554598
`);
555599
});
556600
});

src/runtime/test/hydrate-shadow-in-shadow.spec.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ describe('hydrate, shadow in shadow', () => {
6060
<mock:shadow-root>
6161
<slot></slot>
6262
</mock:shadow-root>
63+
<!---->
6364
<slot></slot>
6465
</cmp-b>
6566
</mock:shadow-root>

src/runtime/test/shadow.spec.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,36 @@ describe('shadow', () => {
135135
expect(page.root).toEqualHtml(expected);
136136
expect(page.root).toEqualLightHtml(expected);
137137
});
138+
139+
it('test shadow root innerHTML', async () => {
140+
@Component({
141+
tag: 'cmp-a',
142+
shadow: true,
143+
})
144+
class CmpA {
145+
render() {
146+
return <div>Shadow Content</div>;
147+
}
148+
}
149+
150+
const page = await newSpecPage({
151+
components: [CmpA],
152+
html: `
153+
<cmp-a>
154+
Light Content
155+
</cmp-a>
156+
`,
157+
});
158+
159+
expect(page.root).toEqualHtml(`
160+
<cmp-a>
161+
<mock:shadow-root>
162+
<div>
163+
Shadow Content
164+
</div>
165+
</mock:shadow-root>
166+
Light Content
167+
</cmp-a>
168+
`);
169+
});
138170
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { renderToString } from '../hydrate/index.mjs';
2+
3+
describe('ssr-shadow-cmp', () => {
4+
function getNodeNames(chidNodes: NodeListOf<ChildNode>) {
5+
return Array.from(chidNodes)
6+
.flatMap((node) => {
7+
if (node.nodeType === 3) {
8+
if (node.textContent?.trim()) {
9+
return 'text';
10+
} else {
11+
return [];
12+
}
13+
} else if (node.nodeType === 8) {
14+
return 'comment';
15+
} else {
16+
return node.nodeName.toLowerCase();
17+
}
18+
})
19+
.join(' ');
20+
}
21+
22+
it('verifies all nodes are preserved during hydration', async () => {
23+
if (!document.querySelector('#stage')) {
24+
const { html } = await renderToString(
25+
`
26+
<ssr-shadow-cmp>
27+
A text node
28+
<!-- a comment -->
29+
<div>An element</div>
30+
<!-- another comment -->
31+
Another text node
32+
</ssr-shadow-cmp>
33+
`,
34+
{
35+
fullDocument: true,
36+
serializeShadowRoot: true,
37+
constrainTimeouts: false,
38+
},
39+
);
40+
const stage = document.createElement('div');
41+
stage.setAttribute('id', 'stage');
42+
stage.setHTMLUnsafe(html);
43+
document.body.appendChild(stage);
44+
}
45+
46+
// @ts-expect-error resolved through WDIO
47+
const { defineCustomElements } = await import('/dist/loader/index.js');
48+
defineCustomElements().catch(console.error);
49+
50+
// wait for Stencil to take over and reconcile
51+
await browser.waitUntil(async () => customElements.get('ssr-shadow-cmp'));
52+
expect(typeof customElements.get('ssr-shadow-cmp')).toBe('function');
53+
await expect(getNodeNames(document.querySelector('ssr-shadow-cmp').childNodes)).toBe(
54+
`text comment div comment text`,
55+
);
56+
57+
document.querySelector('#stage')?.remove();
58+
});
59+
});

test/wdio/ssr-hydration/cmp.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Component, h } from '@stencil/core';
2+
3+
@Component({
4+
tag: 'ssr-shadow-cmp',
5+
shadow: true,
6+
})
7+
export class SsrShadowCmp {
8+
render() {
9+
return (
10+
<div>
11+
<slot />
12+
</div>
13+
);
14+
}
15+
}

0 commit comments

Comments
 (0)