Skip to content

chore: rewrite set_style() to handle directives #15418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ebc286e
add style attribute when needed
adiguba Mar 2, 2025
4a3561b
set_style()
adiguba Mar 2, 2025
555f11c
to_style()
adiguba Mar 2, 2025
526a2b8
remove `style=""`
adiguba Mar 2, 2025
7a4886e
use cssTest for perfs
adiguba Mar 2, 2025
6af6978
base test
adiguba Mar 2, 2025
7804a1f
test
adiguba Mar 2, 2025
fce4169
changeset
adiguba Mar 2, 2025
84b1d7f
revert dom.style.cssText
adiguba Mar 2, 2025
d43d14a
format name
adiguba Mar 3, 2025
6895d88
use style.cssText + adapt test
adiguba Mar 3, 2025
ca07d89
Apply suggestions from code review
adiguba Mar 4, 2025
962e8bb
fix priority
adiguba Mar 4, 2025
c960c3f
Merge branch 'dev/style' of https://github.com/adiguba/svelte into de…
adiguba Mar 4, 2025
c3e9c39
lint
adiguba Mar 4, 2025
ae8975b
Merge branch 'main' into dev/style
Rich-Harris Mar 5, 2025
6a66c28
merge main
Rich-Harris Mar 5, 2025
3deff5b
yawn
Rich-Harris Mar 5, 2025
8c619af
update test
Rich-Harris Mar 5, 2025
e55db6c
we can simplify some stuff now
Rich-Harris Mar 5, 2025
5398e15
simplify
Rich-Harris Mar 5, 2025
8057475
more
Rich-Harris Mar 5, 2025
af918b2
simplify some more
Rich-Harris Mar 5, 2025
542363c
more
Rich-Harris Mar 5, 2025
1aa17c5
more
Rich-Harris Mar 5, 2025
2cc4c1a
more
Rich-Harris Mar 5, 2025
b079256
more
Rich-Harris Mar 5, 2025
2115c8c
more
Rich-Harris Mar 5, 2025
db72611
remove continue
Rich-Harris Mar 5, 2025
abbe46f
tweak
Rich-Harris Mar 5, 2025
84d7d74
tweak
Rich-Harris Mar 5, 2025
35c5003
tweak
Rich-Harris Mar 5, 2025
2bf0a22
skip hash argument where possible
Rich-Harris Mar 5, 2025
a7a17b6
tweak
Rich-Harris Mar 5, 2025
c7c1ade
tweak
Rich-Harris Mar 5, 2025
52667cc
tweak
Rich-Harris Mar 5, 2025
4894e3d
tweak
Rich-Harris Mar 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/strange-planes-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

chore: rewrite set_style() to handle directives
26 changes: 24 additions & 2 deletions packages/svelte/src/compiler/phases/2-analyze/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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' */
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;

Expand All @@ -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'))
Expand Down Expand Up @@ -528,6 +525,35 @@ 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;
const property = b.init(directive.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.
Expand Down Expand Up @@ -555,6 +581,7 @@ export function build_class_directives_object(class_directives, context) {
* @param {AST.Attribute} attribute
* @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
* @param {AST.ClassDirective[]} class_directives
* @param {AST.StyleDirective[]} style_directives
* @param {ComponentContext} context
* @returns {boolean}
*/
Expand All @@ -564,6 +591,7 @@ function build_element_attribute_update_assignment(
attribute,
attributes,
class_directives,
style_directives,
context
) {
const state = context.state;
Expand Down Expand Up @@ -612,6 +640,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') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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' &&
Expand All @@ -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],
Expand All @@ -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,
Expand All @@ -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) {
Expand Down
Loading