eva-css-purge 2.0.6 ā 2.0.7
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/package.json +1 -1
- package/src/purge.js +200 -282
package/package.json
CHANGED
package/src/purge.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
/**
|
|
3
|
-
* CSS Purge Script for EvaCSS
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Usage: npm run purge
|
|
3
|
+
* CSS Purge Script for EvaCSS - FIXED VERSION
|
|
4
|
+
*
|
|
5
|
+
* FIX: Use config.content patterns instead of hardcoded patterns that ignore dist/
|
|
6
|
+
* This allows scanning of built files in docs/.vuepress/dist/
|
|
9
7
|
*/
|
|
10
8
|
|
|
11
9
|
const fs = require('fs');
|
|
@@ -33,29 +31,26 @@ class CSSPurger {
|
|
|
33
31
|
*/
|
|
34
32
|
async purge() {
|
|
35
33
|
console.log('š Starting CSS purge process...');
|
|
36
|
-
|
|
34
|
+
|
|
37
35
|
try {
|
|
38
|
-
// Step 1: Analyze
|
|
39
|
-
await this.
|
|
40
|
-
|
|
41
|
-
// Step 2:
|
|
42
|
-
await this.analyzeJavaScriptFiles();
|
|
43
|
-
|
|
44
|
-
// Step 3: Read compiled CSS
|
|
36
|
+
// Step 1: Analyze content files using config patterns - FIXED
|
|
37
|
+
await this.analyzeContentFiles();
|
|
38
|
+
|
|
39
|
+
// Step 2: Read compiled CSS
|
|
45
40
|
await this.readCompiledCSS();
|
|
46
|
-
|
|
47
|
-
// Step
|
|
41
|
+
|
|
42
|
+
// Step 3: Extract used styles
|
|
48
43
|
await this.extractUsedStyles();
|
|
49
|
-
|
|
50
|
-
// Step
|
|
44
|
+
|
|
45
|
+
// Step 4: Compress and optimize
|
|
51
46
|
await this.compressCSS();
|
|
52
|
-
|
|
53
|
-
// Step
|
|
47
|
+
|
|
48
|
+
// Step 5: Write output
|
|
54
49
|
await this.writeCompressedCSS();
|
|
55
|
-
|
|
50
|
+
|
|
56
51
|
console.log('ā
CSS purge completed successfully!');
|
|
57
52
|
this.showStats();
|
|
58
|
-
|
|
53
|
+
|
|
59
54
|
} catch (error) {
|
|
60
55
|
console.error('ā Error during CSS purge:', error);
|
|
61
56
|
process.exit(1);
|
|
@@ -63,177 +58,152 @@ class CSSPurger {
|
|
|
63
58
|
}
|
|
64
59
|
|
|
65
60
|
/**
|
|
66
|
-
* Analyze
|
|
61
|
+
* Analyze content files using config patterns - FIXED
|
|
62
|
+
* This now uses config.content instead of hardcoded patterns
|
|
67
63
|
*/
|
|
68
|
-
async
|
|
69
|
-
console.log('š Analyzing
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Extract IDs from id attributes
|
|
92
|
-
const idMatches = content.match(/id=["']([^"']+)["']/g);
|
|
93
|
-
if (idMatches) {
|
|
94
|
-
idMatches.forEach(match => {
|
|
95
|
-
const id = match.replace(/id=["']([^"']+)["']/, '$1').trim();
|
|
96
|
-
if (id) {
|
|
97
|
-
this.usedIds.add(id);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Extract CSS variables from style attributes and content
|
|
103
|
-
const varMatches = content.match(/var\(--[^)]+\)/g);
|
|
104
|
-
if (varMatches) {
|
|
105
|
-
varMatches.forEach(match => {
|
|
106
|
-
const varName = match.replace(/var\((--[^)]+)\)/, '$1');
|
|
107
|
-
this.usedVariables.add(varName);
|
|
108
|
-
});
|
|
64
|
+
async analyzeContentFiles() {
|
|
65
|
+
console.log('š Analyzing content files...');
|
|
66
|
+
|
|
67
|
+
const allFiles = new Set();
|
|
68
|
+
|
|
69
|
+
// Use config.content patterns instead of hardcoded patterns - THIS IS THE FIX
|
|
70
|
+
for (const pattern of this.config.content) {
|
|
71
|
+
const files = glob.sync(pattern, {
|
|
72
|
+
ignore: ['node_modules/**'], // Only ignore node_modules, NOT dist/
|
|
73
|
+
nodir: true
|
|
74
|
+
});
|
|
75
|
+
files.forEach(file => allFiles.add(file));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(`š Found ${allFiles.size} files to analyze`);
|
|
79
|
+
|
|
80
|
+
for (const file of allFiles) {
|
|
81
|
+
try {
|
|
82
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
83
|
+
this.extractFromContent(content, file);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.warn(`ā ļø Could not read file: ${file}`);
|
|
109
86
|
}
|
|
110
87
|
}
|
|
111
|
-
|
|
112
|
-
console.log(`š Found ${this.usedClasses.size} unique classes
|
|
113
|
-
console.log(`š Found ${this.usedIds.size} unique IDs
|
|
114
|
-
console.log(`š Found ${this.usedVariables.size} CSS variables
|
|
88
|
+
|
|
89
|
+
console.log(`š Found ${this.usedClasses.size} unique classes`);
|
|
90
|
+
console.log(`š Found ${this.usedIds.size} unique IDs`);
|
|
91
|
+
console.log(`š Found ${this.usedVariables.size} CSS variables`);
|
|
115
92
|
}
|
|
116
93
|
|
|
117
94
|
/**
|
|
118
|
-
*
|
|
95
|
+
* Extract classes, IDs and variables from content
|
|
119
96
|
*/
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
let jsVarCount = 0;
|
|
130
|
-
|
|
131
|
-
for (const file of jsFiles) {
|
|
132
|
-
const content = fs.readFileSync(file, 'utf8');
|
|
133
|
-
|
|
134
|
-
// Extract classes from classList methods
|
|
135
|
-
const classListMatches = content.match(/classList\.(add|remove|toggle|contains)\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g);
|
|
136
|
-
if (classListMatches) {
|
|
137
|
-
classListMatches.forEach(match => {
|
|
138
|
-
const className = match.replace(/.*['"`]([^'"`]+)['"`].*/, '$1');
|
|
139
|
-
if (className) {
|
|
140
|
-
this.usedClasses.add(className);
|
|
141
|
-
jsClassCount++;
|
|
97
|
+
extractFromContent(content, filename) {
|
|
98
|
+
// Extract classes from class attributes
|
|
99
|
+
const classMatches = content.match(/class=["']([^"']+)["']/g);
|
|
100
|
+
if (classMatches) {
|
|
101
|
+
classMatches.forEach(match => {
|
|
102
|
+
const classes = match.replace(/class=["']([^"']+)["']/, '$1').split(/\s+/);
|
|
103
|
+
classes.forEach(cls => {
|
|
104
|
+
if (cls.trim()) {
|
|
105
|
+
this.usedClasses.add(cls.trim());
|
|
142
106
|
}
|
|
143
107
|
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Extract IDs from getElementById
|
|
189
|
-
const getElementByIdMatches = content.match(/getElementById\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g);
|
|
190
|
-
if (getElementByIdMatches) {
|
|
191
|
-
getElementByIdMatches.forEach(match => {
|
|
192
|
-
const id = match.replace(/.*['"`]([^'"`]+)['"`].*/, '$1');
|
|
193
|
-
if (id) {
|
|
194
|
-
this.usedIds.add(id);
|
|
195
|
-
jsIdCount++;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Extract IDs from id attributes
|
|
112
|
+
const idMatches = content.match(/id=["']([^"']+)["']/g);
|
|
113
|
+
if (idMatches) {
|
|
114
|
+
idMatches.forEach(match => {
|
|
115
|
+
const id = match.replace(/id=["']([^"']+)["']/, '$1').trim();
|
|
116
|
+
if (id) {
|
|
117
|
+
this.usedIds.add(id);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Extract CSS variables
|
|
123
|
+
const varMatches = content.match(/var\(--[^)]+\)/g);
|
|
124
|
+
if (varMatches) {
|
|
125
|
+
varMatches.forEach(match => {
|
|
126
|
+
const varName = match.replace(/var\((--[^)]+)\)/, '$1');
|
|
127
|
+
this.usedVariables.add(varName);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Extract classes from classList methods (JS)
|
|
132
|
+
const classListMatches = content.match(/classList\.(add|remove|toggle|contains)\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g);
|
|
133
|
+
if (classListMatches) {
|
|
134
|
+
classListMatches.forEach(match => {
|
|
135
|
+
const className = match.replace(/.*['"`]([^'"`]+)['"`].*/, '$1');
|
|
136
|
+
if (className) {
|
|
137
|
+
this.usedClasses.add(className);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Extract classes from className assignments (JS)
|
|
143
|
+
const classNameMatches = content.match(/\.className\s*=\s*['"`]([^'"`]+)['"`]/g);
|
|
144
|
+
if (classNameMatches) {
|
|
145
|
+
classNameMatches.forEach(match => {
|
|
146
|
+
const classes = match.replace(/.*['"`]([^'"`]+)['"`].*/, '$1').split(/\s+/);
|
|
147
|
+
classes.forEach(cls => {
|
|
148
|
+
if (cls.trim()) {
|
|
149
|
+
this.usedClasses.add(cls.trim());
|
|
196
150
|
}
|
|
197
151
|
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Extract from querySelector/querySelectorAll
|
|
156
|
+
const querySelectorMatches = content.match(/querySelector(?:All)?\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g);
|
|
157
|
+
if (querySelectorMatches) {
|
|
158
|
+
querySelectorMatches.forEach(match => {
|
|
159
|
+
const selector = match.replace(/.*['"`]([^'"`]+)['"`].*/, '$1');
|
|
160
|
+
|
|
161
|
+
// Extract class names
|
|
162
|
+
const selectorClassMatches = selector.match(/\.([a-zA-Z_-][a-zA-Z0-9_-]*)/g);
|
|
163
|
+
if (selectorClassMatches) {
|
|
164
|
+
selectorClassMatches.forEach(cls => {
|
|
165
|
+
this.usedClasses.add(cls.substring(1));
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Extract IDs
|
|
170
|
+
const selectorIdMatches = selector.match(/#([a-zA-Z_-][a-zA-Z0-9_-]*)/g);
|
|
171
|
+
if (selectorIdMatches) {
|
|
172
|
+
selectorIdMatches.forEach(id => {
|
|
173
|
+
this.usedIds.add(id.substring(1));
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Extract from getElementById
|
|
180
|
+
const getElementByIdMatches = content.match(/getElementById\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g);
|
|
181
|
+
if (getElementByIdMatches) {
|
|
182
|
+
getElementByIdMatches.forEach(match => {
|
|
183
|
+
const id = match.replace(/.*['"`]([^'"`]+)['"`].*/, '$1');
|
|
184
|
+
if (id) {
|
|
185
|
+
this.usedIds.add(id);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Extract CSS variables from setProperty calls
|
|
191
|
+
const setPropMatches = content.match(/setProperty\s*\(\s*['"`](--[^'"`]+)['"`]/g);
|
|
192
|
+
if (setPropMatches) {
|
|
193
|
+
setPropMatches.forEach(match => {
|
|
194
|
+
const varName = match.replace(/.*['"`](--[^'"`]+)['"`].*/, '$1');
|
|
195
|
+
this.usedVariables.add(varName);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Extract CSS variables from getPropertyValue calls
|
|
200
|
+
const getPropMatches = content.match(/getPropertyValue\s*\(\s*['"`](--[^'"`]+)['"`]/g);
|
|
201
|
+
if (getPropMatches) {
|
|
202
|
+
getPropMatches.forEach(match => {
|
|
203
|
+
const varName = match.replace(/.*['"`](--[^'"`]+)['"`].*/, '$1');
|
|
204
|
+
this.usedVariables.add(varName);
|
|
205
|
+
});
|
|
229
206
|
}
|
|
230
|
-
|
|
231
|
-
console.log(`š Found ${jsClassCount} class references in JavaScript`);
|
|
232
|
-
console.log(`š Found ${jsIdCount} ID references in JavaScript`);
|
|
233
|
-
console.log(`š Found ${jsVarCount} CSS variable references in JavaScript`);
|
|
234
|
-
console.log(`š Total unique classes: ${this.usedClasses.size}`);
|
|
235
|
-
console.log(`š Total unique IDs: ${this.usedIds.size}`);
|
|
236
|
-
console.log(`š Total unique CSS variables: ${this.usedVariables.size}`);
|
|
237
207
|
}
|
|
238
208
|
|
|
239
209
|
/**
|
|
@@ -257,105 +227,88 @@ class CSSPurger {
|
|
|
257
227
|
*/
|
|
258
228
|
async extractUsedStyles() {
|
|
259
229
|
console.log('š Extracting used styles...');
|
|
260
|
-
|
|
230
|
+
|
|
261
231
|
const lines = this.cssContent.split('\n');
|
|
262
232
|
const usedLines = [];
|
|
263
233
|
let currentRule = '';
|
|
264
234
|
let currentRuleContent = '';
|
|
265
235
|
let inRule = false;
|
|
266
|
-
let inRootRule = false;
|
|
267
|
-
let inMediaQuery = false;
|
|
236
|
+
let inRootRule = false;
|
|
237
|
+
let inMediaQuery = false;
|
|
268
238
|
let braceCount = 0;
|
|
269
239
|
let ruleLines = [];
|
|
270
240
|
let processedRules = 0;
|
|
271
241
|
let mediaQueryCount = 0;
|
|
272
|
-
|
|
242
|
+
|
|
273
243
|
for (let i = 0; i < lines.length; i++) {
|
|
274
244
|
const line = lines[i];
|
|
275
245
|
const trimmedLine = line.trim();
|
|
276
|
-
|
|
277
|
-
// Progress indicator
|
|
246
|
+
|
|
278
247
|
if (i % 1000 === 0) {
|
|
279
248
|
console.log(`š Processing line ${i}/${lines.length} (${Math.round(i/lines.length*100)}%)`);
|
|
280
249
|
}
|
|
281
|
-
|
|
282
|
-
// Skip empty lines and comments at the root level
|
|
250
|
+
|
|
283
251
|
if (!trimmedLine || (trimmedLine.startsWith('/*') && !inRule)) {
|
|
284
252
|
continue;
|
|
285
253
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (!inRule && !trimmedLine.includes('{') && !trimmedLine.includes('}') &&
|
|
254
|
+
|
|
255
|
+
if (!inRule && !trimmedLine.includes('{') && !trimmedLine.includes('}') &&
|
|
289
256
|
(trimmedLine.includes(',') || /^[a-zA-Z*#.\[]/.test(trimmedLine) || trimmedLine.includes('::') || trimmedLine.includes(':'))) {
|
|
290
|
-
// This might be part of a multi-line selector
|
|
291
257
|
if (!currentRule) {
|
|
292
258
|
currentRule = trimmedLine;
|
|
293
259
|
ruleLines = [line];
|
|
294
260
|
} else {
|
|
295
|
-
// Append to existing selector
|
|
296
261
|
currentRule += ',' + trimmedLine;
|
|
297
262
|
ruleLines.push(line);
|
|
298
263
|
}
|
|
299
264
|
continue;
|
|
300
265
|
}
|
|
301
|
-
|
|
302
|
-
// Handle opening braces
|
|
266
|
+
|
|
303
267
|
if (trimmedLine.includes('{')) {
|
|
304
268
|
braceCount++;
|
|
305
269
|
if (!inRule && braceCount === 1) {
|
|
306
|
-
// Complete the selector if it wasn't already accumulated
|
|
307
270
|
if (!currentRule) {
|
|
308
271
|
currentRule = trimmedLine.replace(/\s*\{.*/, '');
|
|
309
272
|
ruleLines = [line];
|
|
310
273
|
} else {
|
|
311
|
-
// Use accumulated selector and add the line with the opening brace
|
|
312
274
|
currentRule += trimmedLine.replace(/\s*\{.*/, '');
|
|
313
275
|
ruleLines.push(line);
|
|
314
276
|
}
|
|
315
277
|
currentRuleContent = '';
|
|
316
278
|
inRule = true;
|
|
317
|
-
|
|
318
|
-
// Check if this is a media query (always keep these completely)
|
|
279
|
+
|
|
319
280
|
if (currentRule.includes('@media')) {
|
|
320
281
|
inMediaQuery = true;
|
|
321
282
|
mediaQueryCount++;
|
|
322
283
|
console.log(`š± Found media query: ${currentRule.trim()} - keeping completely`);
|
|
323
|
-
}
|
|
324
|
-
// Check if this is a :root or .all-grads rule (always keep these completely)
|
|
325
|
-
else if (currentRule.includes(':root') || currentRule.includes('.all-grads')) {
|
|
284
|
+
} else if (currentRule.includes(':root') || currentRule.includes('.all-grads')) {
|
|
326
285
|
inRootRule = true;
|
|
327
286
|
console.log(`š Found ${currentRule.trim()} block - keeping all variables`);
|
|
328
287
|
}
|
|
329
288
|
} else if (inRule) {
|
|
330
289
|
ruleLines.push(line);
|
|
331
290
|
}
|
|
332
|
-
}
|
|
333
|
-
// Handle closing braces
|
|
334
|
-
else if (trimmedLine.includes('}')) {
|
|
291
|
+
} else if (trimmedLine.includes('}')) {
|
|
335
292
|
braceCount--;
|
|
336
293
|
if (inRule) {
|
|
337
294
|
ruleLines.push(line);
|
|
338
|
-
|
|
339
|
-
// Check if rule should be kept when we close the main rule
|
|
295
|
+
|
|
340
296
|
if (braceCount === 0) {
|
|
341
297
|
processedRules++;
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
this.isUsedSelector(currentRule) ||
|
|
298
|
+
|
|
299
|
+
const shouldKeep = inMediaQuery ||
|
|
300
|
+
inRootRule ||
|
|
301
|
+
this.isUsedSelector(currentRule) ||
|
|
347
302
|
this.hasCurrentVariables(currentRuleContent);
|
|
348
|
-
|
|
303
|
+
|
|
349
304
|
if (shouldKeep) {
|
|
350
305
|
usedLines.push(...ruleLines);
|
|
351
306
|
if (inRootRule) {
|
|
352
|
-
// Count variables in this :root block
|
|
353
307
|
const varCount = ruleLines.join('').match(/--[a-zA-Z][a-zA-Z0-9_-]*:/g)?.length || 0;
|
|
354
308
|
console.log(`š Kept ${varCount} CSS variables from ${currentRule.trim()}`);
|
|
355
309
|
}
|
|
356
310
|
}
|
|
357
|
-
|
|
358
|
-
// Reset for next rule
|
|
311
|
+
|
|
359
312
|
inRule = false;
|
|
360
313
|
inRootRule = false;
|
|
361
314
|
inMediaQuery = false;
|
|
@@ -364,14 +317,12 @@ class CSSPurger {
|
|
|
364
317
|
ruleLines = [];
|
|
365
318
|
}
|
|
366
319
|
}
|
|
367
|
-
}
|
|
368
|
-
// Handle properties within rules
|
|
369
|
-
else if (inRule) {
|
|
320
|
+
} else if (inRule) {
|
|
370
321
|
ruleLines.push(line);
|
|
371
322
|
currentRuleContent += ' ' + trimmedLine;
|
|
372
323
|
}
|
|
373
324
|
}
|
|
374
|
-
|
|
325
|
+
|
|
375
326
|
console.log(`š Processed ${processedRules} CSS rules`);
|
|
376
327
|
console.log(`š± Kept ${mediaQueryCount} media queries`);
|
|
377
328
|
this.compressedCSS = usedLines.join('\n');
|
|
@@ -390,19 +341,17 @@ class CSSPurger {
|
|
|
390
341
|
*/
|
|
391
342
|
isElementOrResetSelector(selector) {
|
|
392
343
|
const cleanSelector = selector.replace(/\s*\{.*/, '').trim();
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
cleanSelector.includes('::before') ||
|
|
344
|
+
|
|
345
|
+
if (cleanSelector.includes('*') ||
|
|
346
|
+
cleanSelector.includes('::before') ||
|
|
397
347
|
cleanSelector.includes('::after') ||
|
|
398
348
|
cleanSelector.includes(':target') ||
|
|
399
349
|
/^body[,\s]|^html[,\s]/.test(cleanSelector)) {
|
|
400
350
|
return true;
|
|
401
351
|
}
|
|
402
|
-
|
|
403
|
-
// Handle multiple selectors separated by commas - if ANY selector in the group is an element/reset, keep the whole group
|
|
352
|
+
|
|
404
353
|
const selectors = cleanSelector.split(',');
|
|
405
|
-
|
|
354
|
+
|
|
406
355
|
const htmlElements = [
|
|
407
356
|
'html', 'body', 'head', 'title', 'meta', 'link', 'script', 'style',
|
|
408
357
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'br', 'hr',
|
|
@@ -415,34 +364,29 @@ class CSSPurger {
|
|
|
415
364
|
'em', 'strong', 'b', 'i', 'u', 's', 'small', 'mark', 'del', 'ins', 'sub', 'sup',
|
|
416
365
|
'video', 'audio', 'canvas', 'svg', 'iframe', 'embed', 'object', 'param'
|
|
417
366
|
];
|
|
418
|
-
|
|
367
|
+
|
|
419
368
|
for (const sel of selectors) {
|
|
420
369
|
const trimmedSel = sel.trim();
|
|
421
|
-
|
|
422
|
-
// Check if it's a pure element selector
|
|
370
|
+
|
|
423
371
|
const baseElement = trimmedSel.replace(/::[^,\s]+|:[^,\s(]+(\([^)]*\))?/g, '').trim();
|
|
424
372
|
if (htmlElements.includes(baseElement)) {
|
|
425
373
|
return true;
|
|
426
374
|
}
|
|
427
|
-
|
|
428
|
-
// Check for element selectors with pseudo-classes/elements (like a:hover, input:focus)
|
|
375
|
+
|
|
429
376
|
if (htmlElements.some(element => trimmedSel.startsWith(element + ':') || trimmedSel.startsWith(element + '::') || trimmedSel === element)) {
|
|
430
377
|
return true;
|
|
431
378
|
}
|
|
432
|
-
|
|
433
|
-
// Check for attribute selectors on elements (like input[type="text"])
|
|
379
|
+
|
|
434
380
|
if (htmlElements.some(element => trimmedSel.startsWith(element + '[') && trimmedSel.includes(']'))) {
|
|
435
381
|
return true;
|
|
436
382
|
}
|
|
437
|
-
|
|
438
|
-
// Check for selectors starting with element but containing other selectors
|
|
439
|
-
// e.g., "ul[role=list]", "a:not([class])", etc.
|
|
383
|
+
|
|
440
384
|
const match = trimmedSel.match(/^([a-zA-Z][a-zA-Z0-9]*)/);
|
|
441
385
|
if (match && htmlElements.includes(match[1])) {
|
|
442
386
|
return true;
|
|
443
387
|
}
|
|
444
388
|
}
|
|
445
|
-
|
|
389
|
+
|
|
446
390
|
return false;
|
|
447
391
|
}
|
|
448
392
|
|
|
@@ -450,75 +394,65 @@ class CSSPurger {
|
|
|
450
394
|
* Check if a CSS selector is used in the HTML or JavaScript
|
|
451
395
|
*/
|
|
452
396
|
isUsedSelector(selector) {
|
|
453
|
-
// Clean the selector
|
|
454
397
|
const cleanSelector = selector.replace(/\s*\{.*/, '').trim();
|
|
455
|
-
|
|
456
|
-
// Always keep element and reset selectors
|
|
398
|
+
|
|
457
399
|
if (this.isElementOrResetSelector(selector)) {
|
|
458
400
|
return true;
|
|
459
401
|
}
|
|
460
|
-
|
|
461
|
-
// Handle multiple selectors separated by commas
|
|
402
|
+
|
|
462
403
|
const selectors = cleanSelector.split(',');
|
|
463
|
-
|
|
404
|
+
|
|
464
405
|
for (const sel of selectors) {
|
|
465
406
|
const trimmedSel = sel.trim();
|
|
466
|
-
|
|
467
|
-
// Skip pseudo-elements and pseudo-classes for now
|
|
407
|
+
|
|
468
408
|
if (trimmedSel.includes('::') || trimmedSel.includes(':hover') || trimmedSel.includes(':before') || trimmedSel.includes(':after')) {
|
|
469
|
-
// Check the base selector
|
|
470
409
|
const baseSelector = trimmedSel.replace(/::[^,\s]+|:[^,\s]+/g, '').trim();
|
|
471
410
|
if (this.isUsedSelector(baseSelector)) {
|
|
472
411
|
return true;
|
|
473
412
|
}
|
|
474
413
|
continue;
|
|
475
414
|
}
|
|
476
|
-
|
|
477
|
-
// Handle ID selectors
|
|
415
|
+
|
|
478
416
|
if (trimmedSel.startsWith('#')) {
|
|
479
417
|
const idName = trimmedSel.substring(1).replace(/[^\w-_]/g, '');
|
|
480
418
|
if (this.usedIds.has(idName)) {
|
|
481
419
|
return true;
|
|
482
420
|
}
|
|
483
421
|
}
|
|
484
|
-
|
|
485
|
-
// Handle class selectors
|
|
422
|
+
|
|
486
423
|
if (trimmedSel.startsWith('.')) {
|
|
487
424
|
const className = trimmedSel.substring(1).replace(/[^\w-_]/g, '');
|
|
488
425
|
if (this.usedClasses.has(className)) {
|
|
489
426
|
return true;
|
|
490
427
|
}
|
|
491
428
|
}
|
|
492
|
-
|
|
493
|
-
// Handle compound selectors (e.g., .class1.class2, #id.class)
|
|
429
|
+
|
|
494
430
|
const classes = trimmedSel.match(/\.[a-zA-Z][a-zA-Z0-9_-]*/g);
|
|
495
431
|
const ids = trimmedSel.match(/#[a-zA-Z][a-zA-Z0-9_-]*/g);
|
|
496
|
-
|
|
432
|
+
|
|
497
433
|
if (classes || ids) {
|
|
498
434
|
let allSelectorsUsed = true;
|
|
499
|
-
|
|
500
|
-
// Check all classes in compound selector
|
|
435
|
+
|
|
501
436
|
if (classes) {
|
|
502
437
|
allSelectorsUsed = allSelectorsUsed && classes.every(cls => {
|
|
503
438
|
const className = cls.substring(1);
|
|
504
439
|
return this.usedClasses.has(className);
|
|
505
440
|
});
|
|
506
441
|
}
|
|
507
|
-
|
|
508
|
-
// Check all IDs in compound selector
|
|
442
|
+
|
|
509
443
|
if (ids) {
|
|
510
444
|
allSelectorsUsed = allSelectorsUsed && ids.every(id => {
|
|
511
445
|
const idName = id.substring(1);
|
|
512
446
|
return this.usedIds.has(idName);
|
|
513
447
|
});
|
|
514
448
|
}
|
|
515
|
-
|
|
449
|
+
|
|
516
450
|
if (allSelectorsUsed && (classes?.length > 0 || ids?.length > 0)) {
|
|
517
451
|
return true;
|
|
518
452
|
}
|
|
519
453
|
}
|
|
520
454
|
}
|
|
521
|
-
|
|
455
|
+
|
|
522
456
|
return false;
|
|
523
457
|
}
|
|
524
458
|
|
|
@@ -527,34 +461,20 @@ class CSSPurger {
|
|
|
527
461
|
*/
|
|
528
462
|
async compressCSS() {
|
|
529
463
|
console.log('šļø Compressing CSS...');
|
|
530
|
-
|
|
464
|
+
|
|
531
465
|
let compressed = this.compressedCSS;
|
|
532
|
-
|
|
533
|
-
// Remove comments
|
|
466
|
+
|
|
534
467
|
compressed = compressed.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
535
|
-
|
|
536
|
-
// Remove unnecessary whitespace but preserve selector grouping structure
|
|
537
468
|
compressed = compressed.replace(/\s+/g, ' ');
|
|
538
|
-
|
|
539
|
-
// Remove whitespace around braces and semicolons - but preserve commas in selectors
|
|
540
469
|
compressed = compressed.replace(/\s*{\s*/g, '{');
|
|
541
470
|
compressed = compressed.replace(/\s*}\s*/g, '}');
|
|
542
471
|
compressed = compressed.replace(/\s*;\s*/g, ';');
|
|
543
472
|
compressed = compressed.replace(/\s*:\s*/g, ':');
|
|
544
|
-
|
|
545
|
-
// CAREFULLY handle comma spacing - preserve commas in selectors but remove extra spaces
|
|
546
|
-
// Only remove spaces around commas that are NOT inside function calls or @media queries
|
|
547
473
|
compressed = compressed.replace(/(\w|[)\]])\s*,\s*(\w|[.#*:])/g, '$1,$2');
|
|
548
|
-
|
|
549
|
-
// Remove trailing semicolons before }
|
|
550
474
|
compressed = compressed.replace(/;}/g, '}');
|
|
551
|
-
|
|
552
|
-
// Remove empty rules
|
|
553
475
|
compressed = compressed.replace(/[^}]+{\s*}/g, '');
|
|
554
|
-
|
|
555
|
-
// Final cleanup
|
|
556
476
|
compressed = compressed.trim();
|
|
557
|
-
|
|
477
|
+
|
|
558
478
|
this.compressedCSS = compressed;
|
|
559
479
|
console.log(`š Compressed CSS size: ${(compressed.length / 1024).toFixed(2)} KB`);
|
|
560
480
|
}
|
|
@@ -567,13 +487,11 @@ class CSSPurger {
|
|
|
567
487
|
|
|
568
488
|
const outputPath = path.resolve(process.cwd(), this.config.output);
|
|
569
489
|
|
|
570
|
-
// Ensure output directory exists
|
|
571
490
|
const outputDir = path.dirname(outputPath);
|
|
572
491
|
if (!fs.existsSync(outputDir)) {
|
|
573
492
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
574
493
|
}
|
|
575
494
|
|
|
576
|
-
// Add a header comment
|
|
577
495
|
const header = `/* EVA CSS Purged - Generated on ${new Date().toISOString()} */\n`;
|
|
578
496
|
const finalCSS = header + this.compressedCSS;
|
|
579
497
|
|
|
@@ -588,7 +506,7 @@ class CSSPurger {
|
|
|
588
506
|
const originalSize = this.cssContent.length;
|
|
589
507
|
const compressedSize = this.compressedCSS.length;
|
|
590
508
|
const savings = ((originalSize - compressedSize) / originalSize * 100).toFixed(2);
|
|
591
|
-
|
|
509
|
+
|
|
592
510
|
console.log('\nš Compression Statistics:');
|
|
593
511
|
console.log(` Original size: ${(originalSize / 1024).toFixed(2)} KB`);
|
|
594
512
|
console.log(` Compressed size: ${(compressedSize / 1024).toFixed(2)} KB`);
|
|
@@ -605,4 +523,4 @@ if (require.main === module) {
|
|
|
605
523
|
purger.purge();
|
|
606
524
|
}
|
|
607
525
|
|
|
608
|
-
module.exports = CSSPurger;
|
|
526
|
+
module.exports = CSSPurger;
|