Skip to content

Commit

Permalink
fix(runtime): create unique host ids (#6018)
Browse files Browse the repository at this point in the history
* fix(runtime): create unique host ids

* find a better solution

* even better

* prettier

* code docs

* wording fix

* update tests

* prettier
  • Loading branch information
christian-bromann authored Oct 8, 2024
1 parent 9c3ece9 commit 1564b7a
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 58 deletions.
7 changes: 7 additions & 0 deletions src/declarations/stencil-private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ import type {
import type { JsonDocMethodParameter } from './stencil-public-docs';
import type { ComponentInterface, ListenTargetOptions, VNode } from './stencil-public-runtime';

export interface DocData {
hostIds: number;
rootLevelIds: number;
staticComponents: Set<string>;
}
export type StencilDocument = Document & { _stencilDocData: DocData };

export interface SourceMap {
file: string;
mappings: string;
Expand Down
13 changes: 13 additions & 0 deletions src/hydrate/runner/window-initialize.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { constrainTimeouts, type MockWindow } from '@stencil/core/mock-doc';
import { STENCIL_DOC_DATA } from 'src/runtime/runtime-constants';

import type * as d from '../../declarations';
import { runtimeLogging } from './runtime-log';

/**
* Maintain a unique `docData` object across multiple hydration runs
* to ensure that host ids remain unique.
*/
const docData: d.DocData = {
hostIds: 0,
rootLevelIds: 0,
staticComponents: new Set<string>(),
} as d.DocData;

export function initializeWindow(
win: MockWindow,
doc: Document,
Expand Down Expand Up @@ -58,5 +69,7 @@ export function initializeWindow(

runtimeLogging(win, opts, results);

(doc as d.StencilDocument)[STENCIL_DOC_DATA] = docData;

return win;
}
7 changes: 7 additions & 0 deletions src/runtime/runtime-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ export const HYDRATED_STYLE_ID = 'sty-id';
export const HYDRATE_CHILD_ID = 'c-id';
export const HYDRATED_CSS = '{visibility:hidden}.hydrated{visibility:inherit}';

export const STENCIL_DOC_DATA = '_stencilDocData';
export const DEFAULT_DOC_DATA = {
hostIds: 0,
rootLevelIds: 0,
staticComponents: new Set<string>(),
};

/**
* Constant for styles to be globally applied to `slot-fb` elements for pseudo-slot behavior.
*
Expand Down
23 changes: 10 additions & 13 deletions src/runtime/vdom/vdom-annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { getHostRef } from '@platform';
import type * as d from '../../declarations';
import {
CONTENT_REF_ID,
DEFAULT_DOC_DATA,
HYDRATE_CHILD_ID,
HYDRATE_ID,
NODE_TYPE,
ORG_LOCATION_ID,
SLOT_NODE_ID,
STENCIL_DOC_DATA,
TEXT_NODE_ID,
} from '../runtime-constants';
import { insertBefore } from './vdom-render';
Expand All @@ -23,11 +25,12 @@ import { insertBefore } from './vdom-render';
*/
export const insertVdomAnnotations = (doc: Document, staticComponents: string[]) => {
if (doc != null) {
const docData: DocData = {
hostIds: 0,
rootLevelIds: 0,
staticComponents: new Set(staticComponents),
};
/**
* Initiated `docData` object from the document if it exists to ensure we
* maintain the same `docData` object across multiple hydration hydration runs.
*/
const docData: d.DocData = STENCIL_DOC_DATA in doc ? (doc[STENCIL_DOC_DATA] as d.DocData) : { ...DEFAULT_DOC_DATA };
docData.staticComponents = new Set(staticComponents);
const orgLocationNodes: d.RenderNode[] = [];

parseVNodeAnnotations(doc, doc.body, docData, orgLocationNodes);
Expand Down Expand Up @@ -99,7 +102,7 @@ export const insertVdomAnnotations = (doc: Document, staticComponents: string[])
const parseVNodeAnnotations = (
doc: Document,
node: d.RenderNode,
docData: DocData,
docData: d.DocData,
orgLocationNodes: d.RenderNode[],
) => {
if (node == null) {
Expand Down Expand Up @@ -145,7 +148,7 @@ const insertVNodeAnnotations = (
doc: Document,
hostElm: d.HostElement,
vnode: d.VNode | undefined,
docData: DocData,
docData: d.DocData,
cmpData: CmpData,
) => {
if (vnode != null) {
Expand Down Expand Up @@ -245,12 +248,6 @@ const insertChildVNodeAnnotations = (
}
};

interface DocData {
hostIds: number;
rootLevelIds: number;
staticComponents: Set<string>;
}

interface CmpData {
nodeIds: number;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renderToString can render a scoped component within a shadow component 1`] = `"<car-list cars=\\"[{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Vento&quot;,&quot;year&quot;:2024},{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Beetle&quot;,&quot;year&quot;:2023}]\\" custom-hydrate-flag=\\"\\" s-id=\\"1\\"><template shadowrootmode=\\"open\\"><style>:host{display:block;margin:10px;padding:10px;border:1px solid blue}ul{display:block;margin:0;padding:0}li{list-style:none;margin:0;padding:20px}.selected{font-weight:bold;background:rgb(255, 255, 210)}</style><ul c-id=\\"1.0.0.0\\"><li c-id=\\"1.1.1.0\\"><car-detail custom-hydrate-flag=\\"\\" c-id=\\"1.2.2.0\\" s-id=\\"2\\"><!--r.2--><section c-id=\\"2.0.0.0\\"><!--t.2.1.1.0-->2024 VW Vento</section></car-detail></li><li c-id=\\"1.3.1.1\\"><car-detail custom-hydrate-flag=\\"\\" c-id=\\"1.4.2.0\\" s-id=\\"3\\"><!--r.3--><section c-id=\\"3.0.0.0\\"><!--t.3.1.1.0-->2023 VW Beetle</section></car-detail></li></ul></template><!--r.1--></car-list>"`;
exports[`renderToString can render a scoped component within a shadow component 1`] = `"<car-list cars=\\"[{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Vento&quot;,&quot;year&quot;:2024},{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Beetle&quot;,&quot;year&quot;:2023}]\\" custom-hydrate-flag=\\"\\" s-id=\\"9\\"><template shadowrootmode=\\"open\\"><style>:host{display:block;margin:10px;padding:10px;border:1px solid blue}ul{display:block;margin:0;padding:0}li{list-style:none;margin:0;padding:20px}.selected{font-weight:bold;background:rgb(255, 255, 210)}</style><ul c-id=\\"9.0.0.0\\"><li c-id=\\"9.1.1.0\\"><car-detail custom-hydrate-flag=\\"\\" c-id=\\"9.2.2.0\\" s-id=\\"10\\"><!--r.10--><section c-id=\\"10.0.0.0\\"><!--t.10.1.1.0-->2024 VW Vento</section></car-detail></li><li c-id=\\"9.3.1.1\\"><car-detail custom-hydrate-flag=\\"\\" c-id=\\"9.4.2.0\\" s-id=\\"11\\"><!--r.11--><section c-id=\\"11.0.0.0\\"><!--t.11.1.1.0-->2023 VW Beetle</section></car-detail></li></ul></template><!--r.9--></car-list>"`;

exports[`renderToString can render a simple shadow component 1`] = `
"<another-car-detail custom-hydrate-flag=\\"\\" s-id=\\"1\\">
Expand All @@ -14,72 +14,72 @@ exports[`renderToString can render a simple shadow component 1`] = `
`;
exports[`renderToString can render nested components 1`] = `
"<another-car-list cars=\\"[{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Vento&quot;,&quot;year&quot;:2024},{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Beetle&quot;,&quot;year&quot;:2023}]\\" custom-hydrate-flag=\\"\\" s-id=\\"1\\">
"<another-car-list cars=\\"[{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Vento&quot;,&quot;year&quot;:2024},{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Beetle&quot;,&quot;year&quot;:2023}]\\" custom-hydrate-flag=\\"\\" s-id=\\"6\\">
<template shadowrootmode=\\"open\\">
<style>
:host{display:block;margin:10px;padding:10px;border:1px solid blue}ul{display:block;margin:0;padding:0}li{list-style:none;margin:0;padding:20px}.selected{font-weight:bold;background:rgb(255, 255, 210)}
</style>
<ul c-id=\\"1.0.0.0\\">
<li c-id=\\"1.1.1.0\\">
<another-car-detail c-id=\\"1.2.2.0\\" custom-hydrate-flag=\\"\\" s-id=\\"2\\">
<ul c-id=\\"6.0.0.0\\">
<li c-id=\\"6.1.1.0\\">
<another-car-detail c-id=\\"6.2.2.0\\" custom-hydrate-flag=\\"\\" s-id=\\"7\\">
<template shadowrootmode=\\"open\\">
<style>
section{color:green}
</style>
<section c-id=\\"2.0.0.0\\">
<!--t.2.1.1.0-->
<section c-id=\\"7.0.0.0\\">
<!--t.7.1.1.0-->
2024 VW Vento
</section>
</template>
<!--r.2-->
<!--r.7-->
</another-car-detail>
</li>
<li c-id=\\"1.3.1.1\\">
<another-car-detail c-id=\\"1.4.2.0\\" custom-hydrate-flag=\\"\\" s-id=\\"3\\">
<li c-id=\\"6.3.1.1\\">
<another-car-detail c-id=\\"6.4.2.0\\" custom-hydrate-flag=\\"\\" s-id=\\"8\\">
<template shadowrootmode=\\"open\\">
<style>
section{color:green}
</style>
<section c-id=\\"3.0.0.0\\">
<!--t.3.1.1.0-->
<section c-id=\\"8.0.0.0\\">
<!--t.8.1.1.0-->
2023 VW Beetle
</section>
</template>
<!--r.3-->
<!--r.8-->
</another-car-detail>
</li>
</ul>
</template>
<!--r.1-->
<!--r.6-->
</another-car-list>"
`;
exports[`renderToString supports passing props to components 1`] = `
"<another-car-detail car=\\"{&quot;year&quot;:2024, &quot;make&quot;: &quot;VW&quot;, &quot;model&quot;: &quot;Vento&quot;}\\" custom-hydrate-flag=\\"\\" s-id=\\"1\\">
"<another-car-detail car=\\"{&quot;year&quot;:2024, &quot;make&quot;: &quot;VW&quot;, &quot;model&quot;: &quot;Vento&quot;}\\" custom-hydrate-flag=\\"\\" s-id=\\"2\\">
<template shadowrootmode=\\"open\\">
<style>
section{color:green}
</style>
<section c-id=\\"1.0.0.0\\">
<!--t.1.1.1.0-->
<section c-id=\\"2.0.0.0\\">
<!--t.2.1.1.0-->
2024 VW Vento
</section>
</template>
<!--r.1-->
<!--r.2-->
</another-car-detail>"
`;
exports[`renderToString supports passing props to components with a simple object 1`] = `
"<another-car-detail car=\\"{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Vento&quot;,&quot;year&quot;:2024}\\" custom-hydrate-flag=\\"\\" s-id=\\"1\\">
"<another-car-detail car=\\"{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Vento&quot;,&quot;year&quot;:2024}\\" custom-hydrate-flag=\\"\\" s-id=\\"3\\">
<template shadowrootmode=\\"open\\">
<style>
section{color:green}
</style>
<section c-id=\\"1.0.0.0\\">
<!--t.1.1.1.0-->
<section c-id=\\"3.0.0.0\\">
<!--t.3.1.1.0-->
2024 VW Vento
</section>
</template>
<!--r.1-->
<!--r.3-->
</another-car-detail>"
`;
50 changes: 27 additions & 23 deletions test/end-to-end/src/declarative-shadow-dom/test.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ describe('renderToString', () => {
fullDocument: false,
},
);
expect(html).toContain('<section c-id="1.0.0.0"><!--t.1.1.1.0--> </section>');
expect(html).toContain('<section c-id="4.0.0.0"><!--t.4.1.1.0--> </section>');
});

it('supports styles for DSD', async () => {
Expand Down Expand Up @@ -145,31 +145,35 @@ describe('renderToString', () => {
});
expect(html).toMatchSnapshot();
expect(html).toContain(
`<car-detail custom-hydrate-flag=\"\" c-id=\"1.2.2.0\" s-id=\"2\"><!--r.2--><section c-id=\"2.0.0.0\"><!--t.2.1.1.0-->2024 VW Vento</section></car-detail>`,
`<car-detail custom-hydrate-flag=\"\" c-id=\"9.2.2.0\" s-id=\"10\"><!--r.10--><section c-id=\"10.0.0.0\"><!--t.10.1.1.0-->2024 VW Vento</section></car-detail>`,
);
expect(html).toContain(
`<car-detail custom-hydrate-flag=\"\" c-id=\"1.4.2.0\" s-id=\"3\"><!--r.3--><section c-id=\"3.0.0.0\"><!--t.3.1.1.0-->2023 VW Beetle</section></car-detail>`,
`<car-detail custom-hydrate-flag=\"\" c-id=\"9.4.2.0\" s-id=\"11\"><!--r.11--><section c-id=\"11.0.0.0\"><!--t.11.1.1.0-->2023 VW Beetle</section></car-detail>`,
);
});

it('can render a scoped component within a shadow component (sync)', async () => {
const input = `<car-list cars=${JSON.stringify([vento, beetle])}></car-list>`;
const expectedResults = [
'<car-detail custom-hydrate-flag="" c-id="1.2.2.0" s-id="2"><!--r.2--><section c-id="2.0.0.0"><!--t.2.1.1.0-->2024 VW Vento</section></car-detail>',
'<car-detail custom-hydrate-flag="" c-id="1.4.2.0" s-id="3"><!--r.3--><section c-id="3.0.0.0"><!--t.3.1.1.0-->2023 VW Beetle</section></car-detail>',
] as const;
const opts = {
serializeShadowRoot: true,
fullDocument: false,
};

const resultRenderToString = await readableToString(renderToString(input, opts, true));
expect(resultRenderToString).toContain(expectedResults[0]);
expect(resultRenderToString).toContain(expectedResults[1]);
expect(resultRenderToString).toContain(
'<car-detail custom-hydrate-flag="" c-id="12.2.2.0" s-id="13"><!--r.13--><section c-id="13.0.0.0"><!--t.13.1.1.0-->2024 VW Vento</section></car-detail>',
);
expect(resultRenderToString).toContain(
'<car-detail custom-hydrate-flag="" c-id="12.4.2.0" s-id="14"><!--r.14--><section c-id="14.0.0.0"><!--t.14.1.1.0-->2023 VW Beetle</section></car-detail>',
);

const resultStreamToString = await readableToString(streamToString(input, opts));
expect(resultStreamToString).toContain(expectedResults[0]);
expect(resultStreamToString).toContain(expectedResults[1]);
expect(resultStreamToString).toContain(
'<car-detail custom-hydrate-flag="" c-id="15.2.2.0" s-id="16"><!--r.16--><section c-id="16.0.0.0"><!--t.16.1.1.0-->2024 VW Vento</section></car-detail>',
);
expect(resultStreamToString).toContain(
'<car-detail custom-hydrate-flag="" c-id="15.4.2.0" s-id="17"><!--r.17--><section c-id="17.0.0.0"><!--t.17.1.1.0-->2023 VW Beetle</section></car-detail>',
);
});

it('can take over a server side rendered component and re-render it in the browser', async () => {
Expand Down Expand Up @@ -238,7 +242,7 @@ describe('renderToString', () => {
* ```
*/
expect(html).toContain(
`<dsd-listen-cmp custom-hydrate-flag=\"\" s-id=\"1\"><template shadowrootmode=\"open\"><style>:host{display:block}</style><slot c-id=\"1.0.0.0\"></slot></template><!--r.1-->Hello World</dsd-listen-cmp>`,
`<dsd-listen-cmp custom-hydrate-flag=\"\" s-id=\"21\"><template shadowrootmode=\"open\"><style>:host{display:block}</style><slot c-id=\"21.0.0.0\"></slot></template><!--r.21-->Hello World</dsd-listen-cmp>`,
);

/**
Expand All @@ -253,7 +257,7 @@ describe('renderToString', () => {
* </car-detail>
*/
expect(html).toContain(
`<car-detail custom-hydrate-flag=\"\" c-id=\"2.4.2.0\" s-id=\"4\"><!--r.4--><section c-id=\"4.0.0.0\"><!--t.4.1.1.0-->2023 VW Beetle</section></car-detail>`,
`<car-detail custom-hydrate-flag=\"\" c-id=\"22.4.2.0\" s-id=\"24\"><!--r.24--><section c-id=\"24.0.0.0\"><!--t.24.1.1.0-->2023 VW Beetle</section></car-detail>`,
);

const page = await newE2EPage({ html, url: 'https://stencil.com' });
Expand All @@ -280,15 +284,15 @@ describe('renderToString', () => {
serializeShadowRoot: false,
fullDocument: false,
});
expect(html).toBe('<another-car-detail custom-hydrate-flag="" s-id="1"><!--r.1--></another-car-detail>');
expect(html).toBe('<another-car-detail custom-hydrate-flag="" s-id="25"><!--r.25--></another-car-detail>');
});

it('does not render a shadow component but its light dom', async () => {
const { html } = await renderToString('<cmp-with-slot>Hello World</cmp-with-slot>', {
serializeShadowRoot: false,
fullDocument: false,
});
expect(html).toBe('<cmp-with-slot custom-hydrate-flag="" s-id="1"><!--r.1-->Hello World</cmp-with-slot>');
expect(html).toBe('<cmp-with-slot custom-hydrate-flag="" s-id="26"><!--r.26-->Hello World</cmp-with-slot>');
});

describe('modes in declarative shadow dom', () => {
Expand Down Expand Up @@ -340,20 +344,20 @@ describe('renderToString', () => {
prettyHtml: true,
},
);
expect(html).toBe(`<nested-cmp-parent custom-hydrate-flag="" s-id="1">
expect(html).toBe(`<nested-cmp-parent custom-hydrate-flag="" s-id="29">
<template shadowrootmode="open">
<div c-id="1.0.0.0" class="some-class">
<slot c-id="1.1.1.0"></slot>
<div c-id="29.0.0.0" class="some-class">
<slot c-id="29.1.1.0"></slot>
</div>
</template>
<!--r.1-->
<nested-cmp-child custom-hydrate-flag="" s-id="2">
<!--r.29-->
<nested-cmp-child custom-hydrate-flag="" s-id="30">
<template shadowrootmode="open">
<div c-id="2.0.0.0" class="some-other-class">
<slot c-id="2.1.1.0"></slot>
<div c-id="30.0.0.0" class="some-other-class">
<slot c-id="30.1.1.0"></slot>
</div>
</template>
<!--r.2-->
<!--r.30-->
Hello World
</nested-cmp-child>
</nested-cmp-parent>`);
Expand Down

0 comments on commit 1564b7a

Please sign in to comment.