Skip to content

Commit 2a11b56

Browse files
committed
perf: rewrite using wallace css-parser
1 parent f4f8ff6 commit 2a11b56

File tree

6 files changed

+171
-210
lines changed

6 files changed

+171
-210
lines changed

package-lock.json

Lines changed: 1 addition & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,17 @@
2525
"scripts": {
2626
"build": "vite build",
2727
"test": "c8 --reporter=lcov uvu",
28-
"check": "tsc",
28+
"check": "tsc --noEmit",
2929
"prettier": "prettier --check src/**/*.js test/**/*.js"
3030
},
3131
"devDependencies": {
3232
"@codecov/vite-plugin": "^1.2.1",
33-
"@types/css-tree": "^2.3.8",
3433
"c8": "^10.1.2",
3534
"prettier": "^3.3.3",
3635
"typescript": "5.4.2",
3736
"uvu": "^0.5.6",
3837
"vite": "^5.4.10"
3938
},
40-
"dependencies": {
41-
"css-tree": "^3.0.0"
42-
},
4339
"files": [
4440
"dist",
4541
"index.d.ts"
@@ -59,4 +55,4 @@
5955
"printWidth": 140,
6056
"singleQuote": true
6157
}
62-
}
58+
}

src/index.js

Lines changed: 71 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,12 @@
1-
import * as csstree from 'css-tree'
21
import { TreeNode } from './TreeNode.js'
2+
import { NODE_AT_RULE, NODE_PRELUDE_IMPORT_LAYER, NODE_PRELUDE_LAYER_NAME, parse, walk_enter_leave } from '../../css-parser/dist/index.js'
33

