gh-13270
Rich Harris 20 hours ago
parent 0d384984a9
commit 18cef800c9

@ -24,7 +24,8 @@ import {
get_attribute_name, get_attribute_name,
build_attribute_value, build_attribute_value,
build_class_directives, build_class_directives,
build_style_directives build_style_directives,
build_set_attributes
} from './shared/element.js'; } from './shared/element.js';
import { process_children } from './shared/fragment.js'; import { process_children } from './shared/fragment.js';
import { import {
@ -208,7 +209,7 @@ export function RegularElement(node, context) {
if (has_spread) { if (has_spread) {
const attributes_id = b.id(context.state.scope.generate('attributes')); const attributes_id = b.id(context.state.scope.generate('attributes'));
build_element_spread_attributes( build_set_attributes(
attributes, attributes,
context, context,
node, node,
@ -480,87 +481,6 @@ function setup_select_synchronization(value_binding, context) {
); );
} }
/**
* @param {Array<AST.Attribute | AST.SpreadAttribute>} 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 * 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. * the init or the the init and update arrays, depending on whether or not the value is dynamic.

@ -12,6 +12,7 @@ import { determine_namespace_for_children } from '../../utils.js';
import { import {
build_attribute_value, build_attribute_value,
build_class_directives, build_class_directives,
build_set_attributes,
build_style_directives build_style_directives
} from './shared/element.js'; } from './shared/element.js';
import { build_render_statement, build_update } from './shared/utils.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, // 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. // therefore we need to do the "how to set an attribute" logic at runtime.
is_attributes_reactive = build_dynamic_element_attributes( is_attributes_reactive = build_set_attributes(
node,
attributes, attributes,
inner_context, inner_context,
node,
element_id, element_id,
attributes_id, attributes_id,
b.binary('!==', b.member(element_id, 'namespaceURI'), b.id('$.NAMESPACE_SVG')), 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<AST.Attribute | AST.SpreadAttribute>} 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;
}

@ -1,11 +1,94 @@
/** @import { Expression, Identifier } from 'estree' */ /** @import { Expression, Identifier, ObjectExpression } from 'estree' */
/** @import { AST, Namespace } from '#compiler' */ /** @import { AST, Namespace } from '#compiler' */
/** @import { ComponentContext } from '../../types' */ /** @import { ComponentContext } from '../../types' */
import { normalize_attribute } from '../../../../../../utils.js'; 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 * as b from '../../../../../utils/builders.js';
import { build_getter, create_derived } from '../../utils.js'; import { build_getter, create_derived } from '../../utils.js';
import { build_template_literal, build_update } from './utils.js'; import { build_template_literal, build_update } from './utils.js';
/**
* @param {Array<AST.Attribute | AST.SpreadAttribute>} 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)` * 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. * and adds it either to init or update, depending on whether or not the value or the attributes are dynamic.

Loading…
Cancel
Save