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
pull/15402/head
adiGuba 6 months ago committed by GitHub
parent 474c588067
commit 2032049e47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': minor
---
chore: Reduce hydration comment for {:else if}

@ -48,7 +48,9 @@ export function Fragment(node, context) {
const is_single_element = trimmed.length === 1 && trimmed[0].type === 'RegularElement'; const is_single_element = trimmed.length === 1 && trimmed[0].type === 'RegularElement';
const is_single_child_not_needing_template = const is_single_child_not_needing_template =
trimmed.length === 1 && trimmed.length === 1 &&
(trimmed[0].type === 'SvelteFragment' || trimmed[0].type === 'TitleElement'); (trimmed[0].type === 'SvelteFragment' ||
trimmed[0].type === 'TitleElement' ||
(trimmed[0].type === 'IfBlock' && trimmed[0].elseif));
const template_name = context.state.scope.root.unique('root'); // TODO infer name from parent const template_name = context.state.scope.root.unique('root'); // TODO infer name from parent

@ -1,4 +1,4 @@
/** @import { BlockStatement, Expression } from 'estree' */ /** @import { BlockStatement, Expression, Identifier } from 'estree' */
/** @import { AST } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types' */ /** @import { ComponentContext } from '../types' */
import * as b from '../../../../utils/builders.js'; import * as b from '../../../../utils/builders.js';
@ -19,14 +19,21 @@ export function IfBlock(node, context) {
let alternate_id; let alternate_id;
if (node.alternate) { if (node.alternate) {
const alternate = /** @type {BlockStatement} */ (context.visit(node.alternate));
alternate_id = context.state.scope.generate('alternate'); alternate_id = context.state.scope.generate('alternate');
statements.push(b.var(b.id(alternate_id), b.arrow([b.id('$$anchor')], alternate))); const alternate = /** @type {BlockStatement} */ (context.visit(node.alternate));
const nodes = node.alternate.nodes;
let alternate_args = [b.id('$$anchor')];
if (nodes.length === 1 && nodes[0].type === 'IfBlock' && nodes[0].elseif) {
alternate_args.push(b.id('$$elseif'));
}
statements.push(b.var(b.id(alternate_id), b.arrow(alternate_args, alternate)));
} }
/** @type {Expression[]} */ /** @type {Expression[]} */
const args = [ const args = [
context.state.node, node.elseif ? b.id('$$anchor') : context.state.node,
b.arrow( b.arrow(
[b.id('$$render')], [b.id('$$render')],
b.block([ b.block([
@ -34,13 +41,7 @@ export function IfBlock(node, context) {
/** @type {Expression} */ (context.visit(node.test)), /** @type {Expression} */ (context.visit(node.test)),
b.stmt(b.call(b.id('$$render'), b.id(consequent_id))), b.stmt(b.call(b.id('$$render'), b.id(consequent_id))),
alternate_id alternate_id
? b.stmt( ? b.stmt(b.call(b.id('$$render'), b.id(alternate_id), b.literal(false)))
b.call(
b.id('$$render'),
b.id(alternate_id),
node.alternate ? b.literal(false) : undefined
)
)
: undefined : undefined
) )
]) ])
@ -69,7 +70,7 @@ export function IfBlock(node, context) {
// ...even though they're logically equivalent. In the first case, the // ...even though they're logically equivalent. In the first case, the
// transition will only play when `y` changes, but in the second it // transition will only play when `y` changes, but in the second it
// should play when `x` or `y` change — both are considered 'local' // should play when `x` or `y` change — both are considered 'local'
args.push(b.literal(true)); args.push(b.id('$$elseif'));
} }
statements.push(b.stmt(b.call('$.if', ...args))); statements.push(b.stmt(b.call('$.if', ...args)));

@ -1,4 +1,4 @@
/** @import { BlockStatement, Expression } from 'estree' */ /** @import { BlockStatement, Expression, IfStatement } from 'estree' */
/** @import { AST } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */ /** @import { ComponentContext } from '../types.js' */
import { BLOCK_OPEN_ELSE } from '../../../../../internal/server/hydration.js'; import { BLOCK_OPEN_ELSE } from '../../../../../internal/server/hydration.js';
@ -10,19 +10,29 @@ import { block_close, block_open } from './shared/utils.js';
* @param {ComponentContext} context * @param {ComponentContext} context
*/ */
export function IfBlock(node, context) { export function IfBlock(node, context) {
const test = /** @type {Expression} */ (context.visit(node.test));
const consequent = /** @type {BlockStatement} */ (context.visit(node.consequent)); const consequent = /** @type {BlockStatement} */ (context.visit(node.consequent));
const alternate = node.alternate
? /** @type {BlockStatement} */ (context.visit(node.alternate))
: b.block([]);
consequent.body.unshift(b.stmt(b.assignment('+=', b.id('$$payload.out'), block_open))); consequent.body.unshift(b.stmt(b.assignment('+=', b.id('$$payload.out'), block_open)));
let if_statement = b.if(/** @type {Expression} */ (context.visit(node.test)), consequent);
context.state.template.push(if_statement, block_close);
let index = 1;
let alt = node.alternate;
while (alt && alt.nodes.length === 1 && alt.nodes[0].type === 'IfBlock' && alt.nodes[0].elseif) {
const elseif = alt.nodes[0];
const alternate = /** @type {BlockStatement} */ (context.visit(elseif.consequent));
alternate.body.unshift( alternate.body.unshift(
b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_OPEN_ELSE))) b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(`<!--[${index++}-->`)))
); );
if_statement = if_statement.alternate = b.if(
/** @type {Expression} */ (context.visit(elseif.test)),
alternate
);
alt = elseif.alternate;
}
context.state.template.push(b.if(test, consequent, alternate), block_close); if_statement.alternate = alt ? /** @type {BlockStatement} */ (context.visit(alt)) : b.block([]);
if_statement.alternate.body.unshift(
b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_OPEN_ELSE)))
);
} }

