css-ast-parser 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -9
- package/package.json +1 -1
- package/src/parser.js +141 -8
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@ The goal of this project is to provide high performance CSS processing with mini
|
|
|
4
4
|
Built using: custom parser, generator and AST walker (tokenization stage was intentionally removed for improving performance)
|
|
5
5
|
|
|
6
6
|
## Features
|
|
7
|
+
Legacy browser support (autoprefixes for key properties, pseudo-elements, fullscreen, keyframes)
|
|
7
8
|
Lightweight and fast parsing
|
|
8
9
|
AST-based transformations
|
|
9
10
|
Plugin system (similar to PostCSS/Babel)
|
|
@@ -13,23 +14,41 @@ Zero dependencies
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
## Installation
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
You can install the package from npm:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install css-ast-parser
|
|
22
|
+
```
|
|
23
|
+
Or clone the repository for development:
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/DragonDragging/CSSCustomAST.git
|
|
26
|
+
cd CSSCustomAST
|
|
27
|
+
npm install
|
|
28
|
+
```
|
|
17
29
|
|
|
18
30
|
## Usage
|
|
19
31
|
```js
|
|
20
32
|
const fs = require("fs");
|
|
33
|
+
const { parse, generate, walk } = require("css-ast-parser");
|
|
21
34
|
|
|
22
|
-
|
|
23
|
-
const { generate } = require("./src/generator");
|
|
24
|
-
const { walk } = require("./src/walker");
|
|
25
|
-
|
|
35
|
+
// Example plugin
|
|
26
36
|
const plugins = [
|
|
27
|
-
|
|
37
|
+
() => ({
|
|
38
|
+
decl(node) {
|
|
39
|
+
if (node.prop === "background-color") {
|
|
40
|
+
node.remove();
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
rule(node) {
|
|
44
|
+
console.log("Rule:", node.selector);
|
|
45
|
+
}
|
|
46
|
+
})
|
|
28
47
|
];
|
|
29
48
|
|
|
30
49
|
const css = fs.readFileSync("./input.css", "utf-8");
|
|
31
50
|
|
|
32
|
-
const ast = parse(css);
|
|
51
|
+
const ast = parse(css, { legacySupport: true, comments: false });
|
|
33
52
|
walk(ast, plugins);
|
|
34
53
|
|
|
35
54
|
const out = generate(ast);
|
|
@@ -41,13 +60,15 @@ fs.writeFileSync("./output.css", out);
|
|
|
41
60
|
## How It Works
|
|
42
61
|
The processing pipeline:
|
|
43
62
|
CSS -> parse() -> AST -> walk() -> generate() -> CSS
|
|
44
|
-
parse -> converts CSS into AST
|
|
63
|
+
parse -> converts CSS into AST (optionally applies legacy browser prefixes and removing comments)
|
|
45
64
|
walk -> applies plugins and mutates AST
|
|
46
65
|
generate -> converts AST back to CSS
|
|
47
66
|
|
|
48
67
|
|
|
49
68
|
|
|
50
69
|
## AST Structure
|
|
70
|
+
"nodes" - array of child declarations or rules, may include legacy prefixed versions
|
|
71
|
+
|
|
51
72
|
Example node:
|
|
52
73
|
```json
|
|
53
74
|
{
|
|
@@ -98,7 +119,7 @@ path.isAtRule(name)
|
|
|
98
119
|
|
|
99
120
|
## Transformation Pipeline
|
|
100
121
|
1. Read CSS
|
|
101
|
-
2. Parse AST
|
|
122
|
+
2. Parse AST (optionally apply legacy support and removing comments)
|
|
102
123
|
3. Apply plugins
|
|
103
124
|
4. Generate optimized CSS
|
|
104
125
|
|
package/package.json
CHANGED
package/src/parser.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
function parse(css) {
|
|
1
|
+
function parse(css, options = {}) {
|
|
2
|
+
const { comments = true, legacySupport = false } = options;
|
|
3
|
+
|
|
2
4
|
let i = 0;
|
|
3
5
|
const len = css.length;
|
|
4
6
|
|
|
@@ -63,6 +65,134 @@ function parse(css) {
|
|
|
63
65
|
return clean(css.slice(start, i));
|
|
64
66
|
}
|
|
65
67
|
|
|
68
|
+
// Legacy support helpers
|
|
69
|
+
const propPrefixes = {
|
|
70
|
+
"transform": ["-webkit-transform", "-ms-transform"],
|
|
71
|
+
"transition": ["-webkit-transition"],
|
|
72
|
+
"animation": ["-webkit-animation"],
|
|
73
|
+
"animation-name": ["-webkit-animation-name"],
|
|
74
|
+
"animation-duration": ["-webkit-animation-duration"],
|
|
75
|
+
"animation-timing-function": ["-webkit-animation-timing-function"],
|
|
76
|
+
"animation-delay": ["-webkit-animation-delay"],
|
|
77
|
+
"animation-iteration-count": ["-webkit-animation-iteration-count"],
|
|
78
|
+
"animation-direction": ["-webkit-animation-direction"],
|
|
79
|
+
"box-shadow": ["-webkit-box-shadow"],
|
|
80
|
+
"user-select": ["-webkit-user-select", "-moz-user-select", "-ms-user-select"],
|
|
81
|
+
"flex": ["-webkit-flex", "-ms-flexbox"],
|
|
82
|
+
"flex-direction": ["-webkit-flex-direction", "-ms-flex-direction"],
|
|
83
|
+
"flex-wrap": ["-webkit-flex-wrap", "-ms-flex-wrap"],
|
|
84
|
+
"justify-content": ["-webkit-justify-content", "-ms-flex-pack"],
|
|
85
|
+
"align-items": ["-webkit-align-items", "-ms-flex-align"],
|
|
86
|
+
"align-content": ["-webkit-align-content", "-ms-flex-line-pack"],
|
|
87
|
+
"appearance": ["-webkit-appearance", "-moz-appearance"],
|
|
88
|
+
"backface-visibility": ["-webkit-backface-visibility"],
|
|
89
|
+
"filter": ["-webkit-filter"],
|
|
90
|
+
"columns": ["-webkit-columns", "-moz-columns"],
|
|
91
|
+
"column-count": ["-webkit-column-count", "-moz-column-count"],
|
|
92
|
+
"column-gap": ["-webkit-column-gap", "-moz-column-gap"],
|
|
93
|
+
"border-radius": ["-webkit-border-radius", "-moz-border-radius"],
|
|
94
|
+
"box-sizing": ["-webkit-box-sizing", "-moz-box-sizing"],
|
|
95
|
+
"display": ["-webkit-box", "-ms-flexbox", "-webkit-flex", "flex"]
|
|
96
|
+
};
|
|
97
|
+
const pseudoMap = {
|
|
98
|
+
"::before": ":before",
|
|
99
|
+
"::after": ":after",
|
|
100
|
+
"::first-letter": ":first-letter",
|
|
101
|
+
"::first-line": ":first-line"
|
|
102
|
+
};
|
|
103
|
+
const fullscreenSel = [":fullscreen", ":-webkit-full-screen", ":-moz-full-screen", ":-ms-fullscreen"];
|
|
104
|
+
function cloneNode(n) {
|
|
105
|
+
return {
|
|
106
|
+
...n,
|
|
107
|
+
nodes: n.nodes ? [...n.nodes] : []
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// prefixes and pseudo mappings recursively
|
|
112
|
+
function applyLegacy(nodes) {
|
|
113
|
+
const out = [];
|
|
114
|
+
|
|
115
|
+
// collect exiting old version support elements
|
|
116
|
+
const seenSelectors = new Set();
|
|
117
|
+
const seenKeyframes = new Set();
|
|
118
|
+
for (let n of nodes) {
|
|
119
|
+
if (n.type === "rule" && fullscreenSel.includes(n.selector)) {
|
|
120
|
+
seenSelectors.add(n.selector);
|
|
121
|
+
}
|
|
122
|
+
if (n.type === "atrule" && n.name.endsWith("keyframes")) {
|
|
123
|
+
seenKeyframes.add(n.name);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (let n of nodes) {
|
|
127
|
+
if (n.type === "rule") {
|
|
128
|
+
// pseudo-elements
|
|
129
|
+
for (const pseudo in pseudoMap) {
|
|
130
|
+
if (n.selector.includes(pseudo)) {
|
|
131
|
+
n.selector = n.selector.replace(new RegExp(pseudo, "g"), pseudoMap[pseudo]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// fullscreen handling
|
|
136
|
+
if (n.selector === ":fullscreen") {
|
|
137
|
+
for (const sel of fullscreenSel) {
|
|
138
|
+
if (!seenSelectors.has(sel)) {
|
|
139
|
+
const clone = cloneNode(n);
|
|
140
|
+
clone.selector = sel;
|
|
141
|
+
out.push(clone);
|
|
142
|
+
seenSelectors.add(sel);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
out.push(n);
|
|
146
|
+
} else {
|
|
147
|
+
out.push(n);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// add property prefixes
|
|
151
|
+
const decls = [];
|
|
152
|
+
for (const d of n.nodes) {
|
|
153
|
+
decls.push(d);
|
|
154
|
+
if (propPrefixes[d.prop]) {
|
|
155
|
+
for (const p of propPrefixes[d.prop]) {
|
|
156
|
+
if (!decls.some(existing => existing.prop === p && existing.value === d.value)) {
|
|
157
|
+
decls.push({
|
|
158
|
+
type: "decl",
|
|
159
|
+
prop: p,
|
|
160
|
+
value: d.value
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
n.nodes = decls;
|
|
167
|
+
|
|
168
|
+
// recurse on child nodes
|
|
169
|
+
for (const c of n.nodes) {
|
|
170
|
+
if (c.nodes && c.nodes.length) {
|
|
171
|
+
c.nodes = applyLegacy(c.nodes);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} else if (n.type === "atrule" && n.nodes) {
|
|
175
|
+
// keyframes prefixes
|
|
176
|
+
if (n.name === "keyframes") {
|
|
177
|
+
for (const pref of ["-webkit-", "-moz-"]) {
|
|
178
|
+
const prefixedName = pref + n.name;
|
|
179
|
+
if (!seenKeyframes.has(prefixedName)) {
|
|
180
|
+
const clone = cloneNode(n);
|
|
181
|
+
clone.name = prefixedName;
|
|
182
|
+
out.push(clone);
|
|
183
|
+
seenKeyframes.add(prefixedName);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
n.nodes = applyLegacy(n.nodes);
|
|
188
|
+
out.push(n);
|
|
189
|
+
} else {
|
|
190
|
+
out.push(n);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
195
|
+
|
|
66
196
|
// parse declarations inside {}
|
|
67
197
|
function parseDecls() {
|
|
68
198
|
const nodes = [];
|
|
@@ -78,7 +208,7 @@ function parse(css) {
|
|
|
78
208
|
}
|
|
79
209
|
|
|
80
210
|
// comment
|
|
81
|
-
if (css[i] === "/" && css[i + 1] === "*") {
|
|
211
|
+
if (comments && css[i] === "/" && css[i + 1] === "*") {
|
|
82
212
|
nodes.push(readComment());
|
|
83
213
|
continue;
|
|
84
214
|
}
|
|
@@ -119,7 +249,7 @@ function parse(css) {
|
|
|
119
249
|
}
|
|
120
250
|
|
|
121
251
|
// comment
|
|
122
|
-
if (css[i] === "/" && css[i + 1] === "*") {
|
|
252
|
+
if (comments && css[i] === "/" && css[i + 1] === "*") {
|
|
123
253
|
nodes.push(readComment());
|
|
124
254
|
continue;
|
|
125
255
|
}
|
|
@@ -147,10 +277,7 @@ function parse(css) {
|
|
|
147
277
|
|
|
148
278
|
i++; // {
|
|
149
279
|
|
|
150
|
-
const children = declAtrules.has(name)
|
|
151
|
-
? parseDecls()
|
|
152
|
-
: parseRules();
|
|
153
|
-
|
|
280
|
+
const children = declAtrules.has(name) ? parseDecls() : parseRules();
|
|
154
281
|
nodes.push({ type: "atrule", name, query, nodes: children });
|
|
155
282
|
continue;
|
|
156
283
|
}
|
|
@@ -169,10 +296,16 @@ function parse(css) {
|
|
|
169
296
|
return nodes;
|
|
170
297
|
}
|
|
171
298
|
|
|
172
|
-
|
|
299
|
+
const ast = {
|
|
173
300
|
type: "stylesheet",
|
|
174
301
|
nodes: parseRules()
|
|
175
302
|
};
|
|
303
|
+
|
|
304
|
+
if (legacySupport) {
|
|
305
|
+
ast.nodes = applyLegacy(ast.nodes);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return ast;
|
|
176
309
|
}
|
|
177
310
|
|
|
178
311
|
module.exports = { parse };
|