From 548a6a7a98825bdfd9b54ee75f4b86bf9725ac45 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 20 Aug 2024 12:56:03 -0400 Subject: [PATCH 1/3] feat: omit trailing `$.sibling` calls where possible --- .../phases/3-transform/client/visitors/Fragment.js | 6 +++--- .../phases/3-transform/client/visitors/RegularElement.js | 2 +- .../phases/3-transform/client/visitors/shared/fragment.js | 7 ++++--- .../skip-static-subtree/_expected/client/index.svelte.js | 3 ++- .../skip-static-subtree/_expected/server/index.svelte.js | 2 +- .../snapshot/samples/skip-static-subtree/index.svelte | 7 +++++++ 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js index 7f5d1aa19d15..9dd1a87b27da 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js @@ -147,7 +147,7 @@ export function Fragment(node, context) { // special case — we can use `$.text` instead of creating a unique template const id = b.id(context.state.scope.generate('text')); - process_children(trimmed, () => id, false, { + process_children(trimmed, () => id, false, false, { ...context, state }); @@ -157,12 +157,12 @@ export function Fragment(node, context) { } else { if (is_standalone) { // no need to create a template, we can just use the existing block's anchor - process_children(trimmed, () => b.id('$$anchor'), false, { ...context, state }); + process_children(trimmed, () => b.id('$$anchor'), false, false, { ...context, state }); } else { /** @type {(is_text: boolean) => Expression} */ const expression = (is_text) => b.call('$.first_child', id, is_text && b.true); - process_children(trimmed, expression, false, { ...context, state }); + process_children(trimmed, expression, false, false, { ...context, state }); let flags = TEMPLATE_FRAGMENT; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index 8c19bb4d6612..ca621c9d813a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -340,7 +340,7 @@ export function RegularElement(node, context) { arg = b.member(arg, 'content'); } - process_children(trimmed, () => b.call('$.child', arg), true, { + process_children(trimmed, () => b.call('$.child', arg), true, needs_reset, { ...context, state: child_state }); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js index cbe027282710..fb3a5a82d528 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js @@ -11,10 +11,11 @@ import { build_template_literal, build_update } from './utils.js'; * corresponding template node references these updates are applied to. * @param {SvelteNode[]} nodes * @param {(is_text: boolean) => Expression} initial - * @param {boolean} is_element + * @param {boolean} is_element TODO get rid of this — we should be able to determine controlled status during analysis + * @param {boolean} needs_reset * @param {ComponentContext} context */ -export function process_children(nodes, initial, is_element, { visit, state }) { +export function process_children(nodes, initial, is_element, needs_reset, { visit, state }) { const within_bound_contenteditable = state.metadata.bound_contenteditable; let prev = initial; let skipped = 0; @@ -116,7 +117,7 @@ export function process_children(nodes, initial, is_element, { visit, state }) { // if there are trailing static text nodes/elements, // traverse to the last (n - 1) one when hydrating - if (skipped > 1) { + if (skipped > 1 && !needs_reset) { skipped -= 1; state.init.push(b.stmt(get_node(false))); } diff --git a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js index 5eacd941eaaa..dd02ded28b7e 100644 --- a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js @@ -1,7 +1,7 @@ import "svelte/internal/disclose-version"; import * as $ from "svelte/internal/client"; -var root = $.template(`

we don't need to traverse these nodes

or

these

ones

`, 1); +var root = $.template(`

we don't need to traverse these nodes

or

these

ones

these

trailing

nodes

can

be

completely

ignored

`, 1); export default function Skip_static_subtree($$anchor, $$props) { var fragment = root(); @@ -14,6 +14,7 @@ export default function Skip_static_subtree($$anchor, $$props) { var node = $.sibling(h1, 10); $.html(node, () => $$props.content, false, false); + $.sibling(node, 14); $.reset(main); $.template_effect(() => $.set_text(text, $$props.title)); $.append($$anchor, fragment); diff --git a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js index 8ea5fc9b13c3..a5061036e6a4 100644 --- a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js @@ -3,5 +3,5 @@ import * as $ from "svelte/internal/server"; export default function Skip_static_subtree($$payload, $$props) { let { title, content } = $$props; - $$payload.out += `

${$.escape(title)}

we don't need to traverse these nodes

or

these

ones

${$.html(content)}
`; + $$payload.out += `

${$.escape(title)}

we don't need to traverse these nodes

or

these

ones

${$.html(content)}

these

trailing

nodes

can

be

completely

ignored

`; } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/skip-static-subtree/index.svelte b/packages/svelte/tests/snapshot/samples/skip-static-subtree/index.svelte index 001196a879bc..7a3348de032b 100644 --- a/packages/svelte/tests/snapshot/samples/skip-static-subtree/index.svelte +++ b/packages/svelte/tests/snapshot/samples/skip-static-subtree/index.svelte @@ -18,4 +18,11 @@

these

ones

{@html content} +

these

+

trailing

+

nodes

+

can

+

be

+

completely

+

ignored

From 38468bbb38c2aefcbc1385744be61329c1788d5d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 20 Aug 2024 12:56:17 -0400 Subject: [PATCH 2/3] changeset --- .changeset/flat-points-decide.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/flat-points-decide.md diff --git a/.changeset/flat-points-decide.md b/.changeset/flat-points-decide.md new file mode 100644 index 000000000000..1910819993f4 --- /dev/null +++ b/.changeset/flat-points-decide.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: omit trailing `$.sibling` calls where possible From 5f4e2b24320040a0ad1ff9b7bffd337da0a1cfca Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 20 Aug 2024 13:02:45 -0400 Subject: [PATCH 3/3] update test --- .../samples/skip-static-subtree/_expected/client/index.svelte.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js index dd02ded28b7e..499cebd6ef95 100644 --- a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js @@ -14,7 +14,6 @@ export default function Skip_static_subtree($$anchor, $$props) { var node = $.sibling(h1, 10); $.html(node, () => $$props.content, false, false); - $.sibling(node, 14); $.reset(main); $.template_effect(() => $.set_text(text, $$props.title)); $.append($$anchor, fragment);