@ -9,16 +9,16 @@ import {
set_hydrating set_hydrating
} from '../hydration.js'; } from '../hydration.js';
import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js'; import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js';
import { HYDRATION_START_ELSE, UNINITIALIZED } from '../../../../constants.js'; import { HYDRATION_START, HYDRATION_START_ELSE, UNINITIALIZED } from '../../../../constants.js';
/** /**
* @param {TemplateNode} node * @param {TemplateNode} node
* @param {(branch: (fn: (anchor: Node) => void, flag?: boolean) => void) => void} fn * @param {(branch: (fn: (anchor: Node, elseif?: [number,number]) => void, flag?: boolean) => void) => void} fn
* @param {boolean} [elseif] True if this is an `{:else if ...}` block rather than an `{#if ...}`, as that affects which transitions are considered 'local' * @param {[number,number]} [elseif]
* @returns {void} * @returns {void}
*/ */
export function if_block(node, fn, elseif = false) { export function if_block(node, fn, [root_index, hydrate_index] = [0, 0]) {
if (hydrating) { if (hydrating && root_index === 0) {
hydrate_next(); hydrate_next();
} }
@ -33,26 +33,44 @@ export function if_block(node, fn, elseif = false) {
/** @type {UNINITIALIZED | boolean | null} */ /** @type {UNINITIALIZED | boolean | null} */
var condition = UNINITIALIZED; var condition = UNINITIALIZED;
var flags = elseif ? EFFECT_TRANSPARENT : 0; var flags = root_index > 0 ? EFFECT_TRANSPARENT : 0;
var has_branch = false; var has_branch = false;
const set_branch = (/** @type {(anchor: Node) => void} */ fn, flag = true) => { const set_branch = (
/** @type {(anchor: Node, elseif?: [number,number]) => void} */ fn,
flag = true
) => {
has_branch = true; has_branch = true;
update_branch(flag, fn); update_branch(flag, fn);
}; };
const update_branch = ( const update_branch = (
/** @type {boolean | null} */ new_condition, /** @type {boolean | null} */ new_condition,
/** @type {null | ((anchor: Node) => void)} */ fn /** @type {null | ((anchor: Node, elseif?: [number,number]) => void)} */ fn
) => { ) => {
if (condition === (condition = new_condition)) return; if (condition === (condition = new_condition)) return;
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */ /** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false; let mismatch = false;
if (hydrating) { if (hydrating && hydrate_index !== -1) {
const is_else = /** @type {Comment} */ (anchor).data === HYDRATION_START_ELSE; if (root_index === 0) {
const data = /** @type {Comment} */ (anchor).data;
if (data === HYDRATION_START) {
hydrate_index = 0;
} else if (data === HYDRATION_START_ELSE) {
hydrate_index = Infinity;
} else {
hydrate_index = parseInt(data.substring(1));
if (hydrate_index !== hydrate_index) {
// if hydrate_index is NaN
// we set an invalid index to force mismatch
hydrate_index = condition ? Infinity : -1;
}
}
}
const is_else = hydrate_index > root_index;
if (!!condition === is_else) { if (!!condition === is_else) {
// Hydration mismatch: remove everything inside the anchor and start fresh. // Hydration mismatch: remove everything inside the anchor and start fresh.
@ -62,6 +80,7 @@ export function if_block(node, fn, elseif = false) {
set_hydrate_node(anchor); set_hydrate_node(anchor);
set_hydrating(false); set_hydrating(false);
mismatch = true; mismatch = true;
hydrate_index = -1; // ignore hydration in next else if
} }
} }
@ -81,7 +100,7 @@ export function if_block(node, fn, elseif = false) {
if (alternate_effect) { if (alternate_effect) {
resume_effect(alternate_effect); resume_effect(alternate_effect);
} else if (fn) { } else if (fn) {
alternate_effect = branch(() => fn(anchor)); alternate_effect = branch(() => fn(anchor, [root_index + 1, hydrate_index]));
} }
if (consequent_effect) { if (consequent_effect) {

Loading…
Cancel
Save