Skip to content

Commit dbc2f58

Browse files
fix(mock-doc): avoid double hydration of components (#6003)
1 parent c5a8ea9 commit dbc2f58

File tree

5 files changed

+118
-1
lines changed

5 files changed

+118
-1
lines changed

src/mock-doc/serialize-node.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { CONTENT_REF_ID, ORG_LOCATION_ID, SLOT_NODE_ID, TEXT_NODE_ID, XLINK_NS } from '../runtime/runtime-constants';
1+
import {
2+
CONTENT_REF_ID,
3+
HYDRATE_ID,
4+
ORG_LOCATION_ID,
5+
SLOT_NODE_ID,
6+
TEXT_NODE_ID,
7+
XLINK_NS,
8+
} from '../runtime/runtime-constants';
29
import { cloneAttributes } from './attribute';
310
import { NODE_TYPES } from './constants';
411
import { type MockDocument } from './document';
@@ -274,6 +281,21 @@ function* streamToHtml(
274281
}
275282

276283
for (let i = 0; i < childNodeLength; i++) {
284+
/**
285+
* In cases where a user would pass in a declarative shadow dom of a
286+
* Stencil component, we want to skip over the template tag as we
287+
* will be parsing the shadow root of the component again.
288+
*
289+
* We know it is a hydrated Stencil component by checking if the `HYDRATE_ID`
290+
* is set on the node.
291+
*/
292+
const sId = (node as HTMLElement).attributes.getNamedItem(HYDRATE_ID);
293+
const isStencilDeclarativeShadowDOM = childNodes[i].nodeName.toLowerCase() === 'template' && sId;
294+
if (isStencilDeclarativeShadowDOM) {
295+
yield `\n${' '.repeat(output.indent)}<!--r.${sId.value}-->`;
296+
continue;
297+
}
298+
277299
yield* streamToHtml(childNodes[i], opts, output);
278300
}
279301

test/end-to-end/src/components.d.ts

+26
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ export namespace Components {
100100
"someMethodWithArgs": (unit: string, value: number) => Promise<string>;
101101
"someProp": number;
102102
}
103+
interface NestedCmpChild {
104+
}
105+
interface NestedCmpParent {
106+
}
103107
interface PathAliasCmp {
104108
}
105109
interface PrerenderCmp {
@@ -334,6 +338,18 @@ declare global {
334338
prototype: HTMLMethodCmpElement;
335339
new (): HTMLMethodCmpElement;
336340
};
341+
interface HTMLNestedCmpChildElement extends Components.NestedCmpChild, HTMLStencilElement {
342+
}
343+
var HTMLNestedCmpChildElement: {
344+
prototype: HTMLNestedCmpChildElement;
345+
new (): HTMLNestedCmpChildElement;
346+
};
347+
interface HTMLNestedCmpParentElement extends Components.NestedCmpParent, HTMLStencilElement {
348+
}
349+
var HTMLNestedCmpParentElement: {
350+
prototype: HTMLNestedCmpParentElement;
351+
new (): HTMLNestedCmpParentElement;
352+
};
337353
interface HTMLPathAliasCmpElement extends Components.PathAliasCmp, HTMLStencilElement {
338354
}
339355
var HTMLPathAliasCmpElement: {
@@ -427,6 +443,8 @@ declare global {
427443
"import-assets": HTMLImportAssetsElement;
428444
"listen-cmp": HTMLListenCmpElement;
429445
"method-cmp": HTMLMethodCmpElement;
446+
"nested-cmp-child": HTMLNestedCmpChildElement;
447+
"nested-cmp-parent": HTMLNestedCmpParentElement;
430448
"path-alias-cmp": HTMLPathAliasCmpElement;
431449
"prerender-cmp": HTMLPrerenderCmpElement;
432450
"prop-cmp": HTMLPropCmpElement;
@@ -507,6 +525,10 @@ declare namespace LocalJSX {
507525
interface MethodCmp {
508526
"someProp"?: number;
509527
}
528+
interface NestedCmpChild {
529+
}
530+
interface NestedCmpParent {
531+
}
510532
interface PathAliasCmp {
511533
}
512534
interface PrerenderCmp {
@@ -564,6 +586,8 @@ declare namespace LocalJSX {
564586
"import-assets": ImportAssets;
565587
"listen-cmp": ListenCmp;
566588
"method-cmp": MethodCmp;
589+
"nested-cmp-child": NestedCmpChild;
590+
"nested-cmp-parent": NestedCmpParent;
567591
"path-alias-cmp": PathAliasCmp;
568592
"prerender-cmp": PrerenderCmp;
569593
"prop-cmp": PropCmp;
@@ -609,6 +633,8 @@ declare module "@stencil/core" {
609633
"import-assets": LocalJSX.ImportAssets & JSXBase.HTMLAttributes<HTMLImportAssetsElement>;
610634
"listen-cmp": LocalJSX.ListenCmp & JSXBase.HTMLAttributes<HTMLListenCmpElement>;
611635
"method-cmp": LocalJSX.MethodCmp & JSXBase.HTMLAttributes<HTMLMethodCmpElement>;
636+
"nested-cmp-child": LocalJSX.NestedCmpChild & JSXBase.HTMLAttributes<HTMLNestedCmpChildElement>;
637+
"nested-cmp-parent": LocalJSX.NestedCmpParent & JSXBase.HTMLAttributes<HTMLNestedCmpParentElement>;
612638
"path-alias-cmp": LocalJSX.PathAliasCmp & JSXBase.HTMLAttributes<HTMLPathAliasCmpElement>;
613639
"prerender-cmp": LocalJSX.PrerenderCmp & JSXBase.HTMLAttributes<HTMLPrerenderCmpElement>;
614640
"prop-cmp": LocalJSX.PropCmp & JSXBase.HTMLAttributes<HTMLPropCmpElement>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Component, h } from '@stencil/core';
2+
3+
@Component({
4+
tag: 'nested-cmp-child',
5+
shadow: true,
6+
})
7+
export class NestedCmpChild {
8+
render() {
9+
return (
10+
<div class="some-other-class">
11+
<slot></slot>
12+
</div>
13+
);
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Component, h } from '@stencil/core';
2+
3+
@Component({
4+
tag: 'nested-cmp-parent',
5+
shadow: true,
6+
})
7+
export class NestedCmpParent {
8+
render() {
9+
return (
10+
<div class="some-class">
11+
<slot></slot>
12+
</div>
13+
);
14+
}
15+
}

test/end-to-end/src/declarative-shadow-dom/test.e2e.ts

+39
Original file line numberDiff line numberDiff line change
@@ -319,4 +319,43 @@ describe('renderToString', () => {
319319
expect(color).toBe('rgb(0, 0, 0)');
320320
});
321321
});
322+
323+
it('does not render the shadow root twice', async () => {
324+
const { html } = await renderToString(
325+
`
326+
<nested-cmp-parent>
327+
<nested-cmp-child custom-hydrate-flag="" s-id="3">
328+
<template shadowrootmode="open">
329+
<div c-id="3.0.0.0" class="some-other-class">
330+
<slot c-id="3.1.1.0"></slot>
331+
</div>
332+
</template>
333+
<!--r.3-->
334+
Hello World
335+
</nested-cmp-child>
336+
</nested-cmp-parent>
337+
`,
338+
{
339+
fullDocument: false,
340+
prettyHtml: true,
341+
},
342+
);
343+
expect(html).toBe(`<nested-cmp-parent custom-hydrate-flag="" s-id="1">
344+
<template shadowrootmode="open">
345+
<div c-id="1.0.0.0" class="some-class">
346+
<slot c-id="1.1.1.0"></slot>
347+
</div>
348+
</template>
349+
<!--r.1-->
350+
<nested-cmp-child custom-hydrate-flag="" s-id="2">
351+
<template shadowrootmode="open">
352+
<div c-id="2.0.0.0" class="some-other-class">
353+
<slot c-id="2.1.1.0"></slot>
354+
</div>
355+
</template>
356+
<!--r.2-->
357+
Hello World
358+
</nested-cmp-child>
359+
</nested-cmp-parent>`);
360+
});
322361
});

0 commit comments

Comments
 (0)