diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js index 401cfde42832..e37cdb2bea7a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js @@ -1,9 +1,9 @@ -/** @import { BlockStatement, Expression, ExpressionStatement, Literal, Property } from 'estree' */ +/** @import { BlockStatement, Expression, ExpressionStatement, Literal, Property, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import * as b from '#compiler/builders'; import { build_attribute_value } from './shared/element.js'; -import { memoize_expression } from './shared/utils.js'; +import { Memoizer } from './shared/utils.js'; /** * @param {AST.SlotElement} node @@ -22,7 +22,7 @@ export function SlotElement(node, context) { /** @type {ExpressionStatement[]} */ const lets = []; - let is_default = true; + const memoizer = new Memoizer(); let name = b.literal('default'); @@ -33,12 +33,11 @@ export function SlotElement(node, context) { const { value, has_state } = build_attribute_value( attribute.value, context, - (value, metadata) => (metadata.has_call ? memoize_expression(context.state, value) : value) + (value, metadata) => (metadata.has_call ? b.call('$.get', memoizer.add(value)) : value) ); if (attribute.name === 'name') { name = /** @type {Literal} */ (value); - is_default = false; } else if (attribute.name !== 'slot') { if (has_state) { props.push(b.get(attribute.name, [b.return(value)])); @@ -51,9 +50,14 @@ export function SlotElement(node, context) { } } + memoizer.apply(); + // Let bindings first, they can be used on attributes context.state.init.push(...lets); + /** @type {Statement[]} */ + const statements = memoizer.deriveds(context.state.analysis.runes); + const props_expression = spreads.length === 0 ? b.object(props) : b.call('$.spread_props', b.object(props), ...spreads); @@ -62,14 +66,9 @@ export function SlotElement(node, context) { ? b.null : b.arrow([b.id('$$anchor')], /** @type {BlockStatement} */ (context.visit(node.fragment))); - const slot = b.call( - '$.slot', - context.state.node, - b.id('$$props'), - name, - props_expression, - fallback + statements.push( + b.stmt(b.call('$.slot', context.state.node, b.id('$$props'), name, props_expression, fallback)) ); - context.state.init.push(b.stmt(slot)); + context.state.init.push(statements.length === 1 ? statements[0] : b.block(statements)); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js index aa3704b50b30..6b52db55ba24 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js @@ -4,12 +4,7 @@ import { dev, is_ignored } from '../../../../../state.js'; import { get_attribute_chunks, object } from '../../../../../utils/ast.js'; import * as b from '#compiler/builders'; -import { - build_bind_this, - memoize_expression, - validate_binding, - add_svelte_meta -} from '../shared/utils.js'; +import { add_svelte_meta, build_bind_this, Memoizer, validate_binding } from '../shared/utils.js'; import { build_attribute_value } from '../shared/element.js'; import { build_event_handler } from './events.js'; import { determine_slot } from '../../../../../utils/slot.js'; @@ -48,6 +43,8 @@ export function build_component(node, component_name, context) { /** @type {Record} */ const events = {}; + const memoizer = new Memoizer(); + /** @type {Property[]} */ const custom_css_props = []; @@ -133,15 +130,13 @@ export function build_component(node, component_name, context) { } else if (attribute.type === 'SpreadAttribute') { const expression = /** @type {Expression} */ (context.visit(attribute)); if (attribute.metadata.expression.has_state) { - let value = expression; - - if (attribute.metadata.expression.has_call) { - const id = b.id(context.state.scope.generate('spread_element')); - context.state.init.push(b.var(id, b.call('$.derived', b.thunk(value)))); - value = b.call('$.get', id); - } - - props_and_spreads.push(b.thunk(value)); + props_and_spreads.push( + b.thunk( + attribute.metadata.expression.has_call + ? b.call('$.get', memoizer.add(expression)) + : expression + ) + ); } else { props_and_spreads.push(expression); } @@ -150,10 +145,10 @@ export function build_component(node, component_name, context) { custom_css_props.push( b.init( attribute.name, - build_attribute_value(attribute.value, context, (value, metadata) => + build_attribute_value(attribute.value, context, (value, metadata) => { // TODO put the derived in the local block - metadata.has_call ? memoize_expression(context.state, value) : value - ).value + return metadata.has_call ? b.call('$.get', memoizer.add(value)) : value; + }).value ) ); continue; @@ -184,7 +179,7 @@ export function build_component(node, component_name, context) { ); }); - return should_wrap_in_derived ? memoize_expression(context.state, value) : value; + return should_wrap_in_derived ? b.call('$.get', memoizer.add(value)) : value; } ); @@ -444,7 +439,7 @@ export function build_component(node, component_name, context) { }; } - const statements = [...snippet_declarations]; + const statements = [...snippet_declarations, ...memoizer.deriveds(context.state.analysis.runes)]; if (is_component_dynamic) { const prev = fn; @@ -492,5 +487,7 @@ export function build_component(node, component_name, context) { statements.push(add_svelte_meta(fn(anchor), node, 'component', { componentTag: node.name })); } + memoizer.apply(); + return statements.length > 1 ? b.block(statements) : statements[0]; } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js index de74fede0cef..eed806339d14 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js @@ -8,17 +8,7 @@ import { sanitize_template_string } from '../../../../../utils/sanitize_template import { regex_is_valid_identifier } from '../../../../patterns.js'; import is_reference from 'is-reference'; import { dev, is_ignored, locator, component_name } from '../../../../../state.js'; -import { build_getter, create_derived } from '../../utils.js'; - -/** - * @param {ComponentClientTransformState} state - * @param {Expression} value - */ -export function memoize_expression(state, value) { - const id = b.id(state.scope.generate('expression')); - state.init.push(b.const(id, create_derived(state, b.thunk(value)))); - return b.call('$.get', id); -} +import { build_getter } from '../../utils.js'; /** * A utility for extracting complex expressions (such as call expressions)