From 9c2494b134717141186887fab011972b9293d4ea Mon Sep 17 00:00:00 2001 From: "svelte-triage-bot[bot]" <316883489+svelte-triage-bot[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:42:04 +0200 Subject: [PATCH] fix: preserve namespaces in CSS type selectors (#18678) Fixes #18677. - preserve namespace prefixes on CSS `TypeSelector` AST nodes - accept wildcard local names in `svg|*` and `*|*` - retain namespaces when printing modern ASTs - keep namespaced universal selectors intact while adding scoped CSS selectors - add compile and print regressions covering all four namespace forms --------- Co-authored-by: svelte-triage-bot Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/pink-chairs-matter.md | 5 +++++ .../src/compiler/phases/1-parse/read/style.js | 14 +++++++++---- .../compiler/phases/3-transform/css/index.js | 6 +++++- packages/svelte/src/compiler/print/index.js | 4 ++++ packages/svelte/src/compiler/types/css.d.ts | 1 + .../namespaced-type-selector/expected.css | 17 +++++++++++++++ .../namespaced-type-selector/input.svelte | 21 +++++++++++++++++++ .../css-namespaced-type-selector/input.svelte | 10 +++++++++ .../output.svelte | 12 +++++++++++ packages/svelte/types/index.d.ts | 1 + 10 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 .changeset/pink-chairs-matter.md create mode 100644 packages/svelte/tests/css/samples/namespaced-type-selector/expected.css create mode 100644 packages/svelte/tests/css/samples/namespaced-type-selector/input.svelte create mode 100644 packages/svelte/tests/print/samples/css-namespaced-type-selector/input.svelte create mode 100644 packages/svelte/tests/print/samples/css-namespaced-type-selector/output.svelte diff --git a/.changeset/pink-chairs-matter.md b/.changeset/pink-chairs-matter.md new file mode 100644 index 0000000000..6a8a6e75f3 --- /dev/null +++ b/.changeset/pink-chairs-matter.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: preserve namespaces in CSS type selectors diff --git a/packages/svelte/src/compiler/phases/1-parse/read/style.js b/packages/svelte/src/compiler/phases/1-parse/read/style.js index 02e6d8d064..a07d7f6fea 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/style.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js @@ -204,15 +204,18 @@ function read_selector(parser, inside_pseudo_class = false) { }); } else if (parser.eat('*')) { let name = '*'; + /** @type {string | undefined} */ + let namespace; if (parser.eat('|')) { - // * is the namespace (which we ignore) - name = read_identifier(parser); + namespace = name; + name = parser.eat('*') ? '*' : read_identifier(parser); } relative_selector.selectors.push({ type: 'TypeSelector', name, + ...(namespace !== undefined && { namespace }), start, end: parser.index }); @@ -314,15 +317,18 @@ function read_selector(parser, inside_pseudo_class = false) { }); } else if (!parser.match_regex(REGEX_COMBINATOR)) { let name = read_identifier(parser); + /** @type {string | undefined} */ + let namespace; if (parser.eat('|')) { - // we ignore the namespace when trying to find matching element classes - name = read_identifier(parser); + namespace = name; + name = parser.eat('*') ? '*' : read_identifier(parser); } relative_selector.selectors.push({ type: 'TypeSelector', name, + ...(namespace !== undefined && { namespace }), start, end: parser.index }); diff --git a/packages/svelte/src/compiler/phases/3-transform/css/index.js b/packages/svelte/src/compiler/phases/3-transform/css/index.js index 537ab60a0e..bfeb426493 100644 --- a/packages/svelte/src/compiler/phases/3-transform/css/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/css/index.js @@ -355,7 +355,11 @@ const visitors = { continue; } - if (selector.type === 'TypeSelector' && selector.name === '*') { + if ( + selector.type === 'TypeSelector' && + selector.name === '*' && + selector.namespace === undefined + ) { context.state.code.update(selector.start, selector.end, modifier); } else { context.state.code.appendLeft(selector.end, modifier); diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index a3e10085de..4a78854e1e 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -520,6 +520,10 @@ function css_visitors(comments, js_comments) { }, TypeSelector(node, context) { + if (node.namespace !== undefined) { + context.write(node.namespace === '*' ? '*' : escape_identifier(node.namespace)); + context.write('|'); + } context.write(node.name === '*' ? node.name : escape_identifier(node.name)); } }; diff --git a/packages/svelte/src/compiler/types/css.d.ts b/packages/svelte/src/compiler/types/css.d.ts index b0a763b6a1..96b2bc3a7d 100644 --- a/packages/svelte/src/compiler/types/css.d.ts +++ b/packages/svelte/src/compiler/types/css.d.ts @@ -121,6 +121,7 @@ export namespace _CSS { export interface TypeSelector extends BaseNode { type: 'TypeSelector'; name: string; + namespace?: string; } export interface IdSelector extends BaseNode { diff --git a/packages/svelte/tests/css/samples/namespaced-type-selector/expected.css b/packages/svelte/tests/css/samples/namespaced-type-selector/expected.css new file mode 100644 index 0000000000..7672a34dcf --- /dev/null +++ b/packages/svelte/tests/css/samples/namespaced-type-selector/expected.css @@ -0,0 +1,17 @@ + @namespace svg url(http://www.w3.org/2000/svg); + + svg|circle.svelte-xyz { + fill: red; + } + + *|circle.svelte-xyz { + stroke: blue; + } + + svg|*.svelte-xyz { + color: green; + } + + *|*.svelte-xyz { + opacity: 0.5; + } diff --git a/packages/svelte/tests/css/samples/namespaced-type-selector/input.svelte b/packages/svelte/tests/css/samples/namespaced-type-selector/input.svelte new file mode 100644 index 0000000000..17586eab6f --- /dev/null +++ b/packages/svelte/tests/css/samples/namespaced-type-selector/input.svelte @@ -0,0 +1,21 @@ + + + diff --git a/packages/svelte/tests/print/samples/css-namespaced-type-selector/input.svelte b/packages/svelte/tests/print/samples/css-namespaced-type-selector/input.svelte new file mode 100644 index 0000000000..5e170ceb2a --- /dev/null +++ b/packages/svelte/tests/print/samples/css-namespaced-type-selector/input.svelte @@ -0,0 +1,10 @@ + diff --git a/packages/svelte/tests/print/samples/css-namespaced-type-selector/output.svelte b/packages/svelte/tests/print/samples/css-namespaced-type-selector/output.svelte new file mode 100644 index 0000000000..3723659b14 --- /dev/null +++ b/packages/svelte/tests/print/samples/css-namespaced-type-selector/output.svelte @@ -0,0 +1,12 @@ + + + diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index b89f61959f..aab3f4c0b4 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1774,6 +1774,7 @@ declare module 'svelte/compiler' { export interface TypeSelector extends BaseNode { type: 'TypeSelector'; name: string; + namespace?: string; } export interface IdSelector extends BaseNode {