Skip to content

Commit fe763c6

Browse files
authored
Merge branch 'main' into tweak-reactive-statements
2 parents 17a4c06 + 53d1b17 commit fe763c6

File tree

30 files changed

+705
-348
lines changed

30 files changed

+705
-348
lines changed

.changeset/serious-glasses-kiss.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

packages/svelte/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# svelte
22

3+
## 5.20.3
4+
5+
### Patch Changes
6+
7+
- fix: allow `@const` inside `#key` ([#15377](https://github.com/sveltejs/svelte/pull/15377))
8+
9+
- fix: remove unnecessary `?? ''` on some expressions ([#15287](https://github.com/sveltejs/svelte/pull/15287))
10+
11+
- fix: correctly override class attributes with class directives ([#15352](https://github.com/sveltejs/svelte/pull/15352))
12+
313
## 5.20.2
414

515
### Patch Changes

packages/svelte/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "svelte",
33
"description": "Cybernetically enhanced web apps",
44
"license": "MIT",
5-
"version": "5.20.2",
5+
"version": "5.20.3",
66
"type": "module",
77
"types": "./types/index.d.ts",
88
"engines": {

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 29 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,6 @@ export function analyze_module(ast, options) {
267267
expression: null,
268268
function_depth: 0,
269269
has_props_rune: false,
270-
instance_scope: /** @type {any} */ (null),
271270
options: /** @type {ValidatedCompileOptions} */ (options),
272271
parent_element: null,
273272
reactive_statement: null
@@ -619,7 +618,6 @@ export function analyze_component(root, source, options) {
619618
expression: null,
620619
derived_state: [],
621620
function_depth: scope.function_depth,
622-
instance_scope: instance.scope,
623621
reactive_statement: null
624622
};
625623

@@ -682,7 +680,6 @@ export function analyze_component(root, source, options) {
682680
parent_element: null,
683681
has_props_rune: false,
684682
ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module',
685-
instance_scope: instance.scope,
686683
reactive_statement: null,
687684
component_slots: new Set(),
688685
expression: null,
@@ -764,66 +761,40 @@ export function analyze_component(root, source, options) {
764761
if (!should_ignore_unused) {
765762
warn_unused(analysis.css.ast);
766763
}
764+
}
767765

768-
outer: for (const node of analysis.elements) {
769-
if (node.metadata.scoped) {
770-
// Dynamic elements in dom mode always use spread for attributes and therefore shouldn't have a class attribute added to them
771-
// TODO this happens during the analysis phase, which shouldn't know anything about client vs server
772-
if (node.type === 'SvelteElement' && options.generate === 'client') continue;
773-
774-
/** @type {AST.Attribute | undefined} */
775-
let class_attribute = undefined;
776-
777-
for (const attribute of node.attributes) {
778-
if (attribute.type === 'SpreadAttribute') {
779-
// The spread method appends the hash to the end of the class attribute on its own
780-
continue outer;
781-
}
766+
for (const node of analysis.elements) {
767+
if (node.metadata.scoped && is_custom_element_node(node)) {
768+
mark_subtree_dynamic(node.metadata.path);
769+
}
782770

783-
if (attribute.type !== 'Attribute') continue;
784-
if (attribute.name.toLowerCase() !== 'class') continue;
785-
// The dynamic class method appends the hash to the end of the class attribute on its own
786-
if (attribute.metadata.needs_clsx) continue outer;
771+
let has_class = false;
772+
let has_spread = false;
773+
let has_class_directive = false;
787774

788-
class_attribute = attribute;
789-
}
775+
for (const attribute of node.attributes) {
776+
// The spread method appends the hash to the end of the class attribute on its own
777+
if (attribute.type === 'SpreadAttribute') {
778+
has_spread = true;
779+
break;
780+
}
781+
has_class_directive ||= attribute.type === 'ClassDirective';
782+
has_class ||= attribute.type === 'Attribute' && attribute.name.toLowerCase() === 'class';
783+
}
790784

791-
if (class_attribute && class_attribute.value !== true) {
792-
if (is_text_attribute(class_attribute)) {
793-
class_attribute.value[0].data += ` ${analysis.css.hash}`;
794-
} else {
795-
/** @type {AST.Text} */
796-
const css_text = {
797-
type: 'Text',
798-
data: ` ${analysis.css.hash}`,
799-
raw: ` ${analysis.css.hash}`,
800-
start: -1,
801-
end: -1
802-
};
803-
804-
if (Array.isArray(class_attribute.value)) {
805-
class_attribute.value.push(css_text);
806-
} else {
807-
class_attribute.value = [class_attribute.value, css_text];
808-
}
809-
}
810-
} else {
811-
node.attributes.push(
812-
create_attribute('class', -1, -1, [
813-
{
814-
type: 'Text',
815-
data: analysis.css.hash,
816-
raw: analysis.css.hash,
817-
start: -1,
818-
end: -1
819-
}
820-
])
821-
);
822-
if (is_custom_element_node(node) && node.attributes.length === 1) {
823-
mark_subtree_dynamic(node.metadata.path);
785+
// We need an empty class to generate the set_class() or class="" correctly
786+
if (!has_spread && !has_class && (node.metadata.scoped || has_class_directive)) {
787+
node.attributes.push(
788+
create_attribute('class', -1, -1, [
789+
{
790+
type: 'Text',
791+
data: '',
792+
raw: '',
793+
start: -1,
794+
end: -1
824795
}
825-
}
826-
}
796+
])
797+
);
827798
}
828799
}
829800

packages/svelte/src/compiler/phases/2-analyze/types.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ export interface AnalysisState {
2222
function_depth: number;
2323

2424
// legacy stuff
25-
instance_scope: Scope;
2625
reactive_statement: null | ReactiveStatement;
2726
}
2827

packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export function ConstTag(node, context) {
2525
grand_parent?.type !== 'AwaitBlock' &&
2626
grand_parent?.type !== 'SnippetBlock' &&
2727
grand_parent?.type !== 'SvelteBoundary' &&
28+
grand_parent?.type !== 'KeyBlock' &&
2829
((grand_parent?.type !== 'RegularElement' && grand_parent?.type !== 'SvelteElement') ||
2930
!grand_parent.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')))
3031
) {

packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js

Lines changed: 61 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, Statement } from 'estree' */
1+
/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */
22
/** @import { AST } from '#compiler' */
33
/** @import { SourceLocation } from '#shared' */
44
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
@@ -20,9 +20,9 @@ import { build_getter } from '../utils.js';
2020
import {
2121
get_attribute_name,
2222
build_attribute_value,
23-
build_class_directives,
2423
build_style_directives,
25-
build_set_attributes
24+
build_set_attributes,
25+
build_set_class
2626
} from './shared/element.js';
2727
import { process_children } from './shared/fragment.js';
2828
import {
@@ -223,13 +223,13 @@ export function RegularElement(node, context) {
223223

224224
build_set_attributes(
225225
attributes,
226+
class_directives,
226227
context,
227228
node,
228229
node_id,
229230
attributes_id,
230231
(node.metadata.svg || node.metadata.mathml || is_custom_element_node(node)) && b.true,
231-
is_custom_element_node(node) && b.true,
232-
context.state
232+
is_custom_element_node(node) && b.true
233233
);
234234

235235
// If value binding exists, that one takes care of calling $.init_select
@@ -270,13 +270,22 @@ export function RegularElement(node, context) {
270270
continue;
271271
}
272272

273+
const name = get_attribute_name(node, attribute);
273274
if (
274275
!is_custom_element &&
275276
!cannot_be_set_statically(attribute.name) &&
276-
(attribute.value === true || is_text_attribute(attribute))
277+
(attribute.value === true || is_text_attribute(attribute)) &&
278+
(name !== 'class' || class_directives.length === 0)
277279
) {
278-
const name = get_attribute_name(node, attribute);
279-
const value = is_text_attribute(attribute) ? attribute.value[0].data : true;
280+
let value = is_text_attribute(attribute) ? attribute.value[0].data : true;
281+
282+
if (name === 'class' && node.metadata.scoped && context.state.analysis.css.hash) {
283+
if (value === true || value === '') {
284+
value = context.state.analysis.css.hash;
285+
} else {
286+
value += ' ' + context.state.analysis.css.hash;
287+
}
288+
}
280289

281290
if (name !== 'class' || value) {
282291
context.state.template.push(
@@ -290,15 +299,22 @@ export function RegularElement(node, context) {
290299
continue;
291300
}
292301

293-
const is = is_custom_element
294-
? build_custom_element_attribute_update_assignment(node_id, attribute, context)
295-
: build_element_attribute_update_assignment(node, node_id, attribute, attributes, context);
302+
const is =
303+
is_custom_element && name !== 'class'
304+
? build_custom_element_attribute_update_assignment(node_id, attribute, context)
305+
: build_element_attribute_update_assignment(
306+
node,
307+
node_id,
308+
attribute,
309+
attributes,
310+
class_directives,
311+
context
312+
);
296313
if (is) is_attributes_reactive = true;
297314
}
298315
}
299316

300-
// class/style directives must be applied last since they could override class/style attributes
301-
build_class_directives(class_directives, node_id, context, is_attributes_reactive);
317+
// style directives must be applied last since they could override class/style attributes
302318
build_style_directives(style_directives, node_id, context, is_attributes_reactive);
303319

304320
if (
@@ -491,6 +507,27 @@ function setup_select_synchronization(value_binding, context) {
491507
);
492508
}
493509

510+
/**
511+
* @param {AST.ClassDirective[]} class_directives
512+
* @param {ComponentContext} context
513+
* @return {ObjectExpression}
514+
*/
515+
export function build_class_directives_object(class_directives, context) {
516+
let properties = [];
517+
518+
for (const d of class_directives) {
519+
let expression = /** @type Expression */ (context.visit(d.expression));
520+
521+
if (d.metadata.expression.has_call) {
522+
expression = get_expression_id(context.state, expression);
523+
}
524+
525+
properties.push(b.init(d.name, expression));
526+
}
527+
528+
return b.object(properties);
529+
}
530+
494531
/**
495532
* Serializes an assignment to an element property by adding relevant statements to either only
496533
* the init or the the init and update arrays, depending on whether or not the value is dynamic.
@@ -517,6 +554,7 @@ function setup_select_synchronization(value_binding, context) {
517554
* @param {Identifier} node_id
518555
* @param {AST.Attribute} attribute
519556
* @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
557+
* @param {AST.ClassDirective[]} class_directives
520558
* @param {ComponentContext} context
521559
* @returns {boolean}
522560
*/
@@ -525,6 +563,7 @@ function build_element_attribute_update_assignment(
525563
node_id,
526564
attribute,
527565
attributes,
566+
class_directives,
528567
context
529568
) {
530569
const state = context.state;
@@ -563,19 +602,15 @@ function build_element_attribute_update_assignment(
563602
let update;
564603

565604
if (name === 'class') {
566-
if (attribute.metadata.needs_clsx) {
567-
value = b.call('$.clsx', value);
568-
}
569-
570-
update = b.stmt(
571-
b.call(
572-
is_svg ? '$.set_svg_class' : is_mathml ? '$.set_mathml_class' : '$.set_class',
573-
node_id,
574-
value,
575-
attribute.metadata.needs_clsx && context.state.analysis.css.hash
576-
? b.literal(context.state.analysis.css.hash)
577-
: undefined
578-
)
605+
return build_set_class(
606+
element,
607+
node_id,
608+
attribute,
609+
value,
610+
has_state,
611+
class_directives,
612+
context,
613+
!is_svg && !is_mathml
579614
);
580615
} else if (name === 'value') {
581616
update = b.stmt(b.call('$.set_value', node_id, value));
@@ -639,14 +674,6 @@ function build_custom_element_attribute_update_assignment(node_id, attribute, co
639674
const name = attribute.name; // don't lowercase, as we set the element's property, which might be case sensitive
640675
let { value, has_state } = build_attribute_value(attribute.value, context);
641676

642-
// We assume that noone's going to redefine the semantics of the class attribute on custom elements, i.e. it's still used for CSS classes
643-
if (name === 'class' && attribute.metadata.needs_clsx) {
644-
if (context.state.analysis.css.hash) {
645-
value = b.array([value, b.literal(context.state.analysis.css.hash)]);
646-
}
647-
value = b.call('$.clsx', value);
648-
}
649-
650677
const update = b.stmt(b.call('$.set_custom_element_data', node_id, b.literal(name), value));
651678

652679
if (has_state) {

0 commit comments

Comments
 (0)