@csszyx/unplugin 0.8.0 → 0.9.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.
@@ -1,15 +1,20 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('node:fs');
4
+ const node_module = require('node:module');
4
5
  const path = require('node:path');
6
+ const node_perf_hooks = require('node:perf_hooks');
7
+ const node_url = require('node:url');
5
8
  const compiler = require('@csszyx/compiler');
6
9
  const core = require('@csszyx/core');
7
10
  const svelteAdapter = require('@csszyx/svelte-adapter');
11
+ const types = require('@csszyx/types');
8
12
  const vueAdapter = require('@csszyx/vue-adapter');
9
13
  const unplugin$1 = require('unplugin');
10
14
  const cssMangler = require('../css-mangler.cjs');
11
15
  const node_crypto = require('node:crypto');
12
16
 
17
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
13
18
  function _interopNamespaceCompat(e) {
14
19
  if (e && typeof e === 'object' && 'default' in e) return e;
15
20
  const n = Object.create(null);
@@ -27,12 +32,16 @@ const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
27
32
 
28
33
  const SERVER_DIRECTIVE_RE = /^['"]use server['"];?$/;
29
34
  const CLIENT_DIRECTIVE_RE = /^['"]use client['"];?$/;
30
- const RUNTIME_MODULES = /* @__PURE__ */ new Set([
35
+ const RUNTIME_HELPER_MODULES = /* @__PURE__ */ new Set([
31
36
  "@csszyx/runtime",
32
37
  "@csszyx/runtime/lite",
33
38
  "csszyx",
34
39
  "csszyx/lite"
35
40
  ]);
41
+ const CLIENT_RUNTIME_MODULES = /* @__PURE__ */ new Set(["csszyx/browser"]);
42
+ const CLIENT_RUNTIME_MODULE_ROOTS = ["@csszyx/dynamic", "csszyx/dynamic"];
43
+ const normalizedModuleIdCache = /* @__PURE__ */ new Map();
44
+ const resolvedLocalModuleCache = /* @__PURE__ */ new Map();
36
45
  const FORBIDDEN_SYMBOLS = /* @__PURE__ */ new Set([
37
46
  "_sz",
38
47
  "_sz2",
@@ -102,6 +111,20 @@ function createRSCModuleRecord(code, id) {
102
111
  )
103
112
  };
104
113
  }
114
+ function deleteRSCModuleRecord(records, id) {
115
+ const normalized = normalizeModuleId(id);
116
+ const clean = id.split("?")[0]?.replace(/\\/g, "/") ?? id;
117
+ const resolved = path__namespace.resolve(clean).replace(/\\/g, "/");
118
+ pruneRSCModulePathCaches(/* @__PURE__ */ new Set([normalized, resolved, clean]));
119
+ let deleted = records.delete(normalized);
120
+ if (resolved !== normalized) {
121
+ deleted = records.delete(resolved) || deleted;
122
+ }
123
+ if (clean !== normalized && clean !== resolved) {
124
+ deleted = records.delete(clean) || deleted;
125
+ }
126
+ return deleted;
127
+ }
105
128
  function findRSCGraphViolation(records) {
106
129
  for (const root of records.values()) {
107
130
  if (!root.isServer) {
@@ -125,11 +148,26 @@ function assertNoRSCGraphViolation(records) {
125
148
  }
126
149
  throw new Error(formatRSCViolation(violation));
127
150
  }
151
+ const APP_ROUTER_ENTRIES = /* @__PURE__ */ new Set([
152
+ "page",
153
+ "layout",
154
+ "template",
155
+ "loading",
156
+ "error",
157
+ "not-found",
158
+ "global-error",
159
+ "default",
160
+ "route"
161
+ ]);
128
162
  function isNextAppRouterEntry(id) {
129
163
  const clean = id.split("?")[0]?.replace(/\\/g, "/") ?? id;
130
- return /(^|\/)app\/.*\/?(?:page|layout|template|loading|error|not-found|global-error|default|route)\.[cm]?[tj]sx?$/.test(
131
- clean
132
- );
164
+ if (!clean.includes("/app/") && !clean.startsWith("app/")) return false;
165
+ const basename = clean.split("/").pop() ?? "";
166
+ const dotIdx = basename.indexOf(".");
167
+ if (dotIdx === -1) return false;
168
+ const stem = basename.slice(0, dotIdx);
169
+ const ext = basename.slice(dotIdx + 1);
170
+ return APP_ROUTER_ENTRIES.has(stem) && /^[cm]?[tj]sx?$/.test(ext);
133
171
  }
134
172
  function assertNoRSCBoundaryViolation(code, id) {
135
173
  const violation = findRSCBoundaryViolation(code, id);
@@ -230,35 +268,51 @@ function skipWhitespaceAndComments(code, start) {
230
268
  }
231
269
  function findRuntimeImports(code) {
232
270
  const imports = [];
233
- const staticImportRe = /import\s+(?!type\b)([\s\S]*?)\s+from\s+['"]([^'"]+)['"]/g;
271
+ const scanCode = stripCommentsForImportScan(code);
272
+ const staticImportRe = /import\s+(?!type\b)(\S(?:.*\S)?)\s+from\s+['"]([^'"]+)['"]/g;
234
273
  const sideEffectImportRe = /import\s+['"]([^'"]+)['"]/g;
235
274
  const dynamicImportRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
236
- for (const match of code.matchAll(staticImportRe)) {
275
+ for (const match of scanCode.matchAll(staticImportRe)) {
237
276
  const clause = match[1];
238
277
  const source = match[2];
239
- if (!RUNTIME_MODULES.has(source)) {
278
+ if (!isRuntimeImportSource(source)) {
240
279
  continue;
241
280
  }
242
- imports.push({ source, symbols: readImportedSymbols(clause) });
281
+ imports.push({ source, symbols: readRuntimeImportSymbols(source, clause) });
243
282
  }
244
- for (const match of code.matchAll(sideEffectImportRe)) {
283
+ for (const match of scanCode.matchAll(sideEffectImportRe)) {
245
284
  const source = match[1];
246
- if (RUNTIME_MODULES.has(source)) {
247
- imports.push({ source, symbols: [] });
285
+ if (isRuntimeImportSource(source)) {
286
+ imports.push({
287
+ source,
288
+ symbols: isWholeRuntimeModuleForbidden(source) ? Array.from(FORBIDDEN_SYMBOLS) : []
289
+ });
248
290
  }
249
291
  }
250
- for (const match of code.matchAll(dynamicImportRe)) {
292
+ for (const match of scanCode.matchAll(dynamicImportRe)) {
251
293
  const source = match[1];
252
- if (RUNTIME_MODULES.has(source)) {
294
+ if (isRuntimeImportSource(source)) {
253
295
  imports.push({ source, symbols: Array.from(FORBIDDEN_SYMBOLS) });
254
296
  }
255
297
  }
256
298
  return imports;
257
299
  }
300
+ function isRuntimeImportSource(source) {
301
+ return RUNTIME_HELPER_MODULES.has(source) || source.startsWith("@csszyx/runtime/") || CLIENT_RUNTIME_MODULES.has(source) || CLIENT_RUNTIME_MODULE_ROOTS.some((root) => source === root || source.startsWith(`${root}/`));
302
+ }
303
+ function isWholeRuntimeModuleForbidden(source) {
304
+ return source.startsWith("@csszyx/runtime/") || CLIENT_RUNTIME_MODULES.has(source) || CLIENT_RUNTIME_MODULE_ROOTS.some((root) => source === root || source.startsWith(`${root}/`));
305
+ }
306
+ function readRuntimeImportSymbols(source, clause) {
307
+ if (isWholeRuntimeModuleForbidden(source)) {
308
+ return Array.from(FORBIDDEN_SYMBOLS);
309
+ }
310
+ return readImportedSymbols(clause);
311
+ }
258
312
  function findLocalImportSources(code) {
259
313
  const out = [];
260
- const staticImportRe = /import\s+(?!type\b)(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
261
- const exportFromRe = /export\s+(?!type\b)(?:[\s\S]*?)\s+from\s+['"]([^'"]+)['"]/g;
314
+ const staticImportRe = /import\s+(?!type\b)(?:\S(?:.*\S)?\s+from\s+)?['"]([^'"]+)['"]/g;
315
+ const exportFromRe = /export\s+(?!type\b)\S(?:.*\S)?\s+from\s+['"]([^'"]+)['"]/g;
262
316
  const dynamicImportRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
263
317
  for (const re of [staticImportRe, exportFromRe, dynamicImportRe]) {
264
318
  for (const match of code.matchAll(re)) {
@@ -272,13 +326,25 @@ function findLocalImportSources(code) {
272
326
  }
273
327
  function normalizeModuleId(id) {
274
328
  const clean = id.split("?")[0] ?? id;
329
+ const cached = normalizedModuleIdCache.get(clean);
330
+ if (cached) {
331
+ return cached;
332
+ }
333
+ let normalized;
275
334
  try {
276
- return fs__namespace.realpathSync.native(clean).replace(/\\/g, "/");
335
+ normalized = fs__namespace.realpathSync.native(clean).replace(/\\/g, "/");
277
336
  } catch {
278
- return path__namespace.resolve(clean).replace(/\\/g, "/");
337
+ normalized = path__namespace.resolve(clean).replace(/\\/g, "/");
279
338
  }
339
+ normalizedModuleIdCache.set(clean, normalized);
340
+ return normalized;
280
341
  }
281
342
  function resolveLocalModule(importer, source) {
343
+ const cacheKey = `${importer}\0${source}`;
344
+ const cached = resolvedLocalModuleCache.get(cacheKey);
345
+ if (cached) {
346
+ return cached;
347
+ }
282
348
  const base = source.startsWith("/") ? source : path__namespace.resolve(path__namespace.dirname(importer), source);
283
349
  const candidates = [
284
350
  base,
@@ -295,11 +361,26 @@ function resolveLocalModule(importer, source) {
295
361
  ];
296
362
  for (const candidate of candidates) {
297
363
  if (fs__namespace.existsSync(candidate) && fs__namespace.statSync(candidate).isFile()) {
298
- return normalizeModuleId(candidate);
364
+ const resolved = normalizeModuleId(candidate);
365
+ resolvedLocalModuleCache.set(cacheKey, resolved);
366
+ return resolved;
299
367
  }
300
368
  }
301
369
  return null;
302
370
  }
371
+ function pruneRSCModulePathCaches(moduleIds) {
372
+ for (const [key, value] of normalizedModuleIdCache) {
373
+ const normalizedKey = key.replace(/\\/g, "/");
374
+ if (moduleIds.has(value) || moduleIds.has(normalizedKey)) {
375
+ normalizedModuleIdCache.delete(key);
376
+ }
377
+ }
378
+ for (const [key, value] of resolvedLocalModuleCache) {
379
+ if (moduleIds.has(value)) {
380
+ resolvedLocalModuleCache.delete(key);
381
+ }
382
+ }
383
+ }
303
384
  function readImportedSymbols(clause) {
304
385
  const symbols = [];
305
386
  const named = clause.match(/\{([\s\S]*?)\}/);
@@ -318,8 +399,72 @@ function readImportedSymbols(clause) {
318
399
  if (/\*\s+as\s+\w+/.test(clause)) {
319
400
  symbols.push(...FORBIDDEN_SYMBOLS);
320
401
  }
402
+ const braceStart = clause.indexOf("{");
403
+ const braceEnd = clause.indexOf("}", braceStart);
404
+ const stripped = braceStart !== -1 && braceEnd !== -1 ? clause.slice(0, braceStart) + clause.slice(braceEnd + 1) : clause;
405
+ const defaultImport = stripped.match(/^\s*([A-Z_$][\w$]*)\s*(?:,|$)/i);
406
+ const defaultSymbol = defaultImport?.[1];
407
+ if (defaultSymbol && FORBIDDEN_SYMBOLS.has(defaultSymbol)) {
408
+ symbols.push(defaultSymbol);
409
+ }
321
410
  return symbols;
322
411
  }
412
+ function stripCommentsForImportScan(code) {
413
+ let out = "";
414
+ let i = 0;
415
+ let quote = null;
416
+ let escaped = false;
417
+ while (i < code.length) {
418
+ const ch = code[i];
419
+ const next = code[i + 1];
420
+ if (quote) {
421
+ out += ch;
422
+ if (escaped) {
423
+ escaped = false;
424
+ } else if (ch === "\\") {
425
+ escaped = true;
426
+ } else if (ch === quote) {
427
+ quote = null;
428
+ }
429
+ i++;
430
+ continue;
431
+ }
432
+ if (ch === '"' || ch === "'" || ch === "`") {
433
+ quote = ch;
434
+ out += ch;
435
+ i++;
436
+ continue;
437
+ }
438
+ if (ch === "/" && next === "/") {
439
+ out += " ";
440
+ i += 2;
441
+ while (i < code.length && code[i] !== "\n") {
442
+ out += " ";
443
+ i++;
444
+ }
445
+ continue;
446
+ }
447
+ if (ch === "/" && next === "*") {
448
+ out += " ";
449
+ i += 2;
450
+ while (i < code.length) {
451
+ const blockCh = code[i];
452
+ const blockNext = code[i + 1];
453
+ if (blockCh === "*" && blockNext === "/") {
454
+ out += " ";
455
+ i += 2;
456
+ break;
457
+ }
458
+ out += blockCh === "\n" ? "\n" : " ";
459
+ i++;
460
+ }
461
+ continue;
462
+ }
463
+ out += ch;
464
+ i++;
465
+ }
466
+ return out;
467
+ }
323
468
 
324
469
  const EMPTY_THEME = { colors: [], spacings: [], fonts: [], radii: [], shadows: [] };
325
470
  function stripLayerWrappers(css) {
@@ -718,6 +863,149 @@ function writeThemeDts(opts) {
718
863
  fs.writeFileSync(opts.outputPath, content, "utf-8");
719
864
  }
720
865
 
866
+ const CACHE_SCHEMA_VERSION = 2;
867
+ function resolveTransformCacheDir(rootDir, cacheDir) {
868
+ return path__namespace.resolve(rootDir, cacheDir ?? ".csszyx/cache", "transform");
869
+ }
870
+ function createTransformCacheKey(input) {
871
+ const inputSha256 = node_crypto.createHash("sha256").update(input.source).digest("hex");
872
+ const keyMaterial = [
873
+ `schema=${CACHE_SCHEMA_VERSION}`,
874
+ `plugin=${input.pluginVersion}`,
875
+ `compiler=${input.compilerVersion}`,
876
+ `parser=${input.parserMode}`,
877
+ `producer=${input.producer}`,
878
+ `astBudget=${input.astBudget ?? "default"}`,
879
+ `filename=${input.filename}`,
880
+ `source=${inputSha256}`
881
+ ].join("\n");
882
+ return {
883
+ key: node_crypto.createHash("sha256").update(keyMaterial).digest("hex").slice(0, 16),
884
+ inputSha256
885
+ };
886
+ }
887
+ function readTransformCache(cacheRoot, input, precomputedKey) {
888
+ const { key, inputSha256 } = precomputedKey ?? createTransformCacheKey(input);
889
+ const file = cacheEntryPath(cacheRoot, key);
890
+ let entry;
891
+ try {
892
+ entry = JSON.parse(fs__namespace.readFileSync(file, "utf8"));
893
+ } catch {
894
+ return null;
895
+ }
896
+ if (entry.version !== CACHE_SCHEMA_VERSION || entry.pluginVersion !== input.pluginVersion || entry.compilerVersion !== input.compilerVersion || entry.parserMode !== input.parserMode || entry.producer !== input.producer || entry.astBudget !== (input.astBudget ?? null) || entry.filename !== input.filename || entry.inputSha256 !== inputSha256) {
897
+ return null;
898
+ }
899
+ return deserializeResult(entry.result);
900
+ }
901
+ function writeTransformCache(cacheRoot, input, result, precomputedKey) {
902
+ const { key, inputSha256 } = precomputedKey ?? createTransformCacheKey(input);
903
+ const file = cacheEntryPath(cacheRoot, key);
904
+ const dir = path__namespace.dirname(file);
905
+ const tmp = path__namespace.join(
906
+ dir,
907
+ `.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`
908
+ );
909
+ const entry = {
910
+ version: CACHE_SCHEMA_VERSION,
911
+ pluginVersion: input.pluginVersion,
912
+ compilerVersion: input.compilerVersion,
913
+ parserMode: input.parserMode,
914
+ producer: input.producer,
915
+ astBudget: input.astBudget ?? null,
916
+ filename: input.filename,
917
+ inputSha256,
918
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
919
+ result: serializeResult(result)
920
+ };
921
+ try {
922
+ fs__namespace.mkdirSync(dir, { recursive: true });
923
+ fs__namespace.writeFileSync(tmp, JSON.stringify(entry), "utf8");
924
+ fs__namespace.renameSync(tmp, file);
925
+ } catch {
926
+ try {
927
+ fs__namespace.rmSync(tmp, { force: true });
928
+ } catch {
929
+ }
930
+ }
931
+ }
932
+ function evictOldTransformCacheEntries(cacheRoot, options) {
933
+ let deleted = 0;
934
+ const now = options.now ?? Date.now();
935
+ const survivors = [];
936
+ for (const file of listJsonFiles(cacheRoot)) {
937
+ try {
938
+ const entry = JSON.parse(fs__namespace.readFileSync(file, "utf8"));
939
+ const timestamp = typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) : 0;
940
+ if (!Number.isFinite(timestamp) || now - timestamp > options.maxAgeMs) {
941
+ fs__namespace.rmSync(file, { force: true });
942
+ deleted++;
943
+ } else {
944
+ survivors.push({ file, timestamp });
945
+ }
946
+ } catch {
947
+ fs__namespace.rmSync(file, { force: true });
948
+ deleted++;
949
+ }
950
+ }
951
+ const overflow = survivors.length - options.maxEntries ;
952
+ if (overflow > 0) {
953
+ survivors.sort((a, b) => a.timestamp - b.timestamp);
954
+ for (const survivor of survivors.slice(0, overflow)) {
955
+ fs__namespace.rmSync(survivor.file, { force: true });
956
+ deleted++;
957
+ }
958
+ }
959
+ return deleted;
960
+ }
961
+ function cacheEntryPath(cacheRoot, key) {
962
+ return path__namespace.join(cacheRoot, key.slice(0, 2), `${key.slice(2)}.json`);
963
+ }
964
+ function serializeResult(result) {
965
+ return {
966
+ code: result.code,
967
+ transformed: result.transformed,
968
+ usesRuntime: result.usesRuntime,
969
+ usesMerge: result.usesMerge,
970
+ usesColorVar: result.usesColorVar,
971
+ classes: [...result.classes],
972
+ rawClassNames: [...result.rawClassNames],
973
+ diagnostics: [...result.diagnostics],
974
+ recoveryTokens: [...result.recoveryTokens]
975
+ };
976
+ }
977
+ function deserializeResult(result) {
978
+ return {
979
+ code: result.code,
980
+ transformed: result.transformed,
981
+ usesRuntime: result.usesRuntime,
982
+ usesMerge: result.usesMerge,
983
+ usesColorVar: result.usesColorVar,
984
+ classes: new Set(result.classes),
985
+ rawClassNames: new Set(result.rawClassNames),
986
+ diagnostics: [...result.diagnostics],
987
+ recoveryTokens: new Map(result.recoveryTokens)
988
+ };
989
+ }
990
+ function listJsonFiles(dir) {
991
+ let entries;
992
+ try {
993
+ entries = fs__namespace.readdirSync(dir, { withFileTypes: true });
994
+ } catch {
995
+ return [];
996
+ }
997
+ const files = [];
998
+ for (const entry of entries) {
999
+ const fullPath = path__namespace.join(dir, entry.name);
1000
+ if (entry.isDirectory()) {
1001
+ files.push(...listJsonFiles(fullPath));
1002
+ } else if (entry.isFile() && entry.name.endsWith(".json")) {
1003
+ files.push(fullPath);
1004
+ }
1005
+ }
1006
+ return files;
1007
+ }
1008
+
721
1009
  const VIRTUAL_MODULE_ID = "virtual:csszyx/mangle-map";
722
1010
  const RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
723
1011
  const VIRTUAL_CHECKSUM_ID = "virtual:csszyx/checksum";
@@ -768,7 +1056,37 @@ function resolveVirtualModule(id) {
768
1056
 
769
1057
  const CHECKSUM_PLACEHOLDER = "___CSSZYX_CHECKSUM___";
770
1058
  const MANGLE_MAP_PLACEHOLDER = "___CSSZYX_MANGLE_MAP___";
1059
+ const UNKNOWN_PACKAGE_VERSION = "0.0.0";
1060
+ const TRANSFORM_CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
1061
+ const TRANSFORM_CACHE_MAX_ENTRIES = 1e4;
1062
+ const TRANSFORM_MEMORY_CACHE_MAX_ENTRIES = 1e3;
1063
+ const DIRECTIVE_PROLOGUE_PREFIX_RE = /^((?:\s|\/\/[^\n]*\n|\/\*(?:[^*]|\*(?!\/))*\*\/)*)(['"]use (?:client|server)['"];?\s*)/;
1064
+ const RUNTIME_HELPER_IMPORT_RE = {
1065
+ _sz: /\{[^}]*\b_sz\b[^}]*\}\s*from\s*['"]@csszyx\/runtime['"]/,
1066
+ _szMerge: /\{[^}]*\b_szMerge\b[^}]*\}\s*from\s*['"]@csszyx\/runtime['"]/,
1067
+ __szColorVar: /\{[^}]*\b__szColorVar\b[^}]*\}\s*from\s*['"]@csszyx\/runtime['"]/
1068
+ };
771
1069
  let _hasWarnedTsConfig = false;
1070
+ let _hasWarnedTransformCacheVersion = false;
1071
+ const requireFromHere = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/unplugin.CL0F6RZa.cjs', document.baseURI).href)));
1072
+ const PLUGIN_VERSION = findPackageVersionFromFile(
1073
+ node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/unplugin.CL0F6RZa.cjs', document.baseURI).href))),
1074
+ UNKNOWN_PACKAGE_VERSION
1075
+ );
1076
+ const COMPILER_VERSION = findPackageVersionFromModule("@csszyx/compiler", UNKNOWN_PACKAGE_VERSION);
1077
+ const BENCH_TRACE_ENABLED = process.env.CSSZYX_BENCH_TRACE === "1";
1078
+ const BENCH_TRACE_FILE = process.env.CSSZYX_BENCH_TRACE_FILE;
1079
+ function traceBenchTiming(label, filename, elapsedMs) {
1080
+ if (!BENCH_TRACE_ENABLED) {
1081
+ return;
1082
+ }
1083
+ if (BENCH_TRACE_FILE && !filename.includes(BENCH_TRACE_FILE)) {
1084
+ return;
1085
+ }
1086
+ console.log(
1087
+ `[csszyx:bench] ${label} ${elapsedMs.toFixed(3)}ms ${normalizeSourceFilename(filename)}`
1088
+ );
1089
+ }
772
1090
  function runThemeScan(rootDir, scanCss) {
773
1091
  if (!scanCss) {
774
1092
  return;
@@ -811,6 +1129,42 @@ function runThemeScan(rootDir, scanCss) {
811
1129
  }
812
1130
  }
813
1131
  }
1132
+ function findPackageVersionFromModule(specifier, fallback) {
1133
+ try {
1134
+ return findPackageVersionFromFile(requireFromHere.resolve(specifier), fallback);
1135
+ } catch {
1136
+ return fallback;
1137
+ }
1138
+ }
1139
+ function findPackageVersionFromFile(file, fallback) {
1140
+ let dir = path__namespace.dirname(file);
1141
+ while (true) {
1142
+ const packageJson = path__namespace.join(dir, "package.json");
1143
+ try {
1144
+ const parsed = JSON.parse(fs__namespace.readFileSync(packageJson, "utf8"));
1145
+ if (typeof parsed.version === "string") {
1146
+ return parsed.version;
1147
+ }
1148
+ return fallback;
1149
+ } catch {
1150
+ }
1151
+ const parent = path__namespace.dirname(dir);
1152
+ if (parent === dir) {
1153
+ return fallback;
1154
+ }
1155
+ dir = parent;
1156
+ }
1157
+ }
1158
+ function normalizeSourceFilename(filename) {
1159
+ return filename.replace(/\\/g, "/");
1160
+ }
1161
+ function insertRuntimeImport(code, importStmt) {
1162
+ const directiveMatch = code.match(DIRECTIVE_PROLOGUE_PREFIX_RE);
1163
+ if (!directiveMatch) {
1164
+ return `${importStmt}${code}`;
1165
+ }
1166
+ return code.replace(directiveMatch[0], `${directiveMatch[1]}${directiveMatch[2]}${importStmt}`);
1167
+ }
814
1168
  function mangleCodeClassesSync(code, mangleMap) {
815
1169
  function mangleClassString(classString) {
816
1170
  return classString.split(/\s+/).filter(Boolean).map((cls) => {
@@ -986,8 +1340,20 @@ function mangleCodeClassesSync(code, mangleMap) {
986
1340
  function createCsszyxPlugins(options = {}) {
987
1341
  const manglingEnabled = options.production?.mangle !== false;
988
1342
  const astBudgetOverride = options.build?.astBudgetLimit;
1343
+ const cacheRequested = (options.build?.cache ?? types.DEFAULT_BUILD_CONFIG.cache) !== false;
1344
+ const cacheVersionsKnown = PLUGIN_VERSION !== UNKNOWN_PACKAGE_VERSION && COMPILER_VERSION !== UNKNOWN_PACKAGE_VERSION;
1345
+ const cacheEnabled = cacheRequested && cacheVersionsKnown;
1346
+ if (cacheRequested && !cacheVersionsKnown && !_hasWarnedTransformCacheVersion) {
1347
+ _hasWarnedTransformCacheVersion = true;
1348
+ console.warn(
1349
+ "[csszyx] Transform cache disabled because package versions could not be resolved."
1350
+ );
1351
+ }
989
1352
  const parserOverride = process.env.CSSZYX_PARSER;
990
- const parserMode = parserOverride === "babel" || parserOverride === "oxc" ? parserOverride : options.build?.parser ?? "oxc";
1353
+ const defaultParser = types.DEFAULT_BUILD_CONFIG.parser ?? "rust";
1354
+ const parserMode = parserOverride === "babel" || parserOverride === "oxc" || parserOverride === "rust" ? parserOverride : options.build?.parser ?? defaultParser;
1355
+ let evictedCacheRoot = null;
1356
+ const transformMemoryCache = /* @__PURE__ */ new Map();
991
1357
  const state = {
992
1358
  classes: /* @__PURE__ */ new Set(),
993
1359
  mangleMap: {},
@@ -1017,19 +1383,84 @@ function createCsszyxPlugins(options = {}) {
1017
1383
  }
1018
1384
  function transformConfiguredSource(source, filename) {
1019
1385
  const compilerOptions = { astBudget: astBudgetOverride };
1020
- if (parserMode !== "oxc") {
1021
- return compiler.transformSourceCode(source, filename, compilerOptions);
1386
+ const effectiveFilename = normalizeSourceFilename(filename);
1387
+ const cacheRoot = resolveTransformCacheDir(state.rootDir, options.build?.cacheDir);
1388
+ if (cacheEnabled) {
1389
+ evictTransformCacheOnce();
1390
+ }
1391
+ const cacheInput = {
1392
+ pluginVersion: PLUGIN_VERSION,
1393
+ compilerVersion: COMPILER_VERSION,
1394
+ parserMode,
1395
+ producer: parserMode,
1396
+ astBudget: astBudgetOverride,
1397
+ filename: effectiveFilename,
1398
+ source
1399
+ };
1400
+ if (parserMode === "rust") {
1401
+ compiler.ensureRustTransformAvailable();
1402
+ }
1403
+ const cacheKey = cacheEnabled ? createTransformCacheKey(cacheInput) : null;
1404
+ if (cacheEnabled && cacheKey) {
1405
+ const memoryCached = transformMemoryCache.get(cacheKey.key);
1406
+ if (memoryCached) {
1407
+ transformMemoryCache.delete(cacheKey.key);
1408
+ transformMemoryCache.set(cacheKey.key, memoryCached);
1409
+ return memoryCached;
1410
+ }
1411
+ const cached = readTransformCache(cacheRoot, cacheInput, cacheKey);
1412
+ if (cached) {
1413
+ rememberTransformCacheEntry(cacheKey.key, cached);
1414
+ return cached;
1415
+ }
1022
1416
  }
1023
- try {
1024
- return compiler.transformOxc(source, filename, compilerOptions);
1025
- } catch (err) {
1026
- const result = compiler.transformSourceCode(source, filename, compilerOptions);
1027
- const reason = err instanceof Error ? err.message : String(err);
1028
- result.diagnostics.push(
1029
- `[csszyx] oxc parser fell back to Babel for ${filename}: ${reason}`
1030
- );
1031
- return result;
1417
+ let result;
1418
+ if (parserMode === "babel") {
1419
+ result = compiler.transformSourceCode(source, effectiveFilename, compilerOptions);
1420
+ } else if (parserMode === "rust") {
1421
+ result = compiler.transformRust(source, effectiveFilename, compilerOptions);
1422
+ } else {
1423
+ try {
1424
+ result = compiler.transformOxc(source, effectiveFilename, compilerOptions);
1425
+ } catch (err) {
1426
+ result = compiler.transformSourceCode(source, effectiveFilename, compilerOptions);
1427
+ const reason = err instanceof Error ? err.message : String(err);
1428
+ result.diagnostics.push(
1429
+ `[csszyx] oxc parser fell back to Babel for ${effectiveFilename}: ${reason}`
1430
+ );
1431
+ return result;
1432
+ }
1032
1433
  }
1434
+ if (cacheEnabled && cacheKey) {
1435
+ writeTransformCache(cacheRoot, cacheInput, result, cacheKey);
1436
+ rememberTransformCacheEntry(cacheKey.key, result);
1437
+ }
1438
+ return result;
1439
+ }
1440
+ function rememberTransformCacheEntry(key, result) {
1441
+ transformMemoryCache.delete(key);
1442
+ transformMemoryCache.set(key, result);
1443
+ if (transformMemoryCache.size <= TRANSFORM_MEMORY_CACHE_MAX_ENTRIES) {
1444
+ return;
1445
+ }
1446
+ const oldest = transformMemoryCache.keys().next().value;
1447
+ if (oldest) {
1448
+ transformMemoryCache.delete(oldest);
1449
+ }
1450
+ }
1451
+ function evictTransformCacheOnce() {
1452
+ if (!cacheEnabled) {
1453
+ return;
1454
+ }
1455
+ const cacheRoot = resolveTransformCacheDir(state.rootDir, options.build?.cacheDir);
1456
+ if (evictedCacheRoot === cacheRoot) {
1457
+ return;
1458
+ }
1459
+ evictedCacheRoot = cacheRoot;
1460
+ evictOldTransformCacheEntries(cacheRoot, {
1461
+ maxAgeMs: TRANSFORM_CACHE_MAX_AGE_MS,
1462
+ maxEntries: TRANSFORM_CACHE_MAX_ENTRIES
1463
+ });
1033
1464
  }
1034
1465
  function writeSafelistFile(classes) {
1035
1466
  if (classes.size === 0) {
@@ -1311,7 +1742,13 @@ ${sourceDirective}`
1311
1742
  transformed = true;
1312
1743
  }
1313
1744
  } else {
1745
+ const transformStarted = node_perf_hooks.performance.now();
1314
1746
  const result = transformConfiguredSource(code, id);
1747
+ traceBenchTiming(
1748
+ "transform-hook",
1749
+ id,
1750
+ node_perf_hooks.performance.now() - transformStarted
1751
+ );
1315
1752
  transformedCode = result.code;
1316
1753
  usesRuntime = result.usesRuntime;
1317
1754
  usesMerge = result.usesMerge;
@@ -1355,11 +1792,10 @@ ${sourceDirective}`
1355
1792
  if (usesColorVar) {
1356
1793
  imports.push("__szColorVar");
1357
1794
  }
1358
- const needed = imports.filter(
1359
- (name) => !new RegExp(
1360
- `\\{[^}]*\\b${name}\\b[^}]*\\}\\s*from\\s*['"]@csszyx/runtime['"]`
1361
- ).test(transformedCode)
1362
- );
1795
+ const hasRuntimeImport = imports.length > 0 && transformedCode.includes("@csszyx/runtime");
1796
+ const needed = hasRuntimeImport ? imports.filter(
1797
+ (name) => !RUNTIME_HELPER_IMPORT_RE[name]?.test(transformedCode)
1798
+ ) : imports;
1363
1799
  if (needed.length > 0) {
1364
1800
  const existingImport = transformedCode.match(
1365
1801
  /^(import\s*\{[^}]*)\}\s*from\s*'@csszyx\/runtime'/m
@@ -1372,18 +1808,7 @@ ${sourceDirective}`
1372
1808
  } else {
1373
1809
  const importStmt = `import { ${needed.join(", ")} } from '@csszyx/runtime';
1374
1810
  `;
1375
- const directiveMatch = transformedCode.match(
1376
- /^['"]use (client|server)['"];?\s*/
1377
- );
1378
- if (directiveMatch) {
1379
- const directive = directiveMatch[0];
1380
- transformedCode = transformedCode.replace(
1381
- directive,
1382
- `${directive}${importStmt}`
1383
- );
1384
- } else {
1385
- transformedCode = `${importStmt}${transformedCode}`;
1386
- }
1811
+ transformedCode = insertRuntimeImport(transformedCode, importStmt);
1387
1812
  }
1388
1813
  transformed = true;
1389
1814
  }
@@ -1413,6 +1838,11 @@ ${sourceDirective}`
1413
1838
  globalThis.__csszyx_ssr_mangle_map = state.mangleMap;
1414
1839
  }
1415
1840
  },
1841
+ watchChange(id, change) {
1842
+ if (change.event === "delete") {
1843
+ deleteRSCModuleRecord(state.rscModules, id);
1844
+ }
1845
+ },
1416
1846
  /**
1417
1847
  * Webpack hook: pre-scans source files before compilation for Tailwind class discovery.
1418
1848
  * @param compiler - the Webpack compiler instance
@@ -1421,6 +1851,7 @@ ${sourceDirective}`
1421
1851
  compiler.hooks.beforeCompile.tap("csszyx:prescan", () => {
1422
1852
  const root = compiler.context || process.cwd();
1423
1853
  state.rootDir = root;
1854
+ evictTransformCacheOnce();
1424
1855
  if (state.classes.size === 0) {
1425
1856
  prescanAndWriteClasses();
1426
1857
  }
@@ -1444,6 +1875,7 @@ ${sourceDirective}`
1444
1875
  configResolved(config) {
1445
1876
  const root = config.root || process.cwd();
1446
1877
  state.rootDir = root;
1878
+ evictTransformCacheOnce();
1447
1879
  prescanAndWriteClasses();
1448
1880
  runThemeScan(root, options.build?.scanCss);
1449
1881
  },
@@ -1473,7 +1905,13 @@ ${sourceDirective}`
1473
1905
  return;
1474
1906
  }
1475
1907
  try {
1908
+ const hmrTransformStarted = node_perf_hooks.performance.now();
1476
1909
  result = transformConfiguredSource(fileContent, ctx.file);
1910
+ traceBenchTiming(
1911
+ "handle-hot-update",
1912
+ ctx.file,
1913
+ node_perf_hooks.performance.now() - hmrTransformStarted
1914
+ );
1477
1915
  } catch {
1478
1916
  return;
1479
1917
  }
@@ -1737,6 +2175,7 @@ const esbuildPlugin = (options = {}) => {
1737
2175
  exports.assertNoRSCBoundaryViolation = assertNoRSCBoundaryViolation;
1738
2176
  exports.assertNoRSCGraphViolation = assertNoRSCGraphViolation;
1739
2177
  exports.createRSCModuleRecord = createRSCModuleRecord;
2178
+ exports.deleteRSCModuleRecord = deleteRSCModuleRecord;
1740
2179
  exports.esbuildPlugin = esbuildPlugin;
1741
2180
  exports.findRSCBoundaryViolation = findRSCBoundaryViolation;
1742
2181
  exports.findRSCGraphViolation = findRSCGraphViolation;