From 527644100376fa87263cf5e092c9713dd5a935bb Mon Sep 17 00:00:00 2001 From: bravepg Date: Wed, 2 Jul 2025 10:49:01 +0800 Subject: [PATCH 1/4] fix: css scope support @container --- packages/css-scope/__tests__/index.spec.ts | 22 +++++++++++++++++++++- packages/css-scope/src/cssParser.ts | 21 +++++++++++++++++++++ packages/css-scope/src/cssStringify.ts | 10 ++++++++++ packages/css-scope/src/globalTypes.ts | 6 ++++++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/packages/css-scope/__tests__/index.spec.ts b/packages/css-scope/__tests__/index.spec.ts index 9c2133994..d5466f1e3 100644 --- a/packages/css-scope/__tests__/index.spec.ts +++ b/packages/css-scope/__tests__/index.spec.ts @@ -1,3 +1,23 @@ +import { parse, stringify } from '../src'; + describe('Css scope', () => { - it('needs tests', () => {}); + it('should parse @container syntax correctly', () => { + const css = ` + .card { + container-type: inline-size; + } + + @container (min-width: 400px) { + .card-content { + font-size: 18px; + } + } + `; + + const ast = parse(css); + const scopedCss = stringify(ast, 'MyApp'); + expect(scopedCss).toContain('@container (min-width: 400px)'); + expect(scopedCss).toContain('#MyApp .card-content'); + expect(scopedCss).toContain('font-size: 18px'); + }); }); diff --git a/packages/css-scope/src/cssParser.ts b/packages/css-scope/src/cssParser.ts index 406136290..695d75994 100644 --- a/packages/css-scope/src/cssParser.ts +++ b/packages/css-scope/src/cssParser.ts @@ -19,6 +19,7 @@ import { KeyframesNode, StylesheetNode, CustomMediaNode, + ContainerNode, } from './globalTypes'; const commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g; @@ -342,6 +343,25 @@ export function parse(css: string, options: CssParserOptions = {}) { }) as MediaNode; } + // Parse container. + function atcontainer() { + const pos = position(); + const m = match(/^@container *([^{]+)/); + + if (!m) return; + const container = trim(m[1]); + + if (!open()) return error("@container missing '{'"); + const style = comments().concat(rules()); + if (!close()) return error("@container missing '}'"); + + return pos({ + type: 'container', + container: container, + rules: style, + }) as ContainerNode; + } + // Parse custom-media. function atcustommedia() { const pos = position(); @@ -450,6 +470,7 @@ export function parse(css: string, options: CssParserOptions = {}) { return ( atkeyframes() || atmedia() || + atcontainer() || atcustommedia() || atsupports() || atimport() || diff --git a/packages/css-scope/src/cssStringify.ts b/packages/css-scope/src/cssStringify.ts index 61149575e..fb849ea2b 100644 --- a/packages/css-scope/src/cssStringify.ts +++ b/packages/css-scope/src/cssStringify.ts @@ -17,6 +17,7 @@ import { NamespaceNode, StylesheetNode, CustomMediaNode, + ContainerNode, } from './globalTypes'; import { processAnimation } from './animationParser'; @@ -112,6 +113,15 @@ class Compiler { ); } + container(node: ContainerNode) { + return ( + this.emit(`@container ${node.container}`, node.position) + + this.emit(` {\n${this.indent(1)}`) + + this.mapVisit(node.rules, '\n\n') + + this.emit(`${this.indent(-1)}\n}`) + ); + } + document(node: DocumentNode) { const doc = `@${node.vendor || ''}document ${node.document}`; return ( diff --git a/packages/css-scope/src/globalTypes.ts b/packages/css-scope/src/globalTypes.ts index 371e2d944..72ea0269f 100644 --- a/packages/css-scope/src/globalTypes.ts +++ b/packages/css-scope/src/globalTypes.ts @@ -94,12 +94,18 @@ export interface StylesheetNode extends BaseNode<'stylesheet'> { }; } +export interface ContainerNode extends BaseNode<'container'> { + container: string; + rules: Array; +} + export type Node = | DeclNode | PageNode | HostNode | RuleNode | MediaNode + | ContainerNode | ImportNode | CharsetNode | CommentNode From 50f61e03867c7df4b289856968719c9eebc2cac3 Mon Sep 17 00:00:00 2001 From: bravepg Date: Fri, 12 Sep 2025 14:10:25 +0800 Subject: [PATCH 2/4] fix: css scope animation --- packages/css-scope/__tests__/index.spec.ts | 25 ++++++++++++++++++++++ packages/css-scope/src/animationParser.ts | 2 +- packages/css-scope/src/cssParser.ts | 4 +++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/css-scope/__tests__/index.spec.ts b/packages/css-scope/__tests__/index.spec.ts index d5466f1e3..20a97d8b8 100644 --- a/packages/css-scope/__tests__/index.spec.ts +++ b/packages/css-scope/__tests__/index.spec.ts @@ -20,4 +20,29 @@ describe('Css scope', () => { expect(scopedCss).toContain('#MyApp .card-content'); expect(scopedCss).toContain('font-size: 18px'); }); + + it('should parse @keyframes syntax correctly', () => { + const css = ` + .\@1_0_0_2444__aTWceUPNFbTpZsnHPM94_semi-icon-spinning { + animation: \@1_0_0_2444__XXl2sOw3Pp3KVSQS9PFl_semi-icon-animation-rotate .6s linear infinite; + animation-fill-mode: forwards; + } + + @keyframes \@1_0_0_2444__XXl2sOw3Pp3KVSQS9PFl_semi-icon-animation-rotate { + 0% { + transform: rotate(0) + } + to { + transform: rotate(1turn) + } + } + `; + + const ast = parse(css); + const scopedCss = stringify(ast, 'MyApp'); + console.log(scopedCss); + expect(scopedCss).toContain( + 'MyApp-@1_0_0_2444__XXl2sOw3Pp3KVSQS9PFl_semi-icon-animation-rotate', + ); + }); }); diff --git a/packages/css-scope/src/animationParser.ts b/packages/css-scope/src/animationParser.ts index 63c980959..65d08f25b 100644 --- a/packages/css-scope/src/animationParser.ts +++ b/packages/css-scope/src/animationParser.ts @@ -208,7 +208,7 @@ function parse(tokens: Array): Array { type Props = Array; function stringify(tree: Array, prefix: string) { let output = ''; - const splice = (p) => (isName(p) ? `${p}-${prefix}` : p); + const splice = (p) => (isName(p) ? `${prefix}-${p}` : p); const child = (ps: Array) => { let buf = ''; diff --git a/packages/css-scope/src/cssParser.ts b/packages/css-scope/src/cssParser.ts index 695d75994..d089bbb94 100644 --- a/packages/css-scope/src/cssParser.ts +++ b/packages/css-scope/src/cssParser.ts @@ -268,7 +268,9 @@ export function parse(css: string, options: CssParserOptions = {}) { const vendor = m[1]; // identifier - m = match(/^([-\w]+)\s*/); + // m = match(/^([-\w]+)\s*/); + m = match(/^([-@\w\\]+)\s*/); + if (!m) return error('@keyframes missing name'); const name = m[1]; From 042d398086f4dbd4d3702cbfdf632938c4d6aa84 Mon Sep 17 00:00:00 2001 From: bravepg Date: Fri, 12 Sep 2025 14:15:53 +0800 Subject: [PATCH 3/4] feat: add css scope testcase --- packages/css-scope/__tests__/index.spec.ts | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/css-scope/__tests__/index.spec.ts b/packages/css-scope/__tests__/index.spec.ts index 20a97d8b8..83cca234f 100644 --- a/packages/css-scope/__tests__/index.spec.ts +++ b/packages/css-scope/__tests__/index.spec.ts @@ -45,4 +45,46 @@ describe('Css scope', () => { 'MyApp-@1_0_0_2444__XXl2sOw3Pp3KVSQS9PFl_semi-icon-animation-rotate', ); }); + + it('should parse incorrect syntax @dark-text with silent mode', () => { + const css = ` + body { + --data-1: var(--arcoblue-5); + } + @keyframes arco-msg-fade { + 0% { + opacity: 0; + } + to { + opacity: 1; + } + } + @keyframes arco-msg-scale { + 0% { + transform: scale(0); + } + to { + transform: scale(1); + } + } + @keyframes force-loading-circle { + to { + transform: rotate(1turn); + } + } + @keyframes arco-loading-circle { + to { + transform: rotate(1turn); + } + } + @dark-text: arco-theme= 'dark'; + `; + + // Use silent mode to ignore syntax errors + const ast = parse(css, { silent: true }); + const scopedCss = stringify(ast, 'MyApp'); + + // Verify that valid CSS rules are still processed correctly + expect(scopedCss).toContain('#MyApp [__garfishmockbody__]'); + }); }); From 9492c783926452dec16f461b2c74b5bbbe696378 Mon Sep 17 00:00:00 2001 From: bravepg Date: Fri, 12 Sep 2025 14:26:37 +0800 Subject: [PATCH 4/4] feat: add css scope testcase --- packages/css-scope/__tests__/index.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/css-scope/__tests__/index.spec.ts b/packages/css-scope/__tests__/index.spec.ts index 83cca234f..24d5fa76b 100644 --- a/packages/css-scope/__tests__/index.spec.ts +++ b/packages/css-scope/__tests__/index.spec.ts @@ -86,5 +86,10 @@ describe('Css scope', () => { // Verify that valid CSS rules are still processed correctly expect(scopedCss).toContain('#MyApp [__garfishmockbody__]'); + + console.log(ast.stylesheet.parsingErrors); + // Verify that parsing errors are captured + expect(ast.stylesheet.parsingErrors).toBeDefined(); + expect(ast.stylesheet.parsingErrors.length).toBeGreaterThan(0); }); });