Skip to content

Commit 2032049

Browse files
authored
chore: Reduce hydration comment for {:else if} (#15250)
* rewrite else/if hydration * fix: bad index * reduce args on if_block() * restore the block * don't use isNan() * changeset
1 parent 474c588 commit 2032049

File tree

5 files changed

+71
-34
lines changed

5 files changed

+71
-34
lines changed

Diff for: .changeset/tough-steaks-travel.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': minor
3+
---
4+
5+
chore: Reduce hydration comment for {:else if}

Diff for: packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ export function Fragment(node, context) {
4848
const is_single_element = trimmed.length === 1 && trimmed[0].type === 'RegularElement';
4949
const is_single_child_not_needing_template =
5050
trimmed.length === 1 &&
51-
(trimmed[0].type === 'SvelteFragment' || trimmed[0].type === 'TitleElement');
51+
(trimmed[0].type === 'SvelteFragment' ||
52+
trimmed[0].type === 'TitleElement' ||
53+
(trimmed[0].type === 'IfBlock' && trimmed[0].elseif));
5254

5355
const template_name = context.state.scope.root.unique('root'); // TODO infer name from parent
5456

Diff for: packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js

+13-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { BlockStatement, Expression } from 'estree' */
1+
/** @import { BlockStatement, Expression, Identifier } from 'estree' */
22
/** @import { AST } from '#compiler' */
33
/** @import { ComponentContext } from '../types' */
44
import * as b from '../../../../utils/builders.js';
@@ -19,28 +19,29 @@ export function IfBlock(node, context) {
1919
let alternate_id;
2020

2121
if (node.alternate) {
22-
const alternate = /** @type {BlockStatement} */ (context.visit(node.alternate));
2322
alternate_id = context.state.scope.generate('alternate');
24-
statements.push(b.var(b.id(alternate_id), b.arrow([b.id('$$anchor')], alternate)));
23+
const alternate = /** @type {BlockStatement} */ (context.visit(node.alternate));
24+
const nodes = node.alternate.nodes;
25+
26+
let alternate_args = [b.id('$$anchor')];
27+
if (nodes.length === 1 && nodes[0].type === 'IfBlock' && nodes[0].elseif) {
28+
alternate_args.push(b.id('$$elseif'));
29+
}
30+
31+
statements.push(b.var(b.id(alternate_id), b.arrow(alternate_args, alternate)));
2532
}
2633

2734
/** @type {Expression[]} */
2835
const args = [
29-
context.state.node,
36+
node.elseif ? b.id('$$anchor') : context.state.node,
3037
b.arrow(
3138
[b.id('$$render')],
3239
b.block([
3340
b.if(
3441
/** @type {Expression} */ (context.visit(node.test)),
3542
b.stmt(b.call(b.id('$$render'), b.id(consequent_id))),
3643
alternate_id
37-
? b.stmt(
38-
b.call(
39-
b.id('$$render'),
40-
b.id(alternate_id),
41-
node.alternate ? b.literal(false) : undefined
42-
)
43-
)
44+
? b.stmt(b.call(b.id('$$render'), b.id(alternate_id), b.literal(false)))
4445
: undefined
4546
)
4647
])
@@ -69,7 +70,7 @@ export function IfBlock(node, context) {
6970
// ...even though they're logically equivalent. In the first case, the
7071
// transition will only play when `y` changes, but in the second it
7172
// should play when `x` or `y` change — both are considered 'local'
72-
args.push(b.literal(true));
73+
args.push(b.id('$$elseif'));
7374
}
7475

7576
statements.push(b.stmt(b.call('$.if', ...args)));
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { BlockStatement, Expression } from 'estree' */
1+
/** @import { BlockStatement, Expression, IfStatement } from 'estree' */
22
/** @import { AST } from '#compiler' */
33
/** @import { ComponentContext } from '../types.js' */
44
import { BLOCK_OPEN_ELSE } from '../../../../../internal/server/hydration.js';
@@ -10,19 +10,29 @@ import { block_close, block_open } from './shared/utils.js';
1010
* @param {ComponentContext} context
1111
*/
1212
export function IfBlock(node, context) {
13-
const test = /** @type {Expression} */ (context.visit(node.test));
14-
1513
const consequent = /** @type {BlockStatement} */ (context.visit(node.consequent));
14+
consequent.body.unshift(b.stmt(b.assignment('+=', b.id('$$payload.out'), block_open)));
15+
let if_statement = b.if(/** @type {Expression} */ (context.visit(node.test)), consequent);
1616

17-
const alternate = node.alternate
18-
? /** @type {BlockStatement} */ (context.visit(node.alternate))
19-
: b.block([]);
17+
context.state.template.push(if_statement, block_close);
2018

21-
consequent.body.unshift(b.stmt(b.assignment('+=', b.id('$$payload.out'), block_open)));
19+
let index = 1;
20+
let alt = node.alternate;
21+
while (alt && alt.nodes.length === 1 && alt.nodes[0].type === 'IfBlock' && alt.nodes[0].elseif) {
22+
const elseif = alt.nodes[0];
23+
const alternate = /** @type {BlockStatement} */ (context.visit(elseif.consequent));
24+
alternate.body.unshift(
25+
b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(`<!--[${index++}-->`)))
26+
);
27+
if_statement = if_statement.alternate = b.if(
28+
/** @type {Expression} */ (context.visit(elseif.test)),
29+
alternate
30+
);
31+
alt = elseif.alternate;
32+
}
2233

23-
alternate.body.unshift(
34+
if_statement.alternate = alt ? /** @type {BlockStatement} */ (context.visit(alt)) : b.block([]);
35+
if_statement.alternate.body.unshift(
2436
b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_OPEN_ELSE)))
2537
);
26-
27-
context.state.template.push(b.if(test, consequent, alternate), block_close);
2838
}

Diff for: packages/svelte/src/internal/client/dom/blocks/if.js

+30-11
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ import {
99
set_hydrating
1010
} from '../hydration.js';
1111
import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js';
12-
import { HYDRATION_START_ELSE, UNINITIALIZED } from '../../../../constants.js';
12+
import { HYDRATION_START, HYDRATION_START_ELSE, UNINITIALIZED } from '../../../../constants.js';
1313

1414
/**
1515
* @param {TemplateNode} node
16-
* @param {(branch: (fn: (anchor: Node) => void, flag?: boolean) => void) => void} fn
17-
* @param {boolean} [elseif] True if this is an `{:else if ...}` block rather than an `{#if ...}`, as that affects which transitions are considered 'local'
16+
* @param {(branch: (fn: (anchor: Node, elseif?: [number,number]) => void, flag?: boolean) => void) => void} fn
17+
* @param {[number,number]} [elseif]
1818
* @returns {void}
1919
*/
20-
export function if_block(node, fn, elseif = false) {
21-
if (hydrating) {
20+
export function if_block(node, fn, [root_index, hydrate_index] = [0, 0]) {
21+
if (hydrating && root_index === 0) {
2222
hydrate_next();
2323
}
2424

@@ -33,26 +33,44 @@ export function if_block(node, fn, elseif = false) {
3333
/** @type {UNINITIALIZED | boolean | null} */
3434
var condition = UNINITIALIZED;
3535

36-
var flags = elseif ? EFFECT_TRANSPARENT : 0;
36+
var flags = root_index > 0 ? EFFECT_TRANSPARENT : 0;
3737

3838
var has_branch = false;
3939

40-
const set_branch = (/** @type {(anchor: Node) => void} */ fn, flag = true) => {
40+
const set_branch = (
41+
/** @type {(anchor: Node, elseif?: [number,number]) => void} */ fn,
42+
flag = true
43+
) => {
4144
has_branch = true;
4245
update_branch(flag, fn);
4346
};
4447

4548
const update_branch = (
4649
/** @type {boolean | null} */ new_condition,
47-
/** @type {null | ((anchor: Node) => void)} */ fn
50+
/** @type {null | ((anchor: Node, elseif?: [number,number]) => void)} */ fn
4851
) => {
4952
if (condition === (condition = new_condition)) return;
5053

5154
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
5255
let mismatch = false;
5356

54-
if (hydrating) {
55-
const is_else = /** @type {Comment} */ (anchor).data === HYDRATION_START_ELSE;
57+
if (hydrating && hydrate_index !== -1) {
58+
if (root_index === 0) {
59+
const data = /** @type {Comment} */ (anchor).data;
60+
if (data === HYDRATION_START) {
61+
hydrate_index = 0;
62+
} else if (data === HYDRATION_START_ELSE) {
63+
hydrate_index = Infinity;
64+
} else {
65+
hydrate_index = parseInt(data.substring(1));
66+
if (hydrate_index !== hydrate_index) {
67+
// if hydrate_index is NaN
68+
// we set an invalid index to force mismatch
69+
hydrate_index = condition ? Infinity : -1;
70+
}
71+
}
72+
}
73+
const is_else = hydrate_index > root_index;
5674

5775
if (!!condition === is_else) {
5876
// Hydration mismatch: remove everything inside the anchor and start fresh.
@@ -62,6 +80,7 @@ export function if_block(node, fn, elseif = false) {
6280
set_hydrate_node(anchor);
6381
set_hydrating(false);
6482
mismatch = true;
83+
hydrate_index = -1; // ignore hydration in next else if
6584
}
6685
}
6786

@@ -81,7 +100,7 @@ export function if_block(node, fn, elseif = false) {
81100
if (alternate_effect) {
82101
resume_effect(alternate_effect);
83102
} else if (fn) {
84-
alternate_effect = branch(() => fn(anchor));
103+
alternate_effect = branch(() => fn(anchor, [root_index + 1, hydrate_index]));
85104
}
86105

87106
if (consequent_effect) {

0 commit comments

Comments
 (0)