Skip to content

Commit eae64dd

Browse files
committed
Fix issues by correctly skipping comments in diffChildren
1 parent 1281599 commit eae64dd

File tree

6 files changed

+24
-26
lines changed

6 files changed

+24
-26
lines changed

compat/test/browser/suspense-hydration.test.js

+5-14
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ describe('suspense hydration', () => {
132132
});
133133

134134
it('Should hydrate a fragment with no children correctly', () => {
135-
scratch.innerHTML = '<!--$s--><div>Hello</div><div>World!</div><!--/$s-->';
135+
scratch.innerHTML = '<!--$s--><!--/$s-->';
136136
clearLog();
137137

138138
const [Lazy, resolve] = createLazy();
@@ -143,22 +143,13 @@ describe('suspense hydration', () => {
143143
scratch
144144
);
145145
rerender(); // Flush rerender queue to mimic what preact will really do
146-
expect(scratch.innerHTML).to.equal(
147-
'<!--$s--><div>Hello</div><div>World!</div><!--/$s-->'
148-
);
146+
expect(scratch.innerHTML).to.equal('<!--$s--><!--/$s-->');
149147
expect(getLog()).to.deep.equal([]);
150148
clearLog();
151149

152-
return resolve(() => (
153-
<>
154-
<div>Hello</div>
155-
<div>World!</div>
156-
</>
157-
)).then(() => {
150+
return resolve(() => null).then(() => {
158151
rerender();
159-
expect(scratch.innerHTML).to.equal(
160-
'<!--$s--><div>Hello</div><div>World!</div><!--/$s-->'
161-
);
152+
expect(scratch.innerHTML).to.equal('<!--$s--><!--/$s-->');
162153
expect(getLog()).to.deep.equal([]);
163154

164155
clearLog();
@@ -167,7 +158,7 @@ describe('suspense hydration', () => {
167158

168159
// This is in theory correct but still it shows that our oldDom becomes stale very quickly
169160
// and moves DOM into weird places
170-
it.skip('Should hydrate a fragment with no children and an adjacent node correctly', () => {
161+
it('Should hydrate a fragment with no children and an adjacent node correctly', () => {
171162
scratch.innerHTML = '<!--$s--><!--/$s--><div>Baz</div>';
172163
clearLog();
173164

jsx-runtime/src/index.js

-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ function createVNode(type, props, key, isStaticChildren, __source, __self) {
5959
_nextDom: undefined,
6060
_component: null,
6161
constructor: undefined,
62-
_excess: null,
6362
_original: --vnodeId,
6463
_index: -1,
6564
_flags: 0,

src/create-element.js

-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ export function createVNode(type, props, key, ref, original) {
6666
_parent: null,
6767
_depth: 0,
6868
_dom: null,
69-
_excess: null,
7069
// _nextDom must be initialized to undefined b/c it will eventually
7170
// be set to dom.nextSibling which can return `null` and it is important
7271
// to be able to distinguish between an uninitialized _nextDom and

src/diff/children.js

+3
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ export function diffChildren(
135135
oldDom = childVNode._nextDom;
136136
} else if (newDom) {
137137
oldDom = newDom.nextSibling;
138+
while (oldDom && oldDom.nodeType == 8 && oldDom.nextSibling) {
139+
oldDom = oldDom.nextSibling;
140+
}
138141
}
139142

140143
// Eagerly cleanup _nextDom. We don't need to persist the value because it

src/diff/index.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ export function diff(
5252
// If the previous diff bailed out, resume creating/hydrating.
5353
if (oldVNode._flags & MODE_SUSPENDED) {
5454
isHydrating = !!(oldVNode._flags & MODE_HYDRATE);
55-
if (oldVNode._excess) {
56-
excessDomChildren = oldVNode._excess;
57-
oldDom = newVNode._dom = oldVNode._dom = excessDomChildren[1];
55+
if (oldVNode._component._excess) {
56+
excessDomChildren = oldVNode._component._excess;
57+
oldDom = newVNode._dom = oldVNode._dom = excessDomChildren[0];
5858
} else {
5959
oldDom = newVNode._dom = oldVNode._dom;
6060
excessDomChildren = [oldDom];
@@ -283,26 +283,32 @@ export function diff(
283283
: MODE_HYDRATE;
284284

285285
let found = excessDomChildren.find(
286-
child => child && child.nodeType == 8 && child.data == '$s'
287-
),
288-
index = excessDomChildren.indexOf(found) + 1;
286+
child => child && child.nodeType == 8 && child.data == '$s'
287+
);
289288

290289
newVNode._dom = oldDom;
291290
if (found) {
292-
let commentMarkersToFind = 1;
293-
newVNode._excess = [found];
291+
let commentMarkersToFind = 1,
292+
index = excessDomChildren.indexOf(found) + 1;
293+
newVNode._component._excess = [];
294+
// Clear the comment marker so we don't reuse them for sibling
295+
// Suspenders.
294296
excessDomChildren[index - 1] = null;
297+
295298
while (commentMarkersToFind && index <= excessDomChildren.length) {
296299
const node = excessDomChildren[index];
297300
excessDomChildren[index] = null;
298301
index++;
299-
newVNode._excess.push(node);
302+
// node being undefined here would be a problem as it would
303+
// imply that we have a mismatch.
300304
if (node.nodeType == 8) {
301305
if (node.data == '$s') {
302306
commentMarkersToFind++;
303307
} else if (node.data == '/$s') {
304308
commentMarkersToFind--;
305309
}
310+
} else {
311+
newVNode._component._excess.push(node);
306312
}
307313
}
308314
} else {

src/internal.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ declare global {
145145
* The [first (for Fragments)] DOM child of a VNode
146146
*/
147147
_dom: PreactElement | null;
148-
_excess: PreactElement[] | null;
149148
/**
150149
* The last dom child of a Fragment, or components that return a Fragment
151150
*/
@@ -163,6 +162,7 @@ declare global {
163162
state: S; // Override Component["state"] to not be readonly for internal use, specifically Hooks
164163
base?: PreactElement;
165164

165+
_excess: PreactElement[] | null;
166166
_dirty: boolean;
167167
_force?: boolean;
168168
_renderCallbacks: Array<() => void>; // Only class components

0 commit comments

Comments
 (0)