diff --git a/.changeset/twelve-peaches-jam.md b/.changeset/twelve-peaches-jam.md new file mode 100644 index 000000000000..db5cf85bb514 --- /dev/null +++ b/.changeset/twelve-peaches-jam.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +perf: hoist variables which are not mutated or reassigned diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js index 7f656dc69042..6b26e2ff17c9 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js @@ -151,13 +151,27 @@ export const javascript_visitors_runes = { return { ...node, body }; }, - VariableDeclaration(node, { state, visit }) { + VariableDeclaration(node, { path, state, visit }) { const declarations = []; for (const declarator of node.declarations) { const init = unwrap_ts_expression(declarator.init); const rune = get_rune(init, state.scope); - if (!rune || rune === '$effect.active' || rune === '$effect.root' || rune === '$inspect') { + + if ( + !rune && + init != null && + declarator.id.type === 'Identifier' + ) { + const is_top_level = path.at(-1)?.type === 'Program'; + const binding = state.scope.owner(declarator.id.name)?.declarations.get(declarator.id.name); + if (is_top_level && binding && !binding.mutated && !binding.reassigned) { + state.hoisted.push(b.declaration('const', declarator.id, init)); + continue; + } + } + + if (!rune || rune === '$effect.active' || rune === '$effect.root' || rune === '$inspect') { if (init != null && is_hoistable_function(init)) { const hoistable_function = visit(init); state.hoisted.push( diff --git a/packages/svelte/tests/snapshot/samples/hoist-unmodified-var/_config.js b/packages/svelte/tests/snapshot/samples/hoist-unmodified-var/_config.js new file mode 100644 index 000000000000..f47bee71df87 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/hoist-unmodified-var/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({}); diff --git a/packages/svelte/tests/snapshot/samples/hoist-unmodified-var/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/hoist-unmodified-var/_expected/client/index.svelte.js new file mode 100644 index 000000000000..48d21db80459 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/hoist-unmodified-var/_expected/client/index.svelte.js @@ -0,0 +1,19 @@ +// index.svelte (Svelte VERSION) +// Note: compiler output will change before 5.0 is released! +import "svelte/internal/disclose-version"; +import * as $ from "svelte/internal"; + +const count = 0; +var frag = $.template(`
`); + +export default function Hoist_unmodified_var($$anchor, $$props) { + $.push($$props, true); + + /* Init */ + var p = $.open($$anchor, true, frag); + var text = $.child(p); + + text.nodeValue = $.stringify(count); + $.close($$anchor, p); + $.pop(); +} diff --git a/packages/svelte/tests/snapshot/samples/hoist-unmodified-var/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/hoist-unmodified-var/_expected/server/index.svelte.js new file mode 100644 index 000000000000..b9ae604e22ac --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/hoist-unmodified-var/_expected/server/index.svelte.js @@ -0,0 +1,12 @@ +// index.svelte (Svelte VERSION) +// Note: compiler output will change before 5.0 is released! +import * as $ from "svelte/internal/server"; + +export default function Hoist_unmodified_var($$payload, $$props) { + $.push(true); + + let count = 0; + + $$payload.out += `
${$.escape_text(count)}
`; + $.pop(); +} diff --git a/packages/svelte/tests/snapshot/samples/hoist-unmodified-var/index.svelte b/packages/svelte/tests/snapshot/samples/hoist-unmodified-var/index.svelte new file mode 100644 index 000000000000..fffe06f9af20 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/hoist-unmodified-var/index.svelte @@ -0,0 +1,7 @@ +{count}