4-
/**
5-
* @typedef Location
6-
* @property {number} line
7-
* @property {number} column
8-
* @property {number} start
9-
* @property {number} end
10-
*/
11-
12-
/**
13-
* @param {import('css-tree').CssNode} node
14-
* @returns {Location | undefined}
15-
*/
16-
function get_location(node) {
17-
let loc = node.loc
18-
if (!loc) return
19-
return {
20-
line: loc.start.line,
21-
column: loc.start.column,
22-
start: loc.start.offset,
23-
end: loc.end.offset,
24-
}
25-
}
26-
27-
/** @param {import('css-tree').Atrule} node */
28-
function is_layer(node) {
29-
return node.name.toLowerCase() === 'layer'
4+
/** @param {string} name */
5+
function get_layer_names(name) {
6+
return name.split('.').map((s) => s.trim())
307
}
318

32-
/**
33-
* @param {import('css-tree').AtrulePrelude} prelude
34-
* @returns {string[]}
35-
*/
36-
function get_layer_names(prelude) {
37-
return csstree
38-
// @todo: fewer loops plz
39-
.generate(prelude)
40-
.split('.')
41-
.map((s) => s.trim())
42-
}
43-
44-
/**
45-
* @param {import('css-tree').CssNode} ast
46-
*/
9+
/** @param {import('../../css-parser').CSSNode} ast */
4710
export function layer_tree_from_ast(ast) {
4811
/** @type {string[]} */
4912
let current_stack = []
@@ -56,79 +19,89 @@ export function layer_tree_from_ast(ast) {
5619
return `__anonymous-${anonymous_counter}__`
5720
}
5821

59-
csstree.walk(ast, {
60-
visit: 'Atrule',
22+
walk_enter_leave(ast, {
6123
enter(node) {
62-
if (is_layer(node)) {
63-
let location = get_location(node)
24+
if (node.type !== NODE_AT_RULE) return
6425

65-
if (node.prelude === null) {
66-
let layer_name = get_anonymous_id()
67-
root.add_child(current_stack, layer_name, location)
68-
current_stack.push(layer_name)
69-
} else if (node.prelude.type === 'AtrulePrelude') {
70-
if (node.block === null) {
71-
// @ts-expect-error CSSTree types are not updated yet in @types/css-tree
72-
let prelude = csstree.findAll(node.prelude, n => n.type === 'Layer').map(n => n.name)
73-
for (let name of prelude) {
74-
// Split the layer name by dots to handle nested layers
75-
let parts = name.split('.').map((/** @type {string} */ s) => s.trim())
26+
if (node.name === 'layer') {
27+
let has_prelude = node.has_children && node.children.some((c) => c.type === NODE_PRELUDE_LAYER_NAME)
7628

77-
// Ensure all parent layers exist and add them to the tree
78-
for (let i = 0; i < parts.length; i++) {
79-
let path = parts.slice(0, i)
80-
let layerName = parts[i]
81-
// Only add location to the final layer in dotted notation
82-
// Create a new copy to avoid sharing references
83-
let loc = i === parts.length - 1 ? {...location} : undefined
84-
root.add_child(path, layerName, loc)
29+
if (!has_prelude) {
30+
let name = get_anonymous_id()
31+
root.add_child(current_stack, name, {
32+
line: node.line,
33+
start: node.offset,
34+
})
35+
current_stack.push(name)
36+
} else {
37+
let has_block = node.has_children && node.children.some((c) => c.type !== NODE_PRELUDE_LAYER_NAME)
38+
if (!has_block) {
39+
for (let child of node.children) {
40+
if (child.type === NODE_PRELUDE_LAYER_NAME) {
41+
root.add_child(current_stack, child.text, {
42+
line: node.line,
43+
start: node.offset,
44+
})
8545
}
8646
}
8747
} else {
88-
for (let layer_name of get_layer_names(node.prelude)) {
89-
root.add_child(current_stack, layer_name, location)
90-
current_stack.push(layer_name)
48+
for (let child of node.children) {
49+
if (child.type === NODE_PRELUDE_LAYER_NAME) {
50+
root.add_child(current_stack, child.text, {
51+
line: node.line,
52+
start: node.offset,
53+
})
54+
current_stack.push(child.text)
55+
}
9156
}
9257
}
9358
}
94-
} else if (node.name.toLowerCase() === 'import' && node.prelude !== null && node.prelude.type === 'AtrulePrelude') {
95-
let location = get_location(node)
96-
let prelude = node.prelude
97-
59+
} else if (node.name === 'import') {
9860
// @import url("foo.css") layer(test);
9961
// OR
10062
// @import url("foo.css") layer(test.nested);
101-
// @ts-expect-error CSSTree types are not updated to v3 yet
102-
let layer = csstree.find(prelude, n => n.type === 'Layer')
103-
if (layer) {
104-
// @ts-expect-error CSSTree types are not updated to v3 yet
105-
for (let layer_name of get_layer_names(layer)) {
106-
root.add_child(current_stack, layer_name, location)
107-
current_stack.push(layer_name)
63+
let layerNode = node.children.find((child) => child.type === NODE_PRELUDE_IMPORT_LAYER)
64+
if (layerNode) {
65+
if (layerNode.name.trim()) {
66+
for (let layer_name of get_layer_names(layerNode.name)) {
67+
root.add_child(current_stack, layer_name, {
68+
line: node.line,
69+
start: node.offset,
70+
})
71+
current_stack.push(layer_name)
72+
}
73+
} else {
74+
// @import url("foo.css") layer;
75+
let name = get_anonymous_id()
76+
root.add_child([], name, {
77+
line: node.line,
78+
start: node.offset,
79+
})
10880
}
109-
return this.skip
110-
}
111-
112-
// @import url("foo.css") layer;
113-
let layer_keyword = csstree.find(prelude, n => n.type === 'Identifier' && n.name.toLowerCase() === 'layer')
114-
if (layer_keyword) {
115-
root.add_child([], get_anonymous_id(), location)
116-
return this.skip
11781
}
11882
}
11983
},
12084
leave(node) {
121-
if (is_layer(node)) {
122-
if (node.prelude !== null && node.prelude.type === 'AtrulePrelude') {
123-
let layer_names = get_layer_names(node.prelude)
124-
for (let i = 0; i < layer_names.length; i++) {
125-
current_stack.pop()
85+
if (node.type !== NODE_AT_RULE) return
86+
87+
if (node.name === 'layer') {
88+
let has_prelude = node.has_children && node.children.some((c) => c.type === NODE_PRELUDE_LAYER_NAME)
89+
if (has_prelude) {
90+
let has_block = node.has_children && node.children.some((c) => c.type !== NODE_PRELUDE_LAYER_NAME)
91+
if (has_block) {
92+
let name = node.children.find((child) => child.type === NODE_PRELUDE_LAYER_NAME)
93+
if (name) {
94+
let layer_names = get_layer_names(name.text)
95+
for (let i = 0; i < layer_names.length; i++) {
96+
current_stack.pop()
97+
}
98+
}
12699
}
127100
} else {
128101
// pop the anonymous layer
129102
current_stack.pop()
130103
}
131-
} else if (node.name.toLowerCase() === 'import') {
104+
} else if (node.name === 'import') {
132105
// clear the stack, imports can not be nested
133106
current_stack.length = 0
134107
}
@@ -142,13 +115,11 @@ export function layer_tree_from_ast(ast) {
142115
* @param {string} css
143116
*/
144117
export function layer_tree(css) {
145-
let ast = csstree.parse(css, {
146-
positions: true,
147-
parseAtrulePrelude: true,
148-
parseValue: false,
149-
parseRulePrelude: false,
150-
parseCustomProperty: false,
118+
let ast = parse(css, {
119+
parse_selectors: false,
120+
parse_values: false,
121+
skip_comments: true,
151122
})
152123

153124
return layer_tree_from_ast(ast)
154-
}
125+
}

test/global.spec.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,29 @@ test('mixed imports and layers', () => {
2626
{
2727
name: '__anonymous-1__',
2828
is_anonymous: true,
29-
locations: [{ line: 2, column: 3, start: 3, end: 33 }],
29+
locations: [{ line: 2, start: 3 }],
3030
children: [],
3131
},
3232
{
3333
name: 'test',
3434
is_anonymous: false,
35-
locations: [{ line: 3, column: 3, start: 36, end: 72 }],
35+
locations: [{ line: 3, start: 36 }],
3636
children: [],
3737
},
3838
{
3939
name: 'anotherTest',
4040
is_anonymous: false,
41-
locations: [{ line: 4, column: 3, start: 75, end: 148 }],
41+
locations: [{ line: 4, start: 75 }],
4242
children: [
4343
{
4444
name: 'moreTest',
4545
is_anonymous: false,
46-
locations: [{ line: 5, column: 4, start: 99, end: 144 }],
46+
locations: [{ line: 5, start: 99 }],
4747
children: [
4848
{
4949
name: 'deepTest',
5050
is_anonymous: false,
51-
locations: [{ line: 6, column: 5, start: 121, end: 139 }],
51+
locations: [{ line: 6, start: 121 }],
5252
children: [],
5353
},
5454
],

0 commit comments

Comments
 (0)