@csszyx/unplugin 0.3.1 → 0.5.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/dist/{chunk-3J4XUYF5.js → chunk-CW4XYEQR.js} +607 -71
- package/dist/index.cjs +611 -71
- package/dist/index.d.cts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +9 -1
- package/dist/{unplugin-B9noIooS.d.cts → unplugin-DUbr5w-N.d.cts} +17 -1
- package/dist/{unplugin-B9noIooS.d.ts → unplugin-DUbr5w-N.d.ts} +17 -1
- package/dist/vite.cjs +597 -69
- package/dist/vite.d.cts +1 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +1 -1
- package/dist/webpack.cjs +597 -69
- package/dist/webpack.d.cts +1 -1
- package/dist/webpack.d.ts +1 -1
- package/dist/webpack.js +1 -1
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -34,8 +34,12 @@ __export(index_exports, {
|
|
|
34
34
|
default: () => unplugin,
|
|
35
35
|
esbuildPlugin: () => esbuildPlugin,
|
|
36
36
|
escapeCSSClassName: () => escapeCSSClassName,
|
|
37
|
+
hasTokens: () => hasTokens,
|
|
37
38
|
mangleCSS: () => mangleCSS,
|
|
38
39
|
mangleCSSSync: () => mangleCSSSync,
|
|
40
|
+
mangleCodeClassesSync: () => mangleCodeClassesSync,
|
|
41
|
+
mergeThemes: () => mergeThemes,
|
|
42
|
+
parseThemeBlocks: () => parseThemeBlocks,
|
|
39
43
|
rollupPlugin: () => rollupPlugin,
|
|
40
44
|
unescapeTailwindClass: () => unescapeTailwindClass,
|
|
41
45
|
unplugin: () => unplugin,
|
|
@@ -239,6 +243,148 @@ function createPostCSSPlugin(mangleMap, options = {}) {
|
|
|
239
243
|
};
|
|
240
244
|
}
|
|
241
245
|
|
|
246
|
+
// src/theme-scanner.ts
|
|
247
|
+
var EMPTY_THEME = { colors: [], spacings: [], fonts: [], radii: [], shadows: [] };
|
|
248
|
+
function stripLayerWrappers(css) {
|
|
249
|
+
let result = "";
|
|
250
|
+
let i = 0;
|
|
251
|
+
while (i < css.length) {
|
|
252
|
+
const layerIdx = css.indexOf("@layer", i);
|
|
253
|
+
if (layerIdx === -1) {
|
|
254
|
+
result += css.slice(i);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
result += css.slice(i, layerIdx);
|
|
258
|
+
const openBrace = css.indexOf("{", layerIdx);
|
|
259
|
+
if (openBrace === -1) {
|
|
260
|
+
result += css.slice(layerIdx);
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
let depth = 0;
|
|
264
|
+
let j = openBrace;
|
|
265
|
+
while (j < css.length) {
|
|
266
|
+
if (css[j] === "{") {
|
|
267
|
+
depth++;
|
|
268
|
+
}
|
|
269
|
+
if (css[j] === "}") {
|
|
270
|
+
depth--;
|
|
271
|
+
if (depth === 0) {
|
|
272
|
+
result += css.slice(openBrace + 1, j);
|
|
273
|
+
i = j + 1;
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
j++;
|
|
278
|
+
}
|
|
279
|
+
if (depth !== 0) {
|
|
280
|
+
result += css.slice(openBrace);
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
function extractThemeBlocks(css) {
|
|
287
|
+
const blocks = [];
|
|
288
|
+
const themeStart = /@theme\s+(?:inline\s+)?\{|@theme\{/g;
|
|
289
|
+
let match;
|
|
290
|
+
while ((match = themeStart.exec(css)) !== null) {
|
|
291
|
+
const openPos = css.indexOf("{", match.index);
|
|
292
|
+
let depth = 0;
|
|
293
|
+
let j = openPos;
|
|
294
|
+
while (j < css.length) {
|
|
295
|
+
if (css[j] === "{") {
|
|
296
|
+
depth++;
|
|
297
|
+
}
|
|
298
|
+
if (css[j] === "}") {
|
|
299
|
+
depth--;
|
|
300
|
+
if (depth === 0) {
|
|
301
|
+
blocks.push(css.slice(openPos + 1, j));
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
j++;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return blocks;
|
|
309
|
+
}
|
|
310
|
+
function categorizeProperty(prop) {
|
|
311
|
+
const categoryMap = [
|
|
312
|
+
["color-", "colors"],
|
|
313
|
+
["spacing-", "spacings"],
|
|
314
|
+
["font-", "fonts"],
|
|
315
|
+
["radius-", "radii"],
|
|
316
|
+
["shadow-", "shadows"]
|
|
317
|
+
];
|
|
318
|
+
for (const [prefix, category] of categoryMap) {
|
|
319
|
+
if (prop.startsWith(prefix)) {
|
|
320
|
+
let token = prop.slice(prefix.length);
|
|
321
|
+
token = token.replace(/-\d+$/, "");
|
|
322
|
+
if (token) {
|
|
323
|
+
return { category, token };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
function parseThemeBlocks(cssContent) {
|
|
330
|
+
const result = {
|
|
331
|
+
colors: /* @__PURE__ */ new Set(),
|
|
332
|
+
spacings: /* @__PURE__ */ new Set(),
|
|
333
|
+
fonts: /* @__PURE__ */ new Set(),
|
|
334
|
+
radii: /* @__PURE__ */ new Set(),
|
|
335
|
+
shadows: /* @__PURE__ */ new Set()
|
|
336
|
+
};
|
|
337
|
+
const stripped = stripLayerWrappers(cssContent);
|
|
338
|
+
const blocks = extractThemeBlocks(stripped);
|
|
339
|
+
const propPattern = /--([a-z][a-z0-9-]*)(?:\s*:[^;]+)?;/g;
|
|
340
|
+
for (const block of blocks) {
|
|
341
|
+
let match;
|
|
342
|
+
while ((match = propPattern.exec(block)) !== null) {
|
|
343
|
+
const categorized = categorizeProperty(match[1]);
|
|
344
|
+
if (categorized) {
|
|
345
|
+
result[categorized.category].add(categorized.token);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
propPattern.lastIndex = 0;
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
colors: [...result.colors].sort(),
|
|
352
|
+
spacings: [...result.spacings].sort(),
|
|
353
|
+
fonts: [...result.fonts].sort(),
|
|
354
|
+
radii: [...result.radii].sort(),
|
|
355
|
+
shadows: [...result.shadows].sort()
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function mergeThemes(themes) {
|
|
359
|
+
if (themes.length === 0) {
|
|
360
|
+
return { ...EMPTY_THEME };
|
|
361
|
+
}
|
|
362
|
+
const merged = {
|
|
363
|
+
colors: /* @__PURE__ */ new Set(),
|
|
364
|
+
spacings: /* @__PURE__ */ new Set(),
|
|
365
|
+
fonts: /* @__PURE__ */ new Set(),
|
|
366
|
+
radii: /* @__PURE__ */ new Set(),
|
|
367
|
+
shadows: /* @__PURE__ */ new Set()
|
|
368
|
+
};
|
|
369
|
+
for (const theme of themes) {
|
|
370
|
+
for (const cat of Object.keys(merged)) {
|
|
371
|
+
for (const token of theme[cat]) {
|
|
372
|
+
merged[cat].add(token);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
colors: [...merged.colors].sort(),
|
|
378
|
+
spacings: [...merged.spacings].sort(),
|
|
379
|
+
fonts: [...merged.fonts].sort(),
|
|
380
|
+
radii: [...merged.radii].sort(),
|
|
381
|
+
shadows: [...merged.shadows].sort()
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function hasTokens(theme) {
|
|
385
|
+
return Object.values(theme).some((arr) => arr.length > 0);
|
|
386
|
+
}
|
|
387
|
+
|
|
242
388
|
// src/unplugin.ts
|
|
243
389
|
var fs = __toESM(require("fs"), 1);
|
|
244
390
|
var path = __toESM(require("path"), 1);
|
|
@@ -309,6 +455,55 @@ function transformIndexHtml(html, mangleMap, checksum, options = {}) {
|
|
|
309
455
|
return injectHydrationData(html, mangleMap, checksum, options);
|
|
310
456
|
}
|
|
311
457
|
|
|
458
|
+
// src/theme-type-writer.ts
|
|
459
|
+
var import_node_fs = require("fs");
|
|
460
|
+
var import_node_path = require("path");
|
|
461
|
+
function generateThemeDts(opts) {
|
|
462
|
+
const { theme, sourceFiles } = opts;
|
|
463
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
464
|
+
const sources = sourceFiles.join(", ");
|
|
465
|
+
const toUnion = (tokens) => tokens.map((t) => `'${t}'`).join(" | ");
|
|
466
|
+
const entries = [];
|
|
467
|
+
if (theme.colors.length > 0) {
|
|
468
|
+
entries.push(` colors: ${toUnion(theme.colors)};`);
|
|
469
|
+
}
|
|
470
|
+
if (theme.spacings.length > 0) {
|
|
471
|
+
entries.push(` spacings: ${toUnion(theme.spacings)};`);
|
|
472
|
+
}
|
|
473
|
+
if (theme.fonts.length > 0) {
|
|
474
|
+
entries.push(` fonts: ${toUnion(theme.fonts)};`);
|
|
475
|
+
}
|
|
476
|
+
if (theme.radii.length > 0) {
|
|
477
|
+
entries.push(` radii: ${toUnion(theme.radii)};`);
|
|
478
|
+
}
|
|
479
|
+
if (theme.shadows.length > 0) {
|
|
480
|
+
entries.push(` shadows: ${toUnion(theme.shadows)};`);
|
|
481
|
+
}
|
|
482
|
+
return [
|
|
483
|
+
"// Auto-generated by csszyx theme-scanner \u2014 DO NOT EDIT",
|
|
484
|
+
`// Source: ${sources}`,
|
|
485
|
+
`// Updated: ${timestamp}`,
|
|
486
|
+
"",
|
|
487
|
+
"declare module '@csszyx/compiler' {",
|
|
488
|
+
" /**",
|
|
489
|
+
" * Custom design tokens extracted from @theme blocks.",
|
|
490
|
+
" * These tokens are surfaced in sz prop IntelliSense.",
|
|
491
|
+
" */",
|
|
492
|
+
" interface CustomTheme {",
|
|
493
|
+
...entries,
|
|
494
|
+
" }",
|
|
495
|
+
"}",
|
|
496
|
+
"",
|
|
497
|
+
"export {};",
|
|
498
|
+
""
|
|
499
|
+
].join("\n");
|
|
500
|
+
}
|
|
501
|
+
function writeThemeDts(opts) {
|
|
502
|
+
const content = generateThemeDts(opts);
|
|
503
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(opts.outputPath), { recursive: true });
|
|
504
|
+
(0, import_node_fs.writeFileSync)(opts.outputPath, content, "utf-8");
|
|
505
|
+
}
|
|
506
|
+
|
|
312
507
|
// src/virtual-modules.ts
|
|
313
508
|
var VIRTUAL_MODULE_ID = "virtual:csszyx/mangle-map";
|
|
314
509
|
var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
|
|
@@ -361,19 +556,259 @@ function resolveVirtualModule(id) {
|
|
|
361
556
|
// src/unplugin.ts
|
|
362
557
|
var CHECKSUM_PLACEHOLDER = "___CSSZYX_CHECKSUM___";
|
|
363
558
|
var MANGLE_MAP_PLACEHOLDER = "___CSSZYX_MANGLE_MAP___";
|
|
559
|
+
var _hasWarnedTsConfig = false;
|
|
560
|
+
function runThemeScan(rootDir, scanCss) {
|
|
561
|
+
if (!scanCss) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
const patterns = Array.isArray(scanCss) ? scanCss : [scanCss];
|
|
565
|
+
const sourceFiles = [];
|
|
566
|
+
for (const pattern of patterns) {
|
|
567
|
+
const resolved = path.isAbsolute(pattern) ? pattern : path.join(rootDir, pattern);
|
|
568
|
+
if (fs.existsSync(resolved)) {
|
|
569
|
+
sourceFiles.push(resolved);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (sourceFiles.length === 0) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const themes = sourceFiles.map((f) => {
|
|
576
|
+
try {
|
|
577
|
+
return parseThemeBlocks(fs.readFileSync(f, "utf-8"));
|
|
578
|
+
} catch {
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
}).filter((t) => t !== null);
|
|
582
|
+
const merged = mergeThemes(themes);
|
|
583
|
+
const outputPath = path.join(rootDir, ".csszyx", "theme.d.ts");
|
|
584
|
+
writeThemeDts({ outputPath, theme: merged, sourceFiles });
|
|
585
|
+
if (!_hasWarnedTsConfig) {
|
|
586
|
+
_hasWarnedTsConfig = true;
|
|
587
|
+
try {
|
|
588
|
+
const checkFile = (cfgPath) => {
|
|
589
|
+
if (fs.existsSync(cfgPath)) {
|
|
590
|
+
const content = fs.readFileSync(cfgPath, "utf-8");
|
|
591
|
+
if (!content.includes(".csszyx")) {
|
|
592
|
+
console.warn(`
|
|
593
|
+
\x1B[33m\u26A0\uFE0F CSSzyx: Theme Auto-Scan enabled, but TypeScript isn't configured. Run "npx @csszyx/cli init" to fix.\x1B[0m
|
|
594
|
+
`);
|
|
595
|
+
}
|
|
596
|
+
return true;
|
|
597
|
+
}
|
|
598
|
+
return false;
|
|
599
|
+
};
|
|
600
|
+
if (!checkFile(path.join(rootDir, "tsconfig.json"))) {
|
|
601
|
+
checkFile(path.join(rootDir, "tsconfig.app.json"));
|
|
602
|
+
}
|
|
603
|
+
} catch {
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function mangleCodeClassesSync(code, mangleMap) {
|
|
608
|
+
function mangleClassString(classString) {
|
|
609
|
+
return classString.split(/\s+/).filter(Boolean).map((cls) => {
|
|
610
|
+
return mangleMap[cls.replace(/\\(.)/g, "$1")] || cls;
|
|
611
|
+
}).join(" ");
|
|
612
|
+
}
|
|
613
|
+
let result = code.replace(/(?:class(?:Name)?|sz)[:=]\s*"((?:[^"\\]|\\.)*)"/g, (match, classes) => {
|
|
614
|
+
const mangled = mangleClassString(classes);
|
|
615
|
+
if (mangled === classes) {
|
|
616
|
+
return match;
|
|
617
|
+
}
|
|
618
|
+
return match.replace(classes, mangled);
|
|
619
|
+
}).replace(/(?:class(?:Name)?|sz)[:=]\s*'((?:[^'\\]|\\.)*)'/g, (match, classes) => {
|
|
620
|
+
const mangled = mangleClassString(classes);
|
|
621
|
+
if (mangled === classes) {
|
|
622
|
+
return match;
|
|
623
|
+
}
|
|
624
|
+
return match.replace(classes, mangled);
|
|
625
|
+
});
|
|
626
|
+
result = result.replace(/className:\s*`([^`]+)`/g, (fullMatch, tplContent) => {
|
|
627
|
+
let changed = false;
|
|
628
|
+
let out = "";
|
|
629
|
+
let i = 0;
|
|
630
|
+
while (i < tplContent.length) {
|
|
631
|
+
const interStart = tplContent.indexOf("${", i);
|
|
632
|
+
if (interStart === -1) {
|
|
633
|
+
const quasi2 = tplContent.slice(i);
|
|
634
|
+
const trimmed2 = quasi2.trim();
|
|
635
|
+
if (trimmed2) {
|
|
636
|
+
const m = mangleClassString(trimmed2);
|
|
637
|
+
if (m !== trimmed2) {
|
|
638
|
+
changed = true;
|
|
639
|
+
out += quasi2.replace(trimmed2, m);
|
|
640
|
+
} else {
|
|
641
|
+
out += quasi2;
|
|
642
|
+
}
|
|
643
|
+
} else {
|
|
644
|
+
out += quasi2;
|
|
645
|
+
}
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
const quasi = tplContent.slice(i, interStart);
|
|
649
|
+
const trimmed = quasi.trim();
|
|
650
|
+
if (trimmed) {
|
|
651
|
+
const m = mangleClassString(trimmed);
|
|
652
|
+
if (m !== trimmed) {
|
|
653
|
+
changed = true;
|
|
654
|
+
out += quasi.replace(trimmed, m);
|
|
655
|
+
} else {
|
|
656
|
+
out += quasi;
|
|
657
|
+
}
|
|
658
|
+
} else {
|
|
659
|
+
out += quasi;
|
|
660
|
+
}
|
|
661
|
+
let j = interStart + 2;
|
|
662
|
+
let depth = 0;
|
|
663
|
+
while (j < tplContent.length) {
|
|
664
|
+
if (tplContent[j] === "{") {
|
|
665
|
+
depth++;
|
|
666
|
+
} else if (tplContent[j] === "}") {
|
|
667
|
+
if (depth === 0) {
|
|
668
|
+
j++;
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
depth--;
|
|
672
|
+
}
|
|
673
|
+
j++;
|
|
674
|
+
}
|
|
675
|
+
const interInner = tplContent.slice(interStart + 2, j - 1);
|
|
676
|
+
const mangledInner = interInner.replace(/"([^"]*)"/g, (qm, inner) => {
|
|
677
|
+
const parts = inner.split(/\s+/).filter(Boolean);
|
|
678
|
+
if (parts.length === 0) {
|
|
679
|
+
return qm;
|
|
680
|
+
}
|
|
681
|
+
const m = parts.map((p) => mangleMap[p] || p).join(" ");
|
|
682
|
+
if (m === inner) {
|
|
683
|
+
return qm;
|
|
684
|
+
}
|
|
685
|
+
changed = true;
|
|
686
|
+
return '"' + m + '"';
|
|
687
|
+
});
|
|
688
|
+
out += "${" + mangledInner + "}";
|
|
689
|
+
i = j;
|
|
690
|
+
}
|
|
691
|
+
return changed ? "className:`" + out + "`" : fullMatch;
|
|
692
|
+
});
|
|
693
|
+
{
|
|
694
|
+
const marker = "className:";
|
|
695
|
+
let searchFrom = 0;
|
|
696
|
+
let out = "";
|
|
697
|
+
while (searchFrom < result.length) {
|
|
698
|
+
const idx = result.indexOf(marker, searchFrom);
|
|
699
|
+
if (idx === -1) {
|
|
700
|
+
out += result.slice(searchFrom);
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
out += result.slice(searchFrom, idx + marker.length);
|
|
704
|
+
const afterColon = idx + marker.length;
|
|
705
|
+
let exprStart = afterColon;
|
|
706
|
+
while (exprStart < result.length && result[exprStart] === " ") {
|
|
707
|
+
exprStart++;
|
|
708
|
+
}
|
|
709
|
+
const firstChar = result[exprStart];
|
|
710
|
+
if (firstChar === '"' || firstChar === "'" || firstChar === "`") {
|
|
711
|
+
searchFrom = afterColon;
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
let depth = 0;
|
|
715
|
+
let j = afterColon;
|
|
716
|
+
while (j < result.length) {
|
|
717
|
+
const ch = result[j];
|
|
718
|
+
if (ch === "(" || ch === "[") {
|
|
719
|
+
depth++;
|
|
720
|
+
} else if (ch === ")" || ch === "]") {
|
|
721
|
+
if (depth === 0) {
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
depth--;
|
|
725
|
+
} else if (depth === 0 && (ch === "," || ch === ";" || ch === "\n" || ch === "}")) {
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
j++;
|
|
729
|
+
}
|
|
730
|
+
const expr = result.slice(afterColon, j);
|
|
731
|
+
const qIdx = expr.indexOf("?");
|
|
732
|
+
if (qIdx === -1 || !expr.slice(qIdx).includes(":")) {
|
|
733
|
+
out += expr;
|
|
734
|
+
searchFrom = j;
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
let changed = false;
|
|
738
|
+
const mangled = expr.replace(/"([^"]*)"/g, (qm, inner) => {
|
|
739
|
+
const parts = inner.split(/\s+/).filter(Boolean);
|
|
740
|
+
if (parts.length === 0) {
|
|
741
|
+
return qm;
|
|
742
|
+
}
|
|
743
|
+
const mangledStr = parts.map((p) => mangleMap[p] || p).join(" ");
|
|
744
|
+
if (mangledStr !== inner) {
|
|
745
|
+
changed = true;
|
|
746
|
+
return '"' + mangledStr + '"';
|
|
747
|
+
}
|
|
748
|
+
return qm;
|
|
749
|
+
});
|
|
750
|
+
out += changed ? mangled : expr;
|
|
751
|
+
searchFrom = j;
|
|
752
|
+
}
|
|
753
|
+
result = out;
|
|
754
|
+
}
|
|
755
|
+
result = result.replace(/(?<=(?:[,(]|&&)\s*)"([^"]+)"/g, (match, inner) => {
|
|
756
|
+
const tokens = inner.split(/\s+/).filter(Boolean);
|
|
757
|
+
if (tokens.length === 0) {
|
|
758
|
+
return match;
|
|
759
|
+
}
|
|
760
|
+
let changed = false;
|
|
761
|
+
const mangled = [];
|
|
762
|
+
for (const t of tokens) {
|
|
763
|
+
const m = mangleMap[t];
|
|
764
|
+
if (m === void 0) {
|
|
765
|
+
return match;
|
|
766
|
+
}
|
|
767
|
+
if (m !== t) {
|
|
768
|
+
changed = true;
|
|
769
|
+
}
|
|
770
|
+
mangled.push(m);
|
|
771
|
+
}
|
|
772
|
+
if (!changed) {
|
|
773
|
+
return match;
|
|
774
|
+
}
|
|
775
|
+
return '"' + mangled.join(" ") + '"';
|
|
776
|
+
});
|
|
777
|
+
return result;
|
|
778
|
+
}
|
|
364
779
|
function createCsszyxPlugins(options = {}) {
|
|
365
780
|
const manglingEnabled = options.production?.mangle !== false;
|
|
366
781
|
const state = {
|
|
367
782
|
classes: /* @__PURE__ */ new Set(),
|
|
368
783
|
mangleMap: {},
|
|
369
784
|
checksum: "",
|
|
370
|
-
finalized: false
|
|
785
|
+
finalized: false,
|
|
786
|
+
rootDir: process.cwd()
|
|
371
787
|
};
|
|
372
|
-
const SAFELIST_FILENAME = "csszyx-classes.
|
|
788
|
+
const SAFELIST_FILENAME = "csszyx-classes.html";
|
|
373
789
|
const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js"]);
|
|
374
790
|
const IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "dist", "build", ".turbo"]);
|
|
375
|
-
function
|
|
791
|
+
function writeSafelistFile(classes) {
|
|
792
|
+
if (classes.size === 0) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME);
|
|
796
|
+
const classList = Array.from(classes).join(" ");
|
|
797
|
+
const content = `<!-- Auto-generated by csszyx \u2014 DO NOT EDIT -->
|
|
798
|
+
<!-- Tailwind CSS scans this file for class name detection -->
|
|
799
|
+
<div class="${classList}"><div class="${classList}">x</div><div class="${classList}">x</div></div>
|
|
800
|
+
`;
|
|
801
|
+
try {
|
|
802
|
+
const existing = fs.existsSync(safelistPath) ? fs.readFileSync(safelistPath, "utf-8") : "";
|
|
803
|
+
if (existing !== content) {
|
|
804
|
+
fs.writeFileSync(safelistPath, content);
|
|
805
|
+
}
|
|
806
|
+
} catch {
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
function prescanAndWriteClasses() {
|
|
376
810
|
const discoveredClasses = /* @__PURE__ */ new Set();
|
|
811
|
+
const rawDiscoveredClasses = /* @__PURE__ */ new Set();
|
|
377
812
|
function scanDir(dir) {
|
|
378
813
|
let entries;
|
|
379
814
|
try {
|
|
@@ -400,6 +835,9 @@ function createCsszyxPlugins(options = {}) {
|
|
|
400
835
|
for (const cls of result.classes) {
|
|
401
836
|
discoveredClasses.add(cls);
|
|
402
837
|
}
|
|
838
|
+
for (const cls of result.rawClassNames) {
|
|
839
|
+
rawDiscoveredClasses.add(cls);
|
|
840
|
+
}
|
|
403
841
|
if (result.usesRuntime) {
|
|
404
842
|
const szCallRe = /_sz\(\s*\{/g;
|
|
405
843
|
let szMatch;
|
|
@@ -454,21 +892,12 @@ function createCsszyxPlugins(options = {}) {
|
|
|
454
892
|
}
|
|
455
893
|
}
|
|
456
894
|
}
|
|
457
|
-
scanDir(rootDir);
|
|
895
|
+
scanDir(state.rootDir);
|
|
458
896
|
for (const cls of discoveredClasses) {
|
|
459
897
|
state.classes.add(cls);
|
|
460
898
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
const content = '// Auto-generated by csszyx \u2014 DO NOT EDIT\n// Tailwind CSS scans this file for class name detection\nexport default "' + Array.from(discoveredClasses).join(" ") + '";\n';
|
|
464
|
-
try {
|
|
465
|
-
const existing = fs.existsSync(safelistPath) ? fs.readFileSync(safelistPath, "utf-8") : "";
|
|
466
|
-
if (existing !== content) {
|
|
467
|
-
fs.writeFileSync(safelistPath, content);
|
|
468
|
-
}
|
|
469
|
-
} catch {
|
|
470
|
-
}
|
|
471
|
-
}
|
|
899
|
+
const safelistClasses = /* @__PURE__ */ new Set([...discoveredClasses, ...rawDiscoveredClasses]);
|
|
900
|
+
writeSafelistFile(safelistClasses);
|
|
472
901
|
}
|
|
473
902
|
function extractClasses(code) {
|
|
474
903
|
const dqPattern = /(?:class(?:Name)?|sz)[:=]\s*"([^"]*)"/g;
|
|
@@ -516,47 +945,8 @@ function createCsszyxPlugins(options = {}) {
|
|
|
516
945
|
state.checksum = (0, import_core.compute_mangle_checksum)(state.mangleMap);
|
|
517
946
|
state.finalized = true;
|
|
518
947
|
}
|
|
519
|
-
function mangleClassString(classString) {
|
|
520
|
-
return classString.split(/\s+/).map((cls) => state.mangleMap[cls] || cls).join(" ");
|
|
521
|
-
}
|
|
522
948
|
function mangleCodeClasses(code) {
|
|
523
|
-
|
|
524
|
-
const mangled = mangleClassString(classes);
|
|
525
|
-
if (mangled === classes) {
|
|
526
|
-
return match;
|
|
527
|
-
}
|
|
528
|
-
return match.replace(classes, mangled);
|
|
529
|
-
}).replace(/(?:class(?:Name)?|sz)[:=]\s*'([^']*)'/g, (match, classes) => {
|
|
530
|
-
const mangled = mangleClassString(classes);
|
|
531
|
-
if (mangled === classes) {
|
|
532
|
-
return match;
|
|
533
|
-
}
|
|
534
|
-
return match.replace(classes, mangled);
|
|
535
|
-
});
|
|
536
|
-
result = result.replace(/className:(?!["'])([^,;}\])\n]+)/g, (fullMatch, expr) => {
|
|
537
|
-
const qIdx = expr.indexOf("?");
|
|
538
|
-
if (qIdx === -1 || !expr.slice(qIdx).includes(":")) {
|
|
539
|
-
return fullMatch;
|
|
540
|
-
}
|
|
541
|
-
let changed = false;
|
|
542
|
-
const mangled = expr.replace(/"([^"]*)"/g, (qm, inner) => {
|
|
543
|
-
const parts = inner.split(/\s+/).filter(Boolean);
|
|
544
|
-
if (parts.length === 0) {
|
|
545
|
-
return qm;
|
|
546
|
-
}
|
|
547
|
-
const mangledStr = parts.map((p) => state.mangleMap[p] || p).join(" ");
|
|
548
|
-
if (mangledStr !== inner) {
|
|
549
|
-
changed = true;
|
|
550
|
-
return '"' + mangledStr + '"';
|
|
551
|
-
}
|
|
552
|
-
return qm;
|
|
553
|
-
});
|
|
554
|
-
if (changed) {
|
|
555
|
-
return "className:" + mangled;
|
|
556
|
-
}
|
|
557
|
-
return fullMatch;
|
|
558
|
-
});
|
|
559
|
-
return result;
|
|
949
|
+
return mangleCodeClassesSync(code, state.mangleMap);
|
|
560
950
|
}
|
|
561
951
|
function replacePlaceholders(code) {
|
|
562
952
|
let result = code;
|
|
@@ -564,7 +954,9 @@ function createCsszyxPlugins(options = {}) {
|
|
|
564
954
|
result = result.split(CHECKSUM_PLACEHOLDER).join(state.checksum);
|
|
565
955
|
}
|
|
566
956
|
if (result.includes(MANGLE_MAP_PLACEHOLDER)) {
|
|
567
|
-
|
|
957
|
+
const jsonMap = JSON.stringify(state.mangleMap);
|
|
958
|
+
const escapedMap = result.includes("eval(") ? jsonMap.replace(/"/g, '\\"') : jsonMap;
|
|
959
|
+
result = result.split(MANGLE_MAP_PLACEHOLDER).join(escapedMap);
|
|
568
960
|
}
|
|
569
961
|
return result;
|
|
570
962
|
}
|
|
@@ -626,12 +1018,18 @@ function createCsszyxPlugins(options = {}) {
|
|
|
626
1018
|
if (hasTailwindImport && state.classes.size > 0) {
|
|
627
1019
|
const candidates = Array.from(state.classes).filter((c) => c.length >= 2 && /^[a-z]/.test(c)).join(" ");
|
|
628
1020
|
if (candidates) {
|
|
629
|
-
const
|
|
1021
|
+
const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME).replace(/\\/g, "/");
|
|
1022
|
+
const cssDir = path.dirname(id).replace(/\\/g, "/");
|
|
1023
|
+
let relPath = path.posix.relative(cssDir, safelistPath);
|
|
1024
|
+
if (!relPath.startsWith(".")) {
|
|
1025
|
+
relPath = "./" + relPath;
|
|
1026
|
+
}
|
|
1027
|
+
const sourceDirective = `@source "${relPath}";
|
|
630
1028
|
`;
|
|
631
1029
|
const transformed2 = code.replace(
|
|
632
1030
|
/(@import\s+["']tailwindcss[^"']*["'];)/,
|
|
633
1031
|
`$1
|
|
634
|
-
${
|
|
1032
|
+
${sourceDirective}`
|
|
635
1033
|
);
|
|
636
1034
|
if (transformed2 !== code) {
|
|
637
1035
|
return { code: transformed2, map: null };
|
|
@@ -642,6 +1040,7 @@ ${inlineDirective}`
|
|
|
642
1040
|
}
|
|
643
1041
|
let transformedCode = code;
|
|
644
1042
|
let usesRuntime = false;
|
|
1043
|
+
let usesMerge = false;
|
|
645
1044
|
let usesColorVar = false;
|
|
646
1045
|
let transformed = false;
|
|
647
1046
|
let szClasses;
|
|
@@ -663,9 +1062,16 @@ ${inlineDirective}`
|
|
|
663
1062
|
const result = (0, import_compiler.transformSourceCode)(code);
|
|
664
1063
|
transformedCode = result.code;
|
|
665
1064
|
usesRuntime = result.usesRuntime;
|
|
1065
|
+
usesMerge = result.usesMerge;
|
|
666
1066
|
usesColorVar = result.usesColorVar;
|
|
667
1067
|
transformed = result.transformed;
|
|
668
1068
|
szClasses = result.classes;
|
|
1069
|
+
if (result.diagnostics.length > 0 && process.env.NODE_ENV !== "production") {
|
|
1070
|
+
for (const msg of result.diagnostics) {
|
|
1071
|
+
this.warn(`[csszyx] ${id}
|
|
1072
|
+
${msg}`);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
669
1075
|
}
|
|
670
1076
|
}
|
|
671
1077
|
if (transformedCode.includes("<html") && /layout|Root|Document|app\\.tsx?$/i.test(id)) {
|
|
@@ -685,18 +1091,32 @@ ${inlineDirective}`
|
|
|
685
1091
|
if (usesRuntime) {
|
|
686
1092
|
imports.push("_sz");
|
|
687
1093
|
}
|
|
1094
|
+
if (usesMerge) {
|
|
1095
|
+
imports.push("_szMerge");
|
|
1096
|
+
}
|
|
688
1097
|
if (usesColorVar) {
|
|
689
1098
|
imports.push("__szColorVar");
|
|
690
1099
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
transformedCode = transformedCode.replace(
|
|
1100
|
+
const needed = imports.filter(
|
|
1101
|
+
(name) => !new RegExp(`\\{[^}]*\\b${name}\\b[^}]*\\}\\s*from\\s*['"]@csszyx/runtime['"]`).test(transformedCode)
|
|
1102
|
+
);
|
|
1103
|
+
if (needed.length > 0) {
|
|
1104
|
+
const existingImport = transformedCode.match(/^(import\s*\{[^}]*)\}\s*from\s*'@csszyx\/runtime'/m);
|
|
1105
|
+
if (existingImport) {
|
|
1106
|
+
transformedCode = transformedCode.replace(
|
|
1107
|
+
existingImport[0],
|
|
1108
|
+
`${existingImport[1]}, ${needed.join(", ")} } from '@csszyx/runtime'`
|
|
1109
|
+
);
|
|
698
1110
|
} else {
|
|
699
|
-
|
|
1111
|
+
const importStmt = `import { ${needed.join(", ")} } from '@csszyx/runtime';
|
|
1112
|
+
`;
|
|
1113
|
+
const directiveMatch = transformedCode.match(/^['"]use (client|server)['"];?\s*/);
|
|
1114
|
+
if (directiveMatch) {
|
|
1115
|
+
const directive = directiveMatch[0];
|
|
1116
|
+
transformedCode = transformedCode.replace(directive, `${directive}${importStmt}`);
|
|
1117
|
+
} else {
|
|
1118
|
+
transformedCode = `${importStmt}${transformedCode}`;
|
|
1119
|
+
}
|
|
700
1120
|
}
|
|
701
1121
|
transformed = true;
|
|
702
1122
|
}
|
|
@@ -716,6 +1136,9 @@ ${inlineDirective}`
|
|
|
716
1136
|
/** Finalizes the mangle map after all source modules have been processed. */
|
|
717
1137
|
buildEnd() {
|
|
718
1138
|
finalizeMangleMap();
|
|
1139
|
+
if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
|
|
1140
|
+
globalThis.__csszyx_ssr_mangle_map = state.mangleMap;
|
|
1141
|
+
}
|
|
719
1142
|
},
|
|
720
1143
|
/**
|
|
721
1144
|
* Webpack hook: pre-scans source files before compilation for Tailwind class discovery.
|
|
@@ -723,23 +1146,94 @@ ${inlineDirective}`
|
|
|
723
1146
|
*/
|
|
724
1147
|
webpack(compiler) {
|
|
725
1148
|
compiler.hooks.beforeCompile.tap("csszyx:prescan", () => {
|
|
1149
|
+
const root = compiler.context || process.cwd();
|
|
1150
|
+
state.rootDir = root;
|
|
726
1151
|
if (state.classes.size === 0) {
|
|
727
|
-
prescanAndWriteClasses(
|
|
1152
|
+
prescanAndWriteClasses();
|
|
728
1153
|
}
|
|
1154
|
+
runThemeScan(root, options.build?.scanCss);
|
|
729
1155
|
});
|
|
1156
|
+
if (options.build?.scanCss) {
|
|
1157
|
+
const patterns = Array.isArray(options.build.scanCss) ? options.build.scanCss : [options.build.scanCss];
|
|
1158
|
+
compiler.hooks.thisCompilation.tap("csszyx:theme-deps", (compilation) => {
|
|
1159
|
+
const root = compiler.context || process.cwd();
|
|
1160
|
+
for (const pattern of patterns) {
|
|
1161
|
+
const resolved = path.isAbsolute(pattern) ? pattern : path.join(root, pattern);
|
|
1162
|
+
if (fs.existsSync(resolved)) {
|
|
1163
|
+
compilation.fileDependencies.add(resolved);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
730
1168
|
},
|
|
731
1169
|
vite: {
|
|
732
1170
|
/**
|
|
733
1171
|
* Vite hook: pre-scans source files when config is resolved.
|
|
1172
|
+
* Also runs theme scan to generate .csszyx/theme.d.ts if scanCss is configured.
|
|
734
1173
|
* @param config - the resolved Vite configuration object
|
|
735
1174
|
*/
|
|
736
1175
|
configResolved(config) {
|
|
737
|
-
|
|
1176
|
+
const root = config.root || process.cwd();
|
|
1177
|
+
state.rootDir = root;
|
|
1178
|
+
prescanAndWriteClasses();
|
|
1179
|
+
runThemeScan(root, options.build?.scanCss);
|
|
1180
|
+
},
|
|
1181
|
+
/**
|
|
1182
|
+
* Vite HMR hook: re-runs theme scan when a watched CSS file changes,
|
|
1183
|
+
* and incrementally updates csszyx-classes.html when a source file gains new sz classes.
|
|
1184
|
+
* @param ctx - HMR context containing the changed file
|
|
1185
|
+
*/
|
|
1186
|
+
handleHotUpdate(ctx) {
|
|
1187
|
+
const scanCss = options.build?.scanCss;
|
|
1188
|
+
if (scanCss) {
|
|
1189
|
+
const patterns = Array.isArray(scanCss) ? scanCss : [scanCss];
|
|
1190
|
+
const root = ctx.server.config.root || process.cwd();
|
|
1191
|
+
const isWatched = patterns.some((p) => {
|
|
1192
|
+
const resolved = path.isAbsolute(p) ? p : path.join(root, p);
|
|
1193
|
+
return ctx.file === resolved;
|
|
1194
|
+
});
|
|
1195
|
+
if (isWatched) {
|
|
1196
|
+
runThemeScan(root, scanCss);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
if (!SOURCE_EXTENSIONS.has(path.extname(ctx.file))) {
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
if (ctx.file.includes("node_modules")) {
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
let fileContent, result;
|
|
1206
|
+
try {
|
|
1207
|
+
fileContent = fs.readFileSync(ctx.file, "utf-8");
|
|
1208
|
+
} catch {
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
if (!fileContent.includes("sz=") && !/\bsz\s*:\s*["'{]/.test(fileContent)) {
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
try {
|
|
1215
|
+
result = (0, import_compiler.transformSourceCode)(fileContent);
|
|
1216
|
+
} catch {
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
if (!result.transformed) {
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
const sizeBefore = state.classes.size;
|
|
1223
|
+
for (const cls of result.classes) {
|
|
1224
|
+
state.classes.add(cls);
|
|
1225
|
+
}
|
|
1226
|
+
if (state.classes.size > sizeBefore) {
|
|
1227
|
+
writeSafelistFile(state.classes);
|
|
1228
|
+
const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME);
|
|
1229
|
+
ctx.server.watcher.emit("change", safelistPath);
|
|
1230
|
+
}
|
|
738
1231
|
},
|
|
739
1232
|
transformIndexHtml: {
|
|
740
1233
|
order: "pre",
|
|
741
1234
|
/**
|
|
742
1235
|
* Injects hydration data (mangle map + checksum) into the HTML document.
|
|
1236
|
+
* Also mangles class attributes in SSR-rendered HTML so they match mangled CSS selectors.
|
|
743
1237
|
* @param html - the raw HTML string to transform
|
|
744
1238
|
* @returns transformed HTML with injected hydration data
|
|
745
1239
|
*/
|
|
@@ -774,10 +1268,23 @@ ${inlineDirective}`
|
|
|
774
1268
|
},
|
|
775
1269
|
(assets) => {
|
|
776
1270
|
finalizeMangleMap();
|
|
1271
|
+
const isWebpackDevMode = compiler.options.mode === "development";
|
|
1272
|
+
const manifestData = {
|
|
1273
|
+
version: "0.4.0",
|
|
1274
|
+
buildId: state.checksum,
|
|
1275
|
+
classes: Object.keys(state.mangleMap)
|
|
1276
|
+
};
|
|
1277
|
+
if (manglingEnabled && !isWebpackDevMode && Object.keys(state.mangleMap).length > 0) {
|
|
1278
|
+
manifestData.mangleMap = state.mangleMap;
|
|
1279
|
+
}
|
|
1280
|
+
compilation.emitAsset(
|
|
1281
|
+
"csszyx-manifest.json",
|
|
1282
|
+
new compiler.webpack.sources.RawSource(JSON.stringify(manifestData))
|
|
1283
|
+
);
|
|
777
1284
|
for (const file in assets) {
|
|
778
1285
|
const asset = assets[file];
|
|
779
1286
|
const source = asset.source().toString();
|
|
780
|
-
if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
|
|
1287
|
+
if (manglingEnabled && !isWebpackDevMode && Object.keys(state.mangleMap).length > 0) {
|
|
781
1288
|
if (file.endsWith(".css")) {
|
|
782
1289
|
try {
|
|
783
1290
|
const result = mangleCSSSync(source, state.mangleMap, {
|
|
@@ -797,6 +1304,21 @@ ${inlineDirective}`
|
|
|
797
1304
|
throw e;
|
|
798
1305
|
}
|
|
799
1306
|
}
|
|
1307
|
+
} else if (file.endsWith(".html")) {
|
|
1308
|
+
const mangledHtml = source.replace(/\bclass="([^"]*)"/g, (_m, cls) => {
|
|
1309
|
+
const out = cls.split(/\s+/).filter(Boolean).map((c) => state.mangleMap[c] || c).join(" ");
|
|
1310
|
+
return out !== cls ? `class="${out}"` : _m;
|
|
1311
|
+
}).replace(/\bclass='([^']*)'/g, (_m, cls) => {
|
|
1312
|
+
const out = cls.split(/\s+/).filter(Boolean).map((c) => state.mangleMap[c] || c).join(" ");
|
|
1313
|
+
return out !== cls ? `class='${out}'` : _m;
|
|
1314
|
+
});
|
|
1315
|
+
if (mangledHtml !== source) {
|
|
1316
|
+
compilation.updateAsset(
|
|
1317
|
+
file,
|
|
1318
|
+
new compiler.webpack.sources.RawSource(mangledHtml)
|
|
1319
|
+
);
|
|
1320
|
+
continue;
|
|
1321
|
+
}
|
|
800
1322
|
} else if (file.endsWith(".js")) {
|
|
801
1323
|
let mangled = mangleCodeClasses(source);
|
|
802
1324
|
mangled = replacePlaceholders(mangled);
|
|
@@ -831,6 +1353,19 @@ ${inlineDirective}`
|
|
|
831
1353
|
*/
|
|
832
1354
|
generateBundle(_options, bundle) {
|
|
833
1355
|
finalizeMangleMap();
|
|
1356
|
+
const manifestData = {
|
|
1357
|
+
version: "0.4.0",
|
|
1358
|
+
buildId: state.checksum,
|
|
1359
|
+
classes: Object.keys(state.mangleMap)
|
|
1360
|
+
};
|
|
1361
|
+
if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
|
|
1362
|
+
manifestData.mangleMap = state.mangleMap;
|
|
1363
|
+
}
|
|
1364
|
+
this.emitFile({
|
|
1365
|
+
type: "asset",
|
|
1366
|
+
fileName: "csszyx-manifest.json",
|
|
1367
|
+
source: JSON.stringify(manifestData)
|
|
1368
|
+
});
|
|
834
1369
|
for (const file in bundle) {
|
|
835
1370
|
const chunk = bundle[file];
|
|
836
1371
|
if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
|
|
@@ -904,8 +1439,9 @@ var esbuildPlugin = (options = {}) => {
|
|
|
904
1439
|
* @param build - the esbuild plugin build context
|
|
905
1440
|
*/
|
|
906
1441
|
setup(build) {
|
|
907
|
-
|
|
908
|
-
|
|
1442
|
+
const b = build;
|
|
1443
|
+
prePlugin.esbuild(options).setup(b);
|
|
1444
|
+
postPlugin.esbuild(options).setup(b);
|
|
909
1445
|
}
|
|
910
1446
|
};
|
|
911
1447
|
};
|
|
@@ -914,8 +1450,12 @@ var esbuildPlugin = (options = {}) => {
|
|
|
914
1450
|
createPostCSSPlugin,
|
|
915
1451
|
esbuildPlugin,
|
|
916
1452
|
escapeCSSClassName,
|
|
1453
|
+
hasTokens,
|
|
917
1454
|
mangleCSS,
|
|
918
1455
|
mangleCSSSync,
|
|
1456
|
+
mangleCodeClassesSync,
|
|
1457
|
+
mergeThemes,
|
|
1458
|
+
parseThemeBlocks,
|
|
919
1459
|
rollupPlugin,
|
|
920
1460
|
unescapeTailwindClass,
|
|
921
1461
|
unplugin,
|