Skip to content

Commit 8e108d2

Browse files
authored
Merge pull request #7943 from QwikDev/v2-fix-shadow-dom-resuming
fix: resuming shadow dom container with multiple root children
2 parents 6b582c7 + dba9f49 commit 8e108d2

File tree

14 files changed

+104
-43
lines changed

14 files changed

+104
-43
lines changed

.changeset/twelve-buckets-brake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/core': patch
3+
---
4+
5+
fix: resuming shadow dom container with multiple root children

eslint.config.mjs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ const ignores = [
4545
// packages with eslint.config.mjs
4646
'packages/qwik-labs',
4747
'packages/insights',
48-
'starters',
4948
// eslint.config.*
5049
'**/eslint.config.mjs',
5150
'**/eslint.config.js',

packages/qwik/src/core/client/process-vnode-data.ts

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -102,17 +102,17 @@ export function processVNodeData(document: Document) {
102102
///////////////////////////////
103103

104104
const enum NodeType {
105-
CONTAINER_MASK /* ***************** */ = 0b00000001,
106-
ELEMENT /* ************************ */ = 0b00000010, // regular element
107-
ELEMENT_CONTAINER /* ************** */ = 0b00000011, // container element need to descend into it
108-
ELEMENT_SHADOW_ROOT /* ************ */ = 0b00000110, // shadow root element
109-
COMMENT_SKIP_START /* ************* */ = 0b00000101, // Comment but skip the content until COMMENT_SKIP_END
110-
COMMENT_SKIP_END /* *************** */ = 0b00001000, // Comment end
111-
COMMENT_IGNORE_START /* *********** */ = 0b00010000, // Comment ignore, descend into children and skip the content until COMMENT_ISLAND_START
112-
COMMENT_IGNORE_END /* ************* */ = 0b00100000, // Comment ignore end
113-
COMMENT_ISLAND_START /* *********** */ = 0b01000001, // Comment island, count elements for parent container until COMMENT_ISLAND_END
114-
COMMENT_ISLAND_END /* ************* */ = 0b10000000, // Comment island end
115-
OTHER /* ************************** */ = 0b00000000,
105+
CONTAINER_MASK /* ***************** */ = 0b0000001,
106+
ELEMENT /* ************************ */ = 0b0000010, // regular element
107+
ELEMENT_CONTAINER /* ************** */ = 0b0000011, // container element need to descend into it
108+
ELEMENT_SHADOW_ROOT_WRAPPER /* **** */ = 0b0000110, // shadow root wrapper element with q:shadowroot attribute
109+
COMMENT_SKIP_START /* ************* */ = 0b0001001, // Comment but skip the content until COMMENT_SKIP_END
110+
COMMENT_SKIP_END /* *************** */ = 0b0001000, // Comment end
111+
COMMENT_IGNORE_START /* *********** */ = 0b0010000, // Comment ignore, descend into children and skip the content until COMMENT_ISLAND_START
112+
COMMENT_IGNORE_END /* ************* */ = 0b0100000, // Comment ignore end
113+
COMMENT_ISLAND_START /* *********** */ = 0b1000001, // Comment island, count elements for parent container until COMMENT_ISLAND_END
114+
COMMENT_ISLAND_END /* ************* */ = 0b1000000, // Comment island end
115+
OTHER /* ************************** */ = 0b0000000,
116116
}
117117

118118
/**
@@ -126,7 +126,7 @@ export function processVNodeData(document: Document) {
126126
const qContainer = getAttribute.call(node, Q_CONTAINER);
127127
if (qContainer === null) {
128128
if (hasAttribute.call(node, Q_SHADOW_ROOT)) {
129-
return NodeType.ELEMENT_SHADOW_ROOT;
129+
return NodeType.ELEMENT_SHADOW_ROOT_WRAPPER;
130130
}
131131
const isQElement = hasAttribute.call(node, Q_PROPS_SEPARATOR);
132132
return isQElement ? NodeType.ELEMENT : NodeType.OTHER;
@@ -187,12 +187,6 @@ export function processVNodeData(document: Document) {
187187
return node;
188188
};
189189

190-
const firstChild = (node: Node | null) => {
191-
// eslint-disable-next-line no-empty
192-
while (node && (node = node.firstChild) && getFastNodeType(node) === NodeType.OTHER) {}
193-
return node;
194-
};
195-
196190
/**
197191
* Process the container
198192
*
@@ -289,21 +283,20 @@ export function processVNodeData(document: Document) {
289283
} while (getFastNodeType(nextNode) !== NodeType.COMMENT_SKIP_END);
290284
// console.log('EXIT', nextNode?.outerHTML);
291285
walkContainer(walker, node, node, nextNode, '', null!, prefix + ' ');
292-
} else if (nodeType === NodeType.ELEMENT_SHADOW_ROOT) {
286+
} else if (nodeType === NodeType.ELEMENT_SHADOW_ROOT_WRAPPER) {
293287
// If we are in a shadow root, we need to get the shadow root element.
294288
nextNode = nextSibling(node);
295289
const shadowRootContainer = node as Element | null;
296290
const shadowRoot = shadowRootContainer?.shadowRoot;
297291
if (shadowRoot) {
298-
const firstShadowRootChild = firstChild(shadowRoot)!;
299292
walkContainer(
300293
// we need to create a new walker for the shadow root
301294
document.createTreeWalker(
302-
firstShadowRootChild,
295+
shadowRoot,
303296
0x1 /* NodeFilter.SHOW_ELEMENT */ | 0x80 /* NodeFilter.SHOW_COMMENT */
304297
),
305298
null,
306-
firstShadowRootChild,
299+
shadowRoot,
307300
null,
308301
'',
309302
null!,

pnpm-lock.yaml

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

starters/apps/e2e/src/components/computed/computed.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable */
21
import { component$, useComputed$, useSignal, useTask$ } from "@qwik.dev/core";
32

43
export const ComputedRoot = component$(() => {

starters/apps/e2e/src/components/containers/container.tsx

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import {
55
useSignal,
66
useStyles$,
77
} from "@qwik.dev/core";
8+
import {
9+
SSRRaw,
10+
SSRStream,
11+
type SSRStreamWriter,
12+
} from "@qwik.dev/core/internal";
813

914
interface ContainerProps {
1015
url: string;
@@ -20,6 +25,47 @@ export const Containers = component$(() => {
2025
);
2126
});
2227

28+
const SSRStreamRemoteContainer = component$<{
29+
url: string;
30+
containerId?: string;
31+
}>(({ url, containerId }) => {
32+
const decoder = new TextDecoder();
33+
const getSSRStreamFunction =
34+
(remoteUrl: string) => async (stream: SSRStreamWriter) => {
35+
const _remoteUrl = new URL(
36+
`http://localhost:${(globalThis as any).PORT}${remoteUrl}`,
37+
);
38+
const response = await fetch(_remoteUrl, {
39+
headers: {
40+
accept: "text/html",
41+
},
42+
});
43+
if (response.ok) {
44+
const reader = response.body!.getReader();
45+
let fragmentChunk = await reader.read();
46+
while (!fragmentChunk.done) {
47+
const rawHtml = decoder.decode(fragmentChunk.value);
48+
stream.write((<SSRRaw data={rawHtml} />) as string);
49+
fragmentChunk = await reader.read();
50+
}
51+
} else {
52+
console.error(
53+
"Failed to connect with status:",
54+
response.status,
55+
response.statusText,
56+
);
57+
}
58+
};
59+
60+
return (
61+
<div id={containerId} q:shadowRoot>
62+
<template shadowRootMode="open">
63+
<SSRStream>{getSSRStreamFunction(url)}</SSRStream>
64+
</template>
65+
</div>
66+
);
67+
});
68+
2369
export const Container = component$((props: ContainerProps) => {
2470
useStyles$(`
2571
.container {
@@ -72,7 +118,7 @@ export const Container = component$((props: ContainerProps) => {
72118
</div>
73119
<div style={{ border: "1px solid red" }}>
74120
Shadow DOM
75-
<div q:shadowRoot>
121+
<div id="shadow-dom-resource" q:shadowRoot>
76122
<template shadowRootMode="open">
77123
<Resource
78124
value={resource}
@@ -87,6 +133,10 @@ export const Container = component$((props: ContainerProps) => {
87133
/>
88134
</template>
89135
</div>
136+
<SSRStreamRemoteContainer
137+
url="/e2e/two-listeners?fragment&loader=false"
138+
containerId="shadow-dom-stream"
139+
/>
90140
</div>
91141
</div>
92142
);

starters/apps/playground/src/routes/demo/flower/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export default component$(() => {
1616
number: 20,
1717
});
1818

19-
// eslint-disable-next-line qwik/no-use-visible-task
2019
useVisibleTask$(({ cleanup }) => {
2120
const timeout = setTimeout(() => (state.count = 1), 500);
2221
cleanup(() => clearTimeout(timeout));

starters/apps/preloader-test/src/components/generated/show-dynamic.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default component$(() => {
88
<button
99
onClick$={() => {
1010
showDynamic.value = true;
11+
// eslint-disable-next-line no-console
1112
console.log(
1213
`
1314
************************************************

starters/apps/preloader-test/src/routes/counters/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ export default component$(() => {
215215
<Counter100 />
216216
<Link
217217
href="/hidden"
218+
// eslint-disable-next-line no-console
218219
onQVisible$={() => console.log("visible below fold")}
219220
>
220221
Home

starters/apps/preloader-test/src/routes/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable no-console */
21
import {
32
component$,
43
useTask$,

0 commit comments

Comments
 (0)