@gustcss/vite 0.9.1 → 0.10.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 +41 -0
- package/dist/index.cjs +666 -14
- package/dist/index.d.ts +21 -0
- package/dist/index.mjs +663 -11
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,7 +20,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
20
20
|
}
|
|
21
21
|
return to;
|
|
22
22
|
};
|
|
23
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
24
24
|
value: mod,
|
|
25
25
|
enumerable: true
|
|
26
26
|
}) : target, mod));
|
|
@@ -32,7 +32,7 @@ let fs = require("fs");
|
|
|
32
32
|
fs = __toESM(fs, 1);
|
|
33
33
|
let crypto = require("crypto");
|
|
34
34
|
//#endregion
|
|
35
|
-
//#region src/
|
|
35
|
+
//#region src/mangle.js
|
|
36
36
|
var import_runner = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
37
37
|
/**
|
|
38
38
|
* gustcss shared runner — helpers for the @gustcss/postcss and @gustcss/vite
|
|
@@ -92,6 +92,24 @@ var import_runner = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
92
92
|
return null;
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
95
|
+
* Report whether a resolved gustcss.config.json turns on class-name mangling.
|
|
96
|
+
*
|
|
97
|
+
* Callers that cannot rewrite source (dev server, PostCSS) use this to decide
|
|
98
|
+
* whether they must pass `--mangle-class-names=false`. Only passing the flag
|
|
99
|
+
* when needed keeps older CLI binaries that predate the flag working.
|
|
100
|
+
*
|
|
101
|
+
* @param {{ configPath: string|null, fs?: { readFileSync: Function } }} options
|
|
102
|
+
* @returns {boolean}
|
|
103
|
+
*/
|
|
104
|
+
function configEnablesMangling({ configPath, fs: fs$4 = require("fs") }) {
|
|
105
|
+
if (!configPath) return false;
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(fs$4.readFileSync(configPath, "utf-8"))?.mangleClassNames === true;
|
|
108
|
+
} catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
95
113
|
* Write a temporary config file ({ content }) at <cwd>/<prefix>.<random8hex>.tmp.json,
|
|
96
114
|
* invoke `fn` with the temp file path, and always clean the file up afterwards.
|
|
97
115
|
*
|
|
@@ -104,14 +122,14 @@ var import_runner = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
104
122
|
* @param {(tempConfigPath: string) => any} fn
|
|
105
123
|
* @returns {any} whatever `fn` returns
|
|
106
124
|
*/
|
|
107
|
-
function withTempConfig({ cwd, content, prefix, fs: fs$
|
|
125
|
+
function withTempConfig({ cwd, content, prefix, fs: fs$5 = require("fs") }, fn) {
|
|
108
126
|
const tmpName = `${prefix}.${crypto$1.randomBytes(8).toString("hex")}.tmp.json`;
|
|
109
127
|
const tempConfigPath = path$2.join(cwd, tmpName);
|
|
110
|
-
fs$
|
|
128
|
+
fs$5.writeFileSync(tempConfigPath, JSON.stringify({ content }), { flag: "wx" });
|
|
111
129
|
try {
|
|
112
130
|
return fn(tempConfigPath);
|
|
113
131
|
} finally {
|
|
114
|
-
if (fs$
|
|
132
|
+
if (fs$5.existsSync(tempConfigPath)) fs$5.unlinkSync(tempConfigPath);
|
|
115
133
|
}
|
|
116
134
|
}
|
|
117
135
|
/**
|
|
@@ -123,25 +141,590 @@ var import_runner = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
123
141
|
* @param {string} [options.output] output path (required for 'output' mode)
|
|
124
142
|
* @param {boolean} [options.cssLayers] append --css-layers when truthy
|
|
125
143
|
* @param {boolean} [options.watch] append --watch when truthy
|
|
144
|
+
* @param {boolean} [options.mangleClassNames] explicitly enable or disable mangling
|
|
145
|
+
* @param {string} [options.mangleMap] class-name manifest output path
|
|
146
|
+
* @param {string[]} [options.mangleExclude] class names to preserve
|
|
126
147
|
* @param {string|null} [options.configPath] append --config <path> when present
|
|
127
148
|
* @returns {string[]}
|
|
128
149
|
*/
|
|
129
|
-
function buildArgs({ mode, output, cssLayers, configPath, watch }) {
|
|
150
|
+
function buildArgs({ mode, output, cssLayers, configPath, watch, mangleClassNames, mangleMap, mangleExclude }) {
|
|
130
151
|
const args = ["build"];
|
|
131
152
|
if (watch) args.push("--watch");
|
|
132
153
|
if (mode === "stdout") args.push("--stdout");
|
|
133
154
|
else args.push("-o", output);
|
|
134
155
|
if (cssLayers) args.push("--css-layers");
|
|
156
|
+
if (mangleClassNames !== void 0) args.push(`--mangle-class-names=${mangleClassNames}`);
|
|
157
|
+
if (mangleMap) args.push("--mangle-map", mangleMap);
|
|
158
|
+
if (mangleExclude?.length) args.push("--mangle-exclude", mangleExclude.join(","));
|
|
135
159
|
if (configPath) args.push("--config", configPath);
|
|
136
160
|
return args;
|
|
137
161
|
}
|
|
138
162
|
module.exports = {
|
|
139
163
|
resolveBinary,
|
|
140
164
|
resolveConfig,
|
|
165
|
+
configEnablesMangling,
|
|
141
166
|
withTempConfig,
|
|
142
167
|
buildArgs
|
|
143
168
|
};
|
|
144
169
|
})))(), 1);
|
|
170
|
+
const SUPPORTED_SOURCE = /\.(?:[cm]?[jt]sx?|html)(?:$|\?)/;
|
|
171
|
+
const HTML_SOURCE = /\.html(?:$|\?)/;
|
|
172
|
+
const UNSUPPORTED_FRAMEWORK_SOURCE = /\.(?:vue|astro|svelte)(?:$|\?)/;
|
|
173
|
+
const DEPENDENCY_SOURCE = /(?:^|[\\/])node_modules[\\/]/;
|
|
174
|
+
function hasOwn(classes, token) {
|
|
175
|
+
return Object.hasOwn(classes, token);
|
|
176
|
+
}
|
|
177
|
+
function collectClassListEdits(value, classes, baseOffset, edits) {
|
|
178
|
+
for (const match of value.matchAll(/\S+/g)) {
|
|
179
|
+
const replacement = classes[match[0]];
|
|
180
|
+
if (hasOwn(classes, match[0]) && replacement !== match[0]) edits.push({
|
|
181
|
+
start: baseOffset + match.index,
|
|
182
|
+
end: baseOffset + match.index + match[0].length,
|
|
183
|
+
replacement
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function maskRange(chars, code, start, end) {
|
|
188
|
+
for (let index = start; index < end; index += 1) if (code[index] !== "\n" && code[index] !== "\r") chars[index] = " ";
|
|
189
|
+
}
|
|
190
|
+
function copyRange(chars, code, start, end) {
|
|
191
|
+
for (let index = start; index < end; index += 1) chars[index] = code[index];
|
|
192
|
+
}
|
|
193
|
+
function findHtmlTagEnd(code, start) {
|
|
194
|
+
let quote = null;
|
|
195
|
+
for (let index = start + 1; index < code.length; index += 1) {
|
|
196
|
+
const char = code[index];
|
|
197
|
+
if (quote !== null) {
|
|
198
|
+
if (char === quote) quote = null;
|
|
199
|
+
} else if (char === "\"" || char === "'") quote = char;
|
|
200
|
+
else if (char === ">") return index + 1;
|
|
201
|
+
}
|
|
202
|
+
return -1;
|
|
203
|
+
}
|
|
204
|
+
function findRawTextClosing(code, tagName, start) {
|
|
205
|
+
const lower = code.toLowerCase();
|
|
206
|
+
const needle = `</${tagName}`;
|
|
207
|
+
let index = lower.indexOf(needle, start);
|
|
208
|
+
while (index >= 0) {
|
|
209
|
+
const after = lower[index + needle.length];
|
|
210
|
+
if (after === void 0 || /[\s>]/.test(after)) return index;
|
|
211
|
+
index = lower.indexOf(needle, index + 1);
|
|
212
|
+
}
|
|
213
|
+
return -1;
|
|
214
|
+
}
|
|
215
|
+
function analyzeHtml(code, id) {
|
|
216
|
+
const markup = new Array(code.length).fill(" ");
|
|
217
|
+
const scriptRanges = [];
|
|
218
|
+
const exampleStack = [];
|
|
219
|
+
let index = 0;
|
|
220
|
+
while (index < code.length) {
|
|
221
|
+
const open = code.indexOf("<", index);
|
|
222
|
+
if (open < 0) break;
|
|
223
|
+
if (code.startsWith("<!--", open)) {
|
|
224
|
+
const commentEnd = code.indexOf("-->", open + 4);
|
|
225
|
+
index = commentEnd < 0 ? code.length : commentEnd + 3;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const tagEnd = findHtmlTagEnd(code, open);
|
|
229
|
+
if (tagEnd < 0) break;
|
|
230
|
+
const tag = code.slice(open, tagEnd);
|
|
231
|
+
const closingMatch = tag.match(/^<\s*\/\s*([A-Za-z][\w:-]*)/);
|
|
232
|
+
const openingMatch = closingMatch ? null : tag.match(/^<\s*([A-Za-z][\w:-]*)/);
|
|
233
|
+
const tagName = (closingMatch?.[1] ?? openingMatch?.[1] ?? "").toLowerCase();
|
|
234
|
+
if (!tagName) {
|
|
235
|
+
index = tagEnd;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const isClosing = closingMatch !== null;
|
|
239
|
+
const selfClosing = !isClosing && /\/\s*>$/.test(tag);
|
|
240
|
+
const isExampleTag = tagName === "code" || tagName === "pre";
|
|
241
|
+
if (exampleStack.length === 0 || isExampleTag) copyRange(markup, code, open, tagEnd);
|
|
242
|
+
if (isClosing && isExampleTag) {
|
|
243
|
+
if (exampleStack.at(-1) !== tagName) throw new Error(`[gustcss] mismatched </${tagName}> in ${id}`);
|
|
244
|
+
exampleStack.pop();
|
|
245
|
+
} else if (!isClosing && isExampleTag && !selfClosing) exampleStack.push(tagName);
|
|
246
|
+
if (!isClosing && !selfClosing && exampleStack.length === 0 && (tagName === "script" || tagName === "style")) {
|
|
247
|
+
const rawEnd = findRawTextClosing(code, tagName, tagEnd);
|
|
248
|
+
const contentEnd = rawEnd < 0 ? code.length : rawEnd;
|
|
249
|
+
if (tagName === "script") scriptRanges.push({
|
|
250
|
+
start: tagEnd,
|
|
251
|
+
end: contentEnd
|
|
252
|
+
});
|
|
253
|
+
index = contentEnd;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
index = tagEnd;
|
|
257
|
+
}
|
|
258
|
+
if (exampleStack.length > 0) throw new Error(`[gustcss] unclosed <${exampleStack.at(-1)}> in ${id}`);
|
|
259
|
+
return {
|
|
260
|
+
markup: markup.join(""),
|
|
261
|
+
scriptRanges
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function canStartRegex(code, index, previous) {
|
|
265
|
+
if (previous === void 0 || /[({[,:;=!?&|+\-*%^~<>]/.test(previous)) return true;
|
|
266
|
+
return /\b(?:return|throw|case|delete|void|typeof|instanceof|in|of|yield|await)\s*$/.test(code.slice(Math.max(0, index - 24), index));
|
|
267
|
+
}
|
|
268
|
+
function isJsxAttributeAt(structure, index) {
|
|
269
|
+
const before = structure.slice(0, index);
|
|
270
|
+
const open = before.lastIndexOf("<");
|
|
271
|
+
if (open < 0 || open < before.lastIndexOf(">")) return false;
|
|
272
|
+
return /^<\s*[A-Za-z][\w.$:-]*(?:\s|$)/.test(before.slice(open));
|
|
273
|
+
}
|
|
274
|
+
function createJsMasks(code) {
|
|
275
|
+
const structure = code.split("");
|
|
276
|
+
const inspection = code.split("");
|
|
277
|
+
let index = 0;
|
|
278
|
+
let previousSignificant;
|
|
279
|
+
while (index < code.length) {
|
|
280
|
+
const char = code[index];
|
|
281
|
+
const next = code[index + 1];
|
|
282
|
+
if (char === "/" && next === "/") {
|
|
283
|
+
const end = code.indexOf("\n", index + 2);
|
|
284
|
+
const stop = end < 0 ? code.length : end;
|
|
285
|
+
maskRange(structure, code, index, stop);
|
|
286
|
+
maskRange(inspection, code, index, stop);
|
|
287
|
+
index = stop;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (char === "/" && next === "*") {
|
|
291
|
+
const end = code.indexOf("*/", index + 2);
|
|
292
|
+
const stop = end < 0 ? code.length : end + 2;
|
|
293
|
+
maskRange(structure, code, index, stop);
|
|
294
|
+
maskRange(inspection, code, index, stop);
|
|
295
|
+
index = stop;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (char === "\"" || char === "'" || char === "`") {
|
|
299
|
+
const quote = char;
|
|
300
|
+
const start = index;
|
|
301
|
+
index += 1;
|
|
302
|
+
let escaped = false;
|
|
303
|
+
while (index < code.length) {
|
|
304
|
+
const current = code[index];
|
|
305
|
+
if (escaped) escaped = false;
|
|
306
|
+
else if (current === "\\") escaped = true;
|
|
307
|
+
else if (current === quote) {
|
|
308
|
+
index += 1;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
index += 1;
|
|
312
|
+
}
|
|
313
|
+
maskRange(structure, code, start, index);
|
|
314
|
+
previousSignificant = quote;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (char === "/" && canStartRegex(code, index, previousSignificant)) {
|
|
318
|
+
const start = index;
|
|
319
|
+
index += 1;
|
|
320
|
+
let escaped = false;
|
|
321
|
+
let characterClass = false;
|
|
322
|
+
while (index < code.length) {
|
|
323
|
+
const current = code[index];
|
|
324
|
+
if (escaped) escaped = false;
|
|
325
|
+
else if (current === "\\") escaped = true;
|
|
326
|
+
else if (current === "[") characterClass = true;
|
|
327
|
+
else if (current === "]") characterClass = false;
|
|
328
|
+
else if (current === "/" && !characterClass) {
|
|
329
|
+
index += 1;
|
|
330
|
+
while (/[A-Za-z]/.test(code[index] ?? "")) index += 1;
|
|
331
|
+
break;
|
|
332
|
+
} else if (current === "\n" || current === "\r") break;
|
|
333
|
+
index += 1;
|
|
334
|
+
}
|
|
335
|
+
maskRange(structure, code, start, index);
|
|
336
|
+
maskRange(inspection, code, start, index);
|
|
337
|
+
previousSignificant = "/";
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (!/\s/.test(char)) previousSignificant = char;
|
|
341
|
+
index += 1;
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
structure: structure.join(""),
|
|
345
|
+
inspection: inspection.join("")
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
const classNameTrieCache = /* @__PURE__ */ new WeakMap();
|
|
349
|
+
function classNameTrie(classes) {
|
|
350
|
+
let root = classNameTrieCache.get(classes);
|
|
351
|
+
if (root) return root;
|
|
352
|
+
root = /* @__PURE__ */ new Map();
|
|
353
|
+
for (const className of Object.keys(classes)) {
|
|
354
|
+
let node = root;
|
|
355
|
+
for (const char of className) {
|
|
356
|
+
if (!node.has(char)) node.set(char, /* @__PURE__ */ new Map());
|
|
357
|
+
node = node.get(char);
|
|
358
|
+
}
|
|
359
|
+
node.className = className;
|
|
360
|
+
}
|
|
361
|
+
classNameTrieCache.set(classes, root);
|
|
362
|
+
return root;
|
|
363
|
+
}
|
|
364
|
+
function isBeforeBoundary(char) {
|
|
365
|
+
return char === void 0 || !/[A-Za-z0-9_@:/-]/.test(char);
|
|
366
|
+
}
|
|
367
|
+
function isAfterBoundary(char, before, beforeBefore) {
|
|
368
|
+
if (char === void 0 || char === ":") return true;
|
|
369
|
+
if (char === ".") return before === "." && (beforeBefore === void 0 || !/[A-Za-z0-9_/\\.]/.test(beforeBefore));
|
|
370
|
+
return !/[A-Za-z0-9_@/-]/.test(char);
|
|
371
|
+
}
|
|
372
|
+
function findMappedToken(value, classes) {
|
|
373
|
+
const trie = classNameTrie(classes);
|
|
374
|
+
let found = null;
|
|
375
|
+
for (let start = 0; start < value.length; start += 1) {
|
|
376
|
+
const before = start === 0 ? void 0 : value[start - 1];
|
|
377
|
+
if (!isBeforeBoundary(before)) continue;
|
|
378
|
+
const beforeBefore = start < 2 ? void 0 : value[start - 2];
|
|
379
|
+
let node = trie;
|
|
380
|
+
for (let end = start; end < value.length; end += 1) {
|
|
381
|
+
node = node.get(value[end]);
|
|
382
|
+
if (!node) break;
|
|
383
|
+
if (!node.className) continue;
|
|
384
|
+
const afterIndex = end + 1;
|
|
385
|
+
if (!isAfterBoundary(afterIndex === value.length ? void 0 : value[afterIndex], before, beforeBefore)) continue;
|
|
386
|
+
if (found === null || node.className.length > found.length || node.className.length === found.length && node.className.localeCompare(found) < 0) found = node.className;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return found;
|
|
390
|
+
}
|
|
391
|
+
function scanQuotedSegments(value, visitor) {
|
|
392
|
+
let index = 0;
|
|
393
|
+
while (index < value.length) {
|
|
394
|
+
const quote = value[index];
|
|
395
|
+
if (quote !== "\"" && quote !== "'" && quote !== "`") {
|
|
396
|
+
index += 1;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
const start = index;
|
|
400
|
+
index += 1;
|
|
401
|
+
let escaped = false;
|
|
402
|
+
while (index < value.length) {
|
|
403
|
+
const char = value[index];
|
|
404
|
+
if (escaped) escaped = false;
|
|
405
|
+
else if (char === "\\") escaped = true;
|
|
406
|
+
else if (char === quote) {
|
|
407
|
+
index += 1;
|
|
408
|
+
visitor({
|
|
409
|
+
start,
|
|
410
|
+
end: index,
|
|
411
|
+
quote,
|
|
412
|
+
contents: value.slice(start + 1, index - 1)
|
|
413
|
+
});
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
index += 1;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function findMappedInQuotedSegments(value, classes) {
|
|
421
|
+
let found = null;
|
|
422
|
+
scanQuotedSegments(value, ({ contents }) => {
|
|
423
|
+
if (found === null) found = findMappedToken(contents, classes);
|
|
424
|
+
});
|
|
425
|
+
return found;
|
|
426
|
+
}
|
|
427
|
+
function stripQuotedSegments(value) {
|
|
428
|
+
let output = "";
|
|
429
|
+
let cursor = 0;
|
|
430
|
+
scanQuotedSegments(value, ({ start, end }) => {
|
|
431
|
+
output += value.slice(cursor, start);
|
|
432
|
+
output += " ".repeat(end - start);
|
|
433
|
+
cursor = end;
|
|
434
|
+
});
|
|
435
|
+
return output + value.slice(cursor);
|
|
436
|
+
}
|
|
437
|
+
function collectTemplateBodyEdits(body, classes, baseOffset, edits) {
|
|
438
|
+
let staticStart = 0;
|
|
439
|
+
let index = 0;
|
|
440
|
+
while (index < body.length) {
|
|
441
|
+
if (body[index] !== "$" || body[index + 1] !== "{") {
|
|
442
|
+
index += 1;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
collectClassListEdits(body.slice(staticStart, index), classes, baseOffset + staticStart, edits);
|
|
446
|
+
index += 2;
|
|
447
|
+
let depth = 1;
|
|
448
|
+
let quote = null;
|
|
449
|
+
let escaped = false;
|
|
450
|
+
while (index < body.length && depth > 0) {
|
|
451
|
+
const char = body[index];
|
|
452
|
+
if (quote !== null) {
|
|
453
|
+
if (escaped) escaped = false;
|
|
454
|
+
else if (char === "\\") escaped = true;
|
|
455
|
+
else if (char === quote) quote = null;
|
|
456
|
+
} else if (char === "\"" || char === "'" || char === "`") quote = char;
|
|
457
|
+
else if (char === "{") depth += 1;
|
|
458
|
+
else if (char === "}") depth -= 1;
|
|
459
|
+
index += 1;
|
|
460
|
+
}
|
|
461
|
+
staticStart = index;
|
|
462
|
+
}
|
|
463
|
+
collectClassListEdits(body.slice(staticStart), classes, baseOffset + staticStart, edits);
|
|
464
|
+
}
|
|
465
|
+
function findBalancedEnd(code, open, opening, closing) {
|
|
466
|
+
let index = open + 1;
|
|
467
|
+
let depth = 1;
|
|
468
|
+
let quote = null;
|
|
469
|
+
let escaped = false;
|
|
470
|
+
while (index < code.length && depth > 0) {
|
|
471
|
+
const char = code[index];
|
|
472
|
+
if (quote !== null) {
|
|
473
|
+
if (escaped) escaped = false;
|
|
474
|
+
else if (char === "\\") escaped = true;
|
|
475
|
+
else if (char === quote) quote = null;
|
|
476
|
+
} else if (char === "\"" || char === "'" || char === "`") quote = char;
|
|
477
|
+
else if (char === opening) depth += 1;
|
|
478
|
+
else if (char === closing) depth -= 1;
|
|
479
|
+
index += 1;
|
|
480
|
+
}
|
|
481
|
+
return depth === 0 ? index : -1;
|
|
482
|
+
}
|
|
483
|
+
function rejectAmbiguousClassExpressions(code, structure, classes, id) {
|
|
484
|
+
const pattern = /(?<![:\w-])class(?:Name)?\s*=\s*\{/g;
|
|
485
|
+
let match;
|
|
486
|
+
while ((match = pattern.exec(structure)) !== null) {
|
|
487
|
+
const open = code.indexOf("{", match.index);
|
|
488
|
+
const end = findBalancedEnd(code, open, "{", "}");
|
|
489
|
+
if (end < 0) break;
|
|
490
|
+
const ambiguous = findMappedToken(code.slice(open + 1, end - 1), classes);
|
|
491
|
+
if (ambiguous) throw new Error(`[gustcss] mapped class "${ambiguous}" remains in an ambiguous class expression in ${id}. Use a static class attribute/template segment, or add it to mangleExclude.`);
|
|
492
|
+
pattern.lastIndex = end;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function collectClassListCallEdits(code, structure, classes, id, edits) {
|
|
496
|
+
const pattern = /\.classList\.(?:add|remove|toggle|contains|replace)\s*\(/g;
|
|
497
|
+
let match;
|
|
498
|
+
while ((match = pattern.exec(structure)) !== null) {
|
|
499
|
+
const open = code.indexOf("(", match.index);
|
|
500
|
+
const end = findBalancedEnd(code, open, "(", ")");
|
|
501
|
+
if (end < 0) break;
|
|
502
|
+
const args = code.slice(open + 1, end - 1);
|
|
503
|
+
const withoutStrings = stripQuotedSegments(args);
|
|
504
|
+
const dynamic = findMappedToken(withoutStrings, classes);
|
|
505
|
+
if (dynamic) throw new Error(`[gustcss] mapped class "${dynamic}" remains in a dynamic classList argument in ${id}. Pass it as a direct string argument, or add it to mangleExclude.`);
|
|
506
|
+
if (withoutStrings.includes("(")) {
|
|
507
|
+
const ambiguous = findMappedInQuotedSegments(args, classes);
|
|
508
|
+
if (ambiguous) throw new Error(`[gustcss] mapped class "${ambiguous}" is nested in a classList call in ${id}. Pass it as a direct string argument, or add it to mangleExclude.`);
|
|
509
|
+
}
|
|
510
|
+
scanQuotedSegments(args, ({ start, contents }) => {
|
|
511
|
+
collectClassListEdits(contents, classes, open + 1 + start + 1, edits);
|
|
512
|
+
});
|
|
513
|
+
pattern.lastIndex = end;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function rejectDynamicSetAttributeCalls(code, structure, classes, id) {
|
|
517
|
+
const pattern = /\.setAttribute\s*\(/g;
|
|
518
|
+
let match;
|
|
519
|
+
while ((match = pattern.exec(structure)) !== null) {
|
|
520
|
+
const open = code.indexOf("(", match.index);
|
|
521
|
+
const end = findBalancedEnd(code, open, "(", ")");
|
|
522
|
+
if (end < 0) break;
|
|
523
|
+
const args = code.slice(open + 1, end - 1);
|
|
524
|
+
if (!/^\s*(["'])class\1\s*,/.test(args)) {
|
|
525
|
+
pattern.lastIndex = end;
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const withoutStrings = stripQuotedSegments(args);
|
|
529
|
+
const dynamic = findMappedToken(withoutStrings, classes);
|
|
530
|
+
const nested = withoutStrings.includes("(") ? findMappedInQuotedSegments(args, classes) : null;
|
|
531
|
+
const ambiguous = dynamic ?? nested;
|
|
532
|
+
if (ambiguous) throw new Error(`[gustcss] mapped class "${ambiguous}" remains in a dynamic setAttribute in ${id}. Pass a direct class string, or add it to mangleExclude.`);
|
|
533
|
+
pattern.lastIndex = end;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function rejectDynamicClassNameAssignments(code, structure, classes, id) {
|
|
537
|
+
const pattern = /\.className\s*=(?!=)\s*/g;
|
|
538
|
+
while (pattern.exec(structure) !== null) {
|
|
539
|
+
const start = pattern.lastIndex;
|
|
540
|
+
const candidates = [code.indexOf(";", start), code.indexOf("\n", start)].filter((index) => index >= 0);
|
|
541
|
+
const end = candidates.length > 0 ? Math.min(...candidates) : code.length;
|
|
542
|
+
const ambiguous = findMappedToken(code.slice(start, end), classes);
|
|
543
|
+
if (ambiguous) throw new Error(`[gustcss] mapped class "${ambiguous}" remains in a dynamic className assignment in ${id}. Assign a direct class string, or add it to mangleExclude.`);
|
|
544
|
+
pattern.lastIndex = end;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function applyEdits(code, edits) {
|
|
548
|
+
const sorted = [...edits].sort((a, b) => a.start - b.start || a.end - b.end);
|
|
549
|
+
let output = "";
|
|
550
|
+
let cursor = 0;
|
|
551
|
+
const origins = [];
|
|
552
|
+
for (const edit of sorted) {
|
|
553
|
+
if (edit.start < cursor) throw new Error("[gustcss] overlapping class-name rewrites");
|
|
554
|
+
output += code.slice(cursor, edit.start);
|
|
555
|
+
for (let index = cursor; index < edit.start; index += 1) origins.push(index);
|
|
556
|
+
const sourceLength = Math.max(1, edit.end - edit.start);
|
|
557
|
+
if (edit.replacement.length > sourceLength) throw new Error("[gustcss] class-name replacement must not exceed its source length");
|
|
558
|
+
output += edit.replacement;
|
|
559
|
+
for (let index = 0; index < edit.replacement.length; index += 1) origins.push(edit.start + Math.min(index, sourceLength - 1));
|
|
560
|
+
cursor = edit.end;
|
|
561
|
+
}
|
|
562
|
+
output += code.slice(cursor);
|
|
563
|
+
for (let index = cursor; index < code.length; index += 1) origins.push(index);
|
|
564
|
+
return {
|
|
565
|
+
code: output,
|
|
566
|
+
origins
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
function sourcesForFile(code, id) {
|
|
570
|
+
if (!HTML_SOURCE.test(id)) {
|
|
571
|
+
const js = createJsMasks(code);
|
|
572
|
+
return {
|
|
573
|
+
attributes: code,
|
|
574
|
+
structure: js.structure,
|
|
575
|
+
inspection: js.inspection
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
const html = analyzeHtml(code, id);
|
|
579
|
+
const structure = new Array(code.length).fill(" ");
|
|
580
|
+
const inspection = new Array(code.length).fill(" ");
|
|
581
|
+
for (const range of html.scriptRanges) {
|
|
582
|
+
const script = code.slice(range.start, range.end);
|
|
583
|
+
const js = createJsMasks(script);
|
|
584
|
+
for (let offset = 0; offset < script.length; offset += 1) {
|
|
585
|
+
structure[range.start + offset] = js.structure[offset];
|
|
586
|
+
inspection[range.start + offset] = js.inspection[offset];
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
attributes: html.markup,
|
|
591
|
+
structure: structure.join(""),
|
|
592
|
+
inspection: inspection.join("")
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
function collectRecognizedContextEdits(code, classes, id) {
|
|
596
|
+
const edits = [];
|
|
597
|
+
const sources = sourcesForFile(code, id);
|
|
598
|
+
const attributePattern = /((?<![:\w-])class(?:Name)?\s*=\s*)(?:(["'])([\s\S]*?)(\2)|(\{\s*)(["'])([\s\S]*?)(\6)(\s*\}))/g;
|
|
599
|
+
let match;
|
|
600
|
+
while ((match = attributePattern.exec(sources.attributes)) !== null) {
|
|
601
|
+
if (!HTML_SOURCE.test(id) && !isJsxAttributeAt(sources.structure, match.index)) continue;
|
|
602
|
+
if (match[2] !== void 0) collectClassListEdits(match[3], classes, match.index + match[1].length + 1, edits);
|
|
603
|
+
else collectClassListEdits(match[7], classes, match.index + match[1].length + match[5].length + 1, edits);
|
|
604
|
+
}
|
|
605
|
+
const templatePattern = /((?<![:\w-])class(?:Name)?\s*=\s*\{\s*`)([\s\S]*?)(`\s*\})/g;
|
|
606
|
+
while ((match = templatePattern.exec(sources.attributes)) !== null) {
|
|
607
|
+
if (!HTML_SOURCE.test(id) && !isJsxAttributeAt(sources.structure, match.index)) continue;
|
|
608
|
+
collectTemplateBodyEdits(match[2], classes, match.index + match[1].length, edits);
|
|
609
|
+
}
|
|
610
|
+
collectClassListCallEdits(code, sources.structure, classes, id, edits);
|
|
611
|
+
const setAttributePattern = /(\.setAttribute\(\s*["']class["']\s*,\s*)(["'])([\s\S]*?)(\2)(\s*\))/g;
|
|
612
|
+
while ((match = setAttributePattern.exec(code)) !== null) {
|
|
613
|
+
if (sources.structure[match.index] === " ") continue;
|
|
614
|
+
collectClassListEdits(match[3], classes, match.index + match[1].length + 1, edits);
|
|
615
|
+
}
|
|
616
|
+
return applyEdits(code, edits);
|
|
617
|
+
}
|
|
618
|
+
const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
619
|
+
function encodeVLQ(value) {
|
|
620
|
+
let encoded = "";
|
|
621
|
+
let vlq = Math.abs(value) * 2 + (value < 0 ? 1 : 0);
|
|
622
|
+
do {
|
|
623
|
+
let digit = vlq % 32;
|
|
624
|
+
vlq = Math.floor(vlq / 32);
|
|
625
|
+
if (vlq > 0) digit += 32;
|
|
626
|
+
encoded += BASE64[digit];
|
|
627
|
+
} while (vlq > 0);
|
|
628
|
+
return encoded;
|
|
629
|
+
}
|
|
630
|
+
function createSourceMap(original, generated, origins, id) {
|
|
631
|
+
const originalPositions = new Array(original.length);
|
|
632
|
+
let originalLine = 0;
|
|
633
|
+
let originalColumn = 0;
|
|
634
|
+
for (let index = 0; index < original.length; index += 1) {
|
|
635
|
+
originalPositions[index] = {
|
|
636
|
+
line: originalLine,
|
|
637
|
+
column: originalColumn
|
|
638
|
+
};
|
|
639
|
+
if (original[index] === "\n") {
|
|
640
|
+
originalLine += 1;
|
|
641
|
+
originalColumn = 0;
|
|
642
|
+
} else originalColumn += 1;
|
|
643
|
+
}
|
|
644
|
+
const lines = [""];
|
|
645
|
+
let generatedLine = 0;
|
|
646
|
+
let generatedColumn = 0;
|
|
647
|
+
let previousGeneratedColumn = 0;
|
|
648
|
+
let previousSource = 0;
|
|
649
|
+
let previousOriginalLine = 0;
|
|
650
|
+
let previousOriginalColumn = 0;
|
|
651
|
+
for (let index = 0; index < generated.length; index += 1) {
|
|
652
|
+
if (generated[index] === "\n") {
|
|
653
|
+
generatedLine += 1;
|
|
654
|
+
generatedColumn = 0;
|
|
655
|
+
previousGeneratedColumn = 0;
|
|
656
|
+
lines.push("");
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
const originalPosition = originalPositions[origins[index]];
|
|
660
|
+
if (!originalPosition) {
|
|
661
|
+
generatedColumn += 1;
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
const segment = encodeVLQ(generatedColumn - previousGeneratedColumn) + encodeVLQ(0 - previousSource) + encodeVLQ(originalPosition.line - previousOriginalLine) + encodeVLQ(originalPosition.column - previousOriginalColumn);
|
|
665
|
+
lines[generatedLine] += `${lines[generatedLine] ? "," : ""}${segment}`;
|
|
666
|
+
previousGeneratedColumn = generatedColumn;
|
|
667
|
+
previousSource = 0;
|
|
668
|
+
previousOriginalLine = originalPosition.line;
|
|
669
|
+
previousOriginalColumn = originalPosition.column;
|
|
670
|
+
generatedColumn += 1;
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
version: 3,
|
|
674
|
+
sources: [id],
|
|
675
|
+
sourcesContent: [original],
|
|
676
|
+
names: [],
|
|
677
|
+
mappings: lines.join(";")
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
function transformSource(code, classes, id) {
|
|
681
|
+
const transformed = collectRecognizedContextEdits(code, classes, id);
|
|
682
|
+
const rewritten = transformed.code;
|
|
683
|
+
const sources = sourcesForFile(rewritten, id);
|
|
684
|
+
rejectDynamicSetAttributeCalls(rewritten, sources.structure, classes, id);
|
|
685
|
+
rejectDynamicClassNameAssignments(rewritten, sources.structure, classes, id);
|
|
686
|
+
rejectAmbiguousClassExpressions(rewritten, sources.structure, classes, id);
|
|
687
|
+
return {
|
|
688
|
+
code: rewritten,
|
|
689
|
+
map: createSourceMap(code, rewritten, transformed.origins, id),
|
|
690
|
+
inspection: sources.inspection
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
function findAmbiguousMappedClass(code, classes) {
|
|
694
|
+
return findMappedInQuotedSegments(code, classes);
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Create a fail-closed source transformer for a GustCSS class manifest.
|
|
698
|
+
* Only unambiguous class-name contexts are rewritten automatically.
|
|
699
|
+
*/
|
|
700
|
+
function createClassNameTransformer(classes) {
|
|
701
|
+
const transformWithSourceMap = (code, id) => {
|
|
702
|
+
if (UNSUPPORTED_FRAMEWORK_SOURCE.test(id)) {
|
|
703
|
+
const referenced = findMappedToken(code, classes);
|
|
704
|
+
if (referenced) throw new Error(`[gustcss] class-name mangling does not support Vue, Astro, or Svelte templates yet (${id}). Add "${referenced}" to mangleExclude or disable mangleClassNames.`);
|
|
705
|
+
return {
|
|
706
|
+
code,
|
|
707
|
+
map: null
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
if (DEPENDENCY_SOURCE.test(id) || !SUPPORTED_SOURCE.test(id)) return {
|
|
711
|
+
code,
|
|
712
|
+
map: null
|
|
713
|
+
};
|
|
714
|
+
const result = transformSource(code, classes, id);
|
|
715
|
+
const ambiguous = findAmbiguousMappedClass(result.inspection, classes, id);
|
|
716
|
+
if (ambiguous) throw new Error(`[gustcss] mapped class "${ambiguous}" remains in an ambiguous string in ${id}. Move it to a static class/className or classList context, or add it to mangleExclude.`);
|
|
717
|
+
return {
|
|
718
|
+
code: result.code,
|
|
719
|
+
map: result.map
|
|
720
|
+
};
|
|
721
|
+
};
|
|
722
|
+
const transform = (code, id) => transformWithSourceMap(code, id).code;
|
|
723
|
+
transform.withSourceMap = transformWithSourceMap;
|
|
724
|
+
return transform;
|
|
725
|
+
}
|
|
726
|
+
//#endregion
|
|
727
|
+
//#region src/index.js
|
|
145
728
|
/**
|
|
146
729
|
* @gustcss/vite - Vite plugin for CSS Utility Generator
|
|
147
730
|
*
|
|
@@ -160,6 +743,12 @@ function cssUtility(opts = {}) {
|
|
|
160
743
|
const content = opts.content || ["./src/**/*.{js,ts,jsx,tsx,astro,vue}"];
|
|
161
744
|
const configPath = opts.config;
|
|
162
745
|
const outputToCssLayers = opts.outputToCssLayers;
|
|
746
|
+
const mangleClassNames = opts.mangleClassNames === true;
|
|
747
|
+
const mangleMap = opts.mangleMap || `${output}.classes.json`;
|
|
748
|
+
const mangleExclude = opts.mangleExclude || [];
|
|
749
|
+
let classNameTransformer = null;
|
|
750
|
+
let rollbackContext = null;
|
|
751
|
+
let manifestSnapshot = null;
|
|
163
752
|
let watchProcess = null;
|
|
164
753
|
function findBinary(cwd) {
|
|
165
754
|
return import_runner.default.resolveBinary({
|
|
@@ -174,14 +763,21 @@ function cssUtility(opts = {}) {
|
|
|
174
763
|
fs: fs.default
|
|
175
764
|
});
|
|
176
765
|
}
|
|
177
|
-
function buildCSS(cwd, binaryPath) {
|
|
766
|
+
function buildCSS(cwd, binaryPath, enableMangle) {
|
|
178
767
|
const resolvedConfigPath = findConfigFile(cwd);
|
|
179
768
|
const run = (cfgPath) => {
|
|
769
|
+
const disableConfigMangle = !enableMangle && import_runner.default.configEnablesMangling({
|
|
770
|
+
configPath: cfgPath,
|
|
771
|
+
fs: fs.default
|
|
772
|
+
});
|
|
180
773
|
const args = import_runner.default.buildArgs({
|
|
181
774
|
mode: "output",
|
|
182
775
|
output,
|
|
183
776
|
cssLayers: outputToCssLayers,
|
|
184
|
-
configPath: cfgPath
|
|
777
|
+
configPath: cfgPath,
|
|
778
|
+
mangleClassNames: enableMangle ? true : disableConfigMangle ? false : void 0,
|
|
779
|
+
mangleMap: enableMangle ? mangleMap : void 0,
|
|
780
|
+
mangleExclude: enableMangle ? mangleExclude : void 0
|
|
185
781
|
});
|
|
186
782
|
try {
|
|
187
783
|
const result = (0, child_process.spawnSync)(binaryPath, args, {
|
|
@@ -195,6 +791,24 @@ function cssUtility(opts = {}) {
|
|
|
195
791
|
});
|
|
196
792
|
if (result.status !== 0) throw new Error(`gustcss build failed: ${result.stderr}`);
|
|
197
793
|
if (result.stderr) process.stderr.write(result.stderr);
|
|
794
|
+
if (enableMangle) {
|
|
795
|
+
const manifestPath = path.default.resolve(cwd, mangleMap);
|
|
796
|
+
const manifestText = fs.default.readFileSync(manifestPath, "utf-8");
|
|
797
|
+
const manifest = JSON.parse(manifestText);
|
|
798
|
+
const classes = manifest?.classes;
|
|
799
|
+
const entries = classes && !Array.isArray(classes) ? Object.entries(classes) : [];
|
|
800
|
+
const shortNames = entries.map(([, short]) => short);
|
|
801
|
+
const validEntries = entries.every(([original, short]) => original.length > 0 && typeof short === "string" && /^[A-Za-z]+$/.test(short));
|
|
802
|
+
if (manifest?.version !== 1 || !classes || Array.isArray(classes) || !validEntries || new Set(shortNames).size !== shortNames.length) throw new Error(`invalid class-name manifest: ${manifestPath}`);
|
|
803
|
+
classNameTransformer = createClassNameTransformer(manifest.classes);
|
|
804
|
+
manifestSnapshot = {
|
|
805
|
+
path: manifestPath,
|
|
806
|
+
text: manifestText
|
|
807
|
+
};
|
|
808
|
+
} else {
|
|
809
|
+
classNameTransformer = null;
|
|
810
|
+
manifestSnapshot = null;
|
|
811
|
+
}
|
|
198
812
|
} catch (error) {
|
|
199
813
|
throw new Error(`gustcss build failed: ${error.message}`);
|
|
200
814
|
}
|
|
@@ -209,23 +823,38 @@ function cssUtility(opts = {}) {
|
|
|
209
823
|
}
|
|
210
824
|
return {
|
|
211
825
|
name: "gustcss",
|
|
826
|
+
...mangleClassNames ? { enforce: "pre" } : {},
|
|
212
827
|
configResolved(config) {
|
|
213
828
|
const cwd = config.root || process.cwd();
|
|
214
829
|
const binaryPath = findBinary(cwd);
|
|
215
830
|
try {
|
|
216
|
-
|
|
831
|
+
const enableMangle = mangleClassNames && config.command === "build";
|
|
832
|
+
buildCSS(cwd, binaryPath, enableMangle);
|
|
833
|
+
rollbackContext = enableMangle ? {
|
|
834
|
+
cwd,
|
|
835
|
+
binaryPath
|
|
836
|
+
} : null;
|
|
217
837
|
} catch (error) {
|
|
218
838
|
console.error(`[gustcss] Failed to build CSS: ${error.message}`);
|
|
219
839
|
throw error;
|
|
220
840
|
}
|
|
221
841
|
},
|
|
842
|
+
transform(code, id) {
|
|
843
|
+
if (!classNameTransformer) return null;
|
|
844
|
+
const transformed = classNameTransformer.withSourceMap(code, id);
|
|
845
|
+
return transformed.code === code ? null : transformed;
|
|
846
|
+
},
|
|
847
|
+
transformIndexHtml(html, context) {
|
|
848
|
+
if (!classNameTransformer) return html;
|
|
849
|
+
return classNameTransformer(html, context?.filename || "index.html");
|
|
850
|
+
},
|
|
222
851
|
configureServer(server) {
|
|
223
852
|
const cwd = server.config.root || process.cwd();
|
|
224
853
|
const binaryPath = findBinary(cwd);
|
|
225
854
|
const resolvedConfigPath = findConfigFile(cwd);
|
|
226
855
|
const outputPath = path.default.resolve(cwd, output);
|
|
227
856
|
try {
|
|
228
|
-
buildCSS(cwd, binaryPath);
|
|
857
|
+
buildCSS(cwd, binaryPath, false);
|
|
229
858
|
} catch (error) {
|
|
230
859
|
console.error(`[gustcss] Failed to build CSS: ${error.message}`);
|
|
231
860
|
}
|
|
@@ -237,13 +866,18 @@ function cssUtility(opts = {}) {
|
|
|
237
866
|
fs.default.writeFileSync(tempConfigPath, JSON.stringify({ content }), { flag: "wx" });
|
|
238
867
|
watchConfigPath = tempConfigPath;
|
|
239
868
|
}
|
|
240
|
-
|
|
869
|
+
const watchArgs = import_runner.default.buildArgs({
|
|
241
870
|
mode: "output",
|
|
242
871
|
output,
|
|
243
872
|
cssLayers: outputToCssLayers,
|
|
244
873
|
configPath: watchConfigPath,
|
|
245
|
-
watch: true
|
|
246
|
-
|
|
874
|
+
watch: true,
|
|
875
|
+
mangleClassNames: import_runner.default.configEnablesMangling({
|
|
876
|
+
configPath: watchConfigPath,
|
|
877
|
+
fs: fs.default
|
|
878
|
+
}) ? false : void 0
|
|
879
|
+
});
|
|
880
|
+
watchProcess = (0, child_process.spawn)(binaryPath, watchArgs, {
|
|
247
881
|
cwd,
|
|
248
882
|
stdio: [
|
|
249
883
|
"pipe",
|
|
@@ -277,11 +911,29 @@ function cssUtility(opts = {}) {
|
|
|
277
911
|
process.once("SIGINT", cleanup);
|
|
278
912
|
process.once("SIGTERM", cleanup);
|
|
279
913
|
},
|
|
280
|
-
|
|
914
|
+
writeBundle() {
|
|
915
|
+
if (!manifestSnapshot || fs.default.existsSync(manifestSnapshot.path)) return;
|
|
916
|
+
fs.default.mkdirSync(path.default.dirname(manifestSnapshot.path), { recursive: true });
|
|
917
|
+
fs.default.writeFileSync(manifestSnapshot.path, manifestSnapshot.text);
|
|
918
|
+
},
|
|
919
|
+
buildEnd(error) {
|
|
281
920
|
if (watchProcess) {
|
|
282
921
|
watchProcess.kill();
|
|
283
922
|
watchProcess = null;
|
|
284
923
|
}
|
|
924
|
+
if (error && rollbackContext) {
|
|
925
|
+
const { cwd, binaryPath } = rollbackContext;
|
|
926
|
+
rollbackContext = null;
|
|
927
|
+
classNameTransformer = null;
|
|
928
|
+
manifestSnapshot = null;
|
|
929
|
+
try {
|
|
930
|
+
buildCSS(cwd, binaryPath, false);
|
|
931
|
+
const manifestPath = path.default.resolve(cwd, mangleMap);
|
|
932
|
+
if (fs.default.existsSync(manifestPath)) fs.default.unlinkSync(manifestPath);
|
|
933
|
+
} catch (rollbackError) {
|
|
934
|
+
console.error(`[gustcss] Failed to restore readable CSS: ${rollbackError.message}`);
|
|
935
|
+
}
|
|
936
|
+
} else rollbackContext = null;
|
|
285
937
|
}
|
|
286
938
|
};
|
|
287
939
|
}
|