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 f7d72cbb3b..7ed941efb7 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 @@ -27,17 +27,21 @@ export function build_set_attributes( element_id, attributes_id ) { - let is_dynamic = false; - /** @type {ObjectExpression['properties']} */ const values = []; + /** @type {Expression[]} */ + const expressions = []; + + /** @param {Expression} value */ + function memoize(value) { + return b.id(`$${expressions.push(value) - 1}`); + } + for (const attribute of attributes) { if (attribute.type === 'Attribute') { - const { value, has_state } = build_attribute_value( - attribute.value, - context, - (value, metadata) => (metadata.has_call ? get_expression_id(context.state, value) : value) + const { value } = build_attribute_value(attribute.value, context, (value, metadata) => + metadata.has_call ? memoize(value) : value ); if ( @@ -51,16 +55,11 @@ export function build_set_attributes( } else { values.push(b.init(attribute.name, value)); } - - is_dynamic ||= has_state; } else { - // objects could contain reactive getters -> play it safe and always assume spread attributes are reactive - is_dynamic = true; - let value = /** @type {Expression} */ (context.visit(attribute)); if (attribute.metadata.expression.has_call) { - value = get_expression_id(context.state, value); + value = memoize(value); } values.push(b.spread(value)); @@ -75,9 +74,6 @@ export function build_set_attributes( build_class_directives_object(class_directives, context) ) ); - - is_dynamic ||= - class_directives.find((directive) => directive.metadata.expression.has_state) !== null; } if (style_directives.length) { @@ -88,29 +84,25 @@ export function build_set_attributes( build_style_directives_object(style_directives, context) ) ); - - is_dynamic ||= style_directives.some((directive) => directive.metadata.expression.has_state); } - const call = b.call( - '$.set_attributes', - element_id, - is_dynamic ? attributes_id : b.null, - b.object(values), - element.metadata.scoped && - context.state.analysis.css.hash !== '' && - b.literal(context.state.analysis.css.hash), - is_ignored(element, 'hydration_attribute_changed') && b.true + context.state.init.push( + b.stmt( + b.call( + '$.set_attribute_effect', + element_id, + b.arrow( + expressions.map((_, i) => b.id(`$${i}`)), + b.object(values) + ), + expressions.length > 0 && b.array(expressions.map((expression) => b.thunk(expression))), + element.metadata.scoped && + context.state.analysis.css.hash !== '' && + b.literal(context.state.analysis.css.hash), + is_ignored(element, 'hydration_attribute_changed') && b.true + ) + ) ); - - if (is_dynamic) { - context.state.init.push( - b.let(attributes_id), - b.stmt(b.call('$.set_attribute_effect', element_id, b.thunk(b.object(values)))) - ); - } else { - context.state.init.push(b.stmt(call)); - } } /** diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 4c689e6969..8561de31da 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -10,6 +10,7 @@ import { is_capture_event, is_delegated, normalize_attribute } from '../../../.. import { active_effect, active_reaction, + get, set_active_effect, set_active_reaction } from '../../runtime.js'; @@ -19,6 +20,7 @@ import { set_class } from './class.js'; import { set_style } from './style.js'; import { ATTACHMENT_KEY, NAMESPACE_HTML } from '../../../../constants.js'; import { block, branch, destroy_effect } from '../../reactivity/effects.js'; +import { derived } from '../../reactivity/deriveds.js'; export const CLASS = Symbol('class'); export const STYLE = Symbol('style'); @@ -448,22 +450,32 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal set_hydrating(true); } - for (let symbol of Object.getOwnPropertySymbols(next)) { - if (symbol.description === ATTACHMENT_KEY) { - attach(element, () => next[symbol]); - } - } + // for (let symbol of Object.getOwnPropertySymbols(next)) { + // if (symbol.description === ATTACHMENT_KEY) { + // attach(element, () => next[symbol]); + // } + // } return current; } /** * @param {Element & ElementCSSInlineStyle} element - * @param {() => Record} fn + * @param {(...expressions: any) => Record} fn + * @param {Array<() => any>} thunks * @param {string} [css_hash] * @param {boolean} [skip_warning] */ -export function set_attribute_effect(element, fn, css_hash, skip_warning = false) { +export function set_attribute_effect( + element, + fn, + thunks = [], + css_hash, + skip_warning = false, + d = derived +) { + const deriveds = thunks.map(d); + /** @type {Record} */ var prev = {}; @@ -471,24 +483,9 @@ export function set_attribute_effect(element, fn, css_hash, skip_warning = false var effects = {}; block(() => { - var next = fn(); - - for (const key in prev) { - if (!next[key]) { - element.removeAttribute(key); - } - } + var next = fn(...deriveds.map(get)); - for (let symbol of Object.getOwnPropertySymbols(prev)) { - if (!next[symbol]) { - destroy_effect(effects[symbol]); - delete effects[symbol]; - } - } - - for (const key in next) { - set_attribute(element, key, next[key], skip_warning); - } + set_attributes(element, prev, next, css_hash, skip_warning); for (let symbol of Object.getOwnPropertySymbols(next)) { if (symbol.description === ATTACHMENT_KEY && next[symbol] !== prev[symbol]) {