From 48d2e83b15ac3b7e2e59bcf5c9bc212cd795c946 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 14 Nov 2024 12:39:08 +0100 Subject: [PATCH] WIP --- packages/svelte/package.json | 1 + .../src/compiler/phases/2-analyze/index.js | 2 ++ .../phases/2-analyze/visitors/Attribute.js | 6 ++++++ .../client/visitors/RegularElement.js | 8 ++++++- .../server/visitors/shared/element.js | 21 ++++++++++++++++--- packages/svelte/src/compiler/phases/nodes.js | 3 ++- .../svelte/src/compiler/types/template.d.ts | 2 ++ .../src/internal/client/dom/elements/class.js | 5 +++-- packages/svelte/src/internal/client/index.js | 1 + packages/svelte/src/internal/server/index.js | 1 + pnpm-lock.yaml | 9 ++++++++ 11 files changed, 52 insertions(+), 7 deletions(-) diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 760272680e..9fcd1eaab0 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -142,6 +142,7 @@ "acorn-typescript": "^1.4.13", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", + "clsx": "^2.1.1", "esm-env": "^1.0.0", "esrap": "^1.2.2", "is-reference": "^3.0.2", diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 9e4f72a66d..b16210332c 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -739,6 +739,8 @@ export function analyze_component(root, source, options) { if (attribute.type !== 'Attribute') continue; if (attribute.name.toLowerCase() !== 'class') continue; + // The dynamic class method appends the hash to the end of the class attribute on its own + if (attribute.metadata.is_dynamic_class) continue outer; class_attribute = attribute; } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js index 2a281a1aa3..0daa85e47c 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js @@ -24,6 +24,12 @@ export function Attribute(node, context) { } } + // class={[...]} or class={{...}} or `class={x}` need clsx to resolve the classes + if (node.name === 'class' && !Array.isArray(node.value) && node.value !== true) { + mark_subtree_dynamic(context.path); + node.metadata.is_dynamic_class = true; + } + if (node.value !== true) { for (const chunk of get_attribute_chunks(node.value)) { if (chunk.type !== 'ExpressionTag') continue; 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 4d3cebcee6..4694819313 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 @@ -544,6 +544,10 @@ function build_element_attribute_update_assignment(element, node_id, attribute, let update; if (name === 'class') { + if (attribute.metadata.is_dynamic_class) { + value = b.call('$.clsx', value); + } + if (attribute.metadata.expression.has_state && has_call) { // ensure we're not creating a separate template effect for this so that // potential class directives are added to the same effect and therefore always apply @@ -552,11 +556,13 @@ function build_element_attribute_update_assignment(element, node_id, attribute, value = b.call('$.get', id); has_call = false; } + update = b.stmt( b.call( is_svg ? '$.set_svg_class' : is_mathml ? '$.set_mathml_class' : '$.set_class', node_id, - value + value, + attribute.metadata.is_dynamic_class ? b.literal(context.state.analysis.css.hash) : undefined ) ); } else if (name === 'value') { 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 c386c4f7c0..0702ae208d 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 @@ -85,10 +85,25 @@ export function build_element_attributes(node, context) { } else { if (attribute.name === 'class') { class_index = attributes.length; - } else if (attribute.name === 'style') { - style_index = attributes.length; + if (attribute.metadata.is_dynamic_class) { + attributes.push({ + ...attribute, + value: { + .../** @type {AST.ExpressionTag} */ (attribute.value), + expression: b.call( + '$.clsx', + /** @type {AST.ExpressionTag} */ (attribute.value).expression, + b.literal(context.state.analysis.css.hash) + ) + } + }); + } + } else { + if (attribute.name === 'style') { + style_index = attributes.length; + } + attributes.push(attribute); } - attributes.push(attribute); } } else if (attribute.type === 'BindDirective') { if (attribute.name === 'value' && node.name === 'select') continue; diff --git a/packages/svelte/src/compiler/phases/nodes.js b/packages/svelte/src/compiler/phases/nodes.js index ead525aaa1..89bb18ff54 100644 --- a/packages/svelte/src/compiler/phases/nodes.js +++ b/packages/svelte/src/compiler/phases/nodes.js @@ -46,7 +46,8 @@ export function create_attribute(name, start, end, value) { parent: null, metadata: { expression: create_expression_metadata(), - delegated: null + delegated: null, + is_dynamic_class: false } }; } diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index fd1824d3b3..e48b1de016 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -452,6 +452,8 @@ export namespace AST { expression: ExpressionMetadata; /** May be set if this is an event attribute */ delegated: null | DelegatedEvent; + /** May be `true` if this is a `class` attribute that needs `clsx` */ + is_dynamic_class: boolean; }; } diff --git a/packages/svelte/src/internal/client/dom/elements/class.js b/packages/svelte/src/internal/client/dom/elements/class.js index 22f3da0f44..bad88735b5 100644 --- a/packages/svelte/src/internal/client/dom/elements/class.js +++ b/packages/svelte/src/internal/client/dom/elements/class.js @@ -61,12 +61,13 @@ export function set_mathml_class(dom, value) { /** * @param {HTMLElement} dom * @param {string} value + * @param {string} [hash] * @returns {void} */ -export function set_class(dom, value) { +export function set_class(dom, value, hash) { // @ts-expect-error need to add __className to patched prototype var prev_class_name = dom.__className; - var next_class_name = to_class(value); + var next_class_name = to_class(value) + (hash != null ? ' ' + hash : ''); if (hydrating && dom.className === next_class_name) { // In case of hydration don't reset the class as it's already correct. diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index c401867a0f..de8f2c007a 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -1,3 +1,4 @@ +export { clsx } from 'clsx'; export { FILENAME, HMR, NAMESPACE_SVG } from '../../constants.js'; export { cleanup_styles } from './dev/css.js'; export { add_locations } from './dev/elements.js'; diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 9a66095de4..121be70c28 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -1,6 +1,7 @@ /** @import { ComponentType, SvelteComponent } from 'svelte' */ /** @import { Component, Payload, RenderOutput } from '#server' */ /** @import { Store } from '#shared' */ +export { clsx } from 'clsx'; export { FILENAME, HMR } from '../../constants.js'; import { is_promise, noop } from '../shared/utils.js'; import { subscribe_to_store } from '../../store/utils.js'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f33a207fc..e25122cd88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,6 +80,9 @@ importers: axobject-query: specifier: ^4.1.0 version: 4.1.0 + clsx: + specifier: ^2.1.1 + version: 2.1.1 esm-env: specifier: ^1.0.0 version: 1.0.0 @@ -1211,6 +1214,10 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + codemirror@6.0.1: resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} @@ -3950,6 +3957,8 @@ snapshots: ci-info@3.9.0: {} + clsx@2.1.1: {} + codemirror@6.0.1(@lezer/common@1.2.1): dependencies: '@codemirror/autocomplete': 6.18.0(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.33.0)(@lezer/common@1.2.1)