Skip to content

Commit 7664aee

Browse files
committed
rename, smooth over incompatibilities
1 parent 5358491 commit 7664aee

File tree

14 files changed

+58
-30
lines changed

14 files changed

+58
-30
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,7 @@ export function analyze_component(root, source, options) {
770770
if (attribute.type !== 'Attribute') continue;
771771
if (attribute.name.toLowerCase() !== 'class') continue;
772772
// The dynamic class method appends the hash to the end of the class attribute on its own
773-
if (attribute.metadata.is_dynamic_class) continue outer;
773+
if (attribute.metadata.needs_clsx) continue outer;
774774

775775
class_attribute = attribute;
776776
}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,16 @@ export function Attribute(node, context) {
3939
}
4040

4141
// class={[...]} or class={{...}} or `class={x}` need clsx to resolve the classes
42-
if (node.name === 'class' && !Array.isArray(node.value) && node.value !== true) {
42+
if (
43+
node.name === 'class' &&
44+
!Array.isArray(node.value) &&
45+
node.value !== true &&
46+
node.value.expression.type !== 'Literal' &&
47+
node.value.expression.type !== 'TemplateLiteral' &&
48+
node.value.expression.type !== 'BinaryExpression'
49+
) {
4350
mark_subtree_dynamic(context.path);
44-
node.metadata.is_dynamic_class = true;
51+
node.metadata.needs_clsx = true;
4552
}
4653

4754
if (node.value !== true) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ function build_element_attribute_update_assignment(
553553
let update;
554554

555555
if (name === 'class') {
556-
if (attribute.metadata.is_dynamic_class) {
556+
if (attribute.metadata.needs_clsx) {
557557
value = b.call('$.clsx', value);
558558
}
559559

@@ -571,7 +571,7 @@ function build_element_attribute_update_assignment(
571571
is_svg ? '$.set_svg_class' : is_mathml ? '$.set_mathml_class' : '$.set_class',
572572
node_id,
573573
value,
574-
attribute.metadata.is_dynamic_class ? b.literal(context.state.analysis.css.hash) : undefined
574+
attribute.metadata.needs_clsx ? b.literal(context.state.analysis.css.hash) : undefined
575575
)
576576
);
577577
} else if (name === 'value') {

packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,22 @@ export function build_element_attributes(node, context) {
8787
if (attribute.name === 'class') {
8888
class_index = attributes.length;
8989

90-
if (attribute.metadata.is_dynamic_class) {
90+
if (attribute.metadata.needs_clsx) {
91+
const clsx_value = b.call(
92+
'$.clsx',
93+
/** @type {AST.ExpressionTag} */ (attribute.value).expression
94+
);
9195
attributes.push({
9296
...attribute,
9397
value: {
9498
.../** @type {AST.ExpressionTag} */ (attribute.value),
95-
expression: b.call(
96-
'$.clsx',
97-
/** @type {AST.ExpressionTag} */ (attribute.value).expression,
98-
b.literal(context.state.analysis.css.hash)
99-
)
99+
expression: context.state.analysis.css.hash
100+
? b.binary(
101+
'+',
102+
b.binary('+', clsx_value, b.literal(' ')),
103+
b.literal(context.state.analysis.css.hash)
104+
)
105+
: clsx_value
100106
}
101107
});
102108
} else {

packages/svelte/src/compiler/phases/nodes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export function create_attribute(name, start, end, value) {
4646
metadata: {
4747
expression: create_expression_metadata(),
4848
delegated: null,
49-
is_dynamic_class: false
49+
needs_clsx: false
5050
}
5151
};
5252
}

packages/svelte/src/compiler/types/template.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ export namespace AST {
483483
/** May be set if this is an event attribute */
484484
delegated: null | DelegatedEvent;
485485
/** May be `true` if this is a `class` attribute that needs `clsx` */
486-
is_dynamic_class: boolean;
486+
needs_clsx: boolean;
487487
};
488488
}
489489

packages/svelte/src/internal/client/dom/elements/class.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import { hydrating } from '../hydration.js';
33
/**
44
* @param {SVGElement} dom
55
* @param {string} value
6+
* @param {string} [hash]
67
* @returns {void}
78
*/
8-
export function set_svg_class(dom, value) {
9+
export function set_svg_class(dom, value, hash) {
910
// @ts-expect-error need to add __className to patched prototype
1011
var prev_class_name = dom.__className;
11-
var next_class_name = to_class(value);
12+
var next_class_name = to_class(value, hash);
1213

1314
if (hydrating && dom.getAttribute('class') === next_class_name) {
1415
// In case of hydration don't reset the class as it's already correct.
@@ -32,12 +33,13 @@ export function set_svg_class(dom, value) {
3233
/**
3334
* @param {MathMLElement} dom
3435
* @param {string} value
36+
* @param {string} [hash]
3537
* @returns {void}
3638
*/
37-
export function set_mathml_class(dom, value) {
39+
export function set_mathml_class(dom, value, hash) {
3840
// @ts-expect-error need to add __className to patched prototype
3941
var prev_class_name = dom.__className;
40-
var next_class_name = to_class(value);
42+
var next_class_name = to_class(value, hash);
4143

4244
if (hydrating && dom.getAttribute('class') === next_class_name) {
4345
// In case of hydration don't reset the class as it's already correct.
@@ -67,7 +69,7 @@ export function set_mathml_class(dom, value) {
6769
export function set_class(dom, value, hash) {
6870
// @ts-expect-error need to add __className to patched prototype
6971
var prev_class_name = dom.__className;
70-
var next_class_name = to_class(value) + (hash ? ' ' + hash : '');
72+
var next_class_name = to_class(value, hash);
7173

7274
if (hydrating && dom.className === next_class_name) {
7375
// In case of hydration don't reset the class as it's already correct.
@@ -80,7 +82,7 @@ export function set_class(dom, value, hash) {
8082
// Removing the attribute when the value is only an empty string causes
8183
// peformance issues vs simply making the className an empty string. So
8284
// we should only remove the class if the the value is nullish.
83-
if (value == null) {
85+
if (value == null && !hash) {
8486
dom.removeAttribute('class');
8587
} else {
8688
dom.className = next_class_name;
@@ -94,10 +96,11 @@ export function set_class(dom, value, hash) {
9496
/**
9597
* @template V
9698
* @param {V} value
99+
* @param {string} [hash]
97100
* @returns {string | V}
98101
*/
99-
function to_class(value) {
100-
return value == null ? '' : value;
102+
function to_class(value, hash) {
103+
return (value == null ? '' : value) + (hash ? ' ' + hash : '');
101104
}
102105

103106
/**

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
export { clsx } from 'clsx';
21
export { FILENAME, HMR, NAMESPACE_SVG } from '../../constants.js';
32
export { assign, assign_and, assign_or, assign_nullish } from './dev/assign.js';
43
export { cleanup_styles } from './dev/css.js';
@@ -161,7 +160,7 @@ export {
161160
$window as window,
162161
$document as document
163162
} from './dom/operations.js';
164-
export { attr } from '../shared/attributes.js';
163+
export { attr, clsx } from '../shared/attributes.js';
165164
export { snapshot } from '../shared/clone.js';
166165
export { noop, fallback } from '../shared/utils.js';
167166
export {

packages/svelte/src/internal/server/index.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
/** @import { ComponentType, SvelteComponent } from 'svelte' */
22
/** @import { Component, Payload, RenderOutput } from '#server' */
33
/** @import { Store } from '#shared' */
4-
export { clsx } from 'clsx';
54
export { FILENAME, HMR } from '../../constants.js';
6-
import { attr } from '../shared/attributes.js';
5+
import { attr, clsx } from '../shared/attributes.js';
76
import { is_promise, noop } from '../shared/utils.js';
87
import { subscribe_to_store } from '../../store/utils.js';
98
import {
@@ -523,7 +522,7 @@ export function once(get_value) {
523522
};
524523
}
525524

526-
export { attr };
525+
export { attr, clsx };
527526

528527
export { html } from './blocks/html.js';
529528

packages/svelte/src/internal/shared/attributes.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { escape_html } from '../../escaping.js';
2+
import { clsx as _clsx } from 'clsx';
23

34
/**
45
* `<div translate={false}>` should be rendered as `<div translate="no">` and _not_
@@ -26,3 +27,16 @@ export function attr(name, value, is_boolean = false) {
2627
const assignment = is_boolean ? '' : `="${escape_html(normalized, true)}"`;
2728
return ` ${name}${assignment}`;
2829
}
30+
31+
/**
32+
* Small wrapper around clsx to preserve Svelte's (weird) handling of falsy values.
33+
* TODO Svelte 6 revisit this, and likely turn all falsy values into the empty string (what clsx also does)
34+
* @param {any} value
35+
*/
36+
export function clsx(value) {
37+
if (typeof value === 'object') {
38+
return _clsx(value);
39+
} else {
40+
return value ?? '';
41+
}
42+
}

0 commit comments

Comments
 (0)