From 9c69cd83f97ddd3184f758d846be3108046bd253 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 4 Dec 2024 12:31:48 +0000 Subject: [PATCH 1/6] chore: make if blocks dead code eliminable --- .changeset/beige-windows-happen.md | 5 +++ .../3-transform/client/visitors/IfBlock.js | 34 ++++++++++++------ .../src/internal/client/dom/blocks/if.js | 36 +++++++++++++------ .../samples/bind-this-no-state/main.svelte | 4 +-- 4 files changed, 57 insertions(+), 22 deletions(-) create mode 100644 .changeset/beige-windows-happen.md diff --git a/.changeset/beige-windows-happen.md b/.changeset/beige-windows-happen.md new file mode 100644 index 000000000000..82c9588ffb0b --- /dev/null +++ b/.changeset/beige-windows-happen.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: make if blocks dead code eliminable diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js index 0aa6e5d24a53..65b8d09d6fab 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js @@ -11,21 +11,35 @@ export function IfBlock(node, context) { context.state.template.push(''); const consequent = /** @type {BlockStatement} */ (context.visit(node.consequent)); + const consequent_id = context.state.scope.generate('consequent'); + context.state.init.push(b.var(b.id(consequent_id), b.arrow([b.id('$$anchor')], consequent))); + + let alternate_id; + + if (node.alternate) { + const alternate = /** @type {BlockStatement} */ (context.visit(node.alternate)); + alternate_id = context.state.scope.generate('alternate'); + context.state.init.push(b.var(b.id(alternate_id), b.arrow([b.id('$$anchor')], alternate))); + } + + /** @type {Expression[]} */ const args = [ context.state.node, - b.thunk(/** @type {Expression} */ (context.visit(node.test))), - b.arrow([b.id('$$anchor')], consequent) + b.arrow( + [b.id('$$branch')], + b.block([ + b.if( + /** @type {Expression} */ (context.visit(node.test)), + b.stmt(b.call(b.id('$$branch'), b.literal(0), b.id(consequent_id))), + alternate_id + ? b.stmt(b.call(b.id('$$branch'), b.literal(1), b.id(alternate_id))) + : undefined + ) + ]) + ) ]; - if (node.alternate || node.elseif) { - args.push( - node.alternate - ? b.arrow([b.id('$$anchor')], /** @type {BlockStatement} */ (context.visit(node.alternate))) - : b.literal(null) - ); - } - if (node.elseif) { // We treat this... // diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index 4d8e9412d3e2..734cd7a0ae41 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -13,13 +13,11 @@ import { HYDRATION_START_ELSE } from '../../../../constants.js'; /** * @param {TemplateNode} node - * @param {() => boolean} get_condition - * @param {(anchor: Node) => void} consequent_fn - * @param {null | ((anchor: Node) => void)} [alternate_fn] + * @param {(branch: (flag: 0 | 1, fn: (anchor: Node) => void) => 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' * @returns {void} */ -export function if_block(node, get_condition, consequent_fn, alternate_fn = null, elseif = false) { +export function if_block(node, fn, elseif = false) { if (hydrating) { hydrate_next(); } @@ -37,8 +35,18 @@ export function if_block(node, get_condition, consequent_fn, alternate_fn = null var flags = elseif ? EFFECT_TRANSPARENT : 0; - block(() => { - if (condition === (condition = !!get_condition())) return; + var has_branch = false; + + const set_branch = (/** @type {0 | 1} */ flag, /** @type {(anchor: Node) => void} */ fn) => { + has_branch = true; + update_branch(flag === 0, fn); + }; + + const update_branch = ( + /** @type {boolean | null} */ new_condition, + /** @type {null | ((anchor: Node) => void)} */ fn + ) => { + 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 */ let mismatch = false; @@ -60,8 +68,8 @@ export function if_block(node, get_condition, consequent_fn, alternate_fn = null if (condition) { if (consequent_effect) { resume_effect(consequent_effect); - } else { - consequent_effect = branch(() => consequent_fn(anchor)); + } else if (fn) { + consequent_effect = branch(() => fn(anchor)); } if (alternate_effect) { @@ -72,8 +80,8 @@ export function if_block(node, get_condition, consequent_fn, alternate_fn = null } else { if (alternate_effect) { resume_effect(alternate_effect); - } else if (alternate_fn) { - alternate_effect = branch(() => alternate_fn(anchor)); + } else if (fn) { + alternate_effect = branch(() => fn(anchor)); } if (consequent_effect) { @@ -87,6 +95,14 @@ export function if_block(node, get_condition, consequent_fn, alternate_fn = null // continue in hydration mode set_hydrating(true); } + }; + + block(() => { + has_branch = false; + fn(set_branch); + if (!has_branch) { + update_branch(null, null); + } }, flags); if (hydrating) { diff --git a/packages/svelte/tests/runtime-runes/samples/bind-this-no-state/main.svelte b/packages/svelte/tests/runtime-runes/samples/bind-this-no-state/main.svelte index be400d1d9cc7..390653818474 100644 --- a/packages/svelte/tests/runtime-runes/samples/bind-this-no-state/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/bind-this-no-state/main.svelte @@ -1,6 +1,6 @@