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.
Files changed (3) hide show
  1. package/README.md +30 -9
  2. package/package.json +1 -1
  3. 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
- Clone the repository
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
- const { parse } = require("./src/parser");
23
- const { generate } = require("./src/generator");
24
- const { walk } = require("./src/walker");
25
-
35
+ // Example plugin
26
36
  const plugins = [
27
- require("./plugins/removeBackground")
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "css-ast-parser",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Fast CSS parser with AST transformations",
5
5
  "main": "src/index.js",
6
6
  "author": "DragonDragging",
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
- return {
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 };