From ebc286e75ed16fdabc610c2a529460d474250fae Mon Sep 17 00:00:00 2001 From: adiguba Date: Sun, 2 Mar 2025 19:51:11 +0100 Subject: [PATCH 01/34] add style attribute when needed --- .../src/compiler/phases/2-analyze/index.js | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 322293bf6b91..1f636c32df6d 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -769,17 +769,24 @@ export function analyze_component(root, source, options) { } let has_class = false; + let has_style = false; let has_spread = false; let has_class_directive = false; + let has_style_directive = false; for (const attribute of node.attributes) { // The spread method appends the hash to the end of the class attribute on its own if (attribute.type === 'SpreadAttribute') { has_spread = true; break; + } else if (attribute.type === 'Attribute') { + has_class ||= attribute.name.toLowerCase() === 'class'; + has_style ||= attribute.name.toLowerCase() === 'style'; + } else if (attribute.type === 'ClassDirective') { + has_class_directive = true; + } else if (attribute.type === 'StyleDirective') { + has_style_directive = true; } - has_class_directive ||= attribute.type === 'ClassDirective'; - has_class ||= attribute.type === 'Attribute' && attribute.name.toLowerCase() === 'class'; } // We need an empty class to generate the set_class() or class="" correctly @@ -796,6 +803,21 @@ export function analyze_component(root, source, options) { ]) ); } + + // We need an empty style to generate the set_style() correctly + if (!has_spread && !has_style && has_style_directive) { + node.attributes.push( + create_attribute('style', -1, -1, [ + { + type: 'Text', + data: '', + raw: '', + start: -1, + end: -1 + } + ]) + ); + } } // TODO From 4a3561b6a96ab51c25aff766331eecc7febafb66 Mon Sep 17 00:00:00 2001 From: adiguba Date: Sun, 2 Mar 2025 19:53:22 +0100 Subject: [PATCH 02/34] set_style() --- .../client/visitors/RegularElement.js | 79 +++++--- .../client/visitors/SvelteElement.js | 18 +- .../client/visitors/shared/element.js | 108 +++++----- .../server/visitors/shared/element.js | 185 +++++++----------- .../client/dom/elements/attributes.js | 23 +-- .../src/internal/client/dom/elements/style.js | 62 ++++-- .../src/internal/client/dom/operations.js | 2 +- packages/svelte/src/internal/server/index.js | 50 ++--- 8 files changed, 275 insertions(+), 252 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 434b49caa1e6..33012b7bbd62 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 @@ -1,4 +1,4 @@ -/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */ +/** @import { ArrayExpression, Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { SourceLocation } from '#shared' */ /** @import { ComponentClientTransformState, ComponentContext } from '../types' */ @@ -20,9 +20,9 @@ import { build_getter } from '../utils.js'; import { get_attribute_name, build_attribute_value, - build_style_directives, build_set_attributes, - build_set_class + build_set_class, + build_set_style } from './shared/element.js'; import { process_children } from './shared/fragment.js'; import { @@ -215,15 +215,13 @@ export function RegularElement(node, context) { const node_id = context.state.node; - // Then do attributes - let is_attributes_reactive = has_spread; - if (has_spread) { const attributes_id = b.id(context.state.scope.generate('attributes')); build_set_attributes( attributes, class_directives, + style_directives, context, node, node_id, @@ -275,7 +273,8 @@ export function RegularElement(node, context) { !is_custom_element && !cannot_be_set_statically(attribute.name) && (attribute.value === true || is_text_attribute(attribute)) && - (name !== 'class' || class_directives.length === 0) + (name !== 'class' || class_directives.length === 0) && + (name !== 'style' || style_directives.length === 0) ) { let value = is_text_attribute(attribute) ? attribute.value[0].data : true; @@ -299,24 +298,22 @@ export function RegularElement(node, context) { continue; } - const is = - is_custom_element && name !== 'class' - ? build_custom_element_attribute_update_assignment(node_id, attribute, context) - : build_element_attribute_update_assignment( - node, - node_id, - attribute, - attributes, - class_directives, - context - ); - if (is) is_attributes_reactive = true; + if (is_custom_element && name !== 'class' && name !== 'style') { + build_custom_element_attribute_update_assignment(node_id, attribute, context); + } else { + build_element_attribute_update_assignment( + node, + node_id, + attribute, + attributes, + class_directives, + style_directives, + context + ); + } } } - // style directives must be applied last since they could override class/style attributes - build_style_directives(style_directives, node_id, context, is_attributes_reactive); - if ( is_load_error_element(node.name) && (has_spread || has_use || lookup.has('onload') || lookup.has('onerror')) @@ -528,6 +525,40 @@ export function build_class_directives_object(class_directives, context) { return b.object(properties); } +/** + * @param {AST.StyleDirective[]} style_directives + * @param {ComponentContext} context + * @return {ObjectExpression | ArrayExpression}} + */ +export function build_style_directives_object(style_directives, context) { + let normal_properties = []; + let important_properties = []; + + for (const directive of style_directives) { + let expression = + directive.value === true + ? build_getter({ name: directive.name, type: 'Identifier' }, context.state) + : build_attribute_value(directive.value, context, (value, metadata) => + metadata.has_call ? get_expression_id(context.state, value) : value + ).value; + let name = directive.name; + if (name[0] !== '-' || name[1] !== '-') { + name = name.toLowerCase(); + } + + const property = b.init(name, expression); + if (directive.modifiers.includes('important')) { + important_properties.push(property); + } else { + normal_properties.push(property); + } + } + + return important_properties.length + ? b.array([b.object(normal_properties), b.object(important_properties)]) + : b.object(normal_properties); +} + /** * 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. @@ -555,6 +586,7 @@ export function build_class_directives_object(class_directives, context) { * @param {AST.Attribute} attribute * @param {Array} attributes * @param {AST.ClassDirective[]} class_directives + * @param {AST.StyleDirective[]} style_directives * @param {ComponentContext} context * @returns {boolean} */ @@ -564,6 +596,7 @@ function build_element_attribute_update_assignment( attribute, attributes, class_directives, + style_directives, context ) { const state = context.state; @@ -612,6 +645,8 @@ function build_element_attribute_update_assignment( context, !is_svg && !is_mathml ); + } else if (name === 'style') { + return build_set_style(node_id, value, has_state, style_directives, context); } else if (name === 'value') { update = b.stmt(b.call('$.set_value', node_id, value)); } else if (name === 'checked') { 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 ac284c818d3d..71849739e6f7 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 @@ -5,12 +5,7 @@ import { dev, locator } from '../../../../state.js'; import { is_text_attribute } from '../../../../utils/ast.js'; import * as b from '../../../../utils/builders.js'; import { determine_namespace_for_children } from '../../utils.js'; -import { - build_attribute_value, - build_set_attributes, - build_set_class, - build_style_directives -} from './shared/element.js'; +import { build_attribute_value, build_set_attributes, build_set_class } from './shared/element.js'; import { build_render_statement, get_expression_id } from './shared/utils.js'; /** @@ -77,9 +72,6 @@ export function SvelteElement(node, context) { // Let bindings first, they can be used on attributes context.state.init.push(...lets); // create computeds in the outer context; the dynamic element is the single child of this slot - // Then do attributes - let is_attributes_reactive = false; - if ( attributes.length === 1 && attributes[0].type === 'Attribute' && @@ -93,7 +85,7 @@ export function SvelteElement(node, context) { (value, metadata) => (metadata.has_call ? get_expression_id(context.state, value) : value) ); - is_attributes_reactive = build_set_class( + build_set_class( node, element_id, attributes[0], @@ -108,9 +100,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_set_attributes( + build_set_attributes( attributes, class_directives, + style_directives, inner_context, node, element_id, @@ -120,9 +113,6 @@ export function SvelteElement(node, context) { ); } - // style directives must be applied last since they could override class/style attributes - build_style_directives(style_directives, element_id, inner_context, is_attributes_reactive); - const get_tag = b.thunk(/** @type {Expression} */ (context.visit(node.tag))); if (dev) { 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 81a4b45288eb..50cdd1acd267 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,4 +1,4 @@ -/** @import { Expression, Identifier, ObjectExpression } from 'estree' */ +/** @import { ArrayExpression, Expression, Identifier, ObjectExpression } from 'estree' */ /** @import { AST, ExpressionMetadata } from '#compiler' */ /** @import { ComponentClientTransformState, ComponentContext } from '../../types' */ import { escape_html } from '../../../../../../escaping.js'; @@ -6,13 +6,13 @@ import { normalize_attribute } from '../../../../../../utils.js'; import { is_ignored } from '../../../../../state.js'; import { is_event_attribute } from '../../../../../utils/ast.js'; import * as b from '../../../../../utils/builders.js'; -import { build_getter } from '../../utils.js'; -import { build_class_directives_object } from '../RegularElement.js'; +import { build_class_directives_object, build_style_directives_object } from '../RegularElement.js'; import { build_template_chunk, get_expression_id } from './utils.js'; /** * @param {Array} attributes * @param {AST.ClassDirective[]} class_directives + * @param {AST.StyleDirective[]} style_directives * @param {ComponentContext} context * @param {AST.RegularElement | AST.SvelteElement} element * @param {Identifier} element_id @@ -23,6 +23,7 @@ import { build_template_chunk, get_expression_id } from './utils.js'; export function build_set_attributes( attributes, class_directives, + style_directives, context, element, element_id, @@ -83,6 +84,19 @@ export function build_set_attributes( class_directives.find((directive) => directive.metadata.expression.has_state) !== null; } + if (style_directives.length) { + values.push( + b.prop( + 'init', + b.array([b.id('$.STYLE')]), + build_style_directives_object(style_directives, context) + ) + ); + + is_dynamic ||= + style_directives.find((directive) => directive.metadata.expression.has_state) !== null; + } + const call = b.call( '$.set_attributes', element_id, @@ -107,50 +121,6 @@ export function build_set_attributes( 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. - * @param {AST.StyleDirective[]} style_directives - * @param {Identifier} element_id - * @param {ComponentContext} context - * @param {boolean} is_attributes_reactive - */ -export function build_style_directives( - style_directives, - element_id, - context, - is_attributes_reactive -) { - const state = context.state; - - for (const directive of style_directives) { - const { has_state } = directive.metadata.expression; - - let value = - directive.value === true - ? build_getter({ name: directive.name, type: 'Identifier' }, context.state) - : build_attribute_value(directive.value, context, (value, metadata) => - metadata.has_call ? get_expression_id(context.state, value) : value - ).value; - - const update = b.stmt( - b.call( - '$.set_style', - element_id, - b.literal(directive.name), - value, - /** @type {Expression} */ (directive.modifiers.includes('important') ? b.true : undefined) - ) - ); - - if (has_state || is_attributes_reactive) { - state.update.push(update); - } else { - state.init.push(update); - } - } -} - /** * @param {AST.Attribute['value']} value * @param {ComponentContext} context @@ -281,3 +251,47 @@ export function build_set_class( context.state.init.push(update); return false; } + +/** + * @param {Identifier} node_id + * @param {Expression} value + * @param {boolean} has_state + * @param {AST.StyleDirective[]} style_directives + * @param {ComponentContext} context + * @returns {boolean} + */ +export function build_set_style(node_id, value, has_state, style_directives, context) { + /** @type {Identifier | undefined} */ + let previous_id; + /** @type {ObjectExpression | Identifier | undefined} */ + let prev; + /** @type {ArrayExpression | ObjectExpression | undefined} */ + let next; + if (style_directives.length) { + next = build_style_directives_object(style_directives, context); + has_state ||= style_directives.some((d) => d.metadata.expression.has_state); + if (has_state) { + previous_id = b.id(context.state.scope.generate('styles')); + context.state.init.push(b.declaration('let', [b.declarator(previous_id)])); + prev = previous_id; + } else { + prev = b.object([]); + } + } + + /** @type {Expression} */ + let set_style = b.call('$.set_style', node_id, value, prev, next); + + if (previous_id) { + set_style = b.assignment('=', previous_id, set_style); + } + + const update = b.stmt(set_style); + if (has_state) { + context.state.update.push(update); + return true; + } else { + context.state.init.push(update); + return false; + } +} diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js index 281d8f061768..12ced8c3731f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js @@ -1,4 +1,4 @@ -/** @import { Expression, Literal, ObjectExpression } from 'estree' */ +/** @import { ArrayExpression, Expression, Literal, ObjectExpression } from 'estree' */ /** @import { AST, Namespace } from '#compiler' */ /** @import { ComponentContext, ComponentServerTransformState } from '../../types.js' */ import { @@ -48,9 +48,6 @@ export function build_element_attributes(node, context) { let content = null; let has_spread = false; - // Use the index to keep the attributes order which is important for spreading - let class_index = -1; - let style_index = -1; let events_to_capture = new Set(); for (const attribute of node.attributes) { @@ -86,7 +83,6 @@ export function build_element_attributes(node, context) { // the defaultValue/defaultChecked properties don't exist as attributes } else if (attribute.name !== 'defaultValue' && attribute.name !== 'defaultChecked') { if (attribute.name === 'class') { - class_index = attributes.length; if (attribute.metadata.needs_clsx) { attributes.push({ ...attribute, @@ -102,10 +98,6 @@ export function build_element_attributes(node, context) { attributes.push(attribute); } } else { - if (attribute.name === 'style') { - style_index = attributes.length; - } - attributes.push(attribute); } } @@ -212,41 +204,27 @@ export function build_element_attributes(node, context) { } } - if ((node.metadata.scoped || class_directives.length) && !has_spread) { - const class_attribute = build_to_class( - node.metadata.scoped ? context.state.analysis.css.hash : null, - class_directives, - /** @type {AST.Attribute | null} */ (attributes[class_index] ?? null) - ); - if (class_index === -1) { - attributes.push(class_attribute); - } - } - - if (style_directives.length > 0 && !has_spread) { - build_style_directives( - style_directives, - /** @type {AST.Attribute | null} */ (attributes[style_index] ?? null), - context - ); - if (style_index > -1) { - attributes.splice(style_index, 1); - } - } - if (has_spread) { build_element_spread_attributes(node, attributes, style_directives, class_directives, context); } else { + const css_hash = node.metadata.scoped ? context.state.analysis.css.hash : null; for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) { - if (attribute.value === true || is_text_attribute(attribute)) { - const name = get_attribute_name(node, attribute); - const literal_value = /** @type {Literal} */ ( + const name = get_attribute_name(node, attribute); + const can_use_literal = + (name !== 'class' || class_directives.length === 0) && + (name !== 'style' || style_directives.length === 0); + + if (can_use_literal && (attribute.value === true || is_text_attribute(attribute))) { + let literal_value = /** @type {Literal} */ ( build_attribute_value( attribute.value, context, WHITESPACE_INSENSITIVE_ATTRIBUTES.includes(name) ) ).value; + if (name === 'class' && css_hash) { + literal_value = (String(literal_value) + ' ' + css_hash).trim(); + } if (name !== 'class' || literal_value) { context.state.template.push( b.literal( @@ -261,7 +239,6 @@ export function build_element_attributes(node, context) { continue; } - const name = get_attribute_name(node, attribute); const value = build_attribute_value( attribute.value, context, @@ -269,8 +246,15 @@ export function build_element_attributes(node, context) { ); // pre-escape and inline literal attributes : - if (value.type === 'Literal' && typeof value.value === 'string') { + if (can_use_literal && value.type === 'Literal' && typeof value.value === 'string') { + if (name === 'class' && css_hash) { + value.value = (value.value + ' ' + css_hash).trim(); + } context.state.template.push(b.literal(` ${name}="${escape_html(value.value, true)}"`)); + } else if (name === 'class') { + context.state.template.push(build_attr_class(class_directives, value, context, css_hash)); + } else if (name === 'style') { + context.state.template.push(build_attr_style(style_directives, value, context)); } else { context.state.template.push( b.call('$.attr', b.literal(name), value, is_boolean_attribute(name) && b.true) @@ -379,100 +363,77 @@ function build_element_spread_attributes( /** * - * @param {string | null} hash * @param {AST.ClassDirective[]} class_directives - * @param {AST.Attribute | null} class_attribute - * @returns + * @param {Expression} expression + * @param {ComponentContext} context + * @param {string | null} hash */ -function build_to_class(hash, class_directives, class_attribute) { - if (class_attribute === null) { - class_attribute = create_attribute('class', -1, -1, []); - } - +function build_attr_class(class_directives, expression, context, hash) { /** @type {ObjectExpression | undefined} */ - let classes; - + let directives; if (class_directives.length) { - classes = b.object( + directives = b.object( class_directives.map((directive) => - b.prop('init', b.literal(directive.name), directive.expression) + b.prop( + 'init', + b.literal(directive.name), + /** @type {Expression} */ (context.visit(directive.expression, context.state)) + ) ) ); } + let css_hash; - /** @type {Expression} */ - let class_name; - - if (class_attribute.value === true) { - class_name = b.literal(''); - } else if (Array.isArray(class_attribute.value)) { - if (class_attribute.value.length === 0) { - class_name = b.null; - } else { - class_name = class_attribute.value - .map((val) => (val.type === 'Text' ? b.literal(val.data) : val.expression)) - .reduce((left, right) => b.binary('+', left, right)); - } - } else { - class_name = class_attribute.value.expression; - } - - /** @type {Expression} */ - let expression; - - if ( - hash && - !classes && - class_name.type === 'Literal' && - (class_name.value === null || class_name.value === '' || typeof class_name.value === 'string') - ) { - if (class_name.value === null || class_name.value === '') { - expression = b.literal(hash); + if (hash) { + if (expression.type === 'Literal' && typeof expression.value === 'string') { + expression.value = (expression.value + ' ' + hash).trim(); } else { - expression = b.literal(escape_html(class_name.value, true) + ' ' + hash); + css_hash = b.literal(hash); } - } else { - expression = b.call('$.to_class', class_name, b.literal(hash), classes); + } else if (directives) { + css_hash = b.null; } - - class_attribute.value = { - type: 'ExpressionTag', - start: -1, - end: -1, - expression: expression, - metadata: { - expression: create_expression_metadata() - } - }; - - return class_attribute; + return b.call('$.attr_class', expression, css_hash, directives); } /** + * * @param {AST.StyleDirective[]} style_directives - * @param {AST.Attribute | null} style_attribute + * @param {Expression} expression * @param {ComponentContext} context */ -function build_style_directives(style_directives, style_attribute, context) { - const styles = style_directives.map((directive) => { - let value = - directive.value === true - ? b.id(directive.name) - : build_attribute_value(directive.value, context, true); - if (directive.modifiers.includes('important')) { - value = b.binary('+', value, b.literal(' !important')); +function build_attr_style(style_directives, expression, context) { + /** @type {ArrayExpression | ObjectExpression | undefined} */ + let directives; + if (style_directives.length) { + let normal_properties = []; + let important_properties = []; + + for (const directive of style_directives) { + const expression = + directive.value === true + ? b.id(directive.name) + : build_attribute_value(directive.value, context, true); + + let name = directive.name; + if (name[0] !== '-' || name[1] !== '-') { + name = name.toLowerCase(); + } + + const property = b.init(directive.name, expression); + if (directive.modifiers.includes('important')) { + important_properties.push(property); + } else { + normal_properties.push(property); + } } - return b.init(directive.name, value); - }); - - const arg = - style_attribute === null - ? b.object(styles) - : b.call( - '$.merge_styles', - build_attribute_value(style_attribute.value, context, true), - b.object(styles) - ); - context.state.template.push(b.call('$.add_styles', arg)); + if (important_properties.length) { + directives = b.array([b.object(normal_properties), b.object(important_properties)]); + } else { + directives = b.object(normal_properties); + } + } + + return b.call('$.attr_style', expression, directives); } diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index dd408dcf8715..c5aeb2efe6c6 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -15,6 +15,7 @@ import { } from '../../runtime.js'; import { clsx } from '../../../shared/attributes.js'; import { set_class } from './class.js'; +import { set_style } from './style.js'; export const CLASS = Symbol('class'); export const STYLE = Symbol('style'); @@ -176,11 +177,6 @@ export function set_attribute(element, attribute, value, skip_warning) { if (attributes[attribute] === (attributes[attribute] = value)) return; - if (attribute === 'style' && '__styles' in element) { - // reset styles to force style: directive to update - element.__styles = {}; - } - if (attribute === 'loading') { // @ts-expect-error element[LOADING_ATTR_SYMBOL] = value; @@ -297,6 +293,10 @@ export function set_attributes( next.class = null; /* force call to set_class() */ } + if (next[STYLE]) { + next.style ??= null; /* force call to set_style() */ + } + var setters = get_setters(element); // @ts-expect-error @@ -334,6 +334,13 @@ export function set_attributes( continue; } + if (key === 'style') { + set_style(element, value, prev?.[STYLE], next[STYLE]); + current[key] = value; + current[STYLE] = next[STYLE]; + continue; + } + var prev_value = current[key]; if (value === prev_value) continue; @@ -385,8 +392,6 @@ export function set_attributes( // @ts-ignore element[`__${event_name}`] = undefined; } - } else if (key === 'style' && value != null) { - element.style.cssText = value + ''; } else if (key === 'autofocus') { autofocus(/** @type {HTMLElement} */ (element), Boolean(value)); } else if (!is_custom_element && (key === '__value' || (key === 'value' && value != null))) { @@ -435,10 +440,6 @@ export function set_attributes( set_attribute(element, name, value); } } - if (key === 'style' && '__styles' in element) { - // reset styles to force style: directive to update - element.__styles = {}; - } } if (is_hydrating_custom_element) { diff --git a/packages/svelte/src/internal/client/dom/elements/style.js b/packages/svelte/src/internal/client/dom/elements/style.js index 34531029c9c6..4ddb96405b97 100644 --- a/packages/svelte/src/internal/client/dom/elements/style.js +++ b/packages/svelte/src/internal/client/dom/elements/style.js @@ -1,22 +1,52 @@ +import { to_style } from '../../../shared/attributes.js'; +import { hydrating } from '../hydration.js'; + /** - * @param {HTMLElement} dom - * @param {string} key - * @param {string} value - * @param {boolean} [important] + * @param {Element & ElementCSSInlineStyle} dom + * @param {Record} prev + * @param {Record} next + * @param {string} important */ -export function set_style(dom, key, value, important) { - // @ts-expect-error - var styles = (dom.__styles ??= {}); - - if (styles[key] === value) { - return; +function update_styles(dom, prev = {}, next, important) { + for (const key in next) { + const value = next[key]; + if (prev[key] !== value) { + if (next[key] == null) { + dom.style.removeProperty(key); + } else { + dom.style.setProperty(key, value, important); + } + } } +} - styles[key] = value; - - if (value == null) { - dom.style.removeProperty(key); - } else { - dom.style.setProperty(key, value, important ? 'important' : ''); +/** + * @param {Element & ElementCSSInlineStyle} dom + * @param {string|null} value + * @param {Record|[Record,Record]} [prev_styles] + * @param {Record|[Record,Record]} [next_styles] + */ +export function set_style(dom, value, prev_styles, next_styles) { + // @ts-expect-error + var prev = dom.__style; + if (hydrating || prev !== value) { + var next_style_attr = to_style(value, next_styles); + if (!hydrating || next_style_attr !== dom.getAttribute('style')) { + if (next_style_attr == null) { + dom.removeAttribute('style'); + } else { + dom.setAttribute('style', next_style_attr); + } + } + // @ts-expect-error + dom.__style = value; + } else if (next_styles) { + if (Array.isArray(next_styles)) { + update_styles(dom, prev_styles?.[0], next_styles[0], ''); + update_styles(dom, prev_styles?.[1], next_styles[1], ''); + } else { + update_styles(dom, prev_styles, next_styles, ''); + } } + return next_styles; } diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index f6ac92456e78..0ad9045b2062 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -48,7 +48,7 @@ export function init_operations() { // @ts-expect-error element_prototype.__attributes = null; // @ts-expect-error - element_prototype.__styles = null; + element_prototype.__style = undefined; // @ts-expect-error element_prototype.__e = undefined; diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 160a1faa653e..1a52229bedfe 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -2,7 +2,7 @@ /** @import { Component, Payload, RenderOutput } from '#server' */ /** @import { Store } from '#shared' */ export { FILENAME, HMR } from '../../constants.js'; -import { attr, clsx, to_class } from '../shared/attributes.js'; +import { attr, clsx, to_class, to_style } from '../shared/attributes.js'; import { is_promise, noop } from '../shared/utils.js'; import { subscribe_to_store } from '../../store/utils.js'; import { @@ -205,9 +205,7 @@ export function css_props(payload, is_html, props, component, dynamic = false) { */ export function spread_attributes(attrs, css_hash, classes, styles, flags = 0) { if (styles) { - attrs.style = attrs.style - ? style_object_to_string(merge_styles(/** @type {string} */ (attrs.style), styles)) - : style_object_to_string(styles); + attrs.style = to_style(attrs.style, styles); } if (attrs.class) { @@ -281,35 +279,29 @@ function style_object_to_string(style_object) { .join(' '); } -/** @param {Record} style_object */ -export function add_styles(style_object) { - const styles = style_object_to_string(style_object); - return styles ? ` style="${styles}"` : ''; -} - /** - * @param {string} attribute - * @param {Record} styles + * @param {any} value + * @param {string | null} [hash] + * @param {Record} [directives] */ -export function merge_styles(attribute, styles) { - /** @type {Record} */ - var merged = {}; - - if (attribute) { - for (var declaration of attribute.split(';')) { - var i = declaration.indexOf(':'); - var name = declaration.slice(0, i).trim(); - var value = declaration.slice(i + 1).trim(); - - if (name !== '') merged[name] = value; - } +export function attr_class(value, hash, directives) { + var result = to_class(value, hash, directives); + if (result) { + return ` class="${escape_html(result, true)}"`; } + return ''; +} - for (name in styles) { - merged[name] = styles[name]; +/** + * @param {any} value + * @param {Record|[Record,Record]} [directives] + */ +export function attr_style(value, directives) { + var result = to_style(value, directives); + if (result) { + return ` style="${escape_html(result, true)}"`; } - - return merged; + return ''; } /** @@ -544,7 +536,7 @@ export function props_id(payload) { return uid; } -export { attr, clsx, to_class }; +export { attr, clsx }; export { html } from './blocks/html.js'; From 555f11c7a162ebc1d832464e6b97691748ae74a2 Mon Sep 17 00:00:00 2001 From: adiguba Date: Sun, 2 Mar 2025 19:53:45 +0100 Subject: [PATCH 03/34] to_style() --- .../svelte/src/internal/shared/attributes.js | 120 +++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/shared/attributes.js b/packages/svelte/src/internal/shared/attributes.js index 89cc17e51b9d..e0705c2f99dc 100644 --- a/packages/svelte/src/internal/shared/attributes.js +++ b/packages/svelte/src/internal/shared/attributes.js @@ -22,7 +22,7 @@ const replacements = { * @returns {string} */ export function attr(name, value, is_boolean = false) { - if (value == null || (!value && is_boolean) || (value === '' && name === 'class')) return ''; + if (value == null || (!value && is_boolean)) return ''; const normalized = (name in replacements && replacements[name].get(value)) || value; const assignment = is_boolean ? '' : `="${escape_html(normalized, true)}"`; return ` ${name}${assignment}`; @@ -82,3 +82,121 @@ export function to_class(value, hash, directives) { return classname === '' ? null : classname; } + +/** + * + * @param {Record} styles + * @param {boolean} important + */ +function append_styles(styles, important = false) { + let separator = important ? ' !important;' : ';'; + let css = ''; + + for (const key in styles) { + const value = styles[key]; + if (value != null && value !== '') { + css += ' ' + key + ': ' + value + separator; + } + } + return css; +} + +/** + * @param {any} value + * @param {Record|[Record,Record]} [styles] + * @returns {string|null} + */ +export function to_style(value, styles) { + if (styles) { + var new_style = ''; + /** @type {Record | undefined} */ + var normal_styles; + /** @type {Record | undefined} */ + var important_styles; + if (Array.isArray(styles)) { + normal_styles = styles[0]; + important_styles = styles[1]; + } else { + normal_styles = styles; + } + if (value) { + value = String(value) + .replaceAll(/\s*\/\*.*?\*\/\s*/g, '') + .trim(); + + /** @type {boolean | '"' | "'"} */ + var in_str = false; + var in_apo = 0; + var in_comment = false; + + var reserved_names = []; + if (normal_styles) { + reserved_names.push(...Object.keys(normal_styles)); + } + if (important_styles) { + reserved_names.push(...Object.keys(important_styles)); + } + + var start_index = 0; + var name_index = -1; + const len = value.length; + for (var i = 0; i < len; i++) { + var c = value[i]; + + if (in_comment) { + if (c === '/' && value[i - 1] === '*') { + in_comment = false; + } + } else if (in_str) { + if (in_str === c) { + in_str = false; + } + } else if (c === '/' && value[i + 1] === '*') { + in_comment = true; + } else if (c === '"' || c === "'") { + in_str = c; + } else if (c === '(') { + in_apo++; + } else if (c === ')') { + in_apo--; + } + if (!in_comment && in_str === false && in_apo === 0) { + if (c === ':' && name_index < 0) { + name_index = i; + } else if (c === ';' || i === len - 1) { + if (name_index > 0) { + let name = value.substring(start_index, name_index).trim(); + // if (name.indexOf('/*') > 0) { + // name = name.replaceAll(/\/\*.*?\*\//g, '').trim(); + // } + if (name[0] !== '-' || name[1] !== '-') { + name = name.toLowerCase(); + } + if (!reserved_names.includes(name)) { + if (c !== ';') { + i++; + } + const property = value.substring(start_index, i).trim(); + new_style += ' ' + property + ';'; + } + } + start_index = i + 1; + name_index = -1; + } + } + } + } + + if (normal_styles) { + new_style += append_styles(normal_styles); + } + if (important_styles) { + new_style += append_styles(important_styles, true); + } + new_style = new_style.trim(); + return new_style === '' ? null : new_style; + } else if (value == null) { + return null; + } + return String(value); +} From 526a2b85b66efe2df7c44a534706d2f08f48a947 Mon Sep 17 00:00:00 2001 From: adiguba Date: Sun, 2 Mar 2025 19:54:34 +0100 Subject: [PATCH 04/34] remove `style=""` --- .../inline-style-directive-spread-and-attr-empty/_config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-style-directive-spread-and-attr-empty/_config.js b/packages/svelte/tests/runtime-legacy/samples/inline-style-directive-spread-and-attr-empty/_config.js index 9ff0007c3713..04c9868ac378 100644 --- a/packages/svelte/tests/runtime-legacy/samples/inline-style-directive-spread-and-attr-empty/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/inline-style-directive-spread-and-attr-empty/_config.js @@ -2,6 +2,6 @@ import { test } from '../../test'; export default test({ html: ` -

+

` }); From 7a4886e39288c1b3d92d911f995d93c7719e57c7 Mon Sep 17 00:00:00 2001 From: adiguba Date: Sun, 2 Mar 2025 20:20:29 +0100 Subject: [PATCH 05/34] use cssTest for perfs --- packages/svelte/src/internal/client/dom/elements/style.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dom/elements/style.js b/packages/svelte/src/internal/client/dom/elements/style.js index 4ddb96405b97..296795bf5dc3 100644 --- a/packages/svelte/src/internal/client/dom/elements/style.js +++ b/packages/svelte/src/internal/client/dom/elements/style.js @@ -35,7 +35,7 @@ export function set_style(dom, value, prev_styles, next_styles) { if (next_style_attr == null) { dom.removeAttribute('style'); } else { - dom.setAttribute('style', next_style_attr); + dom.style.cssText = next_style_attr; } } // @ts-expect-error From 6af6978d4cedf23c6b4fa538e64e269dd4ddf64b Mon Sep 17 00:00:00 2001 From: adiguba Date: Sun, 2 Mar 2025 20:20:38 +0100 Subject: [PATCH 06/34] base test --- .../style-directive-mutations/_config.js | 112 ++++++++++++++++++ .../style-directive-mutations/main.svelte | 54 +++++++++ 2 files changed, 166 insertions(+) create mode 100644 packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js new file mode 100644 index 000000000000..80e4cb826786 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js @@ -0,0 +1,112 @@ +import { flushSync, tick } from 'svelte'; +import { test } from '../../test'; + +// This test counts mutations on hydration +// set_class() should not mutate class on hydration, except if mismatch +export default test({ + mode: ['server', 'hydrate'], + + server_props: { + browser: false + }, + + props: { + browser: true + }, + + html: ` +
+
+
+
+
+
+
+
+
+
+
+ `, + + async test({ target, assert, component, instance }) { + flushSync(); + tick(); + assert.deepEqual(instance.get_and_clear_mutations(), []); + + // component.foo = false; + // flushSync(); + // tick(); + // assert.deepEqual( + // instance.get_and_clear_mutations(), + // ['DIV', 'SPAN', 'B', 'I', 'DIV', 'SPAN', 'B', 'I'], + // 'first mutation' + // ); + + // assert.htmlEqual( + // target.innerHTML, + // ` + //
+ //
+ // + // + // + + //
+ // + // + // + //
+ // ` + // ); + + // component.foo = true; + // flushSync(); + // assert.deepEqual( + // instance.get_and_clear_mutations(), + // ['DIV', 'SPAN', 'B', 'I', 'DIV', 'SPAN', 'B', 'I'], + // 'second mutation' + // ); + + // assert.htmlEqual( + // target.innerHTML, + // ` + //
+ //
+ // + // + // + + //
+ // + // + // + //
+ // ` + // ); + + // component.classname = 'another'; + // flushSync(); + // assert.deepEqual( + // instance.get_and_clear_mutations(), + // ['DIV', 'B', 'DIV', 'B'], + // 'class mutation' + // ); + + // assert.htmlEqual( + // target.innerHTML, + // ` + //
+ //
+ // + // + // + + //
+ // + // + // + //
+ // ` + // ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte new file mode 100644 index 000000000000..e58bba5e9aca --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte @@ -0,0 +1,54 @@ + + +
+
+
+
+
+
+
+
+
+
+
From 7804a1f3ff240041b004f4a0d1c59e67e0e07705 Mon Sep 17 00:00:00 2001 From: adiguba Date: Sun, 2 Mar 2025 20:39:46 +0100 Subject: [PATCH 07/34] test --- .../style-directive-mutations/_config.js | 127 ++++++++---------- .../style-directive-mutations/main.svelte | 2 +- 2 files changed, 56 insertions(+), 73 deletions(-) diff --git a/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js index 80e4cb826786..bd76e4e6b929 100644 --- a/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js @@ -2,7 +2,7 @@ import { flushSync, tick } from 'svelte'; import { test } from '../../test'; // This test counts mutations on hydration -// set_class() should not mutate class on hydration, except if mismatch +// set_style() should not mutate style on hydration, except if mismatch export default test({ mode: ['server', 'hydrate'], @@ -14,8 +14,22 @@ export default test({ browser: true }, + ssrHtml: ` +
+
+
+
+
+
+
+
+
+
+
+ `, + html: ` -
+
@@ -31,82 +45,51 @@ export default test({ async test({ target, assert, component, instance }) { flushSync(); tick(); - assert.deepEqual(instance.get_and_clear_mutations(), []); - - // component.foo = false; - // flushSync(); - // tick(); - // assert.deepEqual( - // instance.get_and_clear_mutations(), - // ['DIV', 'SPAN', 'B', 'I', 'DIV', 'SPAN', 'B', 'I'], - // 'first mutation' - // ); - - // assert.htmlEqual( - // target.innerHTML, - // ` - //
- //
- // - // - // + assert.deepEqual(instance.get_and_clear_mutations(), ['MAIN']); - //
- // - // - // - //
- // ` - // ); + let divs = target.querySelectorAll('div'); - // component.foo = true; - // flushSync(); - // assert.deepEqual( - // instance.get_and_clear_mutations(), - // ['DIV', 'SPAN', 'B', 'I', 'DIV', 'SPAN', 'B', 'I'], - // 'second mutation' - // ); + // Note : we cannot compare HTML because set_style() use dom.style.cssText + // which can alter the format of the attribute... - // assert.htmlEqual( - // target.innerHTML, - // ` - //
- //
- // - // - // + divs.forEach((d) => assert.equal(d.style.margin, '')); + divs.forEach((d) => assert.equal(d.style.color, 'red')); + divs.forEach((d) => assert.equal(d.style.fontSize, '18px')); - //
- // - // - // - //
- // ` - // ); + component.margin = '1px'; + flushSync(); + assert.deepEqual( + instance.get_and_clear_mutations(), + ['DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV'], + 'margin' + ); + divs.forEach((d) => assert.equal(d.style.margin, '1px')); - // component.classname = 'another'; - // flushSync(); - // assert.deepEqual( - // instance.get_and_clear_mutations(), - // ['DIV', 'B', 'DIV', 'B'], - // 'class mutation' - // ); + component.color = 'yellow'; + flushSync(); + assert.deepEqual( + instance.get_and_clear_mutations(), + ['DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV'], + 'color' + ); + divs.forEach((d) => assert.equal(d.style.color, 'yellow')); - // assert.htmlEqual( - // target.innerHTML, - // ` - //
- //
- // - // - // + component.fontSize = '10px'; + flushSync(); + assert.deepEqual( + instance.get_and_clear_mutations(), + ['DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV'], + 'fontSize' + ); + divs.forEach((d) => assert.equal(d.style.fontSize, '10px')); - //
- // - // - // - //
- // ` - // ); + component.fontSize = null; + flushSync(); + assert.deepEqual( + instance.get_and_clear_mutations(), + ['DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV'], + 'fontSize' + ); + divs.forEach((d) => assert.equal(d.style.fontSize, '')); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte index e58bba5e9aca..ae4da8ae37c7 100644 --- a/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte @@ -41,7 +41,7 @@ } -
+
From fce4169ed5b5dcd49e070955622909b622bf1087 Mon Sep 17 00:00:00 2001 From: adiguba Date: Sun, 2 Mar 2025 20:41:10 +0100 Subject: [PATCH 08/34] changeset --- .changeset/strange-planes-shout.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strange-planes-shout.md diff --git a/.changeset/strange-planes-shout.md b/.changeset/strange-planes-shout.md new file mode 100644 index 000000000000..a2bc4edbf6d7 --- /dev/null +++ b/.changeset/strange-planes-shout.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: rewrite set_style() to handle directives From 84b1d7fed2c07166904595344f499a9d8512518c Mon Sep 17 00:00:00 2001 From: adiguba Date: Sun, 2 Mar 2025 20:49:42 +0100 Subject: [PATCH 09/34] revert dom.style.cssText --- packages/svelte/src/internal/client/dom/elements/style.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dom/elements/style.js b/packages/svelte/src/internal/client/dom/elements/style.js index 296795bf5dc3..0693879569eb 100644 --- a/packages/svelte/src/internal/client/dom/elements/style.js +++ b/packages/svelte/src/internal/client/dom/elements/style.js @@ -35,7 +35,8 @@ export function set_style(dom, value, prev_styles, next_styles) { if (next_style_attr == null) { dom.removeAttribute('style'); } else { - dom.style.cssText = next_style_attr; + // TODO : dom.style.cssText = next_style_attr; + dom.setAttribute('style', next_style_attr); } } // @ts-expect-error From d43d14a709c4db90f51995a93776364a014a094f Mon Sep 17 00:00:00 2001 From: adiguba Date: Mon, 3 Mar 2025 19:21:56 +0100 Subject: [PATCH 10/34] format name --- .../client/visitors/RegularElement.js | 7 +----- .../svelte/src/internal/shared/attributes.js | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 15 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 33012b7bbd62..e9840ed1d16e 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 @@ -541,12 +541,7 @@ export function build_style_directives_object(style_directives, context) { : build_attribute_value(directive.value, context, (value, metadata) => metadata.has_call ? get_expression_id(context.state, value) : value ).value; - let name = directive.name; - if (name[0] !== '-' || name[1] !== '-') { - name = name.toLowerCase(); - } - - const property = b.init(name, expression); + const property = b.init(directive.name, expression); if (directive.modifiers.includes('important')) { important_properties.push(property); } else { diff --git a/packages/svelte/src/internal/shared/attributes.js b/packages/svelte/src/internal/shared/attributes.js index e0705c2f99dc..431caf014972 100644 --- a/packages/svelte/src/internal/shared/attributes.js +++ b/packages/svelte/src/internal/shared/attributes.js @@ -101,6 +101,17 @@ function append_styles(styles, important = false) { return css; } +/** + * @param {string} name + * @returns {string} + */ +function to_css_name(name) { + if (name[0] !== '-' || name[1] !== '-') { + return name.toLowerCase(); + } + return name; +} + /** * @param {any} value * @param {Record|[Record,Record]} [styles] @@ -131,10 +142,10 @@ export function to_style(value, styles) { var reserved_names = []; if (normal_styles) { - reserved_names.push(...Object.keys(normal_styles)); + reserved_names.push(...Object.keys(normal_styles).map(to_css_name)); } if (important_styles) { - reserved_names.push(...Object.keys(important_styles)); + reserved_names.push(...Object.keys(important_styles).map(to_css_name)); } var start_index = 0; @@ -165,13 +176,7 @@ export function to_style(value, styles) { name_index = i; } else if (c === ';' || i === len - 1) { if (name_index > 0) { - let name = value.substring(start_index, name_index).trim(); - // if (name.indexOf('/*') > 0) { - // name = name.replaceAll(/\/\*.*?\*\//g, '').trim(); - // } - if (name[0] !== '-' || name[1] !== '-') { - name = name.toLowerCase(); - } + let name = to_css_name(value.substring(start_index, name_index).trim()); if (!reserved_names.includes(name)) { if (c !== ';') { i++; From 6895d881ec32ea8f0c994493d73abe2cb48007dd Mon Sep 17 00:00:00 2001 From: adiguba Date: Mon, 3 Mar 2025 19:22:18 +0100 Subject: [PATCH 11/34] use style.cssText + adapt test --- packages/svelte/src/internal/client/dom/elements/style.js | 3 +-- .../samples/inline-style-optimisation-bailout/_config.js | 2 +- .../samples/inline-style-optimisation-bailout/main.svelte | 2 +- .../tests/runtime-runes/samples/dynamic-style-attr/_config.js | 4 ++-- .../runtime-runes/samples/dynamic-style-attr/main.svelte | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/elements/style.js b/packages/svelte/src/internal/client/dom/elements/style.js index 0693879569eb..296795bf5dc3 100644 --- a/packages/svelte/src/internal/client/dom/elements/style.js +++ b/packages/svelte/src/internal/client/dom/elements/style.js @@ -35,8 +35,7 @@ export function set_style(dom, value, prev_styles, next_styles) { if (next_style_attr == null) { dom.removeAttribute('style'); } else { - // TODO : dom.style.cssText = next_style_attr; - dom.setAttribute('style', next_style_attr); + dom.style.cssText = next_style_attr; } } // @ts-expect-error diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/_config.js b/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/_config.js index adcdc4706d88..e9965b2b1e26 100644 --- a/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/_config.js @@ -2,7 +2,7 @@ import { ok, test } from '../../test'; export default test({ html: ` -

color: red

+

color: red;

`, test({ assert, component, target, window }) { diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/main.svelte b/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/main.svelte index 35b768547e25..e07adaa1c9d8 100644 --- a/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/main.svelte +++ b/packages/svelte/tests/runtime-legacy/samples/inline-style-optimisation-bailout/main.svelte @@ -1,5 +1,5 @@

{styles}

\ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/dynamic-style-attr/_config.js b/packages/svelte/tests/runtime-runes/samples/dynamic-style-attr/_config.js index f6829721795c..20092ddadf34 100644 --- a/packages/svelte/tests/runtime-runes/samples/dynamic-style-attr/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/dynamic-style-attr/_config.js @@ -2,7 +2,7 @@ import { test } from '../../test'; import { flushSync } from 'svelte'; export default test({ - html: `
Hello world
From ca07d89f3ad5e9e4d03becbfed68b10e954996c2 Mon Sep 17 00:00:00 2001 From: adiGuba Date: Tue, 4 Mar 2025 13:37:08 +0100 Subject: [PATCH 12/34] Apply suggestions from code review suggestions from dummdidumm Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/strange-planes-shout.md | 2 +- .../phases/3-transform/client/visitors/shared/element.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.changeset/strange-planes-shout.md b/.changeset/strange-planes-shout.md index a2bc4edbf6d7..58ef25274022 100644 --- a/.changeset/strange-planes-shout.md +++ b/.changeset/strange-planes-shout.md @@ -2,4 +2,4 @@ 'svelte': patch --- -chore: rewrite set_style() to handle directives +fix: make `style:` directive and CSS handling more robust 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 50cdd1acd267..132fd1b3984b 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 @@ -94,7 +94,7 @@ export function build_set_attributes( ); is_dynamic ||= - style_directives.find((directive) => directive.metadata.expression.has_state) !== null; + style_directives.some((directive) => directive.metadata.expression.has_state); } const call = b.call( @@ -267,6 +267,7 @@ export function build_set_style(node_id, value, has_state, style_directives, con let prev; /** @type {ArrayExpression | ObjectExpression | undefined} */ let next; + if (style_directives.length) { next = build_style_directives_object(style_directives, context); has_state ||= style_directives.some((d) => d.metadata.expression.has_state); From 962e8bb2c9e51d667d9e87fd09d29574c5f5910a Mon Sep 17 00:00:00 2001 From: adiguba Date: Tue, 4 Mar 2025 18:43:17 +0100 Subject: [PATCH 13/34] fix priority --- .../svelte/src/internal/client/dom/elements/style.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/elements/style.js b/packages/svelte/src/internal/client/dom/elements/style.js index 296795bf5dc3..92da18543101 100644 --- a/packages/svelte/src/internal/client/dom/elements/style.js +++ b/packages/svelte/src/internal/client/dom/elements/style.js @@ -5,16 +5,16 @@ import { hydrating } from '../hydration.js'; * @param {Element & ElementCSSInlineStyle} dom * @param {Record} prev * @param {Record} next - * @param {string} important + * @param {string} [priority] */ -function update_styles(dom, prev = {}, next, important) { +function update_styles(dom, prev = {}, next, priority) { for (const key in next) { const value = next[key]; if (prev[key] !== value) { if (next[key] == null) { dom.style.removeProperty(key); } else { - dom.style.setProperty(key, value, important); + dom.style.setProperty(key, value, priority); } } } @@ -42,10 +42,10 @@ export function set_style(dom, value, prev_styles, next_styles) { dom.__style = value; } else if (next_styles) { if (Array.isArray(next_styles)) { - update_styles(dom, prev_styles?.[0], next_styles[0], ''); - update_styles(dom, prev_styles?.[1], next_styles[1], ''); + update_styles(dom, prev_styles?.[0], next_styles[0]); + update_styles(dom, prev_styles?.[1], next_styles[1], 'important'); } else { - update_styles(dom, prev_styles, next_styles, ''); + update_styles(dom, prev_styles, next_styles); } } return next_styles; From c3e9c392c02b241deb584b89195a3fa7594fb7c6 Mon Sep 17 00:00:00 2001 From: adiguba Date: Tue, 4 Mar 2025 21:31:57 +0100 Subject: [PATCH 14/34] lint --- .../phases/3-transform/client/visitors/shared/element.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 132fd1b3984b..3b6651e3dd4b 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 @@ -93,8 +93,7 @@ export function build_set_attributes( ) ); - is_dynamic ||= - style_directives.some((directive) => directive.metadata.expression.has_state); + is_dynamic ||= style_directives.some((directive) => directive.metadata.expression.has_state); } const call = b.call( From 3deff5bd73bafbf389e3bde87d5e08e64507d9cd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 14:25:37 -0500 Subject: [PATCH 15/34] yawn --- .../3-transform/client/visitors/RegularElement.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 a5afaa272114..2a7640293eb9 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 @@ -218,7 +218,15 @@ export function RegularElement(node, context) { if (has_spread) { const attributes_id = b.id(context.state.scope.generate('attributes')); - build_set_attributes(attributes, class_directives, style_directives, context, node, node_id, attributes_id); + build_set_attributes( + attributes, + class_directives, + style_directives, + context, + node, + node_id, + attributes_id + ); // If value binding exists, that one takes care of calling $.init_select if (node.name === 'select' && !bindings.has('value')) { From 8c619afbb98240e42c36a5a38bdc61788cff9d32 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 14:29:49 -0500 Subject: [PATCH 16/34] update test --- .../runtime-runes/samples/style-update/_config.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/svelte/tests/runtime-runes/samples/style-update/_config.js b/packages/svelte/tests/runtime-runes/samples/style-update/_config.js index f0b7f2648e6d..52690a431a4d 100644 --- a/packages/svelte/tests/runtime-runes/samples/style-update/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/style-update/_config.js @@ -3,6 +3,7 @@ import { test } from '../../test'; const style_1 = 'invalid-key:0; margin:4px;;color: green ;color:blue '; const style_2 = ' other-key : 0 ; padding:2px; COLOR:green; color: blue'; +const style_2_normalized = 'padding: 2px; color: blue;'; // https://github.com/sveltejs/svelte/issues/15309 export default test({ @@ -10,7 +11,7 @@ export default test({ style: style_1 }, - html: ` + ssrHtml: `
@@ -25,11 +26,11 @@ export default test({ assert.htmlEqual( target.innerHTML, ` -
-
+
+
- - + + ` ); From e55db6caafe89b0b800f984c3b0bce65b5c0923c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 15:00:17 -0500 Subject: [PATCH 17/34] we can simplify some stuff now --- .../client/visitors/RegularElement.js | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 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 2a7640293eb9..5dbfec7ee116 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 @@ -581,7 +581,6 @@ export function build_style_directives_object(style_directives, context) { * @param {AST.ClassDirective[]} class_directives * @param {AST.StyleDirective[]} style_directives * @param {ComponentContext} context - * @returns {boolean} */ function build_element_attribute_update_assignment( element, @@ -611,24 +610,17 @@ function build_element_attribute_update_assignment( if (is_autofocus) { state.init.push(b.stmt(b.call('$.autofocus', node_id, value))); - return false; - } - - // Special case for Firefox who needs it set as a property in order to work - if (name === 'muted') { - if (!has_state) { - state.init.push(b.stmt(b.assignment('=', b.member(node_id, b.id('muted')), value))); - return false; - } - state.update.push(b.stmt(b.assignment('=', b.member(node_id, b.id('muted')), value))); - return false; + return; } /** @type {Statement} */ let update; - if (name === 'class') { - return build_set_class( + if (name === 'muted') { + // Special case for Firefox who needs it set as a property in order to work + update = b.stmt(b.assignment('=', b.member(node_id, b.id('muted')), value)); + } else if (name === 'class') { + build_set_class( element, node_id, attribute, @@ -638,8 +630,10 @@ function build_element_attribute_update_assignment( context, !is_svg && !is_mathml ); + return; // TODO } else if (name === 'style') { - return build_set_style(node_id, value, has_state, style_directives, context); + build_set_style(node_id, value, has_state, style_directives, context); + return; // TODO } else if (name === 'value') { update = b.stmt(b.call('$.set_value', node_id, value)); } else if (name === 'checked') { @@ -681,13 +675,7 @@ function build_element_attribute_update_assignment( ); } - if (has_state) { - state.update.push(update); - return true; - } else { - state.init.push(update); - return false; - } + (has_state ? state.update : state.init).push(update); } /** From 5398e15b3ad7dcfe3880aac420b10195077b9b40 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 15:04:37 -0500 Subject: [PATCH 18/34] simplify --- .../3-transform/client/visitors/shared/element.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) 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 cdd88f60b70a..e096c50e9bc8 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 @@ -164,7 +164,6 @@ export function get_attribute_name(element, attribute) { * @param {AST.ClassDirective[]} class_directives * @param {ComponentContext} context * @param {boolean} is_html - * @returns {boolean} */ export function build_set_class( element, @@ -234,15 +233,8 @@ export function build_set_class( set_class = b.assignment('=', previous_id, set_class); } - const update = b.stmt(set_class); - - if (has_state) { - context.state.update.push(update); - return true; - } - - context.state.init.push(update); - return false; + // TODO just return the statement + (has_state ? context.state.update : context.state.init).push(b.stmt(set_class)); } /** From 80574758e25c0c41c5d465a613cad5ff505f588e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 15:22:52 -0500 Subject: [PATCH 19/34] more --- .../client/visitors/RegularElement.js | 12 +++--------- .../client/visitors/shared/element.js | 16 +++------------- 2 files changed, 6 insertions(+), 22 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 5dbfec7ee116..bff46477451c 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 @@ -683,23 +683,20 @@ function build_element_attribute_update_assignment( * @param {Identifier} node_id * @param {AST.Attribute} attribute * @param {ComponentContext} context - * @returns {boolean} */ function build_custom_element_attribute_update_assignment(node_id, attribute, context) { const state = context.state; const name = attribute.name; // don't lowercase, as we set the element's property, which might be case sensitive let { value, has_state } = build_attribute_value(attribute.value, context); - const update = b.stmt(b.call('$.set_custom_element_data', node_id, b.literal(name), value)); + const call = b.call('$.set_custom_element_data', node_id, b.literal(name), value); if (has_state) { // this is different from other updates — it doesn't get grouped, // because set_custom_element_data may not be idempotent - state.init.push(b.stmt(b.call('$.template_effect', b.thunk(update.expression)))); - return true; + state.init.push(b.stmt(b.call('$.template_effect', b.thunk(call)))); } else { - state.init.push(update); - return false; + state.init.push(b.stmt(call)); } } @@ -711,7 +708,6 @@ function build_custom_element_attribute_update_assignment(node_id, attribute, co * @param {Identifier} node_id * @param {AST.Attribute} attribute * @param {ComponentContext} context - * @returns {boolean} */ function build_element_special_value_attribute(element, node_id, attribute, context) { const state = context.state; @@ -768,9 +764,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont value, update ); - return true; } else { state.init.push(update); - 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 e096c50e9bc8..d26d04091138 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 @@ -107,11 +107,9 @@ export function build_set_attributes( context.state.init.push(b.let(attributes_id)); const update = b.stmt(b.assignment('=', attributes_id, call)); context.state.update.push(update); - return true; + } else { + context.state.init.push(b.stmt(call)); } - - context.state.init.push(b.stmt(call)); - return false; } /** @@ -243,7 +241,6 @@ export function build_set_class( * @param {boolean} has_state * @param {AST.StyleDirective[]} style_directives * @param {ComponentContext} context - * @returns {boolean} */ export function build_set_style(node_id, value, has_state, style_directives, context) { /** @type {Identifier | undefined} */ @@ -272,12 +269,5 @@ export function build_set_style(node_id, value, has_state, style_directives, con set_style = b.assignment('=', previous_id, set_style); } - const update = b.stmt(set_style); - if (has_state) { - context.state.update.push(update); - return true; - } else { - context.state.init.push(update); - return false; - } + (has_state ? context.state.update : context.state.init).push(b.stmt(set_style)); } From af918b248e5296f1cdd0856d3ea52847bbb37426 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 16:09:38 -0500 Subject: [PATCH 20/34] simplify some more --- .../client/visitors/RegularElement.js | 26 ++++++----------- .../client/visitors/SvelteElement.js | 18 +----------- .../client/visitors/shared/element.js | 28 ++++++++----------- 3 files changed, 21 insertions(+), 51 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 bff46477451c..b735b399f286 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 @@ -296,7 +296,14 @@ export function RegularElement(node, context) { continue; } - if (is_custom_element && name !== 'class' && name !== 'style') { + if (name === 'class') { + const is_svg = context.state.metadata.namespace === 'svg' || node.name === 'svg'; + const is_mathml = context.state.metadata.namespace === 'mathml'; + + build_set_class(node, node_id, attribute, class_directives, context, !is_svg && !is_mathml); + } else if (name === 'style') { + build_set_style(node_id, attribute, style_directives, context); + } else if (is_custom_element) { build_custom_element_attribute_update_assignment(node_id, attribute, context); } else { build_element_attribute_update_assignment( @@ -593,8 +600,6 @@ function build_element_attribute_update_assignment( ) { const state = context.state; const name = get_attribute_name(element, attribute); - const is_svg = context.state.metadata.namespace === 'svg' || element.name === 'svg'; - const is_mathml = context.state.metadata.namespace === 'mathml'; const is_autofocus = name === 'autofocus'; @@ -619,21 +624,6 @@ function build_element_attribute_update_assignment( if (name === 'muted') { // Special case for Firefox who needs it set as a property in order to work update = b.stmt(b.assignment('=', b.member(node_id, b.id('muted')), value)); - } else if (name === 'class') { - build_set_class( - element, - node_id, - attribute, - value, - has_state, - class_directives, - context, - !is_svg && !is_mathml - ); - return; // TODO - } else if (name === 'style') { - build_set_style(node_id, value, has_state, style_directives, context); - return; // TODO } else if (name === 'value') { update = b.stmt(b.call('$.set_value', node_id, value)); } else if (name === 'checked') { 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 3b9c50710a63..115eb6ccc11e 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 @@ -78,23 +78,7 @@ export function SvelteElement(node, context) { attributes[0].name.toLowerCase() === 'class' && is_text_attribute(attributes[0]) ) { - // special case when there only a class attribute, without call expression - let { value, has_state } = build_attribute_value( - attributes[0].value, - context, - (value, metadata) => (metadata.has_call ? get_expression_id(context.state, value) : value) - ); - - build_set_class( - node, - element_id, - attributes[0], - value, - has_state, - class_directives, - inner_context, - false - ); + build_set_class(node, element_id, attributes[0], class_directives, inner_context, false); } else if (attributes.length) { const attributes_id = b.id(context.state.scope.generate('attributes')); 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 d26d04091138..eca08fd88a72 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 @@ -156,23 +156,16 @@ export function get_attribute_name(element, attribute) { /** * @param {AST.RegularElement | AST.SvelteElement} element * @param {Identifier} node_id - * @param {AST.Attribute | null} attribute - * @param {Expression} value - * @param {boolean} has_state + * @param {AST.Attribute} attribute * @param {AST.ClassDirective[]} class_directives * @param {ComponentContext} context * @param {boolean} is_html */ -export function build_set_class( - element, - node_id, - attribute, - value, - has_state, - class_directives, - context, - is_html -) { +export function build_set_class(element, node_id, attribute, class_directives, context, is_html) { + let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => + metadata.has_call ? get_expression_id(context.state, value) : value + ); + if (attribute && attribute.metadata.needs_clsx) { value = b.call('$.clsx', value); } @@ -237,12 +230,15 @@ export function build_set_class( /** * @param {Identifier} node_id - * @param {Expression} value - * @param {boolean} has_state + * @param {AST.Attribute} attribute * @param {AST.StyleDirective[]} style_directives * @param {ComponentContext} context */ -export function build_set_style(node_id, value, has_state, style_directives, context) { +export function build_set_style(node_id, attribute, style_directives, context) { + let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => + metadata.has_call ? get_expression_id(context.state, value) : value + ); + /** @type {Identifier | undefined} */ let previous_id; /** @type {ObjectExpression | Identifier | undefined} */ From 542363cd4355a5f47d3a5d07b0978a63575f0918 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 16:13:23 -0500 Subject: [PATCH 21/34] more --- .../client/visitors/RegularElement.js | 20 +++---------------- 1 file changed, 3 insertions(+), 17 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 b735b399f286..fe014c4b8c90 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 @@ -297,24 +297,14 @@ export function RegularElement(node, context) { } if (name === 'class') { - const is_svg = context.state.metadata.namespace === 'svg' || node.name === 'svg'; - const is_mathml = context.state.metadata.namespace === 'mathml'; - - build_set_class(node, node_id, attribute, class_directives, context, !is_svg && !is_mathml); + const is_html = context.state.metadata.namespace === 'html' && node.name !== 'svg'; + build_set_class(node, node_id, attribute, class_directives, context, is_html); } else if (name === 'style') { build_set_style(node_id, attribute, style_directives, context); } else if (is_custom_element) { build_custom_element_attribute_update_assignment(node_id, attribute, context); } else { - build_element_attribute_update_assignment( - node, - node_id, - attribute, - attributes, - class_directives, - style_directives, - context - ); + build_element_attribute_update_assignment(node, node_id, attribute, attributes, context); } } } @@ -585,8 +575,6 @@ export function build_style_directives_object(style_directives, context) { * @param {Identifier} node_id * @param {AST.Attribute} attribute * @param {Array} attributes - * @param {AST.ClassDirective[]} class_directives - * @param {AST.StyleDirective[]} style_directives * @param {ComponentContext} context */ function build_element_attribute_update_assignment( @@ -594,8 +582,6 @@ function build_element_attribute_update_assignment( node_id, attribute, attributes, - class_directives, - style_directives, context ) { const state = context.state; From 1aa17c572b5745b373cabbd132c6fbe6ecf6c65e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 16:18:38 -0500 Subject: [PATCH 22/34] more --- .../client/visitors/RegularElement.js | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 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 fe014c4b8c90..03150571a020 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 @@ -296,7 +296,10 @@ export function RegularElement(node, context) { continue; } - if (name === 'class') { + if (name === 'autofocus') { + let { value } = build_attribute_value(attribute.value, context); + context.state.init.push(b.stmt(b.call('$.autofocus', node_id, value))); + } else if (name === 'class') { const is_html = context.state.metadata.namespace === 'html' && node.name !== 'svg'; build_set_class(node, node_id, attribute, class_directives, context, is_html); } else if (name === 'style') { @@ -587,23 +590,10 @@ function build_element_attribute_update_assignment( const state = context.state; const name = get_attribute_name(element, attribute); - const is_autofocus = name === 'autofocus'; - let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => - metadata.has_call - ? // if it's autofocus we will not add this to a template effect so we don't want to get the expression id - // but separately memoize the expression - is_autofocus - ? memoize_expression(state, value) - : get_expression_id(state, value) - : value + metadata.has_call ? get_expression_id(state, value) : value ); - if (is_autofocus) { - state.init.push(b.stmt(b.call('$.autofocus', node_id, value))); - return; - } - /** @type {Statement} */ let update; From 2cc4c1a8b746f10478431d612566eec59390f394 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 17:12:50 -0500 Subject: [PATCH 23/34] more --- .../client/visitors/RegularElement.js | 89 +++++++++---------- 1 file changed, 44 insertions(+), 45 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 03150571a020..7f0dbe3d0706 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 @@ -307,7 +307,15 @@ export function RegularElement(node, context) { } else if (is_custom_element) { build_custom_element_attribute_update_assignment(node_id, attribute, context); } else { - build_element_attribute_update_assignment(node, node_id, attribute, attributes, context); + const { value, has_state } = build_attribute_value( + attribute.value, + context, + (value, metadata) => (metadata.has_call ? get_expression_id(context.state, value) : value) + ); + + const update = build_element_attribute_update(node, node_id, name, value, attributes); + + (has_state ? context.state.update : context.state.init).push(b.stmt(update)); } } } @@ -576,37 +584,29 @@ export function build_style_directives_object(style_directives, context) { * Returns true if attribute is deemed reactive, false otherwise. * @param {AST.RegularElement} element * @param {Identifier} node_id - * @param {AST.Attribute} attribute + * @param {string} name + * @param {Expression} value * @param {Array} attributes - * @param {ComponentContext} context */ -function build_element_attribute_update_assignment( - element, - node_id, - attribute, - attributes, - context -) { - const state = context.state; - const name = get_attribute_name(element, attribute); +function build_element_attribute_update(element, node_id, name, value, attributes) { + if (name === 'muted') { + // Special case for Firefox who needs it set as a property in order to work + return b.assignment('=', b.member(node_id, b.id('muted')), value); + } - let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => - metadata.has_call ? get_expression_id(state, value) : value - ); + if (name === 'value') { + return b.call('$.set_value', node_id, value); + } - /** @type {Statement} */ - let update; + if (name === 'checked') { + return b.call('$.set_checked', node_id, value); + } - if (name === 'muted') { - // Special case for Firefox who needs it set as a property in order to work - update = b.stmt(b.assignment('=', b.member(node_id, b.id('muted')), value)); - } else if (name === 'value') { - update = b.stmt(b.call('$.set_value', node_id, value)); - } else if (name === 'checked') { - update = b.stmt(b.call('$.set_checked', node_id, value)); - } else if (name === 'selected') { - update = b.stmt(b.call('$.set_selected', node_id, value)); - } else if ( + if (name === 'selected') { + return b.call('$.set_selected', node_id, value); + } + + if ( // If we would just set the defaultValue property, it would override the value property, // because it is set in the template which implicitly means it's also setting the default value, // and if one updates the default value while the input is pristine it will also update the @@ -617,35 +617,34 @@ function build_element_attribute_update_assignment( ) || (element.name === 'textarea' && element.fragment.nodes.length > 0)) ) { - update = b.stmt(b.call('$.set_default_value', node_id, value)); - } else if ( + return b.call('$.set_default_value', node_id, value); + } + + if ( // See defaultValue comment name === 'defaultChecked' && attributes.some( (attr) => attr.type === 'Attribute' && attr.name === 'checked' && attr.value === true ) ) { - update = b.stmt(b.call('$.set_default_checked', node_id, value)); - } else if (is_dom_property(name)) { - update = b.stmt(b.assignment('=', b.member(node_id, name), value)); - } else { - const callee = name.startsWith('xlink') ? '$.set_xlink_attribute' : '$.set_attribute'; - update = b.stmt( - b.call( - callee, - node_id, - b.literal(name), - value, - is_ignored(element, 'hydration_attribute_changed') && b.true - ) - ); + return b.call('$.set_default_checked', node_id, value); } - (has_state ? state.update : state.init).push(update); + if (is_dom_property(name)) { + return b.assignment('=', b.member(node_id, name), value); + } + + return b.call( + name.startsWith('xlink') ? '$.set_xlink_attribute' : '$.set_attribute', + node_id, + b.literal(name), + value, + is_ignored(element, 'hydration_attribute_changed') && b.true + ); } /** - * Like `build_element_attribute_update_assignment` but without any special attribute treatment. + * Like `build_element_attribute_update` but without any special attribute treatment. * @param {Identifier} node_id * @param {AST.Attribute} attribute * @param {ComponentContext} context From b079256d8ef4a4a2e7b9f80de5a5622f222693e6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 17:18:16 -0500 Subject: [PATCH 24/34] more --- .../client/visitors/RegularElement.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 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 7f0dbe3d0706..e4affb1ccc47 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 @@ -650,19 +650,16 @@ function build_element_attribute_update(element, node_id, name, value, attribute * @param {ComponentContext} context */ function build_custom_element_attribute_update_assignment(node_id, attribute, context) { - const state = context.state; - const name = attribute.name; // don't lowercase, as we set the element's property, which might be case sensitive - let { value, has_state } = build_attribute_value(attribute.value, context); + const { value, has_state } = build_attribute_value(attribute.value, context); - const call = b.call('$.set_custom_element_data', node_id, b.literal(name), value); + // don't lowercase name, as we set the element's property, which might be case sensitive + const call = b.call('$.set_custom_element_data', node_id, b.literal(attribute.name), value); - if (has_state) { - // this is different from other updates — it doesn't get grouped, - // because set_custom_element_data may not be idempotent - state.init.push(b.stmt(b.call('$.template_effect', b.thunk(call)))); - } else { - state.init.push(b.stmt(call)); - } + // this is different from other updates — it doesn't get grouped, + // because set_custom_element_data may not be idempotent + const update = has_state ? b.call('$.template_effect', b.thunk(call)) : call; + + context.state.init.push(b.stmt(update)); } /** From 2115c8cbdea9429b6a9c7fb0769482dee42b17fa Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 17:23:05 -0500 Subject: [PATCH 25/34] more --- .../phases/3-transform/client/visitors/RegularElement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e4affb1ccc47..cd22c41a6663 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 @@ -682,7 +682,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont metadata.has_call ? // if is a select with value we will also invoke `init_select` which need a reference before the template effect so we memoize separately is_select_with_value - ? memoize_expression(context.state, value) + ? memoize_expression(state, value) : get_expression_id(state, value) : value ); From db72611f1e3f3218a90562fde0bdac1123ec79ad Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 17:52:42 -0500 Subject: [PATCH 26/34] remove continue --- .../phases/3-transform/client/visitors/RegularElement.js | 6 ++---- 1 file changed, 2 insertions(+), 4 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 cd22c41a6663..4192c023be4f 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 @@ -267,6 +267,7 @@ export function RegularElement(node, context) { } const name = get_attribute_name(node, attribute); + if ( !is_custom_element && !cannot_be_set_statically(attribute.name) && @@ -293,10 +294,7 @@ export function RegularElement(node, context) { }` ); } - continue; - } - - if (name === 'autofocus') { + } else if (name === 'autofocus') { let { value } = build_attribute_value(attribute.value, context); context.state.init.push(b.stmt(b.call('$.autofocus', node_id, value))); } else if (name === 'class') { From abbe46f0c6839a202a9d880f89e2332a300d87e0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 18:09:13 -0500 Subject: [PATCH 27/34] tweak --- .../phases/3-transform/client/visitors/shared/element.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 eca08fd88a72..35de776e1c63 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 @@ -224,7 +224,6 @@ export function build_set_class(element, node_id, attribute, class_directives, c set_class = b.assignment('=', previous_id, set_class); } - // TODO just return the statement (has_state ? context.state.update : context.state.init).push(b.stmt(set_class)); } @@ -241,8 +240,10 @@ export function build_set_style(node_id, attribute, style_directives, context) { /** @type {Identifier | undefined} */ let previous_id; + /** @type {ObjectExpression | Identifier | undefined} */ let prev; + /** @type {ArrayExpression | ObjectExpression | undefined} */ let next; From 84d7d7492cd250c1064cdc5d88468204e1072c30 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 18:15:36 -0500 Subject: [PATCH 28/34] tweak --- .../phases/3-transform/client/visitors/shared/element.js | 1 + .../phases/3-transform/server/visitors/shared/element.js | 8 ++++++++ packages/svelte/src/internal/server/index.js | 5 +---- 3 files changed, 10 insertions(+), 4 deletions(-) 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 35de776e1c63..e0eb04d8236a 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 @@ -250,6 +250,7 @@ export function build_set_style(node_id, attribute, style_directives, context) { if (style_directives.length) { next = build_style_directives_object(style_directives, context); has_state ||= style_directives.some((d) => d.metadata.expression.has_state); + if (has_state) { previous_id = b.id(context.state.scope.generate('styles')); context.state.init.push(b.declaration('let', [b.declarator(previous_id)])); diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js index 12ced8c3731f..00ea5265ad51 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js @@ -208,6 +208,7 @@ export function build_element_attributes(node, context) { build_element_spread_attributes(node, attributes, style_directives, class_directives, context); } else { const css_hash = node.metadata.scoped ? context.state.analysis.css.hash : null; + for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) { const name = get_attribute_name(node, attribute); const can_use_literal = @@ -222,9 +223,11 @@ export function build_element_attributes(node, context) { WHITESPACE_INSENSITIVE_ATTRIBUTES.includes(name) ) ).value; + if (name === 'class' && css_hash) { literal_value = (String(literal_value) + ' ' + css_hash).trim(); } + if (name !== 'class' || literal_value) { context.state.template.push( b.literal( @@ -236,6 +239,7 @@ export function build_element_attributes(node, context) { ) ); } + continue; } @@ -371,6 +375,7 @@ function build_element_spread_attributes( function build_attr_class(class_directives, expression, context, hash) { /** @type {ObjectExpression | undefined} */ let directives; + if (class_directives.length) { directives = b.object( class_directives.map((directive) => @@ -382,6 +387,7 @@ function build_attr_class(class_directives, expression, context, hash) { ) ); } + let css_hash; if (hash) { @@ -393,6 +399,7 @@ function build_attr_class(class_directives, expression, context, hash) { } else if (directives) { css_hash = b.null; } + return b.call('$.attr_class', expression, css_hash, directives); } @@ -405,6 +412,7 @@ function build_attr_class(class_directives, expression, context, hash) { function build_attr_style(style_directives, expression, context) { /** @type {ArrayExpression | ObjectExpression | undefined} */ let directives; + if (style_directives.length) { let normal_properties = []; let important_properties = []; diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 46abcb475c21..aac9c37dbe00 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -291,10 +291,7 @@ function style_object_to_string(style_object) { */ export function attr_class(value, hash, directives) { var result = to_class(value, hash, directives); - if (result) { - return ` class="${escape_html(result, true)}"`; - } - return ''; + return result ? ` class="${escape_html(result, true)}"` : ''; } /** From 35c500313dbe7496858b8263820047f39c6afebc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 18:15:57 -0500 Subject: [PATCH 29/34] tweak --- packages/svelte/src/internal/server/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index aac9c37dbe00..44cd10a4d7c6 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -300,10 +300,7 @@ export function attr_class(value, hash, directives) { */ export function attr_style(value, directives) { var result = to_style(value, directives); - if (result) { - return ` style="${escape_html(result, true)}"`; - } - return ''; + return result ? ` style="${escape_html(result, true)}"` : ''; } /** From 2bf0a22093544e65ee4dcf8fa7c390706f95a298 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 18:17:34 -0500 Subject: [PATCH 30/34] skip hash argument where possible --- .../phases/3-transform/server/visitors/shared/element.js | 2 -- packages/svelte/src/internal/server/index.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js index 00ea5265ad51..4a5becfb2fc6 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js @@ -396,8 +396,6 @@ function build_attr_class(class_directives, expression, context, hash) { } else { css_hash = b.literal(hash); } - } else if (directives) { - css_hash = b.null; } return b.call('$.attr_class', expression, css_hash, directives); diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 44cd10a4d7c6..6098b496c5ac 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -286,7 +286,7 @@ function style_object_to_string(style_object) { /** * @param {any} value - * @param {string | null} [hash] + * @param {string | undefined} [hash] * @param {Record} [directives] */ export function attr_class(value, hash, directives) { From a7a17b65ad040c4e9aaaee0f1b03bb7c30af06b7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 18:19:35 -0500 Subject: [PATCH 31/34] tweak --- .../phases/3-transform/client/visitors/RegularElement.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 4192c023be4f..6122dc4e0e66 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 @@ -539,13 +539,14 @@ export function build_style_directives_object(style_directives, context) { let important_properties = []; for (const directive of style_directives) { - let expression = + const expression = directive.value === true ? build_getter({ name: directive.name, type: 'Identifier' }, context.state) : build_attribute_value(directive.value, context, (value, metadata) => metadata.has_call ? get_expression_id(context.state, value) : value ).value; const property = b.init(directive.name, expression); + if (directive.modifiers.includes('important')) { important_properties.push(property); } else { From c7c1ade86c0406e66c1f6143c4fc0513912f99c9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 18:21:04 -0500 Subject: [PATCH 32/34] tweak --- .../svelte/src/internal/client/dom/elements/style.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/elements/style.js b/packages/svelte/src/internal/client/dom/elements/style.js index 92da18543101..ee42f624eec8 100644 --- a/packages/svelte/src/internal/client/dom/elements/style.js +++ b/packages/svelte/src/internal/client/dom/elements/style.js @@ -8,8 +8,9 @@ import { hydrating } from '../hydration.js'; * @param {string} [priority] */ function update_styles(dom, prev = {}, next, priority) { - for (const key in next) { - const value = next[key]; + for (var key in next) { + var value = next[key]; + if (prev[key] !== value) { if (next[key] == null) { dom.style.removeProperty(key); @@ -29,8 +30,10 @@ function update_styles(dom, prev = {}, next, priority) { export function set_style(dom, value, prev_styles, next_styles) { // @ts-expect-error var prev = dom.__style; + if (hydrating || prev !== value) { var next_style_attr = to_style(value, next_styles); + if (!hydrating || next_style_attr !== dom.getAttribute('style')) { if (next_style_attr == null) { dom.removeAttribute('style'); @@ -38,6 +41,7 @@ export function set_style(dom, value, prev_styles, next_styles) { dom.style.cssText = next_style_attr; } } + // @ts-expect-error dom.__style = value; } else if (next_styles) { @@ -48,5 +52,6 @@ export function set_style(dom, value, prev_styles, next_styles) { update_styles(dom, prev_styles, next_styles); } } + return next_styles; } From 52667ccc46a2169e02197490d9e07861b53a4eb8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 18:30:17 -0500 Subject: [PATCH 33/34] tweak --- .../svelte/src/internal/shared/attributes.js | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/svelte/src/internal/shared/attributes.js b/packages/svelte/src/internal/shared/attributes.js index 431caf014972..c8758c1d4d4d 100644 --- a/packages/svelte/src/internal/shared/attributes.js +++ b/packages/svelte/src/internal/shared/attributes.js @@ -89,15 +89,16 @@ export function to_class(value, hash, directives) { * @param {boolean} important */ function append_styles(styles, important = false) { - let separator = important ? ' !important;' : ';'; - let css = ''; + var separator = important ? ' !important;' : ';'; + var css = ''; - for (const key in styles) { - const value = styles[key]; + for (var key in styles) { + var value = styles[key]; if (value != null && value !== '') { css += ' ' + key + ': ' + value + separator; } } + return css; } @@ -114,22 +115,26 @@ function to_css_name(name) { /** * @param {any} value - * @param {Record|[Record,Record]} [styles] - * @returns {string|null} + * @param {Record | [Record, Record]} [styles] + * @returns {string | null} */ export function to_style(value, styles) { if (styles) { var new_style = ''; + /** @type {Record | undefined} */ var normal_styles; + /** @type {Record | undefined} */ var important_styles; + if (Array.isArray(styles)) { normal_styles = styles[0]; important_styles = styles[1]; } else { normal_styles = styles; } + if (value) { value = String(value) .replaceAll(/\s*\/\*.*?\*\/\s*/g, '') @@ -141,6 +146,7 @@ export function to_style(value, styles) { var in_comment = false; var reserved_names = []; + if (normal_styles) { reserved_names.push(...Object.keys(normal_styles).map(to_css_name)); } @@ -150,6 +156,7 @@ export function to_style(value, styles) { var start_index = 0; var name_index = -1; + const len = value.length; for (var i = 0; i < len; i++) { var c = value[i]; @@ -171,20 +178,24 @@ export function to_style(value, styles) { } else if (c === ')') { in_apo--; } + if (!in_comment && in_str === false && in_apo === 0) { - if (c === ':' && name_index < 0) { + if (c === ':' && name_index === -1) { name_index = i; } else if (c === ';' || i === len - 1) { - if (name_index > 0) { - let name = to_css_name(value.substring(start_index, name_index).trim()); + if (name_index !== -1) { + var name = to_css_name(value.substring(start_index, name_index).trim()); + if (!reserved_names.includes(name)) { if (c !== ';') { i++; } - const property = value.substring(start_index, i).trim(); + + var property = value.substring(start_index, i).trim(); new_style += ' ' + property + ';'; } } + start_index = i + 1; name_index = -1; } @@ -195,13 +206,14 @@ export function to_style(value, styles) { if (normal_styles) { new_style += append_styles(normal_styles); } + if (important_styles) { new_style += append_styles(important_styles, true); } + new_style = new_style.trim(); return new_style === '' ? null : new_style; - } else if (value == null) { - return null; } - return String(value); + + return value == null ? null : String(value); } From 4894e3d606e2ab1bf9aa635edf79c2c58b8ad081 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Mar 2025 18:32:57 -0500 Subject: [PATCH 34/34] tweak --- .../svelte/src/internal/client/dom/elements/style.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/elements/style.js b/packages/svelte/src/internal/client/dom/elements/style.js index ee42f624eec8..3e05eec30efa 100644 --- a/packages/svelte/src/internal/client/dom/elements/style.js +++ b/packages/svelte/src/internal/client/dom/elements/style.js @@ -3,8 +3,8 @@ import { hydrating } from '../hydration.js'; /** * @param {Element & ElementCSSInlineStyle} dom - * @param {Record} prev - * @param {Record} next + * @param {Record} prev + * @param {Record} next * @param {string} [priority] */ function update_styles(dom, prev = {}, next, priority) { @@ -23,9 +23,9 @@ function update_styles(dom, prev = {}, next, priority) { /** * @param {Element & ElementCSSInlineStyle} dom - * @param {string|null} value - * @param {Record|[Record,Record]} [prev_styles] - * @param {Record|[Record,Record]} [next_styles] + * @param {string | null} value + * @param {Record | [Record, Record]} [prev_styles] + * @param {Record | [Record, Record]} [next_styles] */ export function set_style(dom, value, prev_styles, next_styles) { // @ts-expect-error