@csszyx/unplugin 0.7.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.
- package/README.md +32 -1
- package/dist/css-mangler.cjs +27 -72
- package/dist/css-mangler.d.cts +2 -1
- package/dist/{css-mangler.d.ts → css-mangler.d.mts} +2 -1
- package/dist/{chunk-4M7CPGP7.js → css-mangler.mjs} +13 -31
- package/dist/index.cjs +47 -1856
- package/dist/index.d.cts +15 -2
- package/dist/{index.d.ts → index.d.mts} +13 -3
- package/dist/index.mjs +16 -0
- package/dist/shared/unplugin.BEOG6ePC.mjs +2159 -0
- package/dist/shared/unplugin.CL0F6RZa.cjs +2192 -0
- package/dist/vite.cjs +24 -1678
- package/dist/vite.d.cts +2 -2
- package/dist/{vite.d.ts → vite.d.mts} +1 -1
- package/dist/vite.mjs +20 -0
- package/dist/webpack.cjs +24 -1687
- package/dist/webpack.d.cts +2 -2
- package/dist/{webpack.d.ts → webpack.d.mts} +1 -1
- package/dist/webpack.mjs +20 -0
- package/package.json +51 -27
- package/dist/chunk-GXGGTRUA.js +0 -1602
- package/dist/css-mangler.js +0 -14
- package/dist/index.js +0 -51
- package/dist/vite.js +0 -10
- package/dist/webpack.js +0 -10
- /package/dist/{unplugin-DUbr5w-N.d.cts → shared/unplugin.DUbr5w-N.d.cts} +0 -0
- /package/dist/{unplugin-DUbr5w-N.d.ts → shared/unplugin.DUbr5w-N.d.mts} +0 -0
package/dist/chunk-GXGGTRUA.js
DELETED
|
@@ -1,1602 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
mangleCSSSync
|
|
3
|
-
} from "./chunk-4M7CPGP7.js";
|
|
4
|
-
|
|
5
|
-
// src/rsc-boundary.ts
|
|
6
|
-
import * as fs from "fs";
|
|
7
|
-
import * as path from "path";
|
|
8
|
-
var SERVER_DIRECTIVE_RE = /^['"]use server['"];?$/;
|
|
9
|
-
var CLIENT_DIRECTIVE_RE = /^['"]use client['"];?$/;
|
|
10
|
-
var RUNTIME_MODULES = /* @__PURE__ */ new Set([
|
|
11
|
-
"@csszyx/runtime",
|
|
12
|
-
"@csszyx/runtime/lite",
|
|
13
|
-
"csszyx",
|
|
14
|
-
"csszyx/lite"
|
|
15
|
-
]);
|
|
16
|
-
var FORBIDDEN_SYMBOLS = /* @__PURE__ */ new Set([
|
|
17
|
-
"_sz",
|
|
18
|
-
"_sz2",
|
|
19
|
-
"_sz3",
|
|
20
|
-
"_szIf",
|
|
21
|
-
"_szMerge",
|
|
22
|
-
"_szSwitch",
|
|
23
|
-
"__csszyx_runtime__"
|
|
24
|
-
]);
|
|
25
|
-
function hasUseServerDirective(code) {
|
|
26
|
-
for (const statement of readDirectivePrologue(code)) {
|
|
27
|
-
if (SERVER_DIRECTIVE_RE.test(statement)) {
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
if (CLIENT_DIRECTIVE_RE.test(statement)) {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
function hasUseClientDirective(code) {
|
|
37
|
-
for (const statement of readDirectivePrologue(code)) {
|
|
38
|
-
if (CLIENT_DIRECTIVE_RE.test(statement)) {
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
if (SERVER_DIRECTIVE_RE.test(statement)) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
function isRSCServerModule(code, id) {
|
|
48
|
-
if (hasUseServerDirective(code)) {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
if (hasUseClientDirective(code)) {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
return isNextAppRouterEntry(id);
|
|
55
|
-
}
|
|
56
|
-
function findRSCBoundaryViolation(code, id) {
|
|
57
|
-
if (!isRSCServerModule(code, id)) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
for (const imported of findRuntimeImports(code)) {
|
|
61
|
-
for (const symbol of imported.symbols) {
|
|
62
|
-
if (FORBIDDEN_SYMBOLS.has(symbol)) {
|
|
63
|
-
return {
|
|
64
|
-
symbol,
|
|
65
|
-
path: id,
|
|
66
|
-
importChain: [id, imported.source]
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
function createRSCModuleRecord(code, id) {
|
|
74
|
-
const normalized = normalizeModuleId(id);
|
|
75
|
-
return {
|
|
76
|
-
id: normalized,
|
|
77
|
-
isServer: isRSCServerModule(code, normalized),
|
|
78
|
-
isClient: hasUseClientDirective(code),
|
|
79
|
-
imports: findLocalImportSources(code).map((source) => resolveLocalModule(normalized, source)).filter((resolved) => resolved !== null),
|
|
80
|
-
runtimeImports: findRuntimeImports(code).filter((imported) => imported.symbols.some((symbol) => FORBIDDEN_SYMBOLS.has(symbol)))
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function findRSCGraphViolation(records) {
|
|
84
|
-
for (const root of records.values()) {
|
|
85
|
-
if (!root.isServer) {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
const violation = walkRSCGraph(root, records, [root.id], /* @__PURE__ */ new Set([root.id]));
|
|
89
|
-
if (violation) {
|
|
90
|
-
return {
|
|
91
|
-
symbol: violation.symbol,
|
|
92
|
-
path: root.id,
|
|
93
|
-
importChain: violation.importChain
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
function assertNoRSCGraphViolation(records) {
|
|
100
|
-
const violation = findRSCGraphViolation(records);
|
|
101
|
-
if (!violation) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
throw new Error(formatRSCViolation(violation));
|
|
105
|
-
}
|
|
106
|
-
function isNextAppRouterEntry(id) {
|
|
107
|
-
const clean = id.split("?")[0]?.replace(/\\/g, "/") ?? id;
|
|
108
|
-
return /(^|\/)app\/.*\/?(?:page|layout|template|loading|error|not-found|global-error|default|route)\.[cm]?[tj]sx?$/.test(clean);
|
|
109
|
-
}
|
|
110
|
-
function assertNoRSCBoundaryViolation(code, id) {
|
|
111
|
-
const violation = findRSCBoundaryViolation(code, id);
|
|
112
|
-
if (!violation) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
throw new Error(formatRSCViolation(violation));
|
|
116
|
-
}
|
|
117
|
-
function formatRSCViolation(violation) {
|
|
118
|
-
return `csszyxRSCViolation: ${violation.symbol} imported in Server Component ${violation.path}
|
|
119
|
-
Import chain: ${violation.importChain.join(" -> ")}`;
|
|
120
|
-
}
|
|
121
|
-
function walkRSCGraph(current, records, chain, seen) {
|
|
122
|
-
if (current.isClient) {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
const runtime = current.runtimeImports[0];
|
|
126
|
-
const symbol = runtime?.symbols.find((s) => FORBIDDEN_SYMBOLS.has(s));
|
|
127
|
-
if (runtime && symbol) {
|
|
128
|
-
return {
|
|
129
|
-
symbol,
|
|
130
|
-
path: chain[0] ?? current.id,
|
|
131
|
-
importChain: [...chain, runtime.source]
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
for (const importedId of current.imports) {
|
|
135
|
-
if (seen.has(importedId)) {
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
const next = records.get(importedId);
|
|
139
|
-
if (!next) {
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
seen.add(importedId);
|
|
143
|
-
const violation = walkRSCGraph(next, records, [...chain, importedId], seen);
|
|
144
|
-
if (violation) {
|
|
145
|
-
return violation;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
function readDirectivePrologue(code) {
|
|
151
|
-
const out = [];
|
|
152
|
-
let i = code.charCodeAt(0) === 65279 ? 1 : 0;
|
|
153
|
-
while (i < code.length) {
|
|
154
|
-
i = skipWhitespaceAndComments(code, i);
|
|
155
|
-
const quote = code[i];
|
|
156
|
-
if (quote !== '"' && quote !== "'") {
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
let j = i + 1;
|
|
160
|
-
let escaped = false;
|
|
161
|
-
while (j < code.length) {
|
|
162
|
-
const ch = code[j];
|
|
163
|
-
if (escaped) {
|
|
164
|
-
escaped = false;
|
|
165
|
-
} else if (ch === "\\") {
|
|
166
|
-
escaped = true;
|
|
167
|
-
} else if (ch === quote) {
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
170
|
-
j++;
|
|
171
|
-
}
|
|
172
|
-
if (j >= code.length) {
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
|
-
let end = j + 1;
|
|
176
|
-
while (end < code.length && /[ \t\r\n]/.test(code[end])) {
|
|
177
|
-
end++;
|
|
178
|
-
}
|
|
179
|
-
if (code[end] === ";") {
|
|
180
|
-
end++;
|
|
181
|
-
}
|
|
182
|
-
out.push(code.slice(i, end).trim());
|
|
183
|
-
i = end;
|
|
184
|
-
}
|
|
185
|
-
return out;
|
|
186
|
-
}
|
|
187
|
-
function skipWhitespaceAndComments(code, start) {
|
|
188
|
-
let i = start;
|
|
189
|
-
while (i < code.length) {
|
|
190
|
-
while (i < code.length && /\s/.test(code[i])) {
|
|
191
|
-
i++;
|
|
192
|
-
}
|
|
193
|
-
if (code.startsWith("//", i)) {
|
|
194
|
-
const next = code.indexOf("\n", i + 2);
|
|
195
|
-
i = next === -1 ? code.length : next + 1;
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
if (code.startsWith("/*", i)) {
|
|
199
|
-
const next = code.indexOf("*/", i + 2);
|
|
200
|
-
i = next === -1 ? code.length : next + 2;
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
return i;
|
|
206
|
-
}
|
|
207
|
-
function findRuntimeImports(code) {
|
|
208
|
-
const imports = [];
|
|
209
|
-
const staticImportRe = /import\s+(?!type\b)([\s\S]*?)\s+from\s+['"]([^'"]+)['"]/g;
|
|
210
|
-
const sideEffectImportRe = /import\s+['"]([^'"]+)['"]/g;
|
|
211
|
-
const dynamicImportRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
212
|
-
let match;
|
|
213
|
-
while ((match = staticImportRe.exec(code)) !== null) {
|
|
214
|
-
const clause = match[1];
|
|
215
|
-
const source = match[2];
|
|
216
|
-
if (!RUNTIME_MODULES.has(source)) {
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
imports.push({ source, symbols: readImportedSymbols(clause) });
|
|
220
|
-
}
|
|
221
|
-
while ((match = sideEffectImportRe.exec(code)) !== null) {
|
|
222
|
-
const source = match[1];
|
|
223
|
-
if (RUNTIME_MODULES.has(source)) {
|
|
224
|
-
imports.push({ source, symbols: [] });
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
while ((match = dynamicImportRe.exec(code)) !== null) {
|
|
228
|
-
const source = match[1];
|
|
229
|
-
if (RUNTIME_MODULES.has(source)) {
|
|
230
|
-
imports.push({ source, symbols: Array.from(FORBIDDEN_SYMBOLS) });
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return imports;
|
|
234
|
-
}
|
|
235
|
-
function findLocalImportSources(code) {
|
|
236
|
-
const out = [];
|
|
237
|
-
const staticImportRe = /import\s+(?!type\b)(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
238
|
-
const exportFromRe = /export\s+(?!type\b)(?:[\s\S]*?)\s+from\s+['"]([^'"]+)['"]/g;
|
|
239
|
-
const dynamicImportRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
240
|
-
let match;
|
|
241
|
-
for (const re of [staticImportRe, exportFromRe, dynamicImportRe]) {
|
|
242
|
-
while ((match = re.exec(code)) !== null) {
|
|
243
|
-
const source = match[1];
|
|
244
|
-
if (source.startsWith(".") || source.startsWith("/")) {
|
|
245
|
-
out.push(source);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return out;
|
|
250
|
-
}
|
|
251
|
-
function normalizeModuleId(id) {
|
|
252
|
-
const clean = id.split("?")[0] ?? id;
|
|
253
|
-
try {
|
|
254
|
-
return fs.realpathSync.native(clean).replace(/\\/g, "/");
|
|
255
|
-
} catch {
|
|
256
|
-
return path.resolve(clean).replace(/\\/g, "/");
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
function resolveLocalModule(importer, source) {
|
|
260
|
-
const base = source.startsWith("/") ? source : path.resolve(path.dirname(importer), source);
|
|
261
|
-
const candidates = [
|
|
262
|
-
base,
|
|
263
|
-
`${base}.tsx`,
|
|
264
|
-
`${base}.ts`,
|
|
265
|
-
`${base}.jsx`,
|
|
266
|
-
`${base}.js`,
|
|
267
|
-
`${base}.mjs`,
|
|
268
|
-
`${base}.cjs`,
|
|
269
|
-
path.join(base, "index.tsx"),
|
|
270
|
-
path.join(base, "index.ts"),
|
|
271
|
-
path.join(base, "index.jsx"),
|
|
272
|
-
path.join(base, "index.js")
|
|
273
|
-
];
|
|
274
|
-
for (const candidate of candidates) {
|
|
275
|
-
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
276
|
-
return normalizeModuleId(candidate);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return null;
|
|
280
|
-
}
|
|
281
|
-
function readImportedSymbols(clause) {
|
|
282
|
-
const symbols = [];
|
|
283
|
-
const named = clause.match(/\{([\s\S]*?)\}/);
|
|
284
|
-
if (named) {
|
|
285
|
-
for (const part of named[1].split(",")) {
|
|
286
|
-
const trimmed = part.trim();
|
|
287
|
-
if (!trimmed || trimmed.startsWith("type ")) {
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
const sourceName = trimmed.replace(/^type\s+/, "").split(/\s+as\s+/)[0]?.trim();
|
|
291
|
-
if (sourceName) {
|
|
292
|
-
symbols.push(sourceName);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
if (/\*\s+as\s+\w+/.test(clause)) {
|
|
297
|
-
symbols.push(...FORBIDDEN_SYMBOLS);
|
|
298
|
-
}
|
|
299
|
-
return symbols;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// src/theme-scanner.ts
|
|
303
|
-
var EMPTY_THEME = { colors: [], spacings: [], fonts: [], radii: [], shadows: [] };
|
|
304
|
-
function stripLayerWrappers(css) {
|
|
305
|
-
let result = "";
|
|
306
|
-
let i = 0;
|
|
307
|
-
while (i < css.length) {
|
|
308
|
-
const layerIdx = css.indexOf("@layer", i);
|
|
309
|
-
if (layerIdx === -1) {
|
|
310
|
-
result += css.slice(i);
|
|
311
|
-
break;
|
|
312
|
-
}
|
|
313
|
-
result += css.slice(i, layerIdx);
|
|
314
|
-
const openBrace = css.indexOf("{", layerIdx);
|
|
315
|
-
if (openBrace === -1) {
|
|
316
|
-
result += css.slice(layerIdx);
|
|
317
|
-
break;
|
|
318
|
-
}
|
|
319
|
-
let depth = 0;
|
|
320
|
-
let j = openBrace;
|
|
321
|
-
while (j < css.length) {
|
|
322
|
-
if (css[j] === "{") {
|
|
323
|
-
depth++;
|
|
324
|
-
}
|
|
325
|
-
if (css[j] === "}") {
|
|
326
|
-
depth--;
|
|
327
|
-
if (depth === 0) {
|
|
328
|
-
result += css.slice(openBrace + 1, j);
|
|
329
|
-
i = j + 1;
|
|
330
|
-
break;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
j++;
|
|
334
|
-
}
|
|
335
|
-
if (depth !== 0) {
|
|
336
|
-
result += css.slice(openBrace);
|
|
337
|
-
break;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
return result;
|
|
341
|
-
}
|
|
342
|
-
function extractThemeBlocks(css) {
|
|
343
|
-
const blocks = [];
|
|
344
|
-
const themeStart = /@theme\s+(?:inline\s+)?\{|@theme\{/g;
|
|
345
|
-
let match;
|
|
346
|
-
while ((match = themeStart.exec(css)) !== null) {
|
|
347
|
-
const openPos = css.indexOf("{", match.index);
|
|
348
|
-
let depth = 0;
|
|
349
|
-
let j = openPos;
|
|
350
|
-
while (j < css.length) {
|
|
351
|
-
if (css[j] === "{") {
|
|
352
|
-
depth++;
|
|
353
|
-
}
|
|
354
|
-
if (css[j] === "}") {
|
|
355
|
-
depth--;
|
|
356
|
-
if (depth === 0) {
|
|
357
|
-
blocks.push(css.slice(openPos + 1, j));
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
j++;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
return blocks;
|
|
365
|
-
}
|
|
366
|
-
function categorizeProperty(prop) {
|
|
367
|
-
const categoryMap = [
|
|
368
|
-
["color-", "colors"],
|
|
369
|
-
["spacing-", "spacings"],
|
|
370
|
-
["font-", "fonts"],
|
|
371
|
-
["radius-", "radii"],
|
|
372
|
-
["shadow-", "shadows"]
|
|
373
|
-
];
|
|
374
|
-
for (const [prefix, category] of categoryMap) {
|
|
375
|
-
if (prop.startsWith(prefix)) {
|
|
376
|
-
let token = prop.slice(prefix.length);
|
|
377
|
-
token = token.replace(/-\d+$/, "");
|
|
378
|
-
if (token) {
|
|
379
|
-
return { category, token };
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
function parseThemeBlocks(cssContent) {
|
|
386
|
-
const result = {
|
|
387
|
-
colors: /* @__PURE__ */ new Set(),
|
|
388
|
-
spacings: /* @__PURE__ */ new Set(),
|
|
389
|
-
fonts: /* @__PURE__ */ new Set(),
|
|
390
|
-
radii: /* @__PURE__ */ new Set(),
|
|
391
|
-
shadows: /* @__PURE__ */ new Set()
|
|
392
|
-
};
|
|
393
|
-
const stripped = stripLayerWrappers(cssContent);
|
|
394
|
-
const blocks = extractThemeBlocks(stripped);
|
|
395
|
-
const propPattern = /--([a-z][a-z0-9-]*)(?:\s*:[^;]+)?;/g;
|
|
396
|
-
for (const block of blocks) {
|
|
397
|
-
let match;
|
|
398
|
-
while ((match = propPattern.exec(block)) !== null) {
|
|
399
|
-
const categorized = categorizeProperty(match[1]);
|
|
400
|
-
if (categorized) {
|
|
401
|
-
result[categorized.category].add(categorized.token);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
propPattern.lastIndex = 0;
|
|
405
|
-
}
|
|
406
|
-
return {
|
|
407
|
-
colors: [...result.colors].sort(),
|
|
408
|
-
spacings: [...result.spacings].sort(),
|
|
409
|
-
fonts: [...result.fonts].sort(),
|
|
410
|
-
radii: [...result.radii].sort(),
|
|
411
|
-
shadows: [...result.shadows].sort()
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
function mergeThemes(themes) {
|
|
415
|
-
if (themes.length === 0) {
|
|
416
|
-
return { ...EMPTY_THEME };
|
|
417
|
-
}
|
|
418
|
-
const merged = {
|
|
419
|
-
colors: /* @__PURE__ */ new Set(),
|
|
420
|
-
spacings: /* @__PURE__ */ new Set(),
|
|
421
|
-
fonts: /* @__PURE__ */ new Set(),
|
|
422
|
-
radii: /* @__PURE__ */ new Set(),
|
|
423
|
-
shadows: /* @__PURE__ */ new Set()
|
|
424
|
-
};
|
|
425
|
-
for (const theme of themes) {
|
|
426
|
-
for (const cat of Object.keys(merged)) {
|
|
427
|
-
for (const token of theme[cat]) {
|
|
428
|
-
merged[cat].add(token);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return {
|
|
433
|
-
colors: [...merged.colors].sort(),
|
|
434
|
-
spacings: [...merged.spacings].sort(),
|
|
435
|
-
fonts: [...merged.fonts].sort(),
|
|
436
|
-
radii: [...merged.radii].sort(),
|
|
437
|
-
shadows: [...merged.shadows].sort()
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
function hasTokens(theme) {
|
|
441
|
-
return Object.values(theme).some((arr) => arr.length > 0);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// src/unplugin.ts
|
|
445
|
-
import * as fs2 from "fs";
|
|
446
|
-
import * as path2 from "path";
|
|
447
|
-
import { transform, transformSourceCode } from "@csszyx/compiler";
|
|
448
|
-
import { compute_mangle_checksum, encode } from "@csszyx/core";
|
|
449
|
-
import { preprocess as sveltePreprocess } from "@csszyx/svelte-adapter";
|
|
450
|
-
import { preprocess as vuePreprocess } from "@csszyx/vue-adapter";
|
|
451
|
-
import { createUnplugin } from "unplugin";
|
|
452
|
-
|
|
453
|
-
// src/html-transformer.ts
|
|
454
|
-
import { createHash } from "crypto";
|
|
455
|
-
function injectChecksum(html, checksum, minify = false) {
|
|
456
|
-
const attrName = minify ? "data-sz-cs" : "data-sz-checksum";
|
|
457
|
-
const htmlTagPattern = /<html([^>]*)>/i;
|
|
458
|
-
const match = html.match(htmlTagPattern);
|
|
459
|
-
if (!match) {
|
|
460
|
-
return html;
|
|
461
|
-
}
|
|
462
|
-
const existingAttrs = match[1];
|
|
463
|
-
const checksumAttr = ` ${attrName}="${checksum}"`;
|
|
464
|
-
return html.replace(
|
|
465
|
-
htmlTagPattern,
|
|
466
|
-
`<html${checksumAttr}${existingAttrs}>`
|
|
467
|
-
);
|
|
468
|
-
}
|
|
469
|
-
function injectMangleMapScript(html, mangleMap, options = {}) {
|
|
470
|
-
const { prettyPrint = false } = options;
|
|
471
|
-
const jsonContent = prettyPrint ? JSON.stringify(mangleMap, null, 2) : JSON.stringify(mangleMap);
|
|
472
|
-
const scriptTag = `<script id="__CSSZYX_MANGLE_MAP__" type="application/json">${jsonContent}</script>`;
|
|
473
|
-
const debugScript = `<script>(function(){var m=${jsonContent};var r={};for(var k in m)r[m[k]]=k;var cs=document.documentElement.getAttribute("data-sz-checksum")||"";window.__csszyx={mangleMap:m,checksum:cs,decode:function(c){return r[c]},encode:function(c){return m[c]},decodeAll:function(el){return(el.className||"").split(" ").map(function(c){return r[c]||c})}}})()</script>`;
|
|
474
|
-
const combined = `${scriptTag}
|
|
475
|
-
${debugScript}`;
|
|
476
|
-
if (html.includes("</head>")) {
|
|
477
|
-
return html.replace("</head>", `${combined}
|
|
478
|
-
</head>`);
|
|
479
|
-
} else if (html.includes("</html>")) {
|
|
480
|
-
return html.replace("</html>", `${combined}
|
|
481
|
-
</html>`);
|
|
482
|
-
}
|
|
483
|
-
return html + combined;
|
|
484
|
-
}
|
|
485
|
-
function injectMangleMapAttribute(html, mangleMap, minify = false) {
|
|
486
|
-
const attrName = minify ? "data-sz-m" : "data-sz-map";
|
|
487
|
-
const jsonContent = JSON.stringify(mangleMap);
|
|
488
|
-
const htmlTagPattern = /<html([^>]*)>/i;
|
|
489
|
-
const match = html.match(htmlTagPattern);
|
|
490
|
-
if (!match) {
|
|
491
|
-
return html;
|
|
492
|
-
}
|
|
493
|
-
const existingAttrs = match[1];
|
|
494
|
-
const mapAttr = ` ${attrName}='${jsonContent}'`;
|
|
495
|
-
return html.replace(htmlTagPattern, `<html${mapAttr}${existingAttrs}>`);
|
|
496
|
-
}
|
|
497
|
-
function injectHydrationData(html, mangleMap, checksum, options = {}) {
|
|
498
|
-
const { mode = "script", minify = false } = options;
|
|
499
|
-
let result = html;
|
|
500
|
-
result = injectChecksum(result, checksum, minify);
|
|
501
|
-
if (mode === "inline") {
|
|
502
|
-
result = injectMangleMapAttribute(result, mangleMap, minify);
|
|
503
|
-
} else if (mode === "script") {
|
|
504
|
-
result = injectMangleMapScript(result, mangleMap, options);
|
|
505
|
-
} else if (mode === "both") {
|
|
506
|
-
result = injectMangleMapAttribute(result, mangleMap, minify);
|
|
507
|
-
result = injectMangleMapScript(result, mangleMap, options);
|
|
508
|
-
}
|
|
509
|
-
return result;
|
|
510
|
-
}
|
|
511
|
-
function transformIndexHtml(html, mangleMap, checksum, options = {}) {
|
|
512
|
-
return injectHydrationData(html, mangleMap, checksum, options);
|
|
513
|
-
}
|
|
514
|
-
function buildRecoveryManifest(tokens, options) {
|
|
515
|
-
const stripped = options.production === true;
|
|
516
|
-
const strippedDevOnlyPaths = [];
|
|
517
|
-
const sorted = {};
|
|
518
|
-
const sortedKeys = [...tokens.keys()].sort();
|
|
519
|
-
for (const key of sortedKeys) {
|
|
520
|
-
const data = tokens.get(key);
|
|
521
|
-
if (!data) {
|
|
522
|
-
continue;
|
|
523
|
-
}
|
|
524
|
-
if (stripped && data.mode === "dev-only") {
|
|
525
|
-
strippedDevOnlyPaths.push(data.path);
|
|
526
|
-
continue;
|
|
527
|
-
}
|
|
528
|
-
sorted[key] = stripped ? { mode: data.mode, component: data.component, path: "" } : data;
|
|
529
|
-
}
|
|
530
|
-
const serialised = JSON.stringify(sorted);
|
|
531
|
-
const fullChecksum = createHash("sha256").update(serialised).digest("hex");
|
|
532
|
-
const checksum = fullChecksum.substring(0, 16);
|
|
533
|
-
const buildId = `${Date.now().toString(36)}-${fullChecksum.substring(0, 6)}`;
|
|
534
|
-
return {
|
|
535
|
-
manifest: { buildId, checksum, mangleChecksum: options.mangleChecksum, tokens: sorted },
|
|
536
|
-
strippedDevOnlyPaths
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
function injectRecoveryManifest(html, manifest) {
|
|
540
|
-
if (Object.keys(manifest.tokens).length === 0) {
|
|
541
|
-
return html;
|
|
542
|
-
}
|
|
543
|
-
const json = JSON.stringify(manifest);
|
|
544
|
-
const scriptTag = `<script id="__SZ_RECOVERY_MANIFEST__" type="application/json">${json}</script>`;
|
|
545
|
-
if (html.includes("</head>")) {
|
|
546
|
-
return html.replace("</head>", `${scriptTag}
|
|
547
|
-
</head>`);
|
|
548
|
-
} else if (html.includes("</html>")) {
|
|
549
|
-
return html.replace("</html>", `${scriptTag}
|
|
550
|
-
</html>`);
|
|
551
|
-
}
|
|
552
|
-
return html + scriptTag;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// src/theme-type-writer.ts
|
|
556
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
557
|
-
import { dirname as dirname2 } from "path";
|
|
558
|
-
function generateThemeDts(opts) {
|
|
559
|
-
const { theme, sourceFiles } = opts;
|
|
560
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
561
|
-
const sources = sourceFiles.join(", ");
|
|
562
|
-
const toUnion = (tokens) => tokens.map((t) => `'${t}'`).join(" | ");
|
|
563
|
-
const entries = [];
|
|
564
|
-
if (theme.colors.length > 0) {
|
|
565
|
-
entries.push(` colors: ${toUnion(theme.colors)};`);
|
|
566
|
-
}
|
|
567
|
-
if (theme.spacings.length > 0) {
|
|
568
|
-
entries.push(` spacings: ${toUnion(theme.spacings)};`);
|
|
569
|
-
}
|
|
570
|
-
if (theme.fonts.length > 0) {
|
|
571
|
-
entries.push(` fonts: ${toUnion(theme.fonts)};`);
|
|
572
|
-
}
|
|
573
|
-
if (theme.radii.length > 0) {
|
|
574
|
-
entries.push(` radii: ${toUnion(theme.radii)};`);
|
|
575
|
-
}
|
|
576
|
-
if (theme.shadows.length > 0) {
|
|
577
|
-
entries.push(` shadows: ${toUnion(theme.shadows)};`);
|
|
578
|
-
}
|
|
579
|
-
return [
|
|
580
|
-
"// Auto-generated by csszyx theme-scanner \u2014 DO NOT EDIT",
|
|
581
|
-
`// Source: ${sources}`,
|
|
582
|
-
`// Updated: ${timestamp}`,
|
|
583
|
-
"",
|
|
584
|
-
"declare module '@csszyx/compiler' {",
|
|
585
|
-
" /**",
|
|
586
|
-
" * Custom design tokens extracted from @theme blocks.",
|
|
587
|
-
" * These tokens are surfaced in sz prop IntelliSense.",
|
|
588
|
-
" */",
|
|
589
|
-
" interface CustomTheme {",
|
|
590
|
-
...entries,
|
|
591
|
-
" }",
|
|
592
|
-
"}",
|
|
593
|
-
"",
|
|
594
|
-
"export {};",
|
|
595
|
-
""
|
|
596
|
-
].join("\n");
|
|
597
|
-
}
|
|
598
|
-
function writeThemeDts(opts) {
|
|
599
|
-
const content = generateThemeDts(opts);
|
|
600
|
-
mkdirSync(dirname2(opts.outputPath), { recursive: true });
|
|
601
|
-
writeFileSync(opts.outputPath, content, "utf-8");
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// src/virtual-modules.ts
|
|
605
|
-
var VIRTUAL_MODULE_ID = "virtual:csszyx/mangle-map";
|
|
606
|
-
var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
|
|
607
|
-
var VIRTUAL_CHECKSUM_ID = "virtual:csszyx/checksum";
|
|
608
|
-
var RESOLVED_VIRTUAL_CHECKSUM_ID = "\0" + VIRTUAL_CHECKSUM_ID;
|
|
609
|
-
function createMangleMapModule(mangleMap, checksum) {
|
|
610
|
-
return `/**
|
|
611
|
-
* Auto-generated mangle map for csszyx.
|
|
612
|
-
* This module is generated at build time and contains the mapping
|
|
613
|
-
* from original class names to mangled class names.
|
|
614
|
-
*
|
|
615
|
-
* @generated
|
|
616
|
-
*/
|
|
617
|
-
|
|
618
|
-
export const mangleMap = ${JSON.stringify(mangleMap, null, 2)};
|
|
619
|
-
|
|
620
|
-
export const checksum = ${JSON.stringify(checksum)};
|
|
621
|
-
|
|
622
|
-
export default {
|
|
623
|
-
mangleMap,
|
|
624
|
-
checksum,
|
|
625
|
-
};
|
|
626
|
-
`;
|
|
627
|
-
}
|
|
628
|
-
function createChecksumModule(checksum) {
|
|
629
|
-
return `/**
|
|
630
|
-
* Auto-generated checksum for csszyx mangle map.
|
|
631
|
-
*
|
|
632
|
-
* @generated
|
|
633
|
-
*/
|
|
634
|
-
|
|
635
|
-
export const checksum = ${JSON.stringify(checksum)};
|
|
636
|
-
|
|
637
|
-
export default checksum;
|
|
638
|
-
`;
|
|
639
|
-
}
|
|
640
|
-
function isVirtualModule(id) {
|
|
641
|
-
return id === VIRTUAL_MODULE_ID || id === VIRTUAL_CHECKSUM_ID;
|
|
642
|
-
}
|
|
643
|
-
function resolveVirtualModule(id) {
|
|
644
|
-
if (id === VIRTUAL_MODULE_ID) {
|
|
645
|
-
return RESOLVED_VIRTUAL_MODULE_ID;
|
|
646
|
-
}
|
|
647
|
-
if (id === VIRTUAL_CHECKSUM_ID) {
|
|
648
|
-
return RESOLVED_VIRTUAL_CHECKSUM_ID;
|
|
649
|
-
}
|
|
650
|
-
return void 0;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// src/unplugin.ts
|
|
654
|
-
var CHECKSUM_PLACEHOLDER = "___CSSZYX_CHECKSUM___";
|
|
655
|
-
var MANGLE_MAP_PLACEHOLDER = "___CSSZYX_MANGLE_MAP___";
|
|
656
|
-
var _hasWarnedTsConfig = false;
|
|
657
|
-
function runThemeScan(rootDir, scanCss) {
|
|
658
|
-
if (!scanCss) {
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
const patterns = Array.isArray(scanCss) ? scanCss : [scanCss];
|
|
662
|
-
const sourceFiles = [];
|
|
663
|
-
for (const pattern of patterns) {
|
|
664
|
-
const resolved = path2.isAbsolute(pattern) ? pattern : path2.join(rootDir, pattern);
|
|
665
|
-
if (fs2.existsSync(resolved)) {
|
|
666
|
-
sourceFiles.push(resolved);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
if (sourceFiles.length === 0) {
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
const themes = sourceFiles.map((f) => {
|
|
673
|
-
try {
|
|
674
|
-
return parseThemeBlocks(fs2.readFileSync(f, "utf-8"));
|
|
675
|
-
} catch {
|
|
676
|
-
return null;
|
|
677
|
-
}
|
|
678
|
-
}).filter((t) => t !== null);
|
|
679
|
-
const merged = mergeThemes(themes);
|
|
680
|
-
const outputPath = path2.join(rootDir, ".csszyx", "theme.d.ts");
|
|
681
|
-
writeThemeDts({ outputPath, theme: merged, sourceFiles });
|
|
682
|
-
if (!_hasWarnedTsConfig) {
|
|
683
|
-
_hasWarnedTsConfig = true;
|
|
684
|
-
try {
|
|
685
|
-
const checkFile = (cfgPath) => {
|
|
686
|
-
if (fs2.existsSync(cfgPath)) {
|
|
687
|
-
const content = fs2.readFileSync(cfgPath, "utf-8");
|
|
688
|
-
if (!content.includes(".csszyx")) {
|
|
689
|
-
console.warn(`
|
|
690
|
-
\x1B[33m\u26A0\uFE0F CSSzyx: Theme Auto-Scan enabled, but TypeScript isn't configured. Run "npx @csszyx/cli init" to fix.\x1B[0m
|
|
691
|
-
`);
|
|
692
|
-
}
|
|
693
|
-
return true;
|
|
694
|
-
}
|
|
695
|
-
return false;
|
|
696
|
-
};
|
|
697
|
-
if (!checkFile(path2.join(rootDir, "tsconfig.json"))) {
|
|
698
|
-
checkFile(path2.join(rootDir, "tsconfig.app.json"));
|
|
699
|
-
}
|
|
700
|
-
} catch {
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
function mangleCodeClassesSync(code, mangleMap) {
|
|
705
|
-
function mangleClassString(classString) {
|
|
706
|
-
return classString.split(/\s+/).filter(Boolean).map((cls) => {
|
|
707
|
-
return mangleMap[cls.replace(/\\(.)/g, "$1")] || cls;
|
|
708
|
-
}).join(" ");
|
|
709
|
-
}
|
|
710
|
-
let result = code.replace(/(?:class(?:Name)?|sz)[:=]\s*"((?:[^"\\]|\\.)*)"/g, (match, classes) => {
|
|
711
|
-
const mangled = mangleClassString(classes);
|
|
712
|
-
if (mangled === classes) {
|
|
713
|
-
return match;
|
|
714
|
-
}
|
|
715
|
-
return match.replace(classes, mangled);
|
|
716
|
-
}).replace(/(?:class(?:Name)?|sz)[:=]\s*'((?:[^'\\]|\\.)*)'/g, (match, classes) => {
|
|
717
|
-
const mangled = mangleClassString(classes);
|
|
718
|
-
if (mangled === classes) {
|
|
719
|
-
return match;
|
|
720
|
-
}
|
|
721
|
-
return match.replace(classes, mangled);
|
|
722
|
-
});
|
|
723
|
-
result = result.replace(/className:\s*`([^`]+)`/g, (fullMatch, tplContent) => {
|
|
724
|
-
let changed = false;
|
|
725
|
-
let out = "";
|
|
726
|
-
let i = 0;
|
|
727
|
-
while (i < tplContent.length) {
|
|
728
|
-
const interStart = tplContent.indexOf("${", i);
|
|
729
|
-
if (interStart === -1) {
|
|
730
|
-
const quasi2 = tplContent.slice(i);
|
|
731
|
-
const trimmed2 = quasi2.trim();
|
|
732
|
-
if (trimmed2) {
|
|
733
|
-
const m = mangleClassString(trimmed2);
|
|
734
|
-
if (m !== trimmed2) {
|
|
735
|
-
changed = true;
|
|
736
|
-
out += quasi2.replace(trimmed2, m);
|
|
737
|
-
} else {
|
|
738
|
-
out += quasi2;
|
|
739
|
-
}
|
|
740
|
-
} else {
|
|
741
|
-
out += quasi2;
|
|
742
|
-
}
|
|
743
|
-
break;
|
|
744
|
-
}
|
|
745
|
-
const quasi = tplContent.slice(i, interStart);
|
|
746
|
-
const trimmed = quasi.trim();
|
|
747
|
-
if (trimmed) {
|
|
748
|
-
const m = mangleClassString(trimmed);
|
|
749
|
-
if (m !== trimmed) {
|
|
750
|
-
changed = true;
|
|
751
|
-
out += quasi.replace(trimmed, m);
|
|
752
|
-
} else {
|
|
753
|
-
out += quasi;
|
|
754
|
-
}
|
|
755
|
-
} else {
|
|
756
|
-
out += quasi;
|
|
757
|
-
}
|
|
758
|
-
let j = interStart + 2;
|
|
759
|
-
let depth = 0;
|
|
760
|
-
while (j < tplContent.length) {
|
|
761
|
-
if (tplContent[j] === "{") {
|
|
762
|
-
depth++;
|
|
763
|
-
} else if (tplContent[j] === "}") {
|
|
764
|
-
if (depth === 0) {
|
|
765
|
-
j++;
|
|
766
|
-
break;
|
|
767
|
-
}
|
|
768
|
-
depth--;
|
|
769
|
-
}
|
|
770
|
-
j++;
|
|
771
|
-
}
|
|
772
|
-
const interInner = tplContent.slice(interStart + 2, j - 1);
|
|
773
|
-
const mangledInner = interInner.replace(/"([^"]*)"/g, (qm, inner) => {
|
|
774
|
-
const parts = inner.split(/\s+/).filter(Boolean);
|
|
775
|
-
if (parts.length === 0) {
|
|
776
|
-
return qm;
|
|
777
|
-
}
|
|
778
|
-
const m = parts.map((p) => mangleMap[p] || p).join(" ");
|
|
779
|
-
if (m === inner) {
|
|
780
|
-
return qm;
|
|
781
|
-
}
|
|
782
|
-
changed = true;
|
|
783
|
-
return '"' + m + '"';
|
|
784
|
-
});
|
|
785
|
-
out += "${" + mangledInner + "}";
|
|
786
|
-
i = j;
|
|
787
|
-
}
|
|
788
|
-
return changed ? "className:`" + out + "`" : fullMatch;
|
|
789
|
-
});
|
|
790
|
-
{
|
|
791
|
-
const marker = "className:";
|
|
792
|
-
let searchFrom = 0;
|
|
793
|
-
let out = "";
|
|
794
|
-
while (searchFrom < result.length) {
|
|
795
|
-
const idx = result.indexOf(marker, searchFrom);
|
|
796
|
-
if (idx === -1) {
|
|
797
|
-
out += result.slice(searchFrom);
|
|
798
|
-
break;
|
|
799
|
-
}
|
|
800
|
-
out += result.slice(searchFrom, idx + marker.length);
|
|
801
|
-
const afterColon = idx + marker.length;
|
|
802
|
-
let exprStart = afterColon;
|
|
803
|
-
while (exprStart < result.length && result[exprStart] === " ") {
|
|
804
|
-
exprStart++;
|
|
805
|
-
}
|
|
806
|
-
const firstChar = result[exprStart];
|
|
807
|
-
if (firstChar === '"' || firstChar === "'" || firstChar === "`") {
|
|
808
|
-
searchFrom = afterColon;
|
|
809
|
-
continue;
|
|
810
|
-
}
|
|
811
|
-
let depth = 0;
|
|
812
|
-
let j = afterColon;
|
|
813
|
-
while (j < result.length) {
|
|
814
|
-
const ch = result[j];
|
|
815
|
-
if (ch === "(" || ch === "[") {
|
|
816
|
-
depth++;
|
|
817
|
-
} else if (ch === ")" || ch === "]") {
|
|
818
|
-
if (depth === 0) {
|
|
819
|
-
break;
|
|
820
|
-
}
|
|
821
|
-
depth--;
|
|
822
|
-
} else if (depth === 0 && (ch === "," || ch === ";" || ch === "\n" || ch === "}")) {
|
|
823
|
-
break;
|
|
824
|
-
}
|
|
825
|
-
j++;
|
|
826
|
-
}
|
|
827
|
-
const expr = result.slice(afterColon, j);
|
|
828
|
-
const qIdx = expr.indexOf("?");
|
|
829
|
-
if (qIdx === -1 || !expr.slice(qIdx).includes(":")) {
|
|
830
|
-
out += expr;
|
|
831
|
-
searchFrom = j;
|
|
832
|
-
continue;
|
|
833
|
-
}
|
|
834
|
-
let changed = false;
|
|
835
|
-
const mangled = expr.replace(/"([^"]*)"/g, (qm, inner) => {
|
|
836
|
-
const parts = inner.split(/\s+/).filter(Boolean);
|
|
837
|
-
if (parts.length === 0) {
|
|
838
|
-
return qm;
|
|
839
|
-
}
|
|
840
|
-
const mangledStr = parts.map((p) => mangleMap[p] || p).join(" ");
|
|
841
|
-
if (mangledStr !== inner) {
|
|
842
|
-
changed = true;
|
|
843
|
-
return '"' + mangledStr + '"';
|
|
844
|
-
}
|
|
845
|
-
return qm;
|
|
846
|
-
});
|
|
847
|
-
out += changed ? mangled : expr;
|
|
848
|
-
searchFrom = j;
|
|
849
|
-
}
|
|
850
|
-
result = out;
|
|
851
|
-
}
|
|
852
|
-
result = result.replace(/(?<=(?:[,(]|&&)\s*)"([^"]+)"/g, (match, inner) => {
|
|
853
|
-
const tokens = inner.split(/\s+/).filter(Boolean);
|
|
854
|
-
if (tokens.length === 0) {
|
|
855
|
-
return match;
|
|
856
|
-
}
|
|
857
|
-
let changed = false;
|
|
858
|
-
const mangled = [];
|
|
859
|
-
for (const t of tokens) {
|
|
860
|
-
const m = mangleMap[t];
|
|
861
|
-
if (m === void 0) {
|
|
862
|
-
return match;
|
|
863
|
-
}
|
|
864
|
-
if (m !== t) {
|
|
865
|
-
changed = true;
|
|
866
|
-
}
|
|
867
|
-
mangled.push(m);
|
|
868
|
-
}
|
|
869
|
-
if (!changed) {
|
|
870
|
-
return match;
|
|
871
|
-
}
|
|
872
|
-
return '"' + mangled.join(" ") + '"';
|
|
873
|
-
});
|
|
874
|
-
return result;
|
|
875
|
-
}
|
|
876
|
-
function createCsszyxPlugins(options = {}) {
|
|
877
|
-
const manglingEnabled = options.production?.mangle !== false;
|
|
878
|
-
const astBudgetOverride = options.build?.astBudgetLimit;
|
|
879
|
-
const state = {
|
|
880
|
-
classes: /* @__PURE__ */ new Set(),
|
|
881
|
-
mangleMap: {},
|
|
882
|
-
checksum: "",
|
|
883
|
-
finalized: false,
|
|
884
|
-
rootDir: process.cwd(),
|
|
885
|
-
recoveryTokens: /* @__PURE__ */ new Map(),
|
|
886
|
-
rscModules: /* @__PURE__ */ new Map()
|
|
887
|
-
};
|
|
888
|
-
const SAFELIST_FILENAME = "csszyx-classes.html";
|
|
889
|
-
const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js"]);
|
|
890
|
-
const IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "dist", "build", ".turbo"]);
|
|
891
|
-
function writeSafelistFile(classes) {
|
|
892
|
-
if (classes.size === 0) {
|
|
893
|
-
return;
|
|
894
|
-
}
|
|
895
|
-
const safelistPath = path2.join(state.rootDir, SAFELIST_FILENAME);
|
|
896
|
-
const classList = Array.from(classes).join(" ");
|
|
897
|
-
const content = `<!-- Auto-generated by csszyx \u2014 DO NOT EDIT -->
|
|
898
|
-
<!-- Tailwind CSS scans this file for class name detection -->
|
|
899
|
-
<div class="${classList}"><div class="${classList}">x</div><div class="${classList}">x</div></div>
|
|
900
|
-
`;
|
|
901
|
-
try {
|
|
902
|
-
const existing = fs2.existsSync(safelistPath) ? fs2.readFileSync(safelistPath, "utf-8") : "";
|
|
903
|
-
if (existing !== content) {
|
|
904
|
-
fs2.writeFileSync(safelistPath, content);
|
|
905
|
-
}
|
|
906
|
-
} catch {
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
function prescanAndWriteClasses() {
|
|
910
|
-
const discoveredClasses = /* @__PURE__ */ new Set();
|
|
911
|
-
const rawDiscoveredClasses = /* @__PURE__ */ new Set();
|
|
912
|
-
function scanDir(dir) {
|
|
913
|
-
let entries;
|
|
914
|
-
try {
|
|
915
|
-
entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
916
|
-
} catch {
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
for (const entry of entries) {
|
|
920
|
-
if (entry.isDirectory()) {
|
|
921
|
-
if (!IGNORE_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
|
|
922
|
-
scanDir(path2.join(dir, entry.name));
|
|
923
|
-
}
|
|
924
|
-
} else if (SOURCE_EXTENSIONS.has(path2.extname(entry.name))) {
|
|
925
|
-
const filePath = path2.join(dir, entry.name);
|
|
926
|
-
try {
|
|
927
|
-
const content = fs2.readFileSync(filePath, "utf-8");
|
|
928
|
-
if (!content.includes("sz=") && !content.includes("sz:")) {
|
|
929
|
-
continue;
|
|
930
|
-
}
|
|
931
|
-
const result = transformSourceCode(content, filePath, { astBudget: astBudgetOverride });
|
|
932
|
-
if (!result.transformed) {
|
|
933
|
-
continue;
|
|
934
|
-
}
|
|
935
|
-
for (const cls of result.classes) {
|
|
936
|
-
discoveredClasses.add(cls);
|
|
937
|
-
}
|
|
938
|
-
for (const cls of result.rawClassNames) {
|
|
939
|
-
rawDiscoveredClasses.add(cls);
|
|
940
|
-
}
|
|
941
|
-
for (const [token, data] of result.recoveryTokens) {
|
|
942
|
-
state.recoveryTokens.set(token, data);
|
|
943
|
-
}
|
|
944
|
-
if (result.usesRuntime) {
|
|
945
|
-
const szCallRe = /_sz\(\s*\{/g;
|
|
946
|
-
let szMatch;
|
|
947
|
-
while ((szMatch = szCallRe.exec(result.code)) !== null) {
|
|
948
|
-
let depth = 1;
|
|
949
|
-
let idx = szMatch.index + szMatch[0].length;
|
|
950
|
-
while (idx < result.code.length && depth > 0) {
|
|
951
|
-
if (result.code[idx] === "{") {
|
|
952
|
-
depth++;
|
|
953
|
-
} else if (result.code[idx] === "}") {
|
|
954
|
-
depth--;
|
|
955
|
-
}
|
|
956
|
-
idx++;
|
|
957
|
-
}
|
|
958
|
-
const objStr = result.code.slice(szMatch.index + szMatch[0].length, idx - 1);
|
|
959
|
-
const strKv = /(\w+)\s*:\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
960
|
-
let kv;
|
|
961
|
-
while ((kv = strKv.exec(objStr)) !== null) {
|
|
962
|
-
try {
|
|
963
|
-
const val = kv[2] ?? kv[3];
|
|
964
|
-
const r = transform({ [kv[1]]: val });
|
|
965
|
-
for (const c of r.className.split(/\s+/).filter(Boolean)) {
|
|
966
|
-
discoveredClasses.add(c);
|
|
967
|
-
}
|
|
968
|
-
} catch {
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
const numKv = /(\w+)\s*:\s*(-?\d+(?:\.\d+)?)\s*(?=[,}\n])/g;
|
|
972
|
-
while ((kv = numKv.exec(objStr)) !== null) {
|
|
973
|
-
try {
|
|
974
|
-
const r = transform({ [kv[1]]: parseFloat(kv[2]) });
|
|
975
|
-
for (const c of r.className.split(/\s+/).filter(Boolean)) {
|
|
976
|
-
discoveredClasses.add(c);
|
|
977
|
-
}
|
|
978
|
-
} catch {
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
const boolKv = /(\w+)\s*:\s*(true|false)\s*(?=[,}\n])/g;
|
|
982
|
-
while ((kv = boolKv.exec(objStr)) !== null) {
|
|
983
|
-
try {
|
|
984
|
-
const r = transform({ [kv[1]]: kv[2] === "true" });
|
|
985
|
-
for (const c of r.className.split(/\s+/).filter(Boolean)) {
|
|
986
|
-
discoveredClasses.add(c);
|
|
987
|
-
}
|
|
988
|
-
} catch {
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
} catch {
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
scanDir(state.rootDir);
|
|
999
|
-
for (const cls of discoveredClasses) {
|
|
1000
|
-
state.classes.add(cls);
|
|
1001
|
-
}
|
|
1002
|
-
const safelistClasses = /* @__PURE__ */ new Set([...discoveredClasses, ...rawDiscoveredClasses]);
|
|
1003
|
-
writeSafelistFile(safelistClasses);
|
|
1004
|
-
}
|
|
1005
|
-
function extractClasses(code) {
|
|
1006
|
-
const dqPattern = /(?:class(?:Name)?|sz)[:=]\s*"([^"]*)"/g;
|
|
1007
|
-
const sqPattern = /(?:class(?:Name)?|sz)[:=]\s*'([^']*)'/g;
|
|
1008
|
-
let match;
|
|
1009
|
-
for (const classPattern of [dqPattern, sqPattern]) {
|
|
1010
|
-
while ((match = classPattern.exec(code)) !== null) {
|
|
1011
|
-
const classes = match[1].split(/\s+/).filter(Boolean);
|
|
1012
|
-
for (const cls of classes) {
|
|
1013
|
-
state.classes.add(cls);
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
const exprStart = /className=\{/g;
|
|
1018
|
-
while ((match = exprStart.exec(code)) !== null) {
|
|
1019
|
-
let depth = 1;
|
|
1020
|
-
let i = match.index + match[0].length;
|
|
1021
|
-
while (i < code.length && depth > 0) {
|
|
1022
|
-
if (code[i] === "{") {
|
|
1023
|
-
depth++;
|
|
1024
|
-
} else if (code[i] === "}") {
|
|
1025
|
-
depth--;
|
|
1026
|
-
}
|
|
1027
|
-
i++;
|
|
1028
|
-
}
|
|
1029
|
-
const expr = code.slice(match.index + match[0].length, i - 1);
|
|
1030
|
-
const strPattern = /"([^"]+)"|'([^']+)'/g;
|
|
1031
|
-
let strMatch;
|
|
1032
|
-
while ((strMatch = strPattern.exec(expr)) !== null) {
|
|
1033
|
-
const str = strMatch[1] || strMatch[2];
|
|
1034
|
-
const classes = str.split(/\s+/).filter(Boolean);
|
|
1035
|
-
for (const cls of classes) {
|
|
1036
|
-
state.classes.add(cls);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
function finalizeMangleMap() {
|
|
1042
|
-
const sortedClasses = Array.from(state.classes);
|
|
1043
|
-
const newMap = {};
|
|
1044
|
-
for (let i = 0; i < sortedClasses.length; i++) {
|
|
1045
|
-
newMap[sortedClasses[i]] = encode(i);
|
|
1046
|
-
}
|
|
1047
|
-
state.mangleMap = newMap;
|
|
1048
|
-
state.checksum = compute_mangle_checksum(state.mangleMap);
|
|
1049
|
-
state.finalized = true;
|
|
1050
|
-
}
|
|
1051
|
-
function mangleCodeClasses(code) {
|
|
1052
|
-
return mangleCodeClassesSync(code, state.mangleMap);
|
|
1053
|
-
}
|
|
1054
|
-
function replacePlaceholders(code) {
|
|
1055
|
-
let result = code;
|
|
1056
|
-
if (result.includes(CHECKSUM_PLACEHOLDER)) {
|
|
1057
|
-
result = result.split(CHECKSUM_PLACEHOLDER).join(state.checksum);
|
|
1058
|
-
}
|
|
1059
|
-
if (result.includes(MANGLE_MAP_PLACEHOLDER)) {
|
|
1060
|
-
const jsonMap = JSON.stringify(state.mangleMap);
|
|
1061
|
-
const escapedMap = result.includes("eval(") ? jsonMap.replace(/"/g, '\\"') : jsonMap;
|
|
1062
|
-
result = result.split(MANGLE_MAP_PLACEHOLDER).join(escapedMap);
|
|
1063
|
-
}
|
|
1064
|
-
return result;
|
|
1065
|
-
}
|
|
1066
|
-
const prePlugin = createUnplugin((_pluginOptions) => ({
|
|
1067
|
-
name: "csszyx:pre",
|
|
1068
|
-
enforce: "pre",
|
|
1069
|
-
/**
|
|
1070
|
-
* Resolves virtual module IDs for csszyx mangle-map and checksum modules.
|
|
1071
|
-
* @param id - the module ID to resolve
|
|
1072
|
-
* @returns resolved ID if virtual, null otherwise
|
|
1073
|
-
*/
|
|
1074
|
-
resolveId(id) {
|
|
1075
|
-
if (isVirtualModule(id)) {
|
|
1076
|
-
return resolveVirtualModule(id);
|
|
1077
|
-
}
|
|
1078
|
-
return null;
|
|
1079
|
-
},
|
|
1080
|
-
/**
|
|
1081
|
-
* Loads virtual module content — generates mangle map or checksum module code.
|
|
1082
|
-
* @param id - the resolved module ID to load
|
|
1083
|
-
* @returns generated module source if virtual, null otherwise
|
|
1084
|
-
*/
|
|
1085
|
-
load(id) {
|
|
1086
|
-
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
1087
|
-
finalizeMangleMap();
|
|
1088
|
-
return createMangleMapModule(state.mangleMap, state.checksum);
|
|
1089
|
-
}
|
|
1090
|
-
if (id === RESOLVED_VIRTUAL_CHECKSUM_ID) {
|
|
1091
|
-
finalizeMangleMap();
|
|
1092
|
-
return createChecksumModule(state.checksum);
|
|
1093
|
-
}
|
|
1094
|
-
return null;
|
|
1095
|
-
},
|
|
1096
|
-
/**
|
|
1097
|
-
* Filters files for the pre-transform phase — source files plus CSS files.
|
|
1098
|
-
* CSS files need special handling to inject @source inline() for Tailwind class discovery.
|
|
1099
|
-
* @param id - the file path to check for inclusion
|
|
1100
|
-
* @returns true if the file should be transformed, false otherwise
|
|
1101
|
-
*/
|
|
1102
|
-
transformInclude(id) {
|
|
1103
|
-
if (id.includes("node_modules") || id.includes("/packages/") || id.includes(".next") && !id.includes("static")) {
|
|
1104
|
-
return false;
|
|
1105
|
-
}
|
|
1106
|
-
if (/\.css(\?.*)?$/.test(id)) {
|
|
1107
|
-
return true;
|
|
1108
|
-
}
|
|
1109
|
-
return /\.[tj]sx?$/.test(id) || id.endsWith(".vue") || id.endsWith(".svelte");
|
|
1110
|
-
},
|
|
1111
|
-
/**
|
|
1112
|
-
* Core transform: detects sz prop, compiles to className, injects runtime, collects classes.
|
|
1113
|
-
* For CSS files: injects @source inline() so Tailwind generates CSS for sz-derived classes.
|
|
1114
|
-
* @param code - the source code to transform
|
|
1115
|
-
* @param id - the file path of the module being transformed
|
|
1116
|
-
* @returns transformed code with source map, or null if no changes were made
|
|
1117
|
-
*/
|
|
1118
|
-
transform(code, id) {
|
|
1119
|
-
if (/\.[tj]sx?(\?.*)?$/.test(id)) {
|
|
1120
|
-
assertNoRSCBoundaryViolation(code, id);
|
|
1121
|
-
}
|
|
1122
|
-
if (/\.css(\?.*)?$/.test(id)) {
|
|
1123
|
-
const hasTailwindImport = code.includes('@import "tailwindcss') || code.includes("@import 'tailwindcss");
|
|
1124
|
-
if (hasTailwindImport && state.classes.size > 0) {
|
|
1125
|
-
const candidates = Array.from(state.classes).filter((c) => c.length >= 2 && /^[a-z]/.test(c)).join(" ");
|
|
1126
|
-
if (candidates) {
|
|
1127
|
-
const safelistPath = path2.join(state.rootDir, SAFELIST_FILENAME).replace(/\\/g, "/");
|
|
1128
|
-
const cssDir = path2.dirname(id).replace(/\\/g, "/");
|
|
1129
|
-
let relPath = path2.posix.relative(cssDir, safelistPath);
|
|
1130
|
-
if (!relPath.startsWith(".")) {
|
|
1131
|
-
relPath = "./" + relPath;
|
|
1132
|
-
}
|
|
1133
|
-
const sourceDirective = `@source "${relPath}";
|
|
1134
|
-
`;
|
|
1135
|
-
const transformed2 = code.replace(
|
|
1136
|
-
/(@import\s+["']tailwindcss[^"']*["'];)/,
|
|
1137
|
-
`$1
|
|
1138
|
-
${sourceDirective}`
|
|
1139
|
-
);
|
|
1140
|
-
if (transformed2 !== code) {
|
|
1141
|
-
return { code: transformed2, map: null };
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
return null;
|
|
1146
|
-
}
|
|
1147
|
-
let transformedCode = code;
|
|
1148
|
-
let usesRuntime = false;
|
|
1149
|
-
let usesMerge = false;
|
|
1150
|
-
let usesColorVar = false;
|
|
1151
|
-
let transformed = false;
|
|
1152
|
-
let szClasses;
|
|
1153
|
-
const hasSzProp = code.includes("sz=") || /\bsz\s*:\s*["'{]/.test(code) || code.includes('sz: "');
|
|
1154
|
-
if (hasSzProp) {
|
|
1155
|
-
if (id.endsWith(".vue")) {
|
|
1156
|
-
const result = vuePreprocess(code, options);
|
|
1157
|
-
if (result.transformed) {
|
|
1158
|
-
transformedCode = result.code;
|
|
1159
|
-
transformed = true;
|
|
1160
|
-
}
|
|
1161
|
-
} else if (id.endsWith(".svelte")) {
|
|
1162
|
-
const result = sveltePreprocess(code, options);
|
|
1163
|
-
if (result) {
|
|
1164
|
-
transformedCode = result.code;
|
|
1165
|
-
transformed = true;
|
|
1166
|
-
}
|
|
1167
|
-
} else {
|
|
1168
|
-
const result = transformSourceCode(code, id, { astBudget: astBudgetOverride });
|
|
1169
|
-
transformedCode = result.code;
|
|
1170
|
-
usesRuntime = result.usesRuntime;
|
|
1171
|
-
usesMerge = result.usesMerge;
|
|
1172
|
-
usesColorVar = result.usesColorVar;
|
|
1173
|
-
transformed = result.transformed;
|
|
1174
|
-
szClasses = result.classes;
|
|
1175
|
-
if (result.diagnostics.length > 0 && process.env.NODE_ENV !== "production") {
|
|
1176
|
-
for (const msg of result.diagnostics) {
|
|
1177
|
-
this.warn(`[csszyx] ${id}
|
|
1178
|
-
${msg}`);
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
for (const [token, data] of result.recoveryTokens) {
|
|
1182
|
-
state.recoveryTokens.set(token, data);
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
if (transformedCode.includes("<html") && /layout|Root|Document|app\\.tsx?$/i.test(id)) {
|
|
1187
|
-
const attrName = options.production?.minify ? "data-sz-cs" : "data-sz-checksum";
|
|
1188
|
-
transformedCode = transformedCode.replace(/<html([^>]*)>/i, `<html$1 ${attrName}="${CHECKSUM_PLACEHOLDER}">`);
|
|
1189
|
-
const debugScript = `<script dangerouslySetInnerHTML={{__html: \`(function(){var m=${MANGLE_MAP_PLACEHOLDER};var r={};for(var k in m)r[m[k]]=k;window.__csszyx={mangleMap:m,checksum:"${CHECKSUM_PLACEHOLDER}",decode:function(c){return r[c]},encode:function(c){return m[c]},decodeAll:function(el){return(el.className||"").split(" ").map(function(c){return r[c]||c})}}})()\`}} />`;
|
|
1190
|
-
if (transformedCode.includes("<body")) {
|
|
1191
|
-
transformedCode = transformedCode.replace(
|
|
1192
|
-
/(<body[^>]*>)/i,
|
|
1193
|
-
`$1${debugScript}`
|
|
1194
|
-
);
|
|
1195
|
-
}
|
|
1196
|
-
transformed = true;
|
|
1197
|
-
}
|
|
1198
|
-
{
|
|
1199
|
-
const imports = [];
|
|
1200
|
-
if (usesRuntime) {
|
|
1201
|
-
imports.push("_sz");
|
|
1202
|
-
}
|
|
1203
|
-
if (usesMerge) {
|
|
1204
|
-
imports.push("_szMerge");
|
|
1205
|
-
}
|
|
1206
|
-
if (usesColorVar) {
|
|
1207
|
-
imports.push("__szColorVar");
|
|
1208
|
-
}
|
|
1209
|
-
const needed = imports.filter(
|
|
1210
|
-
(name) => !new RegExp(`\\{[^}]*\\b${name}\\b[^}]*\\}\\s*from\\s*['"]@csszyx/runtime['"]`).test(transformedCode)
|
|
1211
|
-
);
|
|
1212
|
-
if (needed.length > 0) {
|
|
1213
|
-
const existingImport = transformedCode.match(/^(import\s*\{[^}]*)\}\s*from\s*'@csszyx\/runtime'/m);
|
|
1214
|
-
if (existingImport) {
|
|
1215
|
-
transformedCode = transformedCode.replace(
|
|
1216
|
-
existingImport[0],
|
|
1217
|
-
`${existingImport[1]}, ${needed.join(", ")} } from '@csszyx/runtime'`
|
|
1218
|
-
);
|
|
1219
|
-
} else {
|
|
1220
|
-
const importStmt = `import { ${needed.join(", ")} } from '@csszyx/runtime';
|
|
1221
|
-
`;
|
|
1222
|
-
const directiveMatch = transformedCode.match(/^['"]use (client|server)['"];?\s*/);
|
|
1223
|
-
if (directiveMatch) {
|
|
1224
|
-
const directive = directiveMatch[0];
|
|
1225
|
-
transformedCode = transformedCode.replace(directive, `${directive}${importStmt}`);
|
|
1226
|
-
} else {
|
|
1227
|
-
transformedCode = `${importStmt}${transformedCode}`;
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
transformed = true;
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
if (/\.[tj]sx?(\?.*)?$/.test(id)) {
|
|
1234
|
-
assertNoRSCBoundaryViolation(transformedCode, id);
|
|
1235
|
-
const record = createRSCModuleRecord(transformedCode, id);
|
|
1236
|
-
state.rscModules.set(record.id, record);
|
|
1237
|
-
}
|
|
1238
|
-
if (transformed || transformedCode.includes("class=") || transformedCode.includes("className=")) {
|
|
1239
|
-
if (szClasses !== void 0) {
|
|
1240
|
-
for (const cls of szClasses) {
|
|
1241
|
-
state.classes.add(cls);
|
|
1242
|
-
}
|
|
1243
|
-
} else {
|
|
1244
|
-
extractClasses(transformedCode);
|
|
1245
|
-
}
|
|
1246
|
-
return { code: transformedCode, map: null };
|
|
1247
|
-
}
|
|
1248
|
-
return null;
|
|
1249
|
-
},
|
|
1250
|
-
/** Finalizes the mangle map after all source modules have been processed. */
|
|
1251
|
-
buildEnd() {
|
|
1252
|
-
finalizeMangleMap();
|
|
1253
|
-
assertNoRSCGraphViolation(state.rscModules);
|
|
1254
|
-
if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
|
|
1255
|
-
globalThis.__csszyx_ssr_mangle_map = state.mangleMap;
|
|
1256
|
-
}
|
|
1257
|
-
},
|
|
1258
|
-
/**
|
|
1259
|
-
* Webpack hook: pre-scans source files before compilation for Tailwind class discovery.
|
|
1260
|
-
* @param compiler - the Webpack compiler instance
|
|
1261
|
-
*/
|
|
1262
|
-
webpack(compiler) {
|
|
1263
|
-
compiler.hooks.beforeCompile.tap("csszyx:prescan", () => {
|
|
1264
|
-
const root = compiler.context || process.cwd();
|
|
1265
|
-
state.rootDir = root;
|
|
1266
|
-
if (state.classes.size === 0) {
|
|
1267
|
-
prescanAndWriteClasses();
|
|
1268
|
-
}
|
|
1269
|
-
runThemeScan(root, options.build?.scanCss);
|
|
1270
|
-
});
|
|
1271
|
-
if (options.build?.scanCss) {
|
|
1272
|
-
const patterns = Array.isArray(options.build.scanCss) ? options.build.scanCss : [options.build.scanCss];
|
|
1273
|
-
compiler.hooks.thisCompilation.tap("csszyx:theme-deps", (compilation) => {
|
|
1274
|
-
const root = compiler.context || process.cwd();
|
|
1275
|
-
for (const pattern of patterns) {
|
|
1276
|
-
const resolved = path2.isAbsolute(pattern) ? pattern : path2.join(root, pattern);
|
|
1277
|
-
if (fs2.existsSync(resolved)) {
|
|
1278
|
-
compilation.fileDependencies.add(resolved);
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
});
|
|
1282
|
-
}
|
|
1283
|
-
},
|
|
1284
|
-
vite: {
|
|
1285
|
-
/**
|
|
1286
|
-
* Vite hook: pre-scans source files when config is resolved.
|
|
1287
|
-
* Also runs theme scan to generate .csszyx/theme.d.ts if scanCss is configured.
|
|
1288
|
-
* @param config - the resolved Vite configuration object
|
|
1289
|
-
*/
|
|
1290
|
-
configResolved(config) {
|
|
1291
|
-
const root = config.root || process.cwd();
|
|
1292
|
-
state.rootDir = root;
|
|
1293
|
-
prescanAndWriteClasses();
|
|
1294
|
-
runThemeScan(root, options.build?.scanCss);
|
|
1295
|
-
},
|
|
1296
|
-
/**
|
|
1297
|
-
* Vite HMR hook: re-runs theme scan when a watched CSS file changes,
|
|
1298
|
-
* and incrementally updates csszyx-classes.html when a source file gains new sz classes.
|
|
1299
|
-
* @param ctx - HMR context containing the changed file
|
|
1300
|
-
*/
|
|
1301
|
-
handleHotUpdate(ctx) {
|
|
1302
|
-
const scanCss = options.build?.scanCss;
|
|
1303
|
-
if (scanCss) {
|
|
1304
|
-
const patterns = Array.isArray(scanCss) ? scanCss : [scanCss];
|
|
1305
|
-
const root = ctx.server.config.root || process.cwd();
|
|
1306
|
-
const isWatched = patterns.some((p) => {
|
|
1307
|
-
const resolved = path2.isAbsolute(p) ? p : path2.join(root, p);
|
|
1308
|
-
return ctx.file === resolved;
|
|
1309
|
-
});
|
|
1310
|
-
if (isWatched) {
|
|
1311
|
-
runThemeScan(root, scanCss);
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
if (!SOURCE_EXTENSIONS.has(path2.extname(ctx.file))) {
|
|
1315
|
-
return;
|
|
1316
|
-
}
|
|
1317
|
-
if (ctx.file.includes("node_modules")) {
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
let fileContent, result;
|
|
1321
|
-
try {
|
|
1322
|
-
fileContent = fs2.readFileSync(ctx.file, "utf-8");
|
|
1323
|
-
} catch {
|
|
1324
|
-
return;
|
|
1325
|
-
}
|
|
1326
|
-
if (!fileContent.includes("sz=") && !/\bsz\s*:\s*["'{]/.test(fileContent)) {
|
|
1327
|
-
return;
|
|
1328
|
-
}
|
|
1329
|
-
try {
|
|
1330
|
-
result = transformSourceCode(fileContent, ctx.file, { astBudget: astBudgetOverride });
|
|
1331
|
-
} catch {
|
|
1332
|
-
return;
|
|
1333
|
-
}
|
|
1334
|
-
if (!result.transformed) {
|
|
1335
|
-
return;
|
|
1336
|
-
}
|
|
1337
|
-
const sizeBefore = state.classes.size;
|
|
1338
|
-
for (const cls of result.classes) {
|
|
1339
|
-
state.classes.add(cls);
|
|
1340
|
-
}
|
|
1341
|
-
for (const [token, data] of result.recoveryTokens) {
|
|
1342
|
-
state.recoveryTokens.set(token, data);
|
|
1343
|
-
}
|
|
1344
|
-
if (state.classes.size > sizeBefore) {
|
|
1345
|
-
writeSafelistFile(state.classes);
|
|
1346
|
-
const safelistPath = path2.join(state.rootDir, SAFELIST_FILENAME);
|
|
1347
|
-
ctx.server.watcher.emit("change", safelistPath);
|
|
1348
|
-
}
|
|
1349
|
-
},
|
|
1350
|
-
transformIndexHtml: {
|
|
1351
|
-
order: "pre",
|
|
1352
|
-
/**
|
|
1353
|
-
* Injects hydration data (mangle map + checksum) into the HTML document.
|
|
1354
|
-
* Also mangles class attributes in SSR-rendered HTML so they match mangled CSS selectors.
|
|
1355
|
-
* @param html - the raw HTML string to transform
|
|
1356
|
-
* @returns transformed HTML with injected hydration data
|
|
1357
|
-
*/
|
|
1358
|
-
handler(html) {
|
|
1359
|
-
finalizeMangleMap();
|
|
1360
|
-
let result = transformIndexHtml(html, state.mangleMap, state.checksum, {
|
|
1361
|
-
mode: options.production?.injectChecksum === false ? "script" : "script",
|
|
1362
|
-
minify: process.env.NODE_ENV === "production"
|
|
1363
|
-
});
|
|
1364
|
-
if (state.recoveryTokens.size > 0) {
|
|
1365
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
1366
|
-
const { manifest, strippedDevOnlyPaths } = buildRecoveryManifest(
|
|
1367
|
-
state.recoveryTokens,
|
|
1368
|
-
{
|
|
1369
|
-
production: isProduction,
|
|
1370
|
-
mangleChecksum: state.checksum
|
|
1371
|
-
}
|
|
1372
|
-
);
|
|
1373
|
-
if (strippedDevOnlyPaths.length > 0) {
|
|
1374
|
-
console.warn(
|
|
1375
|
-
`[csszyx] Stripped ${strippedDevOnlyPaths.length} szRecover="dev-only" token(s) from the production manifest. Recovery for these elements is disabled in production by design. Sites: ${strippedDevOnlyPaths.join(", ")}`
|
|
1376
|
-
);
|
|
1377
|
-
}
|
|
1378
|
-
result = injectRecoveryManifest(result, manifest);
|
|
1379
|
-
}
|
|
1380
|
-
return result;
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
}));
|
|
1385
|
-
const postPlugin = createUnplugin(() => ({
|
|
1386
|
-
name: "csszyx:post",
|
|
1387
|
-
enforce: "post",
|
|
1388
|
-
// No transform hook — all mangling is deferred to asset processing
|
|
1389
|
-
// where the complete mangle map is available.
|
|
1390
|
-
/**
|
|
1391
|
-
* Webpack hook: mangles CSS/JS class names in processAssets after compilation.
|
|
1392
|
-
* @param compiler - the Webpack compiler instance
|
|
1393
|
-
*/
|
|
1394
|
-
webpack(compiler) {
|
|
1395
|
-
compiler.hooks.compilation.tap("csszyx:post", (compilation) => {
|
|
1396
|
-
const stage = compiler.webpack?.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE || // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1397
|
-
compilation.constructor.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE;
|
|
1398
|
-
compilation.hooks.processAssets.tap(
|
|
1399
|
-
{
|
|
1400
|
-
name: "csszyx:post",
|
|
1401
|
-
stage: stage || 400
|
|
1402
|
-
// Fallback integer
|
|
1403
|
-
},
|
|
1404
|
-
(assets) => {
|
|
1405
|
-
finalizeMangleMap();
|
|
1406
|
-
const isWebpackDevMode = compiler.options.mode === "development";
|
|
1407
|
-
const manifestData = {
|
|
1408
|
-
version: "0.4.0",
|
|
1409
|
-
buildId: state.checksum,
|
|
1410
|
-
classes: Object.keys(state.mangleMap)
|
|
1411
|
-
};
|
|
1412
|
-
if (manglingEnabled && !isWebpackDevMode && Object.keys(state.mangleMap).length > 0) {
|
|
1413
|
-
manifestData.mangleMap = state.mangleMap;
|
|
1414
|
-
}
|
|
1415
|
-
compilation.emitAsset(
|
|
1416
|
-
"csszyx-manifest.json",
|
|
1417
|
-
new compiler.webpack.sources.RawSource(JSON.stringify(manifestData))
|
|
1418
|
-
);
|
|
1419
|
-
for (const file in assets) {
|
|
1420
|
-
const asset = assets[file];
|
|
1421
|
-
const source = asset.source().toString();
|
|
1422
|
-
if (manglingEnabled && !isWebpackDevMode && Object.keys(state.mangleMap).length > 0) {
|
|
1423
|
-
if (file.endsWith(".css")) {
|
|
1424
|
-
try {
|
|
1425
|
-
const result = mangleCSSSync(source, state.mangleMap, {
|
|
1426
|
-
debug: options.development?.debug,
|
|
1427
|
-
from: file
|
|
1428
|
-
});
|
|
1429
|
-
if (result.transformedCount > 0) {
|
|
1430
|
-
compilation.updateAsset(
|
|
1431
|
-
file,
|
|
1432
|
-
new compiler.webpack.sources.RawSource(result.css)
|
|
1433
|
-
);
|
|
1434
|
-
continue;
|
|
1435
|
-
}
|
|
1436
|
-
} catch (e) {
|
|
1437
|
-
if (e && typeof e === "object" && "name" in e && e.name === "CssSyntaxError") {
|
|
1438
|
-
} else {
|
|
1439
|
-
throw e;
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
} else if (file.endsWith(".html")) {
|
|
1443
|
-
const mangledHtml = source.replace(/\bclass="([^"]*)"/g, (_m, cls) => {
|
|
1444
|
-
const out = cls.split(/\s+/).filter(Boolean).map((c) => state.mangleMap[c] || c).join(" ");
|
|
1445
|
-
return out !== cls ? `class="${out}"` : _m;
|
|
1446
|
-
}).replace(/\bclass='([^']*)'/g, (_m, cls) => {
|
|
1447
|
-
const out = cls.split(/\s+/).filter(Boolean).map((c) => state.mangleMap[c] || c).join(" ");
|
|
1448
|
-
return out !== cls ? `class='${out}'` : _m;
|
|
1449
|
-
});
|
|
1450
|
-
if (mangledHtml !== source) {
|
|
1451
|
-
compilation.updateAsset(
|
|
1452
|
-
file,
|
|
1453
|
-
new compiler.webpack.sources.RawSource(mangledHtml)
|
|
1454
|
-
);
|
|
1455
|
-
continue;
|
|
1456
|
-
}
|
|
1457
|
-
} else if (file.endsWith(".js")) {
|
|
1458
|
-
let mangled = mangleCodeClasses(source);
|
|
1459
|
-
mangled = replacePlaceholders(mangled);
|
|
1460
|
-
if (mangled !== source) {
|
|
1461
|
-
compilation.updateAsset(
|
|
1462
|
-
file,
|
|
1463
|
-
new compiler.webpack.sources.RawSource(mangled)
|
|
1464
|
-
);
|
|
1465
|
-
continue;
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
if (file.endsWith(".js") && (source.includes(CHECKSUM_PLACEHOLDER) || source.includes(MANGLE_MAP_PLACEHOLDER))) {
|
|
1470
|
-
const replaced = replacePlaceholders(source);
|
|
1471
|
-
if (replaced !== source) {
|
|
1472
|
-
compilation.updateAsset(
|
|
1473
|
-
file,
|
|
1474
|
-
new compiler.webpack.sources.RawSource(replaced)
|
|
1475
|
-
);
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
);
|
|
1481
|
-
});
|
|
1482
|
-
},
|
|
1483
|
-
vite: {
|
|
1484
|
-
/**
|
|
1485
|
-
* Vite hook: mangles CSS selectors and JS class strings in the final bundle.
|
|
1486
|
-
* @param _options - the output options (unused)
|
|
1487
|
-
* @param bundle - the output bundle containing chunks and assets to process
|
|
1488
|
-
*/
|
|
1489
|
-
generateBundle(_options, bundle) {
|
|
1490
|
-
finalizeMangleMap();
|
|
1491
|
-
const manifestData = {
|
|
1492
|
-
version: "0.4.0",
|
|
1493
|
-
buildId: state.checksum,
|
|
1494
|
-
classes: Object.keys(state.mangleMap)
|
|
1495
|
-
};
|
|
1496
|
-
if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
|
|
1497
|
-
manifestData.mangleMap = state.mangleMap;
|
|
1498
|
-
}
|
|
1499
|
-
this.emitFile({
|
|
1500
|
-
type: "asset",
|
|
1501
|
-
fileName: "csszyx-manifest.json",
|
|
1502
|
-
source: JSON.stringify(manifestData)
|
|
1503
|
-
});
|
|
1504
|
-
for (const file in bundle) {
|
|
1505
|
-
const chunk = bundle[file];
|
|
1506
|
-
if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
|
|
1507
|
-
if (chunk.type === "asset" && chunk.fileName.endsWith(".css")) {
|
|
1508
|
-
const css = chunk.source.toString();
|
|
1509
|
-
try {
|
|
1510
|
-
const result = mangleCSSSync(css, state.mangleMap, {
|
|
1511
|
-
debug: options.development?.debug,
|
|
1512
|
-
from: file
|
|
1513
|
-
});
|
|
1514
|
-
if (result.transformedCount > 0) {
|
|
1515
|
-
chunk.source = result.css;
|
|
1516
|
-
}
|
|
1517
|
-
} catch (e) {
|
|
1518
|
-
if (e && typeof e === "object" && "name" in e && e.name === "CssSyntaxError") {
|
|
1519
|
-
} else {
|
|
1520
|
-
throw e;
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
continue;
|
|
1524
|
-
} else if (chunk.type === "chunk") {
|
|
1525
|
-
let mangledCode = mangleCodeClasses(chunk.code);
|
|
1526
|
-
mangledCode = replacePlaceholders(mangledCode);
|
|
1527
|
-
if (mangledCode !== chunk.code) {
|
|
1528
|
-
chunk.code = mangledCode;
|
|
1529
|
-
}
|
|
1530
|
-
continue;
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
if (chunk.type === "chunk" && (chunk.code.includes(CHECKSUM_PLACEHOLDER) || chunk.code.includes(MANGLE_MAP_PLACEHOLDER))) {
|
|
1534
|
-
const replaced = replacePlaceholders(chunk.code);
|
|
1535
|
-
if (replaced !== chunk.code) {
|
|
1536
|
-
chunk.code = replaced;
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
}));
|
|
1543
|
-
return { prePlugin, postPlugin };
|
|
1544
|
-
}
|
|
1545
|
-
var defaultInstance = createCsszyxPlugins();
|
|
1546
|
-
var unplugin = defaultInstance.prePlugin;
|
|
1547
|
-
var vitePlugin = (options = {}) => {
|
|
1548
|
-
const { prePlugin, postPlugin } = createCsszyxPlugins(options);
|
|
1549
|
-
return [prePlugin.vite(options), postPlugin.vite(options)];
|
|
1550
|
-
};
|
|
1551
|
-
var webpackPlugin = (options = {}) => {
|
|
1552
|
-
const { prePlugin, postPlugin } = createCsszyxPlugins(options);
|
|
1553
|
-
return {
|
|
1554
|
-
/**
|
|
1555
|
-
* Applies both pre and post plugins to the Webpack compiler.
|
|
1556
|
-
* @param compiler - the Webpack compiler instance to apply plugins to
|
|
1557
|
-
*/
|
|
1558
|
-
apply(compiler) {
|
|
1559
|
-
prePlugin.webpack(options).apply(compiler);
|
|
1560
|
-
postPlugin.webpack(options).apply(compiler);
|
|
1561
|
-
}
|
|
1562
|
-
};
|
|
1563
|
-
};
|
|
1564
|
-
var rollupPlugin = (options = {}) => {
|
|
1565
|
-
const { prePlugin, postPlugin } = createCsszyxPlugins(options);
|
|
1566
|
-
return [prePlugin.rollup(options), postPlugin.rollup(options)];
|
|
1567
|
-
};
|
|
1568
|
-
var esbuildPlugin = (options = {}) => {
|
|
1569
|
-
const { prePlugin, postPlugin } = createCsszyxPlugins(options);
|
|
1570
|
-
return {
|
|
1571
|
-
name: "csszyx",
|
|
1572
|
-
/**
|
|
1573
|
-
* Registers both pre and post plugin setup hooks with the esbuild build.
|
|
1574
|
-
* @param build - the esbuild plugin build context
|
|
1575
|
-
*/
|
|
1576
|
-
setup(build) {
|
|
1577
|
-
const b = build;
|
|
1578
|
-
prePlugin.esbuild(options).setup(b);
|
|
1579
|
-
postPlugin.esbuild(options).setup(b);
|
|
1580
|
-
}
|
|
1581
|
-
};
|
|
1582
|
-
};
|
|
1583
|
-
|
|
1584
|
-
export {
|
|
1585
|
-
hasUseServerDirective,
|
|
1586
|
-
hasUseClientDirective,
|
|
1587
|
-
isRSCServerModule,
|
|
1588
|
-
findRSCBoundaryViolation,
|
|
1589
|
-
createRSCModuleRecord,
|
|
1590
|
-
findRSCGraphViolation,
|
|
1591
|
-
assertNoRSCGraphViolation,
|
|
1592
|
-
assertNoRSCBoundaryViolation,
|
|
1593
|
-
parseThemeBlocks,
|
|
1594
|
-
mergeThemes,
|
|
1595
|
-
hasTokens,
|
|
1596
|
-
mangleCodeClassesSync,
|
|
1597
|
-
unplugin,
|
|
1598
|
-
vitePlugin,
|
|
1599
|
-
webpackPlugin,
|
|
1600
|
-
rollupPlugin,
|
|
1601
|
-
esbuildPlugin
|
|
1602
|
-
};
|