Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
feat(core): parse @layer in the vendored rework parser
The build-time css→AST serialization (webpack css2json-loader, vite's
bundled-css serializer in @nativescript/vite helpers/main-entry) runs the
rework parser, which mis-parsed `@layer` into garbage selectors and
dropped the rules. Teach it both forms — `@layer name { rules }` and the
order-declaring `@layer a, b;` statement — emitting layer nodes the
cascade now consumes. Also fixes the legacy __CSS_PARSER__=rework
runtime path.

Spec drives the same ordering through the rework AST end to end.
  • Loading branch information
aleclarson committed Sep 28, 2026
commit 4182f37f5d2de702080770ef838a3a73ebfc3fd4
35 changes: 34 additions & 1 deletion packages/core/css/lib/parse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,39 @@ export function parse(css, options) {
});
}

/**
* Parse layer — block form `@layer name { rules }` (anonymous when the name
* is empty) and statement form `@layer a, b;` (declares order only).
*/

function atlayer() {
var pos = position();
var m = match(/^@layer\s*([^{;]*)/);

if (!m) return;

var name = trim(m[1]);

if (match(/^;/)) {
return pos({
type: 'layer',
layer: name,
});
}

if (!open()) return error("@layer missing '{'");

var style = comments().concat(rules());

if (!close()) return error("@layer missing '}'");

return pos({
type: 'layer',
layer: name,
rules: style,
});
}

/**
* Parse custom-media.
*/
Expand Down Expand Up @@ -528,7 +561,7 @@ export function parse(css, options) {
function atrule() {
if (css[0] != '@') return;

return atkeyframes() || atmedia() || atcustommedia() || atsupports() || atimport() || atcharset() || atnamespace() || atdocument() || atpage() || athost() || atfontface();
return atkeyframes() || atmedia() || atlayer() || atcustommedia() || atsupports() || atimport() || atcharset() || atnamespace() || atdocument() || atpage() || athost() || atfontface();
}

/**
Expand Down
26 changes: 24 additions & 2 deletions packages/core/ui/styling/css-layer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { cssTreeParse } from '../../css/css-tree-parser';
import { parse as reworkCssParse } from '../../css/reworkcss.js';
import { RuleSet, StyleSheetSelectorScope } from './css-selector';
import { _populateRules } from './style-scope';

describe('cascade layers', () => {
function create(css: string, source = 'css-layer.ts@test'): { rulesets: RuleSet[]; keyframes: any[]; selectorScope: StyleSheetSelectorScope<any> } {
const parsed = cssTreeParse(css, source);
function createWith(parse: (css: string, source: string) => any, css: string): { rulesets: RuleSet[]; keyframes: any[]; selectorScope: StyleSheetSelectorScope<any> } {
const parsed = parse(css, 'css-layer.ts@test');
const rulesets: RuleSet[] = [];
const keyframes = [];

Expand All @@ -13,6 +14,10 @@ describe('cascade layers', () => {
return { rulesets, keyframes, selectorScope: new StyleSheetSelectorScope(rulesets) };
}

function create(css: string) {
return createWith(cssTreeParse, css);
}

function winningValue(css: string, node: any, property = 'color') {
const { selectorScope } = create(css);
const { selectors } = selectorScope.query(node);
Expand Down Expand Up @@ -148,4 +153,21 @@ describe('cascade layers', () => {
const { rulesets } = create(`.login { color: red; }`);
expect(rulesets[0].layerPath).toBeUndefined();
});

it('parses @layer through the legacy rework parser (build-time css2json path)', () => {
// webpack's css2json-loader and vite's bundled-css serializer both run the
// rework parser — the AST they emit must carry layer nodes through to
// _populateRules, which is what this exercises end to end.
const { rulesets } = createWith(
(css, source) => reworkCssParse(css, { source }),
`
@layer low, high;
@layer high { .login { color: green; } }
@layer low { #main { color: blue; } }
`,
);
expect(rulesets.length).toBe(2);
expect(rulesets[0].layerPath).toHaveLength(1);
expect(rulesets[1].layerPath).toHaveLength(1);
});
});
Loading