fix(compiler): namespace @property declarations

The `@property` atrule allows users to define custom CSS variables. These changes update the compiler to account for when namespacing variables.

(cherry picked from commit 9b80d4ce9e)
This commit is contained in:
Kristiyan Kostadinov
2026-09-09 08:59:21 +02:00
parent 046c16d4a3
commit cbb8702143
2 changed files with 98 additions and 9 deletions
+8 -7
View File
@@ -1036,32 +1036,33 @@ const _cssColonInPlaceholderReGlobal = new RegExp(COLON_IN_PLACEHOLDER, 'g');
// Matches any CSS variable name, defined by a double-hyphen followed by any valid ident.
// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
const _cssVariableRe = /(var\(\s*)?(--(?:[a-zA-Z0-9_-]|[^\x00-\x7F])+)(\s*:)?/g;
const _cssVariableRe = /(var\(\s*|@property\s+)?(--(?:[a-zA-Z0-9_-]|[^\x00-\x7F])+)(\s*:)?/g;
/**
* Transforms CSS variables within a stylesheet to include a namespace placeholder.
*
* E.g. `--foo: bar;` becomes `--%NS%foo: bar;`
* E.g. `color: var(--foo);` becomes `color: var(--%NS%foo);`
* E.g. `@property --foo` becomes `@property --%NS%foo`
*
* If a variable is prefixed with `--global--`, it is NOT namespaced and the prefix is removed.
* E.g. `--global--mycolor: red;` becomes `--mycolor: red;`
*/
export function namespaceCssVariables(cssText: string): string {
return cssText.replace(_cssVariableRe, (match, leadingVar, varName, trailingColon) => {
// Check for a leading `var(` or trailing `:` to approximate whether we're operating on a
// real CSS variable, not another piece of syntax that resembles it. For example, this
// guards against:
return cssText.replace(_cssVariableRe, (match, prefix, varName, trailingColon) => {
// Check for a leading `var(`, `@property`, or trailing `:` to approximate whether we're
// operating on a real CSS variable, not another piece of syntax that resembles it.
// For example, this guards against:
// - `.foo--bar {}`
// - `/* --foo */`
// - `p { content: "--foo" }`
// - `[data---bar] {}`
// - `[data-status=foo--bar] {}`
// etc.
if (!leadingVar && !trailingColon) {
if (!prefix && !trailingColon) {
return match;
}
return (leadingVar ?? '') + namespaceCssVariable(varName) + (trailingColon ?? '');
return (prefix ?? '') + namespaceCssVariable(varName) + (trailingColon ?? '');
});
}
@@ -433,7 +433,7 @@ describe('ShadowCss', () => {
const input = `
.foo {
border: var(--global--border-size) solid var(--border-color);
box-shadow:
box-shadow:
var(--shadow-1),
var(--global--shadow-2),
var(--shadow-3);
@@ -443,7 +443,7 @@ describe('ShadowCss', () => {
const expected = `
.foo {
border: var(--border-size) solid var(--%NS%border-color);
box-shadow:
box-shadow:
var(--%NS%shadow-1),
var(--shadow-2),
var(--%NS%shadow-3);
@@ -597,5 +597,93 @@ p {
'CSS variable "--global-" has a single hyphen after "--global". Use two hyphens ("--global--") to opt-out of namespacing.',
);
});
it('should namespace @property declarations', () => {
const input = `
@property --myColor {
syntax: "<color>";
inherits: true;
initial-value: rebeccapurple;
}
@property --myWidth {
syntax: "<length> | <percentage>";
inherits: true;
initial-value: 200px;
}
p {
background-color: var(--myColor);
width: var(--myWidth);
color: white;
}
`.trim();
const expected = `
@property --%NS%myColor {
syntax: "<color>";
inherits: true;
initial-value: rebeccapurple;
}
@property --%NS%myWidth {
syntax: "<length> | <percentage>";
inherits: true;
initial-value: 200px;
}
p {
background-color: var(--%NS%myColor);
width: var(--%NS%myWidth);
color: white;
}
`.trim();
expect(namespaceCssVariables(input)).toBe(expected);
});
it('should not namespace @property if --global-- is present', () => {
const input = `
@property --global--my-color {
syntax: "<color>";
inherits: true;
initial-value: rebeccapurple;
}
@property --global--my-width {
syntax: "<length> | <percentage>";
inherits: true;
initial-value: 200px;
}
p {
background-color: var(--global--my-color);
width: var(--global--my-width);
color: white;
}
`.trim();
const expected = `
@property --my-color {
syntax: "<color>";
inherits: true;
initial-value: rebeccapurple;
}
@property --my-width {
syntax: "<length> | <percentage>";
inherits: true;
initial-value: 200px;
}
p {
background-color: var(--my-color);
width: var(--my-width);
color: white;
}
`.trim();
expect(namespaceCssVariables(input)).toBe(expected);
});
});
});