@csszyx/unplugin 0.6.2 → 0.7.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-JGJOUK2R.js → chunk-GXGGTRUA.js} +351 -33
- package/dist/index.cjs +357 -31
- package/dist/index.d.cts +96 -1
- package/dist/index.d.ts +96 -1
- package/dist/index.js +17 -1
- package/dist/vite.cjs +341 -31
- package/dist/vite.js +1 -1
- package/dist/webpack.cjs +341 -31
- package/dist/webpack.js +1 -1
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -30,11 +30,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
assertNoRSCBoundaryViolation: () => assertNoRSCBoundaryViolation,
|
|
34
|
+
assertNoRSCGraphViolation: () => assertNoRSCGraphViolation,
|
|
33
35
|
createPostCSSPlugin: () => createPostCSSPlugin,
|
|
36
|
+
createRSCModuleRecord: () => createRSCModuleRecord,
|
|
34
37
|
default: () => unplugin,
|
|
35
38
|
esbuildPlugin: () => esbuildPlugin,
|
|
36
39
|
escapeCSSClassName: () => escapeCSSClassName,
|
|
40
|
+
findRSCBoundaryViolation: () => findRSCBoundaryViolation,
|
|
41
|
+
findRSCGraphViolation: () => findRSCGraphViolation,
|
|
37
42
|
hasTokens: () => hasTokens,
|
|
43
|
+
hasUseClientDirective: () => hasUseClientDirective,
|
|
44
|
+
hasUseServerDirective: () => hasUseServerDirective,
|
|
45
|
+
isRSCServerModule: () => isRSCServerModule,
|
|
38
46
|
mangleCSS: () => mangleCSS,
|
|
39
47
|
mangleCSSSync: () => mangleCSSSync,
|
|
40
48
|
mangleCodeClassesSync: () => mangleCodeClassesSync,
|
|
@@ -243,6 +251,303 @@ function createPostCSSPlugin(mangleMap, options = {}) {
|
|
|
243
251
|
};
|
|
244
252
|
}
|
|
245
253
|
|
|
254
|
+
// src/rsc-boundary.ts
|
|
255
|
+
var fs = __toESM(require("fs"), 1);
|
|
256
|
+
var path = __toESM(require("path"), 1);
|
|
257
|
+
var SERVER_DIRECTIVE_RE = /^['"]use server['"];?$/;
|
|
258
|
+
var CLIENT_DIRECTIVE_RE = /^['"]use client['"];?$/;
|
|
259
|
+
var RUNTIME_MODULES = /* @__PURE__ */ new Set([
|
|
260
|
+
"@csszyx/runtime",
|
|
261
|
+
"@csszyx/runtime/lite",
|
|
262
|
+
"csszyx",
|
|
263
|
+
"csszyx/lite"
|
|
264
|
+
]);
|
|
265
|
+
var FORBIDDEN_SYMBOLS = /* @__PURE__ */ new Set([
|
|
266
|
+
"_sz",
|
|
267
|
+
"_sz2",
|
|
268
|
+
"_sz3",
|
|
269
|
+
"_szIf",
|
|
270
|
+
"_szMerge",
|
|
271
|
+
"_szSwitch",
|
|
272
|
+
"__csszyx_runtime__"
|
|
273
|
+
]);
|
|
274
|
+
function hasUseServerDirective(code) {
|
|
275
|
+
for (const statement of readDirectivePrologue(code)) {
|
|
276
|
+
if (SERVER_DIRECTIVE_RE.test(statement)) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
if (CLIENT_DIRECTIVE_RE.test(statement)) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
function hasUseClientDirective(code) {
|
|
286
|
+
for (const statement of readDirectivePrologue(code)) {
|
|
287
|
+
if (CLIENT_DIRECTIVE_RE.test(statement)) {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
if (SERVER_DIRECTIVE_RE.test(statement)) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
function isRSCServerModule(code, id) {
|
|
297
|
+
if (hasUseServerDirective(code)) {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
if (hasUseClientDirective(code)) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
return isNextAppRouterEntry(id);
|
|
304
|
+
}
|
|
305
|
+
function findRSCBoundaryViolation(code, id) {
|
|
306
|
+
if (!isRSCServerModule(code, id)) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
for (const imported of findRuntimeImports(code)) {
|
|
310
|
+
for (const symbol of imported.symbols) {
|
|
311
|
+
if (FORBIDDEN_SYMBOLS.has(symbol)) {
|
|
312
|
+
return {
|
|
313
|
+
symbol,
|
|
314
|
+
path: id,
|
|
315
|
+
importChain: [id, imported.source]
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
function createRSCModuleRecord(code, id) {
|
|
323
|
+
const normalized = normalizeModuleId(id);
|
|
324
|
+
return {
|
|
325
|
+
id: normalized,
|
|
326
|
+
isServer: isRSCServerModule(code, normalized),
|
|
327
|
+
isClient: hasUseClientDirective(code),
|
|
328
|
+
imports: findLocalImportSources(code).map((source) => resolveLocalModule(normalized, source)).filter((resolved) => resolved !== null),
|
|
329
|
+
runtimeImports: findRuntimeImports(code).filter((imported) => imported.symbols.some((symbol) => FORBIDDEN_SYMBOLS.has(symbol)))
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function findRSCGraphViolation(records) {
|
|
333
|
+
for (const root of records.values()) {
|
|
334
|
+
if (!root.isServer) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const violation = walkRSCGraph(root, records, [root.id], /* @__PURE__ */ new Set([root.id]));
|
|
338
|
+
if (violation) {
|
|
339
|
+
return {
|
|
340
|
+
symbol: violation.symbol,
|
|
341
|
+
path: root.id,
|
|
342
|
+
importChain: violation.importChain
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
function assertNoRSCGraphViolation(records) {
|
|
349
|
+
const violation = findRSCGraphViolation(records);
|
|
350
|
+
if (!violation) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
throw new Error(formatRSCViolation(violation));
|
|
354
|
+
}
|
|
355
|
+
function isNextAppRouterEntry(id) {
|
|
356
|
+
const clean = id.split("?")[0]?.replace(/\\/g, "/") ?? id;
|
|
357
|
+
return /(^|\/)app\/.*\/?(?:page|layout|template|loading|error|not-found|global-error|default|route)\.[cm]?[tj]sx?$/.test(clean);
|
|
358
|
+
}
|
|
359
|
+
function assertNoRSCBoundaryViolation(code, id) {
|
|
360
|
+
const violation = findRSCBoundaryViolation(code, id);
|
|
361
|
+
if (!violation) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
throw new Error(formatRSCViolation(violation));
|
|
365
|
+
}
|
|
366
|
+
function formatRSCViolation(violation) {
|
|
367
|
+
return `csszyxRSCViolation: ${violation.symbol} imported in Server Component ${violation.path}
|
|
368
|
+
Import chain: ${violation.importChain.join(" -> ")}`;
|
|
369
|
+
}
|
|
370
|
+
function walkRSCGraph(current, records, chain, seen) {
|
|
371
|
+
if (current.isClient) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
const runtime = current.runtimeImports[0];
|
|
375
|
+
const symbol = runtime?.symbols.find((s) => FORBIDDEN_SYMBOLS.has(s));
|
|
376
|
+
if (runtime && symbol) {
|
|
377
|
+
return {
|
|
378
|
+
symbol,
|
|
379
|
+
path: chain[0] ?? current.id,
|
|
380
|
+
importChain: [...chain, runtime.source]
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
for (const importedId of current.imports) {
|
|
384
|
+
if (seen.has(importedId)) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const next = records.get(importedId);
|
|
388
|
+
if (!next) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
seen.add(importedId);
|
|
392
|
+
const violation = walkRSCGraph(next, records, [...chain, importedId], seen);
|
|
393
|
+
if (violation) {
|
|
394
|
+
return violation;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
function readDirectivePrologue(code) {
|
|
400
|
+
const out = [];
|
|
401
|
+
let i = code.charCodeAt(0) === 65279 ? 1 : 0;
|
|
402
|
+
while (i < code.length) {
|
|
403
|
+
i = skipWhitespaceAndComments(code, i);
|
|
404
|
+
const quote = code[i];
|
|
405
|
+
if (quote !== '"' && quote !== "'") {
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
let j = i + 1;
|
|
409
|
+
let escaped = false;
|
|
410
|
+
while (j < code.length) {
|
|
411
|
+
const ch = code[j];
|
|
412
|
+
if (escaped) {
|
|
413
|
+
escaped = false;
|
|
414
|
+
} else if (ch === "\\") {
|
|
415
|
+
escaped = true;
|
|
416
|
+
} else if (ch === quote) {
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
j++;
|
|
420
|
+
}
|
|
421
|
+
if (j >= code.length) {
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
let end = j + 1;
|
|
425
|
+
while (end < code.length && /[ \t\r\n]/.test(code[end])) {
|
|
426
|
+
end++;
|
|
427
|
+
}
|
|
428
|
+
if (code[end] === ";") {
|
|
429
|
+
end++;
|
|
430
|
+
}
|
|
431
|
+
out.push(code.slice(i, end).trim());
|
|
432
|
+
i = end;
|
|
433
|
+
}
|
|
434
|
+
return out;
|
|
435
|
+
}
|
|
436
|
+
function skipWhitespaceAndComments(code, start) {
|
|
437
|
+
let i = start;
|
|
438
|
+
while (i < code.length) {
|
|
439
|
+
while (i < code.length && /\s/.test(code[i])) {
|
|
440
|
+
i++;
|
|
441
|
+
}
|
|
442
|
+
if (code.startsWith("//", i)) {
|
|
443
|
+
const next = code.indexOf("\n", i + 2);
|
|
444
|
+
i = next === -1 ? code.length : next + 1;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (code.startsWith("/*", i)) {
|
|
448
|
+
const next = code.indexOf("*/", i + 2);
|
|
449
|
+
i = next === -1 ? code.length : next + 2;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
return i;
|
|
455
|
+
}
|
|
456
|
+
function findRuntimeImports(code) {
|
|
457
|
+
const imports = [];
|
|
458
|
+
const staticImportRe = /import\s+(?!type\b)([\s\S]*?)\s+from\s+['"]([^'"]+)['"]/g;
|
|
459
|
+
const sideEffectImportRe = /import\s+['"]([^'"]+)['"]/g;
|
|
460
|
+
const dynamicImportRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
461
|
+
let match;
|
|
462
|
+
while ((match = staticImportRe.exec(code)) !== null) {
|
|
463
|
+
const clause = match[1];
|
|
464
|
+
const source = match[2];
|
|
465
|
+
if (!RUNTIME_MODULES.has(source)) {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
imports.push({ source, symbols: readImportedSymbols(clause) });
|
|
469
|
+
}
|
|
470
|
+
while ((match = sideEffectImportRe.exec(code)) !== null) {
|
|
471
|
+
const source = match[1];
|
|
472
|
+
if (RUNTIME_MODULES.has(source)) {
|
|
473
|
+
imports.push({ source, symbols: [] });
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
while ((match = dynamicImportRe.exec(code)) !== null) {
|
|
477
|
+
const source = match[1];
|
|
478
|
+
if (RUNTIME_MODULES.has(source)) {
|
|
479
|
+
imports.push({ source, symbols: Array.from(FORBIDDEN_SYMBOLS) });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return imports;
|
|
483
|
+
}
|
|
484
|
+
function findLocalImportSources(code) {
|
|
485
|
+
const out = [];
|
|
486
|
+
const staticImportRe = /import\s+(?!type\b)(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
487
|
+
const exportFromRe = /export\s+(?!type\b)(?:[\s\S]*?)\s+from\s+['"]([^'"]+)['"]/g;
|
|
488
|
+
const dynamicImportRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
489
|
+
let match;
|
|
490
|
+
for (const re of [staticImportRe, exportFromRe, dynamicImportRe]) {
|
|
491
|
+
while ((match = re.exec(code)) !== null) {
|
|
492
|
+
const source = match[1];
|
|
493
|
+
if (source.startsWith(".") || source.startsWith("/")) {
|
|
494
|
+
out.push(source);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return out;
|
|
499
|
+
}
|
|
500
|
+
function normalizeModuleId(id) {
|
|
501
|
+
const clean = id.split("?")[0] ?? id;
|
|
502
|
+
try {
|
|
503
|
+
return fs.realpathSync.native(clean).replace(/\\/g, "/");
|
|
504
|
+
} catch {
|
|
505
|
+
return path.resolve(clean).replace(/\\/g, "/");
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function resolveLocalModule(importer, source) {
|
|
509
|
+
const base = source.startsWith("/") ? source : path.resolve(path.dirname(importer), source);
|
|
510
|
+
const candidates = [
|
|
511
|
+
base,
|
|
512
|
+
`${base}.tsx`,
|
|
513
|
+
`${base}.ts`,
|
|
514
|
+
`${base}.jsx`,
|
|
515
|
+
`${base}.js`,
|
|
516
|
+
`${base}.mjs`,
|
|
517
|
+
`${base}.cjs`,
|
|
518
|
+
path.join(base, "index.tsx"),
|
|
519
|
+
path.join(base, "index.ts"),
|
|
520
|
+
path.join(base, "index.jsx"),
|
|
521
|
+
path.join(base, "index.js")
|
|
522
|
+
];
|
|
523
|
+
for (const candidate of candidates) {
|
|
524
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
525
|
+
return normalizeModuleId(candidate);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
function readImportedSymbols(clause) {
|
|
531
|
+
const symbols = [];
|
|
532
|
+
const named = clause.match(/\{([\s\S]*?)\}/);
|
|
533
|
+
if (named) {
|
|
534
|
+
for (const part of named[1].split(",")) {
|
|
535
|
+
const trimmed = part.trim();
|
|
536
|
+
if (!trimmed || trimmed.startsWith("type ")) {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
const sourceName = trimmed.replace(/^type\s+/, "").split(/\s+as\s+/)[0]?.trim();
|
|
540
|
+
if (sourceName) {
|
|
541
|
+
symbols.push(sourceName);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (/\*\s+as\s+\w+/.test(clause)) {
|
|
546
|
+
symbols.push(...FORBIDDEN_SYMBOLS);
|
|
547
|
+
}
|
|
548
|
+
return symbols;
|
|
549
|
+
}
|
|
550
|
+
|
|
246
551
|
// src/theme-scanner.ts
|
|
247
552
|
var EMPTY_THEME = { colors: [], spacings: [], fonts: [], radii: [], shadows: [] };
|
|
248
553
|
function stripLayerWrappers(css) {
|
|
@@ -386,8 +691,8 @@ function hasTokens(theme) {
|
|
|
386
691
|
}
|
|
387
692
|
|
|
388
693
|
// src/unplugin.ts
|
|
389
|
-
var
|
|
390
|
-
var
|
|
694
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
695
|
+
var path2 = __toESM(require("path"), 1);
|
|
391
696
|
var import_compiler = require("@csszyx/compiler");
|
|
392
697
|
var import_core = require("@csszyx/core");
|
|
393
698
|
var import_svelte_adapter = require("@csszyx/svelte-adapter");
|
|
@@ -455,7 +760,7 @@ function injectHydrationData(html, mangleMap, checksum, options = {}) {
|
|
|
455
760
|
function transformIndexHtml(html, mangleMap, checksum, options = {}) {
|
|
456
761
|
return injectHydrationData(html, mangleMap, checksum, options);
|
|
457
762
|
}
|
|
458
|
-
function buildRecoveryManifest(tokens, options
|
|
763
|
+
function buildRecoveryManifest(tokens, options) {
|
|
459
764
|
const stripped = options.production === true;
|
|
460
765
|
const strippedDevOnlyPaths = [];
|
|
461
766
|
const sorted = {};
|
|
@@ -476,7 +781,7 @@ function buildRecoveryManifest(tokens, options = {}) {
|
|
|
476
781
|
const checksum = fullChecksum.substring(0, 16);
|
|
477
782
|
const buildId = `${Date.now().toString(36)}-${fullChecksum.substring(0, 6)}`;
|
|
478
783
|
return {
|
|
479
|
-
manifest: { buildId, checksum, tokens: sorted },
|
|
784
|
+
manifest: { buildId, checksum, mangleChecksum: options.mangleChecksum, tokens: sorted },
|
|
480
785
|
strippedDevOnlyPaths
|
|
481
786
|
};
|
|
482
787
|
}
|
|
@@ -605,8 +910,8 @@ function runThemeScan(rootDir, scanCss) {
|
|
|
605
910
|
const patterns = Array.isArray(scanCss) ? scanCss : [scanCss];
|
|
606
911
|
const sourceFiles = [];
|
|
607
912
|
for (const pattern of patterns) {
|
|
608
|
-
const resolved =
|
|
609
|
-
if (
|
|
913
|
+
const resolved = path2.isAbsolute(pattern) ? pattern : path2.join(rootDir, pattern);
|
|
914
|
+
if (fs2.existsSync(resolved)) {
|
|
610
915
|
sourceFiles.push(resolved);
|
|
611
916
|
}
|
|
612
917
|
}
|
|
@@ -615,20 +920,20 @@ function runThemeScan(rootDir, scanCss) {
|
|
|
615
920
|
}
|
|
616
921
|
const themes = sourceFiles.map((f) => {
|
|
617
922
|
try {
|
|
618
|
-
return parseThemeBlocks(
|
|
923
|
+
return parseThemeBlocks(fs2.readFileSync(f, "utf-8"));
|
|
619
924
|
} catch {
|
|
620
925
|
return null;
|
|
621
926
|
}
|
|
622
927
|
}).filter((t) => t !== null);
|
|
623
928
|
const merged = mergeThemes(themes);
|
|
624
|
-
const outputPath =
|
|
929
|
+
const outputPath = path2.join(rootDir, ".csszyx", "theme.d.ts");
|
|
625
930
|
writeThemeDts({ outputPath, theme: merged, sourceFiles });
|
|
626
931
|
if (!_hasWarnedTsConfig) {
|
|
627
932
|
_hasWarnedTsConfig = true;
|
|
628
933
|
try {
|
|
629
934
|
const checkFile = (cfgPath) => {
|
|
630
|
-
if (
|
|
631
|
-
const content =
|
|
935
|
+
if (fs2.existsSync(cfgPath)) {
|
|
936
|
+
const content = fs2.readFileSync(cfgPath, "utf-8");
|
|
632
937
|
if (!content.includes(".csszyx")) {
|
|
633
938
|
console.warn(`
|
|
634
939
|
\x1B[33m\u26A0\uFE0F CSSzyx: Theme Auto-Scan enabled, but TypeScript isn't configured. Run "npx @csszyx/cli init" to fix.\x1B[0m
|
|
@@ -638,8 +943,8 @@ function runThemeScan(rootDir, scanCss) {
|
|
|
638
943
|
}
|
|
639
944
|
return false;
|
|
640
945
|
};
|
|
641
|
-
if (!checkFile(
|
|
642
|
-
checkFile(
|
|
946
|
+
if (!checkFile(path2.join(rootDir, "tsconfig.json"))) {
|
|
947
|
+
checkFile(path2.join(rootDir, "tsconfig.app.json"));
|
|
643
948
|
}
|
|
644
949
|
} catch {
|
|
645
950
|
}
|
|
@@ -826,7 +1131,8 @@ function createCsszyxPlugins(options = {}) {
|
|
|
826
1131
|
checksum: "",
|
|
827
1132
|
finalized: false,
|
|
828
1133
|
rootDir: process.cwd(),
|
|
829
|
-
recoveryTokens: /* @__PURE__ */ new Map()
|
|
1134
|
+
recoveryTokens: /* @__PURE__ */ new Map(),
|
|
1135
|
+
rscModules: /* @__PURE__ */ new Map()
|
|
830
1136
|
};
|
|
831
1137
|
const SAFELIST_FILENAME = "csszyx-classes.html";
|
|
832
1138
|
const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js"]);
|
|
@@ -835,16 +1141,16 @@ function createCsszyxPlugins(options = {}) {
|
|
|
835
1141
|
if (classes.size === 0) {
|
|
836
1142
|
return;
|
|
837
1143
|
}
|
|
838
|
-
const safelistPath =
|
|
1144
|
+
const safelistPath = path2.join(state.rootDir, SAFELIST_FILENAME);
|
|
839
1145
|
const classList = Array.from(classes).join(" ");
|
|
840
1146
|
const content = `<!-- Auto-generated by csszyx \u2014 DO NOT EDIT -->
|
|
841
1147
|
<!-- Tailwind CSS scans this file for class name detection -->
|
|
842
1148
|
<div class="${classList}"><div class="${classList}">x</div><div class="${classList}">x</div></div>
|
|
843
1149
|
`;
|
|
844
1150
|
try {
|
|
845
|
-
const existing =
|
|
1151
|
+
const existing = fs2.existsSync(safelistPath) ? fs2.readFileSync(safelistPath, "utf-8") : "";
|
|
846
1152
|
if (existing !== content) {
|
|
847
|
-
|
|
1153
|
+
fs2.writeFileSync(safelistPath, content);
|
|
848
1154
|
}
|
|
849
1155
|
} catch {
|
|
850
1156
|
}
|
|
@@ -855,19 +1161,19 @@ function createCsszyxPlugins(options = {}) {
|
|
|
855
1161
|
function scanDir(dir) {
|
|
856
1162
|
let entries;
|
|
857
1163
|
try {
|
|
858
|
-
entries =
|
|
1164
|
+
entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
859
1165
|
} catch {
|
|
860
1166
|
return;
|
|
861
1167
|
}
|
|
862
1168
|
for (const entry of entries) {
|
|
863
1169
|
if (entry.isDirectory()) {
|
|
864
1170
|
if (!IGNORE_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
|
|
865
|
-
scanDir(
|
|
1171
|
+
scanDir(path2.join(dir, entry.name));
|
|
866
1172
|
}
|
|
867
|
-
} else if (SOURCE_EXTENSIONS.has(
|
|
868
|
-
const filePath =
|
|
1173
|
+
} else if (SOURCE_EXTENSIONS.has(path2.extname(entry.name))) {
|
|
1174
|
+
const filePath = path2.join(dir, entry.name);
|
|
869
1175
|
try {
|
|
870
|
-
const content =
|
|
1176
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
871
1177
|
if (!content.includes("sz=") && !content.includes("sz:")) {
|
|
872
1178
|
continue;
|
|
873
1179
|
}
|
|
@@ -1059,14 +1365,17 @@ function createCsszyxPlugins(options = {}) {
|
|
|
1059
1365
|
* @returns transformed code with source map, or null if no changes were made
|
|
1060
1366
|
*/
|
|
1061
1367
|
transform(code, id) {
|
|
1368
|
+
if (/\.[tj]sx?(\?.*)?$/.test(id)) {
|
|
1369
|
+
assertNoRSCBoundaryViolation(code, id);
|
|
1370
|
+
}
|
|
1062
1371
|
if (/\.css(\?.*)?$/.test(id)) {
|
|
1063
1372
|
const hasTailwindImport = code.includes('@import "tailwindcss') || code.includes("@import 'tailwindcss");
|
|
1064
1373
|
if (hasTailwindImport && state.classes.size > 0) {
|
|
1065
1374
|
const candidates = Array.from(state.classes).filter((c) => c.length >= 2 && /^[a-z]/.test(c)).join(" ");
|
|
1066
1375
|
if (candidates) {
|
|
1067
|
-
const safelistPath =
|
|
1068
|
-
const cssDir =
|
|
1069
|
-
let relPath =
|
|
1376
|
+
const safelistPath = path2.join(state.rootDir, SAFELIST_FILENAME).replace(/\\/g, "/");
|
|
1377
|
+
const cssDir = path2.dirname(id).replace(/\\/g, "/");
|
|
1378
|
+
let relPath = path2.posix.relative(cssDir, safelistPath);
|
|
1070
1379
|
if (!relPath.startsWith(".")) {
|
|
1071
1380
|
relPath = "./" + relPath;
|
|
1072
1381
|
}
|
|
@@ -1170,6 +1479,11 @@ ${sourceDirective}`
|
|
|
1170
1479
|
transformed = true;
|
|
1171
1480
|
}
|
|
1172
1481
|
}
|
|
1482
|
+
if (/\.[tj]sx?(\?.*)?$/.test(id)) {
|
|
1483
|
+
assertNoRSCBoundaryViolation(transformedCode, id);
|
|
1484
|
+
const record = createRSCModuleRecord(transformedCode, id);
|
|
1485
|
+
state.rscModules.set(record.id, record);
|
|
1486
|
+
}
|
|
1173
1487
|
if (transformed || transformedCode.includes("class=") || transformedCode.includes("className=")) {
|
|
1174
1488
|
if (szClasses !== void 0) {
|
|
1175
1489
|
for (const cls of szClasses) {
|
|
@@ -1185,6 +1499,7 @@ ${sourceDirective}`
|
|
|
1185
1499
|
/** Finalizes the mangle map after all source modules have been processed. */
|
|
1186
1500
|
buildEnd() {
|
|
1187
1501
|
finalizeMangleMap();
|
|
1502
|
+
assertNoRSCGraphViolation(state.rscModules);
|
|
1188
1503
|
if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
|
|
1189
1504
|
globalThis.__csszyx_ssr_mangle_map = state.mangleMap;
|
|
1190
1505
|
}
|
|
@@ -1207,8 +1522,8 @@ ${sourceDirective}`
|
|
|
1207
1522
|
compiler.hooks.thisCompilation.tap("csszyx:theme-deps", (compilation) => {
|
|
1208
1523
|
const root = compiler.context || process.cwd();
|
|
1209
1524
|
for (const pattern of patterns) {
|
|
1210
|
-
const resolved =
|
|
1211
|
-
if (
|
|
1525
|
+
const resolved = path2.isAbsolute(pattern) ? pattern : path2.join(root, pattern);
|
|
1526
|
+
if (fs2.existsSync(resolved)) {
|
|
1212
1527
|
compilation.fileDependencies.add(resolved);
|
|
1213
1528
|
}
|
|
1214
1529
|
}
|
|
@@ -1238,14 +1553,14 @@ ${sourceDirective}`
|
|
|
1238
1553
|
const patterns = Array.isArray(scanCss) ? scanCss : [scanCss];
|
|
1239
1554
|
const root = ctx.server.config.root || process.cwd();
|
|
1240
1555
|
const isWatched = patterns.some((p) => {
|
|
1241
|
-
const resolved =
|
|
1556
|
+
const resolved = path2.isAbsolute(p) ? p : path2.join(root, p);
|
|
1242
1557
|
return ctx.file === resolved;
|
|
1243
1558
|
});
|
|
1244
1559
|
if (isWatched) {
|
|
1245
1560
|
runThemeScan(root, scanCss);
|
|
1246
1561
|
}
|
|
1247
1562
|
}
|
|
1248
|
-
if (!SOURCE_EXTENSIONS.has(
|
|
1563
|
+
if (!SOURCE_EXTENSIONS.has(path2.extname(ctx.file))) {
|
|
1249
1564
|
return;
|
|
1250
1565
|
}
|
|
1251
1566
|
if (ctx.file.includes("node_modules")) {
|
|
@@ -1253,7 +1568,7 @@ ${sourceDirective}`
|
|
|
1253
1568
|
}
|
|
1254
1569
|
let fileContent, result;
|
|
1255
1570
|
try {
|
|
1256
|
-
fileContent =
|
|
1571
|
+
fileContent = fs2.readFileSync(ctx.file, "utf-8");
|
|
1257
1572
|
} catch {
|
|
1258
1573
|
return;
|
|
1259
1574
|
}
|
|
@@ -1277,7 +1592,7 @@ ${sourceDirective}`
|
|
|
1277
1592
|
}
|
|
1278
1593
|
if (state.classes.size > sizeBefore) {
|
|
1279
1594
|
writeSafelistFile(state.classes);
|
|
1280
|
-
const safelistPath =
|
|
1595
|
+
const safelistPath = path2.join(state.rootDir, SAFELIST_FILENAME);
|
|
1281
1596
|
ctx.server.watcher.emit("change", safelistPath);
|
|
1282
1597
|
}
|
|
1283
1598
|
},
|
|
@@ -1299,7 +1614,10 @@ ${sourceDirective}`
|
|
|
1299
1614
|
const isProduction = process.env.NODE_ENV === "production";
|
|
1300
1615
|
const { manifest, strippedDevOnlyPaths } = buildRecoveryManifest(
|
|
1301
1616
|
state.recoveryTokens,
|
|
1302
|
-
{
|
|
1617
|
+
{
|
|
1618
|
+
production: isProduction,
|
|
1619
|
+
mangleChecksum: state.checksum
|
|
1620
|
+
}
|
|
1303
1621
|
);
|
|
1304
1622
|
if (strippedDevOnlyPaths.length > 0) {
|
|
1305
1623
|
console.warn(
|
|
@@ -1513,10 +1831,18 @@ var esbuildPlugin = (options = {}) => {
|
|
|
1513
1831
|
};
|
|
1514
1832
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1515
1833
|
0 && (module.exports = {
|
|
1834
|
+
assertNoRSCBoundaryViolation,
|
|
1835
|
+
assertNoRSCGraphViolation,
|
|
1516
1836
|
createPostCSSPlugin,
|
|
1837
|
+
createRSCModuleRecord,
|
|
1517
1838
|
esbuildPlugin,
|
|
1518
1839
|
escapeCSSClassName,
|
|
1840
|
+
findRSCBoundaryViolation,
|
|
1841
|
+
findRSCGraphViolation,
|
|
1519
1842
|
hasTokens,
|
|
1843
|
+
hasUseClientDirective,
|
|
1844
|
+
hasUseServerDirective,
|
|
1845
|
+
isRSCServerModule,
|
|
1520
1846
|
mangleCSS,
|
|
1521
1847
|
mangleCSSSync,
|
|
1522
1848
|
mangleCodeClassesSync,
|
package/dist/index.d.cts
CHANGED
|
@@ -7,6 +7,101 @@ import 'rollup';
|
|
|
7
7
|
import 'unplugin';
|
|
8
8
|
import 'vite';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Direct RSC boundary violation found in a transformed module.
|
|
12
|
+
*/
|
|
13
|
+
interface RSCBoundaryViolation {
|
|
14
|
+
/** Forbidden runtime helper that crossed into an RSC server module. */
|
|
15
|
+
symbol: string;
|
|
16
|
+
/** Server module path where the import was found. */
|
|
17
|
+
path: string;
|
|
18
|
+
/** Import chain used in the fatal build error. */
|
|
19
|
+
importChain: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* RSC module metadata collected during the transform phase.
|
|
23
|
+
*/
|
|
24
|
+
interface RSCModuleRecord {
|
|
25
|
+
/** Normalized absolute module ID. */
|
|
26
|
+
id: string;
|
|
27
|
+
/** True when this module is an RSC server module entry or has `'use server'`. */
|
|
28
|
+
isServer: boolean;
|
|
29
|
+
/** True when this module declares the client boundary. */
|
|
30
|
+
isClient: boolean;
|
|
31
|
+
/** Local modules imported by this file after path resolution. */
|
|
32
|
+
imports: string[];
|
|
33
|
+
/** Forbidden runtime imports found directly in this module. */
|
|
34
|
+
runtimeImports: Array<{
|
|
35
|
+
source: string;
|
|
36
|
+
symbols: string[];
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns true when a module starts with the top-level `'use server'`
|
|
41
|
+
* directive. Comments and blank lines before the directive are allowed, but
|
|
42
|
+
* detection stops at the first real statement.
|
|
43
|
+
*
|
|
44
|
+
* @param code module source
|
|
45
|
+
* @returns true when the module has a top-level `'use server'` directive
|
|
46
|
+
*/
|
|
47
|
+
declare function hasUseServerDirective(code: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Returns true when a module starts with the top-level `'use client'`
|
|
50
|
+
* directive.
|
|
51
|
+
*
|
|
52
|
+
* @param code module source
|
|
53
|
+
* @returns true when the module has a top-level `'use client'` directive
|
|
54
|
+
*/
|
|
55
|
+
declare function hasUseClientDirective(code: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Detects modules that should be treated as RSC server modules by csszyx.
|
|
58
|
+
*
|
|
59
|
+
* @param code module source
|
|
60
|
+
* @param id module ID/path
|
|
61
|
+
* @returns true when the module is server-side for RSC boundary purposes
|
|
62
|
+
*/
|
|
63
|
+
declare function isRSCServerModule(code: string, id: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Finds the first direct forbidden runtime helper import in an RSC server
|
|
66
|
+
* module.
|
|
67
|
+
*
|
|
68
|
+
* @param code module source
|
|
69
|
+
* @param id module ID/path
|
|
70
|
+
* @returns violation details, or null when the module is allowed
|
|
71
|
+
*/
|
|
72
|
+
declare function findRSCBoundaryViolation(code: string, id: string): RSCBoundaryViolation | null;
|
|
73
|
+
/**
|
|
74
|
+
* Builds module metadata for the RSC graph walker.
|
|
75
|
+
*
|
|
76
|
+
* @param code module source
|
|
77
|
+
* @param id module ID/path
|
|
78
|
+
* @returns graph metadata for the module
|
|
79
|
+
*/
|
|
80
|
+
declare function createRSCModuleRecord(code: string, id: string): RSCModuleRecord;
|
|
81
|
+
/**
|
|
82
|
+
* Finds forbidden runtime helper imports reachable from an RSC server module.
|
|
83
|
+
* Traversal stops at `'use client'` modules because they define a separate
|
|
84
|
+
* client module graph.
|
|
85
|
+
*
|
|
86
|
+
* @param records module graph records keyed by normalized module ID
|
|
87
|
+
* @returns first graph violation, or null when the graph is allowed
|
|
88
|
+
*/
|
|
89
|
+
declare function findRSCGraphViolation(records: Map<string, RSCModuleRecord>): RSCBoundaryViolation | null;
|
|
90
|
+
/**
|
|
91
|
+
* Throws the spec-format fatal RSC boundary error for graph-level violations.
|
|
92
|
+
*
|
|
93
|
+
* @param records module graph records keyed by normalized module ID
|
|
94
|
+
*/
|
|
95
|
+
declare function assertNoRSCGraphViolation(records: Map<string, RSCModuleRecord>): void;
|
|
96
|
+
/**
|
|
97
|
+
* Throws the spec-format fatal RSC boundary error when a server module imports
|
|
98
|
+
* a forbidden csszyx runtime helper.
|
|
99
|
+
*
|
|
100
|
+
* @param code module source
|
|
101
|
+
* @param id module ID/path
|
|
102
|
+
*/
|
|
103
|
+
declare function assertNoRSCBoundaryViolation(code: string, id: string): void;
|
|
104
|
+
|
|
10
105
|
/**
|
|
11
106
|
* Theme Scanner — parses Tailwind v4 @theme blocks from CSS files.
|
|
12
107
|
*
|
|
@@ -55,4 +150,4 @@ declare function mergeThemes(themes: ParsedTheme[]): ParsedTheme;
|
|
|
55
150
|
*/
|
|
56
151
|
declare function hasTokens(theme: ParsedTheme): boolean;
|
|
57
152
|
|
|
58
|
-
export { type ParsedTheme, hasTokens, mergeThemes, parseThemeBlocks };
|
|
153
|
+
export { type ParsedTheme, type RSCBoundaryViolation, type RSCModuleRecord, assertNoRSCBoundaryViolation, assertNoRSCGraphViolation, createRSCModuleRecord, findRSCBoundaryViolation, findRSCGraphViolation, hasTokens, hasUseClientDirective, hasUseServerDirective, isRSCServerModule, mergeThemes, parseThemeBlocks };
|