diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 3c93f0fefe..9b75c73cbb 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -535,11 +535,12 @@ export function should_proxy_or_freeze(node) { } /** + * Whether a variable can be referenced directly from template string. * @param {import('#compiler').Binding | undefined} binding * @param {string} name * @returns {boolean} */ -export function is_hoistable_declaration(binding, name) { +export function can_inline_variable(binding, name) { // TODO: allow object expressions that are not passed to functions or components as props // and expressions as long as they do not reference non-hoistable variables return ( @@ -549,8 +550,18 @@ export function is_hoistable_declaration(binding, name) { !binding.mutated && !binding.reassigned && binding.initial?.type === 'Literal' && - binding.scope.has_parent() && // i.e. not when context="module" !binding.scope.declared_in_outer_scope(name) && !GlobalBindings.has(name) ); } + +/** + * @param {import('#compiler').Binding | undefined} binding + * @param {string} name + * @returns {boolean} + */ +export function can_hoist_declaration(binding, name) { + return ( + can_inline_variable(binding, name) && !!binding && binding.scope.has_parent() // i.e. not when context="module" + ); +} diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js index 5271eac0e6..1bec065d5a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js @@ -2,9 +2,9 @@ import { is_hoistable_function } from '../../utils.js'; import * as b from '../../../../utils/builders.js'; import { extract_paths } from '../../../../utils/ast.js'; import { + can_hoist_declaration, create_state_declarators, get_prop_source, - is_hoistable_declaration, serialize_get_binding } from '../utils.js'; @@ -29,7 +29,7 @@ export const javascript_visitors_legacy = { .owner(declarator.id.name) ?.declarations.get(declarator.id.name); - if (is_hoistable_declaration(binding, declarator.id.name)) { + if (can_hoist_declaration(binding, declarator.id.name)) { state.hoisted.push(b.declaration('const', declarator.id, init)); continue; } 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 1c097d3d84..e6724f7632 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 @@ -3,9 +3,9 @@ import { is_hoistable_function, transform_inspect_rune } from '../../utils.js'; import * as b from '../../../../utils/builders.js'; import * as assert from '../../../../utils/assert.js'; import { + can_hoist_declaration, create_state_declarators, get_prop_source, - is_hoistable_declaration, should_proxy_or_freeze } from '../utils.js'; import { unwrap_ts_expression } from '../../../../utils/ast.js'; @@ -164,7 +164,7 @@ export const javascript_visitors_runes = { if (init != null && declarator.id.type === 'Identifier') { const binding = state.scope.owner(declarator.id.name)?.declarations.get(declarator.id.name); - if (is_hoistable_declaration(binding, declarator.id.name)) { + if (can_hoist_declaration(binding, declarator.id.name)) { state.hoisted.push(b.declaration('const', declarator.id, init)); continue; } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 0f6f88ffe1..a1b0fea8b8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -17,9 +17,9 @@ import { is_custom_element_node, is_element_node } from '../../../nodes.js'; import * as b from '../../../../utils/builders.js'; import { error } from '../../../../errors.js'; import { + can_hoist_declaration, function_visitor, get_assignment_value, - is_hoistable_declaration, serialize_get_binding, serialize_set_binding } from '../utils.js'; @@ -564,19 +564,21 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu } }; - let hoistable = false; + let can_inline = false; if (Array.isArray(attribute.value)) { for (let value of attribute.value) { if (value.type === 'ExpressionTag' && value.expression.type === 'Identifier') { const binding = context.state.scope .owner(value.expression.name) ?.declarations.get(value.expression.name); - hoistable ||= is_hoistable_declaration(binding, value.expression.name); + // TODO: use can_inline_variable instead + // and insert template strings after module variables + can_inline ||= can_hoist_declaration(binding, value.expression.name); } } } - if (attribute.metadata.dynamic && !hoistable) { + if (attribute.metadata.dynamic && !can_inline) { const id = state.scope.generate(`${node_id.name}_${name}`); serialize_update_assignment( state, @@ -588,7 +590,7 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu ); return true; } else { - if (hoistable) { + if (can_inline) { push_template_quasi(context.state, ` ${name}="`); push_template_expression(context.state, grouped_value); push_template_quasi(context.state, `"`);