xatriumcss 1.0.25 ā 1.0.26
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 +4 -3
- package/src/cli.js +0 -1
- package/src/collectClasses.js +186 -456
- package/src/parser.js +1 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xatriumcss",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
4
4
|
"description": "Ultra-fast utility-first CSS framework",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -34,8 +34,9 @@
|
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@parcel/watcher": "^2.4.0",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
37
|
+
"@tailwindcss/oxide": "^4.1.18",
|
|
38
|
+
"esbuild": "^0.27.2",
|
|
39
|
+
"glob": "^10.5.0"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"@napi-rs/cli": "^2.18.0"
|
package/src/cli.js
CHANGED
package/src/collectClasses.js
CHANGED
|
@@ -1,97 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import { glob } from 'glob';
|
|
3
2
|
import path from 'path';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
try {
|
|
7
|
-
const module = await import('@xatriumcss/oxide');
|
|
8
|
-
nativeScanner = module.default || module;
|
|
9
|
-
console.log("using native scanner");
|
|
10
|
-
} catch (e) {
|
|
11
|
-
console.log("using fallback scanner");
|
|
12
|
-
console.log("ERROR:", e.message);
|
|
13
|
-
nativeScanner = null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function smartSplitNoBrace(str, ...splitChars) {
|
|
17
|
-
if (splitChars.length === 0) splitChars = [' '];
|
|
18
|
-
|
|
19
|
-
const result = [];
|
|
20
|
-
let current = '';
|
|
21
|
-
let bracketDepth = 0;
|
|
22
|
-
let parenDepth = 0;
|
|
23
|
-
let braceDepth = 0;
|
|
24
|
-
let inQuote = null;
|
|
25
|
-
|
|
26
|
-
for (let i = 0; i < str.length; i++) {
|
|
27
|
-
const char = str[i];
|
|
28
|
-
const prevChar = str[i - 1];
|
|
29
|
-
|
|
30
|
-
// Handle quotes (not escaped)
|
|
31
|
-
if ((char === "'" || char === '"') && prevChar !== '\\') {
|
|
32
|
-
if (!inQuote) {
|
|
33
|
-
inQuote = char;
|
|
34
|
-
} else if (inQuote === char) {
|
|
35
|
-
inQuote = null;
|
|
36
|
-
}
|
|
37
|
-
current += char;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Track depths only when not in quotes
|
|
42
|
-
if (!inQuote) {
|
|
43
|
-
if (char === '[') bracketDepth++;
|
|
44
|
-
if (char === ']' && bracketDepth > 0) bracketDepth--;
|
|
45
|
-
if (char === '(') parenDepth++;
|
|
46
|
-
if (char === ')' && parenDepth > 0) parenDepth--;
|
|
47
|
-
|
|
48
|
-
// Track brace depth with prefix check
|
|
49
|
-
if (char === '{') {
|
|
50
|
-
// Check if there's content in current token (means there's a prefix)
|
|
51
|
-
const hasPrefix = current.trim().length > 0;
|
|
52
|
-
|
|
53
|
-
// Always increment brace depth
|
|
54
|
-
braceDepth++;
|
|
55
|
-
|
|
56
|
-
// But if there's a prefix, we're in "keep together" mode
|
|
57
|
-
// The depth will prevent splitting until we hit the closing }
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (char === '}' && braceDepth > 0) {
|
|
61
|
-
braceDepth--;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ā
Split on specified characters only if all depths are 0 and not in quotes
|
|
66
|
-
let shouldSplit = false;
|
|
67
|
-
if (bracketDepth === 0 && parenDepth === 0 && braceDepth === 0) {
|
|
68
|
-
// Check single character splits
|
|
69
|
-
if (splitChars.includes(char)) {
|
|
70
|
-
shouldSplit = true;
|
|
71
|
-
}
|
|
72
|
-
// Check multi-character splits (like "::")
|
|
73
|
-
for (let splitStr of splitChars) {
|
|
74
|
-
if (splitStr.length > 1 && str.slice(i, i + splitStr.length) === splitStr) {
|
|
75
|
-
shouldSplit = true;
|
|
76
|
-
i += splitStr.length - 1; // Skip ahead
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (shouldSplit) {
|
|
83
|
-
if (current) result.push(current);
|
|
84
|
-
current = '';
|
|
85
|
-
} else {
|
|
86
|
-
current += char;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (current) result.push(current);
|
|
91
|
-
return result;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function collectClasses(contentPaths) {
|
|
4
|
+
export async function collectClasses(contentPaths) {
|
|
95
5
|
const sourceConfig = global.xatriumSourceConfig || {
|
|
96
6
|
basePath: null,
|
|
97
7
|
disableAuto: false,
|
|
@@ -99,411 +9,239 @@ function collectClasses(contentPaths) {
|
|
|
99
9
|
ignorePaths: []
|
|
100
10
|
};
|
|
101
11
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const sources = [];
|
|
120
|
-
|
|
121
|
-
// Add positive patterns
|
|
122
|
-
pathsArray.forEach(pattern => {
|
|
123
|
-
sources.push({
|
|
124
|
-
base: process.cwd(),
|
|
125
|
-
pattern: pattern,
|
|
126
|
-
negated: false
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// Add negative patterns (ignore paths)
|
|
131
|
-
sourceConfig.ignorePaths.forEach(pattern => {
|
|
132
|
-
sources.push({
|
|
133
|
-
base: process.cwd(),
|
|
134
|
-
pattern: pattern,
|
|
135
|
-
negated: true
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
console.log('š Using native scanner (Rust) for file discovery');
|
|
140
|
-
console.log('š Patterns:', pathsArray);
|
|
141
|
-
|
|
142
|
-
// Create scanner with options
|
|
143
|
-
let scanner;
|
|
144
|
-
try {
|
|
145
|
-
scanner = new nativeScanner.Scanner({ sources });
|
|
146
|
-
} catch (error) {
|
|
147
|
-
console.warn('ā ļø Native scanner failed, falling back to JS:', error.message);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Get the files that were matched by the scanner
|
|
151
|
-
const files = scanner.files;
|
|
152
|
-
|
|
153
|
-
if (!files || files.length === 0) {
|
|
154
|
-
console.warn('ā ļø Native scanner found no files');
|
|
155
|
-
throw new Error('No files found');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
console.log(`š Native scanner found ${files.length} files`);
|
|
159
|
-
|
|
160
|
-
// Read files and extract quoted strings manually to preserve grouping
|
|
161
|
-
const quotedStrings = scanner.scan(); // Rust does EVERYTHING!
|
|
162
|
-
|
|
163
|
-
console.log(`ā
Extracted ${quotedStrings.length} quoted strings from files`);
|
|
164
|
-
return processClasses(quotedStrings);
|
|
165
|
-
} catch (error) {
|
|
166
|
-
console.warn('ā ļø Native scanner failed, falling back to JS:', error.message);
|
|
12
|
+
try {
|
|
13
|
+
let pathsArray = sourceConfig.disableAuto
|
|
14
|
+
? sourceConfig.explicitPaths
|
|
15
|
+
: [
|
|
16
|
+
...(Array.isArray(contentPaths) ? contentPaths : [contentPaths]),
|
|
17
|
+
...sourceConfig.explicitPaths,
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
pathsArray = pathsArray.filter(Boolean);
|
|
21
|
+
|
|
22
|
+
if (sourceConfig.ignorePaths?.length) {
|
|
23
|
+
const ignoreSet = new Set(
|
|
24
|
+
sourceConfig.ignorePaths.map(p => p.toLowerCase())
|
|
25
|
+
);
|
|
26
|
+
pathsArray = pathsArray.filter(
|
|
27
|
+
p => !ignoreSet.has(p.toLowerCase())
|
|
28
|
+
);
|
|
167
29
|
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Fallback to JavaScript implementation
|
|
171
|
-
return collectClassesJS(contentPaths);
|
|
172
|
-
}
|
|
173
30
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return [];
|
|
179
|
-
}
|
|
31
|
+
if (pathsArray.length === 0) {
|
|
32
|
+
console.warn('ā ļø No valid patterns to scan');
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
180
35
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
36
|
+
console.log('š Using streaming scanner (blazing fast)');
|
|
37
|
+
console.log('š Raw patterns:', pathsArray);
|
|
38
|
+
|
|
39
|
+
const resolvedPaths = pathsArray.map(p => path.resolve(p));
|
|
40
|
+
const files = await scanFiles(resolvedPaths, sourceConfig.ignorePaths);
|
|
41
|
+
|
|
42
|
+
console.log(`š Found ${files.length} files to scan`);
|
|
43
|
+
|
|
44
|
+
if (files.length === 0) {
|
|
45
|
+
return [];
|
|
188
46
|
}
|
|
189
47
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
let inDoubleQuote = false;
|
|
200
|
-
|
|
201
|
-
for (let i = 0; i < cls.length; i++) {
|
|
202
|
-
const char = cls[i];
|
|
203
|
-
|
|
204
|
-
// Track quote state
|
|
205
|
-
if (char === "'" && !inDoubleQuote) inSingleQuote = !inSingleQuote;
|
|
206
|
-
else if (char === '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote;
|
|
207
|
-
|
|
208
|
-
const inQuotes = inSingleQuote || inDoubleQuote;
|
|
209
|
-
|
|
210
|
-
// Track bracket depths (only if not in quotes)
|
|
211
|
-
if (!inQuotes) {
|
|
212
|
-
if (char === "(") parenDepth++;
|
|
213
|
-
else if (char === ")") parenDepth--;
|
|
214
|
-
else if (char === "[") bracketDepth++;
|
|
215
|
-
else if (char === "]") bracketDepth--;
|
|
216
|
-
else if (char === "{") braceDepth++;
|
|
217
|
-
else if (char === "}") braceDepth--;
|
|
218
|
-
}
|
|
48
|
+
const allClasses = await extractClassesFromFiles(files);
|
|
49
|
+
|
|
50
|
+
console.log(`ā
Extracted ${allClasses.length} classes`);
|
|
51
|
+
return allClasses;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('ā Error during class collection:', error.message);
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
219
57
|
|
|
220
|
-
|
|
58
|
+
async function scanFiles(patterns, ignorePaths = []) {
|
|
59
|
+
const files = new Set();
|
|
60
|
+
const ignoreSet = new Set(ignorePaths.map(p => p.toLowerCase()));
|
|
221
61
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
62
|
+
for (const pattern of patterns) {
|
|
63
|
+
if (pattern.includes('*') || pattern.includes('?')) {
|
|
64
|
+
const dir = path.dirname(pattern);
|
|
65
|
+
const glob = path.basename(pattern);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const matched = await globWalk(dir, glob);
|
|
69
|
+
matched.forEach(f => {
|
|
70
|
+
const lowerF = f.toLowerCase();
|
|
71
|
+
if (!ignoreSet.has(lowerF)) {
|
|
72
|
+
files.add(f);
|
|
230
73
|
}
|
|
74
|
+
});
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.warn(`ā ļø Error scanning ${pattern}:`, err.message);
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
if (fs.existsSync(pattern)) {
|
|
80
|
+
const lowerP = pattern.toLowerCase();
|
|
81
|
+
if (!ignoreSet.has(lowerP)) {
|
|
82
|
+
files.add(pattern);
|
|
231
83
|
}
|
|
232
|
-
} else {
|
|
233
|
-
normalizedCls += char;
|
|
234
84
|
}
|
|
235
85
|
}
|
|
86
|
+
}
|
|
236
87
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const spawnShorthandRegex = /^(.*?:)?spawn-((?:from|to|back|half|\[[^\]]+\])(?:\/(?:from|to|back|half|\[[^\]]+\]))*):(.+)$/;
|
|
241
|
-
const scrollShorthandRegex = /^(.*?:)?scroll-((?:from|to|back|half|\[[^\]]+\])(?:\/(?:from|to|back|half|\[[^\]]+\]))*):(.+)$/;
|
|
242
|
-
const scrollPrefixRegex = /^(.*?:)?scroll:(.+)$/;
|
|
243
|
-
|
|
244
|
-
// Group by prefix for spawn/scroll classes
|
|
245
|
-
const spawnByPrefix = {};
|
|
246
|
-
const scrollByPrefix = {};
|
|
247
|
-
const nonShorthandParts = [];
|
|
248
|
-
|
|
249
|
-
innerParts.forEach(part => {
|
|
250
|
-
if (spawnShorthandRegex.test(part)) {
|
|
251
|
-
const match = part.match(spawnShorthandRegex);
|
|
252
|
-
const prefix = match[1] || ''; // Empty string for no prefix
|
|
88
|
+
return Array.from(files);
|
|
89
|
+
}
|
|
253
90
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
spawnByPrefix[prefix].push(part);
|
|
258
|
-
} else if (scrollShorthandRegex.test(part) || scrollPrefixRegex.test(part)) {
|
|
259
|
-
const match = part.match(scrollShorthandRegex) || part.match(scrollPrefixRegex);
|
|
260
|
-
const prefix = match[1] || ''; // Empty string for no prefix
|
|
91
|
+
async function globWalk(dir, pattern) {
|
|
92
|
+
const files = [];
|
|
93
|
+
const regexPattern = globToRegex(pattern);
|
|
261
94
|
|
|
262
|
-
|
|
263
|
-
|
|
95
|
+
function walk(currentPath) {
|
|
96
|
+
try {
|
|
97
|
+
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
98
|
+
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
101
|
+
|
|
102
|
+
if (entry.isFile() && regexPattern.test(entry.name)) {
|
|
103
|
+
files.push(fullPath);
|
|
104
|
+
} else if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
105
|
+
walk(fullPath);
|
|
264
106
|
}
|
|
265
|
-
scrollByPrefix[prefix].push(part);
|
|
266
|
-
} else {
|
|
267
|
-
nonShorthandParts.push(part);
|
|
268
107
|
}
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
108
|
+
} catch (err) {
|
|
109
|
+
// Silently ignore permission errors
|
|
110
|
+
}
|
|
111
|
+
}
|
|
273
112
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
allClasses.add(group.join(' '));
|
|
278
|
-
}
|
|
279
|
-
});
|
|
113
|
+
if (fs.existsSync(dir)) {
|
|
114
|
+
walk(dir);
|
|
115
|
+
}
|
|
280
116
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (group.length > 0) {
|
|
284
|
-
allClasses.add(group.join(' '));
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
});
|
|
117
|
+
return files;
|
|
118
|
+
}
|
|
288
119
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
120
|
+
function globToRegex(glob) {
|
|
121
|
+
let regexStr = glob
|
|
122
|
+
.replace(/\./g, '\\.')
|
|
123
|
+
.replace(/\*/g, '[^/]*')
|
|
124
|
+
.replace(/\?/g, '.');
|
|
125
|
+
|
|
126
|
+
return new RegExp(`^${regexStr}$`);
|
|
127
|
+
}
|
|
293
128
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const prefix = match[1]; // Everything before "motion-["
|
|
298
|
-
const animName = match[2];
|
|
129
|
+
async function extractClassesFromFiles(filePaths) {
|
|
130
|
+
const allClasses = new Set();
|
|
131
|
+
const start = performance.now();
|
|
299
132
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
133
|
+
// Process all files in parallel with sync reads
|
|
134
|
+
filePaths.forEach((filePath) => {
|
|
135
|
+
try {
|
|
136
|
+
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
137
|
+
const parts = extractQuotedStringsSync(fileContent);
|
|
303
138
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
prefixLength: prefix.length
|
|
139
|
+
parts.forEach(part => {
|
|
140
|
+
const classes = processClassString(part);
|
|
141
|
+
classes.forEach(cls => allClasses.add(cls));
|
|
308
142
|
});
|
|
309
|
-
}
|
|
310
|
-
|
|
143
|
+
} catch (err) {
|
|
144
|
+
console.warn(`ā ļø Error reading file ${filePath}:`, err.message);
|
|
311
145
|
}
|
|
312
146
|
});
|
|
313
147
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
Object.values(motionByName).forEach(group => {
|
|
317
|
-
group.sort((a, b) => a.prefixLength - b.prefixLength);
|
|
318
|
-
sortedMotionClasses.push(...group.map(item => item.cls));
|
|
319
|
-
});
|
|
148
|
+
const elapsed = performance.now() - start;
|
|
149
|
+
console.log(`ā” Extraction time: ${elapsed.toFixed(2)}ms`);
|
|
320
150
|
|
|
321
|
-
|
|
322
|
-
allClasses.clear();
|
|
323
|
-
nonMotionClasses.forEach(c => allClasses.add(c));
|
|
324
|
-
sortedMotionClasses.forEach(c => allClasses.add(c));
|
|
325
|
-
|
|
326
|
-
return Array.from(allClasses);
|
|
151
|
+
return sortMotionClasses(Array.from(allClasses));
|
|
327
152
|
}
|
|
328
153
|
|
|
329
|
-
function
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
basePath: null,
|
|
335
|
-
disableAuto: false,
|
|
336
|
-
explicitPaths: [],
|
|
337
|
-
ignorePaths: []
|
|
338
|
-
};
|
|
154
|
+
function extractQuotedStringsSync(content) {
|
|
155
|
+
const result = [];
|
|
156
|
+
let current = '';
|
|
157
|
+
let quoteChar = null;
|
|
158
|
+
let escaped = false;
|
|
339
159
|
|
|
340
|
-
let
|
|
160
|
+
for (let i = 0; i < content.length; i++) {
|
|
161
|
+
const char = content[i];
|
|
341
162
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
} else {
|
|
347
|
-
// Use provided contentPaths or defaults
|
|
348
|
-
pathsArray = Array.isArray(contentPaths) ? contentPaths : (contentPaths ? [contentPaths] : []);
|
|
349
|
-
|
|
350
|
-
// Add explicit paths from @source directives
|
|
351
|
-
if (sourceConfig.explicitPaths.length > 0) {
|
|
352
|
-
pathsArray = [...pathsArray, ...sourceConfig.explicitPaths];
|
|
353
|
-
console.log('ā Added explicit paths:', sourceConfig.explicitPaths);
|
|
163
|
+
if (escaped) {
|
|
164
|
+
current += char;
|
|
165
|
+
escaped = false;
|
|
166
|
+
continue;
|
|
354
167
|
}
|
|
355
|
-
}
|
|
356
168
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
});
|
|
363
|
-
console.log('š Applied base path:', sourceConfig.basePath);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
console.log('š Scanning paths:', pathsArray);
|
|
169
|
+
if (char === '\\') {
|
|
170
|
+
current += char;
|
|
171
|
+
escaped = true;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
367
174
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
ignore: sourceConfig.ignorePaths.length > 0 ? sourceConfig.ignorePaths : undefined
|
|
374
|
-
});
|
|
375
|
-
files.push(...matches);
|
|
376
|
-
|
|
377
|
-
if (sourceConfig.ignorePaths.length > 0) {
|
|
378
|
-
console.log(`š« Ignoring paths:`, sourceConfig.ignorePaths);
|
|
175
|
+
if (!quoteChar && (char === '"' || char === "'" || char === '`')) {
|
|
176
|
+
quoteChar = char;
|
|
177
|
+
} else if (char === quoteChar) {
|
|
178
|
+
if (current) {
|
|
179
|
+
result.push(current);
|
|
379
180
|
}
|
|
380
|
-
|
|
381
|
-
|
|
181
|
+
current = '';
|
|
182
|
+
quoteChar = null;
|
|
183
|
+
} else if (quoteChar) {
|
|
184
|
+
current += char;
|
|
382
185
|
}
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
console.log(`š Found ${files.length} files to scan`);
|
|
386
|
-
|
|
387
|
-
if (files.length === 0) {
|
|
388
|
-
return [];
|
|
389
186
|
}
|
|
390
187
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
// Grab only strings inside single quotes, double quotes, or backticks
|
|
395
|
-
const parts = Array.from(fileContent.matchAll(/(['"`])(.*?)\1/gs), m => m[2]);
|
|
396
|
-
|
|
397
|
-
parts.forEach(part => {
|
|
398
|
-
let cls = part.trim();
|
|
399
|
-
if (!cls) return;
|
|
400
|
-
|
|
401
|
-
// Normalize whitespace to single spaces, but preserve inside ( [ { " '
|
|
402
|
-
let normalizedCls = "";
|
|
403
|
-
let parenDepth = 0;
|
|
404
|
-
let bracketDepth = 0;
|
|
405
|
-
let braceDepth = 0;
|
|
406
|
-
let inSingleQuote = false;
|
|
407
|
-
let inDoubleQuote = false;
|
|
408
|
-
|
|
409
|
-
for (let i = 0; i < cls.length; i++) {
|
|
410
|
-
const char = cls[i];
|
|
411
|
-
|
|
412
|
-
// Track quote state
|
|
413
|
-
if (char === "'" && !inDoubleQuote) inSingleQuote = !inSingleQuote;
|
|
414
|
-
else if (char === '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote;
|
|
415
|
-
|
|
416
|
-
const inQuotes = inSingleQuote || inDoubleQuote;
|
|
417
|
-
|
|
418
|
-
// Track bracket depths (only if not in quotes)
|
|
419
|
-
if (!inQuotes) {
|
|
420
|
-
if (char === "(") parenDepth++;
|
|
421
|
-
else if (char === ")") parenDepth--;
|
|
422
|
-
else if (char === "[") bracketDepth++;
|
|
423
|
-
else if (char === "]") bracketDepth--;
|
|
424
|
-
else if (char === "{") braceDepth++;
|
|
425
|
-
else if (char === "}") braceDepth--;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const insideGroup = parenDepth > 0 || bracketDepth > 0 || braceDepth > 0 || inQuotes;
|
|
429
|
-
|
|
430
|
-
// Normalize whitespace only when NOT inside groups
|
|
431
|
-
if (/\s/.test(char)) {
|
|
432
|
-
if (insideGroup) {
|
|
433
|
-
normalizedCls += char;
|
|
434
|
-
} else {
|
|
435
|
-
// Collapse consecutive whitespace to single space
|
|
436
|
-
if (normalizedCls.length > 0 && !/\s/.test(normalizedCls[normalizedCls.length - 1])) {
|
|
437
|
-
normalizedCls += " ";
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
} else {
|
|
441
|
-
normalizedCls += char;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
444
190
|
|
|
445
|
-
|
|
446
|
-
|
|
191
|
+
function processClassString(classStr) {
|
|
192
|
+
let cls = classStr.trim();
|
|
193
|
+
if (!cls) return [];
|
|
447
194
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
195
|
+
// Fast path: if no spaces, return as-is
|
|
196
|
+
if (!cls.includes(' ')) {
|
|
197
|
+
return [cls];
|
|
198
|
+
}
|
|
451
199
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
200
|
+
// Only use smartSplit if there are spaces
|
|
201
|
+
const parts = smartSplit(cls, ' ');
|
|
202
|
+
return parts.filter(p => p.length > 0);
|
|
203
|
+
}
|
|
456
204
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
205
|
+
function smartSplit(str, delimiter = ' ') {
|
|
206
|
+
const result = [];
|
|
207
|
+
let current = '';
|
|
208
|
+
let bracketDepth = 0;
|
|
209
|
+
let parenDepth = 0;
|
|
210
|
+
let braceDepth = 0;
|
|
461
211
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
spawnByPrefix[prefix].push(part);
|
|
466
|
-
} else if (scrollShorthandRegex.test(part) || scrollPrefixRegex.test(part)) {
|
|
467
|
-
const match = part.match(scrollShorthandRegex) || part.match(scrollPrefixRegex);
|
|
468
|
-
const prefix = match[1] || ''; // Empty string for no prefix
|
|
212
|
+
for (let i = 0; i < str.length; i++) {
|
|
213
|
+
const char = str[i];
|
|
469
214
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
}
|
|
477
|
-
});
|
|
215
|
+
if (char === '[') bracketDepth++;
|
|
216
|
+
else if (char === ']' && bracketDepth > 0) bracketDepth--;
|
|
217
|
+
else if (char === '(') parenDepth++;
|
|
218
|
+
else if (char === ')' && parenDepth > 0) parenDepth--;
|
|
219
|
+
else if (char === '{') braceDepth++;
|
|
220
|
+
else if (char === '}' && braceDepth > 0) braceDepth--;
|
|
478
221
|
|
|
479
|
-
|
|
480
|
-
nonShorthandParts.forEach(c => allClasses.add(c));
|
|
222
|
+
const shouldSplit = bracketDepth === 0 && parenDepth === 0 && braceDepth === 0 && char === delimiter;
|
|
481
223
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
224
|
+
if (shouldSplit) {
|
|
225
|
+
if (current) result.push(current);
|
|
226
|
+
current = '';
|
|
227
|
+
} else {
|
|
228
|
+
current += char;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
488
231
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
allClasses.add(group.join(' '));
|
|
493
|
-
}
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
});
|
|
232
|
+
if (current) result.push(current);
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
497
235
|
|
|
498
|
-
|
|
236
|
+
function sortMotionClasses(classes) {
|
|
499
237
|
const motionRegex = /^(.*?)motion-\[([^\]]+)\]:/;
|
|
500
238
|
const motionByName = {};
|
|
501
239
|
const nonMotionClasses = [];
|
|
502
240
|
|
|
503
|
-
|
|
241
|
+
classes.forEach(cls => {
|
|
504
242
|
const match = cls.match(motionRegex);
|
|
505
243
|
if (match) {
|
|
506
|
-
const prefix = match[1];
|
|
244
|
+
const prefix = match[1];
|
|
507
245
|
const animName = match[2];
|
|
508
246
|
|
|
509
247
|
if (!motionByName[animName]) {
|
|
@@ -511,8 +249,8 @@ function collectClassesJS(contentPaths) {
|
|
|
511
249
|
}
|
|
512
250
|
|
|
513
251
|
motionByName[animName].push({
|
|
514
|
-
cls
|
|
515
|
-
prefix
|
|
252
|
+
cls,
|
|
253
|
+
prefix,
|
|
516
254
|
prefixLength: prefix.length
|
|
517
255
|
});
|
|
518
256
|
} else {
|
|
@@ -520,19 +258,11 @@ function collectClassesJS(contentPaths) {
|
|
|
520
258
|
}
|
|
521
259
|
});
|
|
522
260
|
|
|
523
|
-
|
|
524
|
-
const sortedMotionClasses = [];
|
|
261
|
+
const sortedMotion = [];
|
|
525
262
|
Object.values(motionByName).forEach(group => {
|
|
526
263
|
group.sort((a, b) => a.prefixLength - b.prefixLength);
|
|
527
|
-
|
|
264
|
+
sortedMotion.push(...group.map(item => item.cls));
|
|
528
265
|
});
|
|
529
266
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
nonMotionClasses.forEach(c => allClasses.add(c));
|
|
533
|
-
sortedMotionClasses.forEach(c => allClasses.add(c));
|
|
534
|
-
|
|
535
|
-
return Array.from(allClasses);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
export default collectClasses;
|
|
267
|
+
return [...nonMotionClasses, ...sortedMotion];
|
|
268
|
+
}
|
package/src/parser.js
CHANGED
|
@@ -12989,20 +12989,6 @@ function _parseClassInternal(cls, isFromGrouped = false, needed, isGlobal) {
|
|
|
12989
12989
|
return buildParsed(null, valueArray)
|
|
12990
12990
|
}
|
|
12991
12991
|
|
|
12992
|
-
if (cls === "viewport" || cls === "max-viewport" || cls === "min-viewport") {
|
|
12993
|
-
let widthProp = "width";
|
|
12994
|
-
let heightProp = "height";
|
|
12995
|
-
|
|
12996
|
-
if (cls === "max-viewport") {
|
|
12997
|
-
widthProp = "max-width";
|
|
12998
|
-
heightProp = "max-height";
|
|
12999
|
-
} else if (cls === "min-viewport") {
|
|
13000
|
-
widthProp = "min-width";
|
|
13001
|
-
heightProp = "min-height";
|
|
13002
|
-
}
|
|
13003
|
-
return buildParsed(null, [{ prop: widthProp, value: "100vw" + importantSuffix }, { prop: heightProp, value: "100vh" + importantSuffix }])
|
|
13004
|
-
}
|
|
13005
|
-
|
|
13006
12992
|
if (cls === "subpixel-antialiased") {
|
|
13007
12993
|
return buildParsed(null, [{ prop: "-webkit-font-smoothing", value: `auto${importantSuffix}` }, { prop: "-moz-osx-font-smoothing", value: `auto${importantSuffix}` }])
|
|
13008
12994
|
}
|
|
@@ -13061,7 +13047,7 @@ function _parseClassInternal(cls, isFromGrouped = false, needed, isGlobal) {
|
|
|
13061
13047
|
prop = "visibility";
|
|
13062
13048
|
value = "hidden";
|
|
13063
13049
|
cssValue = "hidden";
|
|
13064
|
-
return buildParsed(prop,
|
|
13050
|
+
return buildParsed(prop, value)
|
|
13065
13051
|
}
|
|
13066
13052
|
|
|
13067
13053
|
if (cls === "sr-only") {
|