@melcanz85/chaincss 1.11.0 → 1.11.1
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 +114 -45
- package/browser/index.js +1 -1
- package/browser/react-hooks.jsx +1 -63
- package/browser/rtt.js +1 -45
- package/node/atomic-optimizer.js +2 -67
- package/node/btt.js +348 -84
- package/node/cache-manager.js +0 -12
- package/node/chaincss.js +14 -99
- package/node/index.js +2 -13
- package/node/prefixer.js +2 -61
- package/node/strVal.js +1 -1
- package/package.json +1 -1
- package/types.d.ts +0 -26
- package/node/css-properties.json +0 -633
package/node/atomic-optimizer.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
-
|
|
4
3
|
class AtomicOptimizer {
|
|
5
4
|
constructor(options = {}) {
|
|
6
5
|
this.options = {
|
|
@@ -12,7 +11,6 @@ class AtomicOptimizer {
|
|
|
12
11
|
minify: true,
|
|
13
12
|
...options
|
|
14
13
|
};
|
|
15
|
-
|
|
16
14
|
this.usageCount = new Map();
|
|
17
15
|
this.atomicClasses = new Map();
|
|
18
16
|
this.stats = {
|
|
@@ -21,28 +19,21 @@ class AtomicOptimizer {
|
|
|
21
19
|
standardStyles: 0,
|
|
22
20
|
uniqueProperties: 0
|
|
23
21
|
};
|
|
24
|
-
|
|
25
22
|
this.cache = null;
|
|
26
23
|
if (this.options.cache) {
|
|
27
24
|
this.loadCache();
|
|
28
25
|
}
|
|
29
26
|
}
|
|
30
|
-
|
|
31
27
|
loadCache() {
|
|
32
28
|
try {
|
|
33
29
|
if (fs.existsSync(this.options.cachePath)) {
|
|
34
30
|
const data = fs.readFileSync(this.options.cachePath, 'utf8');
|
|
35
31
|
const cache = JSON.parse(data);
|
|
36
|
-
|
|
37
|
-
// Check version compatibility
|
|
38
32
|
if (cache.version === '1.0.0') {
|
|
39
33
|
this.atomicClasses = new Map(cache.atomicClasses || []);
|
|
40
34
|
this.stats = cache.stats || this.stats;
|
|
41
|
-
|
|
42
35
|
const cacheTime = new Date(cache.timestamp).toLocaleString();
|
|
43
36
|
console.log(`--Loaded ${this.atomicClasses.size} atomic classes from cache (${cacheTime})\n`);
|
|
44
|
-
|
|
45
|
-
// Verify config matches
|
|
46
37
|
if (cache.config) {
|
|
47
38
|
if (cache.config.threshold !== this.options.threshold) {
|
|
48
39
|
console.log(`Cache threshold (${cache.config.threshold}) differs from current (${this.options.threshold})`);
|
|
@@ -56,7 +47,6 @@ class AtomicOptimizer {
|
|
|
56
47
|
console.log('Could not load cache:', err.message);
|
|
57
48
|
}
|
|
58
49
|
}
|
|
59
|
-
|
|
60
50
|
saveCache() {
|
|
61
51
|
try {
|
|
62
52
|
const cache = {
|
|
@@ -65,8 +55,6 @@ class AtomicOptimizer {
|
|
|
65
55
|
atomicClasses: Array.from(this.atomicClasses.entries()),
|
|
66
56
|
stats: this.stats
|
|
67
57
|
};
|
|
68
|
-
|
|
69
|
-
// Keep only last 5 cache files for rollback
|
|
70
58
|
const cacheDir = path.dirname(this.options.cachePath);
|
|
71
59
|
if (fs.existsSync(cacheDir)) {
|
|
72
60
|
const files = fs.readdirSync(cacheDir)
|
|
@@ -76,19 +64,15 @@ class AtomicOptimizer {
|
|
|
76
64
|
time: fs.statSync(path.join(cacheDir, f)).mtime.getTime()
|
|
77
65
|
}))
|
|
78
66
|
.sort((a, b) => b.time - a.time);
|
|
79
|
-
|
|
80
67
|
files.slice(4).forEach(f => {
|
|
81
68
|
fs.unlinkSync(path.join(cacheDir, f.name));
|
|
82
69
|
});
|
|
83
70
|
}
|
|
84
|
-
|
|
85
71
|
fs.writeFileSync(this.options.cachePath, JSON.stringify(cache, null, 2), 'utf8');
|
|
86
|
-
|
|
87
72
|
} catch (err) {
|
|
88
73
|
console.log('Could not save cache:', err.message);
|
|
89
74
|
}
|
|
90
75
|
}
|
|
91
|
-
|
|
92
76
|
trackStyles(styles) {
|
|
93
77
|
Object.values(styles).forEach(style => {
|
|
94
78
|
Object.entries(style).forEach(([prop, value]) => {
|
|
@@ -100,25 +84,18 @@ class AtomicOptimizer {
|
|
|
100
84
|
});
|
|
101
85
|
this.stats.uniqueProperties = this.usageCount.size;
|
|
102
86
|
}
|
|
103
|
-
|
|
104
87
|
shouldBeAtomic(prop, value) {
|
|
105
88
|
const key = `${prop}:${value}`;
|
|
106
89
|
const usage = this.usageCount.get(key) || 0;
|
|
107
|
-
|
|
108
|
-
// Don't atomic-ify important layout properties that affect order
|
|
109
90
|
const criticalProps = ['position', 'display', 'flex', 'grid', 'z-index'];
|
|
110
91
|
const isCritical = criticalProps.some(p => prop.includes(p));
|
|
111
|
-
|
|
112
92
|
if (isCritical && usage < this.options.threshold * 2) {
|
|
113
|
-
return false;
|
|
93
|
+
return false;
|
|
114
94
|
}
|
|
115
|
-
|
|
116
95
|
return usage >= this.options.threshold;
|
|
117
96
|
}
|
|
118
|
-
|
|
119
97
|
generateClassName(prop, value) {
|
|
120
98
|
const key = `${prop}:${value}`;
|
|
121
|
-
|
|
122
99
|
if (this.options.naming === 'hash') {
|
|
123
100
|
let hash = 0;
|
|
124
101
|
for (let i = 0; i < key.length; i++) {
|
|
@@ -127,15 +104,12 @@ class AtomicOptimizer {
|
|
|
127
104
|
}
|
|
128
105
|
return `_${Math.abs(hash).toString(36).substring(0, 6)}`;
|
|
129
106
|
}
|
|
130
|
-
|
|
131
107
|
const kebabProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
132
108
|
const safeValue = value.replace(/[^a-zA-Z0-9-]/g, '-');
|
|
133
109
|
return `${kebabProp}-${safeValue}`;
|
|
134
110
|
}
|
|
135
|
-
|
|
136
111
|
getOrCreateAtomic(prop, value) {
|
|
137
112
|
const key = `${prop}:${value}`;
|
|
138
|
-
|
|
139
113
|
if (!this.atomicClasses.has(key)) {
|
|
140
114
|
const className = this.generateClassName(prop, value);
|
|
141
115
|
this.atomicClasses.set(key, {
|
|
@@ -146,36 +120,27 @@ class AtomicOptimizer {
|
|
|
146
120
|
});
|
|
147
121
|
this.stats.atomicStyles++;
|
|
148
122
|
}
|
|
149
|
-
|
|
150
123
|
return this.atomicClasses.get(key).className;
|
|
151
124
|
}
|
|
152
|
-
|
|
153
125
|
findKeyByClassName(className) {
|
|
154
126
|
for (let [key, value] of this.atomicClasses.entries()) {
|
|
155
127
|
if (value.className === className) return key;
|
|
156
128
|
}
|
|
157
129
|
return null;
|
|
158
130
|
}
|
|
159
|
-
|
|
160
131
|
generateAtomicCSS() {
|
|
161
|
-
let css = '';
|
|
162
|
-
|
|
132
|
+
let css = '';
|
|
163
133
|
const sortedClasses = Array.from(this.atomicClasses.values())
|
|
164
134
|
.sort((a, b) => b.usageCount - a.usageCount);
|
|
165
|
-
|
|
166
135
|
sortedClasses.forEach(atomic => {
|
|
167
136
|
const kebabProp = atomic.prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
168
137
|
css += `.${atomic.className} { ${kebabProp}: ${atomic.value}; }\n`;
|
|
169
138
|
});
|
|
170
|
-
|
|
171
139
|
return css;
|
|
172
140
|
}
|
|
173
|
-
|
|
174
141
|
generateComponentCSS(componentName, style, selectors) {
|
|
175
142
|
const atomicClasses = [];
|
|
176
143
|
const standardStyles = {};
|
|
177
|
-
|
|
178
|
-
// Separate atomic and standard styles
|
|
179
144
|
Object.entries(style).forEach(([prop, value]) => {
|
|
180
145
|
if (prop === 'selectors') return;
|
|
181
146
|
|
|
@@ -186,13 +151,9 @@ class AtomicOptimizer {
|
|
|
186
151
|
standardStyles[prop] = value;
|
|
187
152
|
}
|
|
188
153
|
});
|
|
189
|
-
|
|
190
154
|
let componentCSS = '';
|
|
191
|
-
|
|
192
155
|
if (atomicClasses.length > 0 || Object.keys(standardStyles).length > 0) {
|
|
193
156
|
componentCSS += `${selectors.join(', ')} {\n`;
|
|
194
|
-
|
|
195
|
-
// EXPAND atomic classes into actual properties
|
|
196
157
|
atomicClasses.forEach(className => {
|
|
197
158
|
const key = this.findKeyByClassName(className);
|
|
198
159
|
if (key) {
|
|
@@ -201,69 +162,43 @@ class AtomicOptimizer {
|
|
|
201
162
|
componentCSS += ` ${kebabProp}: ${atomic.value};\n`;
|
|
202
163
|
}
|
|
203
164
|
});
|
|
204
|
-
|
|
205
|
-
// Add standard styles (these will override atomic if needed)
|
|
206
165
|
Object.entries(standardStyles).forEach(([prop, value]) => {
|
|
207
166
|
const kebabProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
208
167
|
componentCSS += ` ${kebabProp}: ${value};\n`;
|
|
209
168
|
});
|
|
210
|
-
|
|
211
169
|
componentCSS += `}\n`;
|
|
212
170
|
}
|
|
213
|
-
|
|
214
171
|
return componentCSS;
|
|
215
172
|
}
|
|
216
|
-
|
|
217
173
|
validateStyleOrder(originalStyles, atomicStyles) {
|
|
218
|
-
// Compare original vs atomic to ensure no missing styles
|
|
219
174
|
const originalProps = new Set();
|
|
220
175
|
const atomicProps = new Set();
|
|
221
|
-
|
|
222
176
|
Object.values(originalStyles).forEach(style => {
|
|
223
177
|
Object.keys(style).forEach(prop => {
|
|
224
178
|
if (prop !== 'selectors') originalProps.add(prop);
|
|
225
179
|
});
|
|
226
180
|
});
|
|
227
|
-
|
|
228
|
-
// Check atomic classes for missing props
|
|
229
181
|
this.atomicClasses.forEach(atomic => {
|
|
230
182
|
atomicProps.add(atomic.prop);
|
|
231
183
|
});
|
|
232
|
-
|
|
233
184
|
const missingProps = [...originalProps].filter(p => !atomicProps.has(p));
|
|
234
185
|
if (missingProps.length > 0) {
|
|
235
186
|
console.warn('Missing atomic classes for:', missingProps);
|
|
236
187
|
}
|
|
237
188
|
}
|
|
238
|
-
|
|
239
189
|
optimize(styles) {
|
|
240
|
-
// Track usage first
|
|
241
190
|
this.trackStyles(styles);
|
|
242
|
-
|
|
243
|
-
// Generate atomic CSS
|
|
244
191
|
let atomicCSS = this.generateAtomicCSS();
|
|
245
|
-
|
|
246
|
-
// Generate component CSS with expanded styles
|
|
247
192
|
let componentCSS = '';
|
|
248
|
-
|
|
249
|
-
// Process components in original order to maintain specificity
|
|
250
193
|
Object.entries(styles).forEach(([name, style]) => {
|
|
251
194
|
const selectors = style.selectors || [`.${name}`];
|
|
252
195
|
componentCSS += this.generateComponentCSS(name, style, selectors);
|
|
253
196
|
});
|
|
254
|
-
|
|
255
|
-
// Calculate savings
|
|
256
197
|
const savings = ((this.stats.totalStyles - this.atomicClasses.size) / this.stats.totalStyles * 100).toFixed(1);
|
|
257
|
-
|
|
258
|
-
// Save cache if enabled
|
|
259
198
|
if (this.options.cache) {
|
|
260
199
|
this.saveCache();
|
|
261
200
|
}
|
|
262
|
-
|
|
263
|
-
// Return atomic CSS first, then component CSS
|
|
264
|
-
// This ensures atomic classes are defined before they're used
|
|
265
201
|
return atomicCSS + componentCSS;
|
|
266
202
|
}
|
|
267
203
|
}
|
|
268
|
-
|
|
269
204
|
module.exports = { AtomicOptimizer };
|