From 18cef800c991c92c715d470dbf0db66a0c00e12c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 20 Sep 2024 13:39:02 -0400 Subject: [PATCH] DRY --- .../client/visitors/RegularElement.js | 86 +----------------- .../client/visitors/SvelteElement.js | 89 +------------------ .../client/visitors/shared/element.js | 85 +++++++++++++++++- 3 files changed, 90 insertions(+), 170 deletions(-) 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 0ff3eb5246..911991b90a 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 @@ -24,7 +24,8 @@ import { get_attribute_name, build_attribute_value, build_class_directives, - build_style_directives + build_style_directives, + build_set_attributes } from './shared/element.js'; import { process_children } from './shared/fragment.js'; import { @@ -208,7 +209,7 @@ export function RegularElement(node, context) { if (has_spread) { const attributes_id = b.id(context.state.scope.generate('attributes')); - build_element_spread_attributes( + build_set_attributes( attributes, context, node, @@ -480,87 +481,6 @@ function setup_select_synchronization(value_binding, context) { ); } -/** - * @param {Array} attributes - * @param {ComponentContext} context - * @param {AST.RegularElement} element - * @param {Identifier} element_id - * @param {Identifier} attributes_id - * @param {false | Expression} preserve_attribute_case - * @param {false | Expression} is_custom_element - */ -function build_element_spread_attributes( - attributes, - context, - element, - element_id, - attributes_id, - preserve_attribute_case, - is_custom_element -) { - let needs_isolation = false; - let is_reactive = false; - - /** @type {ObjectExpression['properties']} */ - const values = []; - - for (const attribute of attributes) { - if (attribute.type === 'Attribute') { - const { value } = build_attribute_value(attribute.value, context); - - if ( - is_event_attribute(attribute) && - (get_attribute_expression(attribute).type === 'ArrowFunctionExpression' || - get_attribute_expression(attribute).type === 'FunctionExpression') - ) { - // Give the event handler a stable ID so it isn't removed and readded on every update - const id = context.state.scope.generate('event_handler'); - context.state.init.push(b.var(id, value)); - values.push(b.init(attribute.name, b.id(id))); - } else { - values.push(b.init(attribute.name, value)); - } - } else { - values.push(b.spread(/** @type {Expression} */ (context.visit(attribute)))); - } - - is_reactive ||= - attribute.metadata.expression.has_state || - // objects could contain reactive getters -> play it safe and always assume spread attributes are reactive - attribute.type === 'SpreadAttribute'; - needs_isolation ||= - attribute.type === 'SpreadAttribute' && attribute.metadata.expression.has_call; - } - - const call = b.call( - '$.set_attributes', - element_id, - is_reactive ? attributes_id : b.literal(null), - b.object(values), - context.state.analysis.css.hash !== '' && b.literal(context.state.analysis.css.hash), - preserve_attribute_case, - is_custom_element, - is_ignored(element, 'hydration_attribute_changed') && b.true - ); - - if (is_reactive) { - context.state.init.push(b.let(attributes_id)); - - const update = b.stmt(b.assignment('=', attributes_id, call)); - - if (needs_isolation) { - context.state.init.push(build_update(update)); - return false; - } - - context.state.update.push(update); - return true; - } - - context.state.init.push(b.stmt(call)); - return false; -} - /** * Serializes an assignment to an element property by adding relevant statements to either only * the init or the the init and update arrays, depending on whether or not the value is dynamic. diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js index 11eb1e85ed..85a2b04d91 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js @@ -12,6 +12,7 @@ import { determine_namespace_for_children } from '../../utils.js'; import { build_attribute_value, build_class_directives, + build_set_attributes, build_style_directives } from './shared/element.js'; import { build_render_statement, build_update } from './shared/utils.js'; @@ -94,10 +95,10 @@ export function SvelteElement(node, context) { // Always use spread because we don't know whether the element is a custom element or not, // therefore we need to do the "how to set an attribute" logic at runtime. - is_attributes_reactive = build_dynamic_element_attributes( - node, + is_attributes_reactive = build_set_attributes( attributes, inner_context, + node, element_id, attributes_id, b.binary('!==', b.member(element_id, 'namespaceURI'), b.id('$.NAMESPACE_SVG')), @@ -152,87 +153,3 @@ export function SvelteElement(node, context) { ) ); } - -/** - * Serializes dynamic element attribute assignments. - * Returns the `true` if spread is deemed reactive. - * @param {AST.SvelteElement} element - * @param {Array} attributes - * @param {ComponentContext} context - * @param {Identifier} element_id - * @param {Identifier} attributes_id - * @param {false | Expression} preserve_attribute_case - * @param {false | Expression} is_custom_element - * @returns {boolean} - */ -function build_dynamic_element_attributes( - element, - attributes, - context, - element_id, - attributes_id, - preserve_attribute_case, - is_custom_element -) { - let needs_isolation = false; - let is_reactive = false; - - /** @type {ObjectExpression['properties']} */ - const values = []; - - for (const attribute of attributes) { - if (attribute.type === 'Attribute') { - const { value } = build_attribute_value(attribute.value, context); - - if ( - is_event_attribute(attribute) && - (get_attribute_expression(attribute).type === 'ArrowFunctionExpression' || - get_attribute_expression(attribute).type === 'FunctionExpression') - ) { - // Give the event handler a stable ID so it isn't removed and readded on every update - const id = context.state.scope.generate('event_handler'); - context.state.init.push(b.var(id, value)); - values.push(b.init(attribute.name, b.id(id))); - } else { - values.push(b.init(attribute.name, value)); - } - } else { - values.push(b.spread(/** @type {Expression} */ (context.visit(attribute)))); - } - - is_reactive ||= - attribute.metadata.expression.has_state || - // objects could contain reactive getters -> play it safe and always assume spread attributes are reactive - attribute.type === 'SpreadAttribute'; - needs_isolation ||= - attribute.type === 'SpreadAttribute' && attribute.metadata.expression.has_call; - } - - const call = b.call( - '$.set_attributes', - element_id, - is_reactive ? attributes_id : b.literal(null), - b.object(values), - context.state.analysis.css.hash !== '' && b.literal(context.state.analysis.css.hash), - preserve_attribute_case, - is_custom_element, - is_ignored(element, 'hydration_attribute_changed') && b.true - ); - - if (is_reactive) { - context.state.init.push(b.let(attributes_id)); - - const update = b.stmt(b.assignment('=', attributes_id, call)); - - if (needs_isolation) { - context.state.init.push(build_update(update)); - return false; - } - - context.state.update.push(update); - return true; - } - - context.state.init.push(b.stmt(call)); - return false; -} diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js index dc14fc80ab..ed20aff5c1 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js @@ -1,11 +1,94 @@ -/** @import { Expression, Identifier } from 'estree' */ +/** @import { Expression, Identifier, ObjectExpression } from 'estree' */ /** @import { AST, Namespace } from '#compiler' */ /** @import { ComponentContext } from '../../types' */ import { normalize_attribute } from '../../../../../../utils.js'; +import { is_ignored } from '../../../../../state.js'; +import { get_attribute_expression, is_event_attribute } from '../../../../../utils/ast.js'; import * as b from '../../../../../utils/builders.js'; import { build_getter, create_derived } from '../../utils.js'; import { build_template_literal, build_update } from './utils.js'; +/** + * @param {Array} attributes + * @param {ComponentContext} context + * @param {AST.RegularElement | AST.SvelteElement} element + * @param {Identifier} element_id + * @param {Identifier} attributes_id + * @param {false | Expression} preserve_attribute_case + * @param {false | Expression} is_custom_element + */ +export function build_set_attributes( + attributes, + context, + element, + element_id, + attributes_id, + preserve_attribute_case, + is_custom_element +) { + let needs_isolation = false; + let is_reactive = false; + + /** @type {ObjectExpression['properties']} */ + const values = []; + + for (const attribute of attributes) { + if (attribute.type === 'Attribute') { + const { value } = build_attribute_value(attribute.value, context); + + if ( + is_event_attribute(attribute) && + (get_attribute_expression(attribute).type === 'ArrowFunctionExpression' || + get_attribute_expression(attribute).type === 'FunctionExpression') + ) { + // Give the event handler a stable ID so it isn't removed and readded on every update + const id = context.state.scope.generate('event_handler'); + context.state.init.push(b.var(id, value)); + values.push(b.init(attribute.name, b.id(id))); + } else { + values.push(b.init(attribute.name, value)); + } + } else { + values.push(b.spread(/** @type {Expression} */ (context.visit(attribute)))); + } + + is_reactive ||= + attribute.metadata.expression.has_state || + // objects could contain reactive getters -> play it safe and always assume spread attributes are reactive + attribute.type === 'SpreadAttribute'; + needs_isolation ||= + attribute.type === 'SpreadAttribute' && attribute.metadata.expression.has_call; + } + + const call = b.call( + '$.set_attributes', + element_id, + is_reactive ? attributes_id : b.literal(null), + b.object(values), + context.state.analysis.css.hash !== '' && b.literal(context.state.analysis.css.hash), + preserve_attribute_case, + is_custom_element, + is_ignored(element, 'hydration_attribute_changed') && b.true + ); + + if (is_reactive) { + context.state.init.push(b.let(attributes_id)); + + const update = b.stmt(b.assignment('=', attributes_id, call)); + + if (needs_isolation) { + context.state.init.push(build_update(update)); + return false; + } + + context.state.update.push(update); + return true; + } + + context.state.init.push(b.stmt(call)); + return false; +} + /** * Serializes each style directive into something like `$.set_style(element, style_property, value)` * and adds it either to init or update, depending on whether or not the value or the attributes are dynamic.