Skip to content

Commit 0581cba

Browse files
committed
fix: resuming app with non-qwik elements inside
1 parent d368215 commit 0581cba

File tree

9 files changed

+253
-140
lines changed

9 files changed

+253
-140
lines changed

.changeset/pretty-glasses-mate.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 app with non-qwik elements inside

packages/qwik/src/core/client/process-vnode-data.unit.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ describe('processVnodeData', () => {
1919
<div q:shadowRoot>
2020
<template>
2121
<div q:container="paused">
22-
<button>
22+
<button :>
2323
0
2424
</button>
25-
<script type="qwik/vnode">
25+
<script : type="qwik/vnode">
2626
~{1}!~
2727
</script>
2828
</div>
@@ -66,7 +66,7 @@ describe('processVnodeData', () => {
6666
<html q:container="paused">
6767
<head :></head>
6868
<body :>
69-
<div q:container="html"><span></span></div>
69+
<div q:container="html" :><span></span></div>
7070
<b :>HelloWorld</b>
7171
${encodeVNode({ 2: '2', 4: 'FF' })}
7272
</body>
@@ -91,7 +91,7 @@ describe('processVnodeData', () => {
9191
<html q:container="paused">
9292
<head :></head>
9393
<body :>
94-
<div q:container="html"><span></span></div>
94+
<div q:container="html" :><span></span></div>
9595
<div>ignore this</div>
9696
<b :>HelloWorld</b>
9797
${encodeVNode({ 2: '3', 4: 'FF' })}
@@ -119,7 +119,7 @@ describe('processVnodeData', () => {
119119
<head :></head>
120120
<body :>
121121
Before
122-
<div q:container="paused">
122+
<div q:container="paused" :>
123123
Foo<b :>Bar!</b>
124124
${encodeVNode({ 0: 'D1', 1: 'DB' })}
125125
</div>

packages/qwik/src/core/client/vnode-namespace.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -100,22 +100,36 @@ export function vnode_getDomChildrenWithCorrectNamespacesToInsert(
100100
return domChildren;
101101
}
102102

103-
/** This function clones an element with a different namespace, but without the children. */
104-
function cloneElementWithNamespace(
103+
/** This function clones an element with a different namespace, including the children */
104+
function cloneDomTreeWithNamespace(
105105
element: Element,
106106
elementName: string,
107-
namespace: string
107+
namespace: string,
108+
deep = false
108109
): Element {
109110
const newElement = element.ownerDocument.createElementNS(namespace, elementName);
110-
const attributes = element.attributes;
111-
for (const attribute of attributes) {
112-
const name = attribute.name;
113-
const value = attribute.value;
114-
if (!name || name === Q_PROPS_SEPARATOR) {
115-
continue;
111+
112+
// Copy all attributes
113+
for (const attr of element.attributes) {
114+
if (attr.name !== Q_PROPS_SEPARATOR) {
115+
newElement.setAttribute(attr.name, attr.value);
116116
}
117-
newElement.setAttribute(name, value);
118117
}
118+
119+
if (deep) {
120+
// Recursively clone all child nodes
121+
for (const child of element.childNodes) {
122+
const nodeType = child.nodeType;
123+
if (nodeType === 3 /* Node.TEXT_NODE */) {
124+
newElement.appendChild(child.cloneNode());
125+
} else if (nodeType === 1 /* Node.ELEMENT_NODE */) {
126+
newElement.appendChild(
127+
cloneDomTreeWithNamespace(child as Element, (child as Element).localName, namespace, deep)
128+
);
129+
}
130+
}
131+
}
132+
119133
return newElement;
120134
}
121135

@@ -159,8 +173,15 @@ function vnode_cloneElementWithNamespace(
159173
namespace = namespaceData.elementNamespace;
160174
namespaceFlag = namespaceData.elementNamespaceFlag;
161175
}
176+
const vFirstChild = vnode_getFirstChild(vCursor);
162177

163-
newChildElement = cloneElementWithNamespace(childElement, childElementTag, namespace);
178+
newChildElement = cloneDomTreeWithNamespace(
179+
childElement,
180+
childElementTag,
181+
namespace,
182+
// deep if there is no vnode children, children are probably inserted via innerHTML
183+
!vFirstChild
184+
);
164185

165186
childElement.remove();
166187

@@ -173,7 +194,7 @@ function vnode_cloneElementWithNamespace(
173194

174195
// Descend into children
175196
// We need first get the first child, if any
176-
const vFirstChild = vnode_getFirstChild(vCursor);
197+
177198
// Then we can overwrite the cursor with newly created element.
178199
// This is because we need to materialize the children before we assign new element
179200
vCursor.element = newChildElement;

packages/qwik/src/core/client/vnode.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,27 +1478,28 @@ const fastGetter = <T>(prototype: any, name: string): T => {
14781478
);
14791479
};
14801480

1481-
const isQStyleElement = (node: Node | null): node is Element => {
1481+
const hasQStyleAttribute = (element: Element): boolean => {
14821482
return (
1483-
isElement(node) &&
1484-
node.nodeName === 'STYLE' &&
1485-
(node.hasAttribute(QScopedStyle) || node.hasAttribute(QStyle))
1483+
element.nodeName === 'STYLE' &&
1484+
(element.hasAttribute(QScopedStyle) || element.hasAttribute(QStyle))
14861485
);
14871486
};
14881487

1488+
const hasPropsSeparator = (element: Element): boolean => {
1489+
return element.hasAttribute(Q_PROPS_SEPARATOR);
1490+
};
1491+
14891492
const materializeFromDOM = (vParent: ElementVNode, firstChild: Node | null, vData?: string) => {
14901493
let vFirstChild: VNode | null = null;
14911494

1492-
const skipStyleElements = () => {
1493-
while (isQStyleElement(child)) {
1494-
// skip over style elements, as those need to be moved to the head.
1495-
// VNode pretends that `<style q:style q:sstyle>` elements do not exist.
1495+
const skipElements = () => {
1496+
while (isElement(child) && shouldSkipElement(child)) {
14961497
child = fastNextSibling(child);
14971498
}
14981499
};
14991500
// materialize from DOM
15001501
let child = firstChild;
1501-
skipStyleElements();
1502+
skipElements();
15021503
let vChild: VNode | null = null;
15031504
while (child) {
15041505
const nodeType = fastNodeType(child);
@@ -1518,7 +1519,7 @@ const materializeFromDOM = (vParent: ElementVNode, firstChild: Node | null, vDat
15181519
vParent.firstChild = vFirstChild = vChild;
15191520
}
15201521
child = fastNextSibling(child);
1521-
skipStyleElements();
1522+
skipElements();
15221523
}
15231524
vParent.lastChild = vChild || null;
15241525
vParent.firstChild = vFirstChild;
@@ -1785,6 +1786,17 @@ export function vnode_toString(
17851786
const isNumber = (ch: number) => /* `0` */ 48 <= ch && ch <= 57; /* `9` */
17861787
const isLowercase = (ch: number) => /* `a` */ 97 <= ch && ch <= 122; /* `z` */
17871788

1789+
function shouldSkipElement(element: Element) {
1790+
return (
1791+
// Skip over elements that don't have a props separator. They are not rendered by Qwik.
1792+
!hasPropsSeparator(element) ||
1793+
// We pretend that style element's don't exist as they can get moved out.
1794+
// skip over style elements, as those need to be moved to the head
1795+
// and are not included in the counts.
1796+
hasQStyleAttribute(element)
1797+
);
1798+
}
1799+
17881800
const stack: any[] = [];
17891801
function materializeFromVNodeData(
17901802
vParent: ElementVNode | VirtualVNode,
@@ -1813,16 +1825,15 @@ function materializeFromVNodeData(
18131825
let combinedText: string | null = null;
18141826
let container: ClientContainer | null = null;
18151827

1828+
const shouldSkipNode = (node: Node | null) => {
1829+
const nodeIsElement = isElement(node);
1830+
return !nodeIsElement || (nodeIsElement && shouldSkipElement(node));
1831+
};
1832+
18161833
processVNodeData(vData, (peek, consumeValue, consume, getChar, nextToConsumeIdx) => {
18171834
if (isNumber(peek())) {
18181835
// Element counts get encoded as numbers.
1819-
while (
1820-
!isElement(child) ||
1821-
// We pretend that style element's don't exist as they can get moved out.
1822-
// skip over style elements, as those need to be moved to the head
1823-
// and are not included in the counts.
1824-
isQStyleElement(child)
1825-
) {
1836+
while (shouldSkipNode(child)) {
18261837
child = fastNextSibling(child);
18271838
if (!child) {
18281839
throw qError(QError.materializeVNodeDataError, [vData, peek(), nextToConsumeIdx]);
@@ -1902,8 +1913,8 @@ function materializeFromVNodeData(
19021913
} else if (peek() === VNodeDataChar.SLOT) {
19031914
vParent.setAttr(QSlot, consumeValue(), null);
19041915
} else {
1905-
// skip over style elements in front of text nodes, where text node is the first child (except the style node)
1906-
while (isQStyleElement(child)) {
1916+
// skip over style or non-qwik elements in front of text nodes, where text node is the first child (except the style node)
1917+
while (isElement(child) && shouldSkipElement(child)) {
19071918
child = fastNextSibling(child);
19081919
}
19091920
const textNode =

0 commit comments

Comments
 (0)