@zealakit/css-doctor-next 0.0.0-stage → 1.0.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 +65 -2
- package/README_CN.md +66 -0
- package/README_VI.md +66 -0
- package/dist/index.cjs +1318 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +176 -0
- package/dist/index.d.ts +176 -0
- package/dist/index.js +1278 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1318 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
findCssDoctorConfig: () => findCssDoctorConfig,
|
|
34
|
+
loadCssDoctorConfig: () => loadCssDoctorConfig,
|
|
35
|
+
withCssDoctor: () => withCssDoctor
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// src/config.ts
|
|
40
|
+
var import_node_fs = require("fs");
|
|
41
|
+
var import_node_path = require("path");
|
|
42
|
+
var import_jiti = require("jiti");
|
|
43
|
+
var import_meta = {};
|
|
44
|
+
var CONFIG_FILENAMES = [
|
|
45
|
+
"cssd.config.ts",
|
|
46
|
+
"cssd.config.mts",
|
|
47
|
+
"cssd.config.js",
|
|
48
|
+
"cssd.config.mjs"
|
|
49
|
+
];
|
|
50
|
+
function findCssDoctorConfig(cwd = process.cwd()) {
|
|
51
|
+
let directory = (0, import_node_path.resolve)(cwd);
|
|
52
|
+
while (true) {
|
|
53
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
54
|
+
const candidate = (0, import_node_path.resolve)(directory, filename);
|
|
55
|
+
if ((0, import_node_fs.existsSync)(candidate)) {
|
|
56
|
+
return candidate;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const parent = (0, import_node_path.dirname)(directory);
|
|
60
|
+
if (parent === directory) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
directory = parent;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function loadCssDoctorConfig(options = {}) {
|
|
67
|
+
const cwd = (0, import_node_path.resolve)(options.cwd ?? process.cwd());
|
|
68
|
+
const file = options.configFile ? (0, import_node_path.resolve)(cwd, options.configFile) : findCssDoctorConfig(cwd);
|
|
69
|
+
if (!file) {
|
|
70
|
+
return { options: {} };
|
|
71
|
+
}
|
|
72
|
+
if (!(0, import_node_fs.existsSync)(file)) {
|
|
73
|
+
return { options: {} };
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
if ((0, import_node_fs.readFileSync)(file, "utf8").trim().length === 0) {
|
|
77
|
+
return { options: {}, file };
|
|
78
|
+
}
|
|
79
|
+
const jiti = (0, import_jiti.createJiti)(import_meta.url, {
|
|
80
|
+
interopDefault: true,
|
|
81
|
+
fsCache: false,
|
|
82
|
+
moduleCache: false
|
|
83
|
+
});
|
|
84
|
+
const loaded = jiti(file);
|
|
85
|
+
return {
|
|
86
|
+
options: normalizeConfig(loaded),
|
|
87
|
+
file
|
|
88
|
+
};
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
options: {},
|
|
92
|
+
file,
|
|
93
|
+
error: `failed to load ${file}: ` + (error instanceof Error ? error.message : String(error))
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function normalizeConfig(value) {
|
|
98
|
+
let candidate = value;
|
|
99
|
+
if (isRecord(candidate) && "default" in candidate) {
|
|
100
|
+
candidate = candidate.default;
|
|
101
|
+
}
|
|
102
|
+
if (isRecord(candidate) && "cssDoctor" in candidate) {
|
|
103
|
+
candidate = candidate.cssDoctor;
|
|
104
|
+
}
|
|
105
|
+
return isRecord(candidate) ? candidate : {};
|
|
106
|
+
}
|
|
107
|
+
function isRecord(value) {
|
|
108
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/webpack-plugin.ts
|
|
112
|
+
var import_node_crypto = require("crypto");
|
|
113
|
+
var import_node_fs2 = require("fs");
|
|
114
|
+
var import_promises = require("fs/promises");
|
|
115
|
+
var import_node_path2 = require("path");
|
|
116
|
+
var import_node_perf_hooks = require("perf_hooks");
|
|
117
|
+
var import_css_doctor_binding = require("@zealakit/css-doctor-binding");
|
|
118
|
+
|
|
119
|
+
// src/reporter.ts
|
|
120
|
+
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
121
|
+
var MATRIX_SELECTOR_MAX = 36;
|
|
122
|
+
function formatDiagnostics(diagnostics, options = {}) {
|
|
123
|
+
const colors = import_picocolors.default.createColors(
|
|
124
|
+
options.color ?? import_picocolors.default.isColorSupported
|
|
125
|
+
);
|
|
126
|
+
const badge = colors.bgCyan(colors.black("[css-doctor]"));
|
|
127
|
+
const lines = [];
|
|
128
|
+
lines.push(
|
|
129
|
+
`${badge} Next.js ${diagnostics.pages.length} page${diagnostics.pages.length === 1 ? "" : "s"} ${colors.dim("\xB7")} ${diagnostics.analyzedCssAssets}/${diagnostics.totalCssAssets} CSS assets ${colors.dim("\xB7")} ${formatBytes(
|
|
130
|
+
diagnostics.totalSizeBeforeBytes
|
|
131
|
+
)} \u2192 ${formatBytes(diagnostics.totalSizeAfterBytes)} common ${colors.dim("\xB7")} ${formatDuration(diagnostics.durationMs)} ${colors.dim("\xB7")} ${diagnostics.minify ? colors.green("Minified: YES") : colors.yellow("Minified: NO")}`
|
|
132
|
+
);
|
|
133
|
+
for (const file of diagnostics.files) {
|
|
134
|
+
lines.push(...formatFile(file, colors));
|
|
135
|
+
}
|
|
136
|
+
if (diagnostics.splitApplied) {
|
|
137
|
+
lines.push("");
|
|
138
|
+
lines.push(...formatMatrix(diagnostics, colors));
|
|
139
|
+
}
|
|
140
|
+
if (diagnostics.emittedPageChunks.length > 0) {
|
|
141
|
+
lines.push("");
|
|
142
|
+
lines.push(
|
|
143
|
+
` ${colors.bold("Emitted page chunks")} ${colors.dim(
|
|
144
|
+
`(${diagnostics.emittedPageChunks.length})`
|
|
145
|
+
)}`
|
|
146
|
+
);
|
|
147
|
+
for (const chunk of diagnostics.emittedPageChunks) {
|
|
148
|
+
lines.push(
|
|
149
|
+
` ${colors.magenta(chunk.fileName)} ${colors.dim(
|
|
150
|
+
formatBytes(chunk.sizeBytes)
|
|
151
|
+
)}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (diagnostics.sizeBudget.enabled) {
|
|
156
|
+
lines.push("");
|
|
157
|
+
lines.push(...formatSizeBudget(diagnostics, colors));
|
|
158
|
+
}
|
|
159
|
+
if (diagnostics.deadCodeCount > 0) {
|
|
160
|
+
lines.push("");
|
|
161
|
+
lines.push(
|
|
162
|
+
` ${colors.yellow("\u26A0")} ${colors.bold("Dead code eliminated:")} ${diagnostics.deadCodeCount} selector occurrence${diagnostics.deadCodeCount === 1 ? "" : "s"}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (diagnostics.errors.length > 0) {
|
|
166
|
+
lines.push("");
|
|
167
|
+
lines.push(` ${colors.red(colors.bold("Errors"))}`);
|
|
168
|
+
for (const error of diagnostics.errors) {
|
|
169
|
+
lines.push(` ${colors.red("\u2716")} ${error}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return lines.join("\n");
|
|
173
|
+
}
|
|
174
|
+
function printDiagnostics(diagnostics) {
|
|
175
|
+
console.log(formatDiagnostics(diagnostics));
|
|
176
|
+
}
|
|
177
|
+
function formatFile(file, colors) {
|
|
178
|
+
const failed = file.error !== void 0;
|
|
179
|
+
const hasWarnings = file.warnings.length > 0;
|
|
180
|
+
const icon = failed ? colors.red("\u2716") : hasWarnings ? colors.yellow("\u26A0") : colors.green("\u2713");
|
|
181
|
+
const size = `${formatBytes(file.sizeBeforeBytes)} \u2192 ${formatBytes(
|
|
182
|
+
file.sizeAfterBytes
|
|
183
|
+
)}`;
|
|
184
|
+
const selectors = failed ? "" : ` \xB7 ${file.selectorCount} selectors`;
|
|
185
|
+
const pages = file.associatedPages.length > 0 ? ` \xB7 pages: ${file.associatedPages.join(", ")}` : "";
|
|
186
|
+
const lines = [
|
|
187
|
+
` ${icon} ${colors.bold(file.fileName)} ${colors.dim(size)}${selectors}${colors.dim(pages)}`
|
|
188
|
+
];
|
|
189
|
+
if (file.error) {
|
|
190
|
+
lines.push(` ${colors.red(file.error)}`);
|
|
191
|
+
return lines;
|
|
192
|
+
}
|
|
193
|
+
if (file.split) {
|
|
194
|
+
lines.push(
|
|
195
|
+
` ${colors.dim("split")} common ${formatBytes(
|
|
196
|
+
file.split.commonSizeBytes
|
|
197
|
+
)} ${colors.dim("\xB7")} page-specific ${formatBytes(
|
|
198
|
+
file.split.pageSpecificSizeBytes
|
|
199
|
+
)} ${colors.dim("\xB7")} dead ${file.split.deadCodeCount}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
for (const warning of file.warnings) {
|
|
203
|
+
lines.push(` ${formatWarning(warning, colors)}`);
|
|
204
|
+
}
|
|
205
|
+
return lines;
|
|
206
|
+
}
|
|
207
|
+
function formatMatrix(diagnostics, colors) {
|
|
208
|
+
const pageTotal = diagnostics.pages.length;
|
|
209
|
+
const rows = diagnostics.usageMatrix;
|
|
210
|
+
const header = [
|
|
211
|
+
"Selector".padEnd(MATRIX_SELECTOR_MAX),
|
|
212
|
+
"Total Used".padStart(10),
|
|
213
|
+
"Page Usage".padStart(10),
|
|
214
|
+
"Coverage %".padStart(10),
|
|
215
|
+
"Assigned Chunk"
|
|
216
|
+
].join(" ");
|
|
217
|
+
const lines = [
|
|
218
|
+
` ${colors.bold("Usage Matrix")} ${colors.dim(
|
|
219
|
+
`(${rows.length} selectors \xB7 ${pageTotal} pages)`
|
|
220
|
+
)}`,
|
|
221
|
+
` ${colors.dim(header)}`
|
|
222
|
+
];
|
|
223
|
+
for (const row of rows) {
|
|
224
|
+
const selector = truncate(row.selector, MATRIX_SELECTOR_MAX).padEnd(
|
|
225
|
+
MATRIX_SELECTOR_MAX
|
|
226
|
+
);
|
|
227
|
+
const totalUsed = String(row.totalOccurrences).padStart(10);
|
|
228
|
+
const pageUsage = `${row.pageCount}/${pageTotal}`.padStart(10);
|
|
229
|
+
const coverage = `${(row.coverageRatio * 100).toFixed(1)}%`.padStart(10);
|
|
230
|
+
const chunk = formatChunk(row.targetChunk, colors);
|
|
231
|
+
lines.push(
|
|
232
|
+
` ${colors.cyan(selector)} ${totalUsed} ${pageUsage} ${colorCoverage(
|
|
233
|
+
coverage,
|
|
234
|
+
row.coverageRatio,
|
|
235
|
+
diagnostics.minPageRatio,
|
|
236
|
+
colors
|
|
237
|
+
)} ${chunk}`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
return lines;
|
|
241
|
+
}
|
|
242
|
+
function formatSizeBudget(diagnostics, colors) {
|
|
243
|
+
const budget = diagnostics.sizeBudget;
|
|
244
|
+
const lines = [
|
|
245
|
+
` ${colors.bold("Size budget")} ${colors.dim(
|
|
246
|
+
`(max ${formatBytes(budget.maxChunkSize)} \xB7 variables ${budget.isolateVariables ? "isolated" : "inline"})`
|
|
247
|
+
)}`
|
|
248
|
+
];
|
|
249
|
+
const chunks = diagnostics.emittedCssChunks;
|
|
250
|
+
if (chunks.length === 0) {
|
|
251
|
+
lines.push(` ${colors.dim("No CSS chunks were emitted.")}`);
|
|
252
|
+
return lines;
|
|
253
|
+
}
|
|
254
|
+
for (const chunk of chunks) {
|
|
255
|
+
const markers = [];
|
|
256
|
+
if (chunk.variables) {
|
|
257
|
+
markers.push("vars");
|
|
258
|
+
}
|
|
259
|
+
if (chunk.part !== void 0) {
|
|
260
|
+
markers.push(`part ${chunk.part}`);
|
|
261
|
+
}
|
|
262
|
+
if (chunk.embedded) {
|
|
263
|
+
markers.push("embedded");
|
|
264
|
+
}
|
|
265
|
+
if (chunk.splitDueToSize) {
|
|
266
|
+
markers.push(colors.yellow("split"));
|
|
267
|
+
}
|
|
268
|
+
if (chunk.oversized) {
|
|
269
|
+
markers.push(colors.red("oversized"));
|
|
270
|
+
}
|
|
271
|
+
lines.push(
|
|
272
|
+
` ${colors.magenta(chunk.fileName)} ${colors.dim(
|
|
273
|
+
formatBytes(chunk.sizeBytes)
|
|
274
|
+
)}${markers.length > 0 ? ` ${colors.dim("\xB7")} ${markers.join(" \xB7 ")}` : ""}`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if (budget.splitChunks === 0) {
|
|
278
|
+
lines.push(
|
|
279
|
+
` ${colors.dim("No chunk exceeded the configured size budget.")}`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return lines;
|
|
283
|
+
}
|
|
284
|
+
function formatWarning(warning, colors) {
|
|
285
|
+
const location = warning.line !== void 0 ? colors.dim(
|
|
286
|
+
`${warning.line}${warning.column !== void 0 ? `:${warning.column}` : ""} `
|
|
287
|
+
) : "";
|
|
288
|
+
return `${colors.yellow("!")} ${location}${warning.message}`;
|
|
289
|
+
}
|
|
290
|
+
function colorCoverage(coverage, ratio, minPageRatio, colors) {
|
|
291
|
+
if (ratio === 0) {
|
|
292
|
+
return colors.red(coverage);
|
|
293
|
+
}
|
|
294
|
+
return ratio >= minPageRatio ? colors.green(coverage) : colors.yellow(coverage);
|
|
295
|
+
}
|
|
296
|
+
function formatChunk(chunk, colors) {
|
|
297
|
+
if (chunk === "common") {
|
|
298
|
+
return colors.cyan(chunk);
|
|
299
|
+
}
|
|
300
|
+
if (chunk === "dead") {
|
|
301
|
+
return colors.red(chunk);
|
|
302
|
+
}
|
|
303
|
+
return colors.magenta(chunk);
|
|
304
|
+
}
|
|
305
|
+
function truncate(value, maxLength) {
|
|
306
|
+
return value.length <= maxLength ? value : `${value.slice(0, maxLength - 1)}\u2026`;
|
|
307
|
+
}
|
|
308
|
+
function formatBytes(bytes) {
|
|
309
|
+
if (bytes < 1024) {
|
|
310
|
+
return `${bytes} B`;
|
|
311
|
+
}
|
|
312
|
+
if (bytes < 1024 * 1024) {
|
|
313
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
314
|
+
}
|
|
315
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
316
|
+
}
|
|
317
|
+
function formatDuration(milliseconds) {
|
|
318
|
+
if (!Number.isFinite(milliseconds)) {
|
|
319
|
+
return "\u2014";
|
|
320
|
+
}
|
|
321
|
+
return milliseconds < 1 ? `${milliseconds.toFixed(2)} ms` : `${milliseconds.toFixed(1)} ms`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/webpack-plugin.ts
|
|
325
|
+
var PLUGIN_NAME = "CssDoctorWebpackPlugin";
|
|
326
|
+
var DEFAULT_MIN_PAGE_RATIO = 0.5;
|
|
327
|
+
var DEFAULT_MAX_CHUNK_SIZE = 60 * 1024;
|
|
328
|
+
var LARGE_CSS_ASSET_BYTES = 500 * 1024;
|
|
329
|
+
var CssDoctorWebpackPlugin = class {
|
|
330
|
+
options;
|
|
331
|
+
pageCoverage;
|
|
332
|
+
sizeBudget;
|
|
333
|
+
minify;
|
|
334
|
+
include;
|
|
335
|
+
exclude;
|
|
336
|
+
configurationErrors = [];
|
|
337
|
+
constructor(options = {}) {
|
|
338
|
+
this.options = options;
|
|
339
|
+
const ratio = options.pageCoverage?.minPageRatio ?? options.minPageRatio ?? DEFAULT_MIN_PAGE_RATIO;
|
|
340
|
+
const maxChunkSize = options.sizeBudget?.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;
|
|
341
|
+
this.pageCoverage = {
|
|
342
|
+
enabled: options.pageCoverage?.enabled ?? true,
|
|
343
|
+
minPageRatio: Number.isFinite(ratio) && ratio >= 0 && ratio <= 1 ? ratio : DEFAULT_MIN_PAGE_RATIO,
|
|
344
|
+
forceCommon: options.pageCoverage?.forceCommon ?? options.forceCommonSelectors ?? []
|
|
345
|
+
};
|
|
346
|
+
if (!Number.isFinite(ratio) || ratio < 0 || ratio > 1) {
|
|
347
|
+
this.configurationErrors.push(
|
|
348
|
+
"minPageRatio must be a number between 0 and 1; using 0.5"
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
this.sizeBudget = {
|
|
352
|
+
enabled: options.sizeBudget?.enabled ?? true,
|
|
353
|
+
maxChunkSize: Number.isInteger(maxChunkSize) && maxChunkSize > 0 ? maxChunkSize : DEFAULT_MAX_CHUNK_SIZE,
|
|
354
|
+
isolateVariables: options.sizeBudget?.isolateVariables ?? true
|
|
355
|
+
};
|
|
356
|
+
if (options.sizeBudget?.enabled !== false && (!Number.isInteger(maxChunkSize) || maxChunkSize <= 0)) {
|
|
357
|
+
this.configurationErrors.push(
|
|
358
|
+
"maxChunkSize must be a positive integer; using 61440"
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
this.minify = options.minify ?? true;
|
|
362
|
+
this.include = options.include ?? [];
|
|
363
|
+
this.exclude = options.exclude ?? [];
|
|
364
|
+
}
|
|
365
|
+
apply(compiler) {
|
|
366
|
+
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
|
|
367
|
+
compilation.hooks.processAssets.tapPromise(
|
|
368
|
+
{
|
|
369
|
+
name: PLUGIN_NAME,
|
|
370
|
+
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE
|
|
371
|
+
},
|
|
372
|
+
async () => {
|
|
373
|
+
try {
|
|
374
|
+
await this.process(compiler, compilation);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
compilation.getLogger(PLUGIN_NAME).warn(
|
|
377
|
+
`[css-doctor] diagnostics failed without interrupting the build: ${describeError(
|
|
378
|
+
error
|
|
379
|
+
)}`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
async process(compiler, compilation) {
|
|
387
|
+
const startedAt = import_node_perf_hooks.performance.now();
|
|
388
|
+
const logger = compilation.getLogger(PLUGIN_NAME);
|
|
389
|
+
const pagesResult = await collectPages(compilation, this.options.pageUsage);
|
|
390
|
+
const pageNames = pagesResult.pages.map((page) => page.name);
|
|
391
|
+
const pageByName = new Map(
|
|
392
|
+
pagesResult.pages.map((page) => [page.name, page])
|
|
393
|
+
);
|
|
394
|
+
const allPages = new Set(pageNames);
|
|
395
|
+
const pageUsage = {};
|
|
396
|
+
const pageUsageCounts = {};
|
|
397
|
+
for (const page of pagesResult.pages) {
|
|
398
|
+
const classes = [...page.classes].sort();
|
|
399
|
+
pageUsage[page.name] = classes;
|
|
400
|
+
pageUsageCounts[page.name] = classes.length;
|
|
401
|
+
}
|
|
402
|
+
const pageCoverageApplied = this.pageCoverage.enabled && pageNames.length > 0;
|
|
403
|
+
const diagnostics = {
|
|
404
|
+
plugin: "css-doctor-next",
|
|
405
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
406
|
+
durationMs: 0,
|
|
407
|
+
minPageRatio: this.pageCoverage.minPageRatio,
|
|
408
|
+
minify: this.minify,
|
|
409
|
+
pageCoverage: {
|
|
410
|
+
enabled: this.pageCoverage.enabled,
|
|
411
|
+
minPageRatio: this.pageCoverage.minPageRatio
|
|
412
|
+
},
|
|
413
|
+
sizeBudget: {
|
|
414
|
+
enabled: this.sizeBudget.enabled,
|
|
415
|
+
maxChunkSize: this.sizeBudget.maxChunkSize,
|
|
416
|
+
isolateVariables: this.sizeBudget.isolateVariables,
|
|
417
|
+
splitChunks: 0,
|
|
418
|
+
oversizedChunks: 0
|
|
419
|
+
},
|
|
420
|
+
pages: pageNames,
|
|
421
|
+
pageUsage: pageUsageCounts,
|
|
422
|
+
splitApplied: pageCoverageApplied,
|
|
423
|
+
totalCssAssets: 0,
|
|
424
|
+
analyzedCssAssets: 0,
|
|
425
|
+
failedCssAssets: 0,
|
|
426
|
+
skippedCssAssets: 0,
|
|
427
|
+
totalSizeBeforeBytes: 0,
|
|
428
|
+
totalSizeAfterBytes: 0,
|
|
429
|
+
totalSelectors: 0,
|
|
430
|
+
totalRules: 0,
|
|
431
|
+
commonCssBytes: 0,
|
|
432
|
+
pageSpecificCssBytes: 0,
|
|
433
|
+
emittedPageChunks: [],
|
|
434
|
+
emittedCssChunks: [],
|
|
435
|
+
usageMatrix: [],
|
|
436
|
+
deadCodeCount: 0,
|
|
437
|
+
files: [],
|
|
438
|
+
errors: []
|
|
439
|
+
};
|
|
440
|
+
diagnostics.errors.push(...this.configurationErrors);
|
|
441
|
+
const matrix = /* @__PURE__ */ new Map();
|
|
442
|
+
const pageFiles = /* @__PURE__ */ new Map();
|
|
443
|
+
const usedAssetNames = new Set(
|
|
444
|
+
compilation.getAssets().map((asset) => asset.name)
|
|
445
|
+
);
|
|
446
|
+
const RawSource = compiler.webpack.sources.RawSource;
|
|
447
|
+
const emitCssChunk = (chunk) => {
|
|
448
|
+
const hash = (0, import_node_crypto.createHash)("sha256").update(chunk.css).digest("hex").slice(0, 8);
|
|
449
|
+
const safeName = chunk.name.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "chunk";
|
|
450
|
+
let fileName = `static/css/${safeName}.${hash}.css`;
|
|
451
|
+
let index = 2;
|
|
452
|
+
while (usedAssetNames.has(fileName)) {
|
|
453
|
+
fileName = `static/css/${safeName}.${hash}-${index}.css`;
|
|
454
|
+
index += 1;
|
|
455
|
+
}
|
|
456
|
+
compilation.emitAsset(fileName, new RawSource(chunk.css));
|
|
457
|
+
usedAssetNames.add(fileName);
|
|
458
|
+
return fileName;
|
|
459
|
+
};
|
|
460
|
+
for (const asset of compilation.getAssets()) {
|
|
461
|
+
if (!asset.name.endsWith(".css")) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
diagnostics.totalCssAssets += 1;
|
|
465
|
+
if (this.shouldSkipAsset(
|
|
466
|
+
compilation,
|
|
467
|
+
asset.name,
|
|
468
|
+
sourceToString(asset.source.source())
|
|
469
|
+
)) {
|
|
470
|
+
diagnostics.skippedCssAssets += 1;
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
const associatedPages = [
|
|
474
|
+
...pagesResult.pagesByCss.get(asset.name) ?? allPages
|
|
475
|
+
].sort();
|
|
476
|
+
const file = analyzeAsset(asset.name, asset.source.source());
|
|
477
|
+
file.associatedPages = associatedPages;
|
|
478
|
+
if (file.error) {
|
|
479
|
+
diagnostics.failedCssAssets += 1;
|
|
480
|
+
diagnostics.errors.push(`${asset.name}: ${file.error}`);
|
|
481
|
+
diagnostics.files.push(file);
|
|
482
|
+
logger.warn(`[css-doctor] ${asset.name}: ${file.error}`);
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
const originalCss = sourceToString(asset.source.source());
|
|
486
|
+
try {
|
|
487
|
+
const pipeline = (0, import_css_doctor_binding.optimizeCss)({
|
|
488
|
+
cssContent: originalCss,
|
|
489
|
+
pageUsage,
|
|
490
|
+
pageCoverage: {
|
|
491
|
+
enabled: this.pageCoverage.enabled,
|
|
492
|
+
minPageRatio: this.pageCoverage.minPageRatio,
|
|
493
|
+
forceCommon: this.pageCoverage.forceCommon
|
|
494
|
+
},
|
|
495
|
+
sizeBudget: {
|
|
496
|
+
enabled: this.sizeBudget.enabled,
|
|
497
|
+
maxChunkSize: this.sizeBudget.maxChunkSize,
|
|
498
|
+
isolateVariables: this.sizeBudget.isolateVariables
|
|
499
|
+
},
|
|
500
|
+
minify: this.minify
|
|
501
|
+
});
|
|
502
|
+
const commonChunks = pipeline.chunks.filter(
|
|
503
|
+
(chunk) => chunk.page === void 0 && chunk.sourceChunk === "common"
|
|
504
|
+
);
|
|
505
|
+
const pageChunks = pipeline.chunks.filter(
|
|
506
|
+
(chunk) => chunk.page !== void 0
|
|
507
|
+
);
|
|
508
|
+
const commonWasSplit = commonChunks.length > 1 || commonChunks.some((chunk) => chunk.splitDueToSize);
|
|
509
|
+
const embeddedCommon = commonWasSplit ? void 0 : commonChunks[0];
|
|
510
|
+
const commonFileNames = [];
|
|
511
|
+
let commonSizeBytes = 0;
|
|
512
|
+
for (const chunk of commonChunks) {
|
|
513
|
+
const embedded = chunk === embeddedCommon;
|
|
514
|
+
const fileName = embedded ? asset.name : emitCssChunk(chunk);
|
|
515
|
+
commonSizeBytes += chunk.sizeBytes;
|
|
516
|
+
commonFileNames.push(fileName);
|
|
517
|
+
diagnostics.emittedCssChunks.push({
|
|
518
|
+
fileName,
|
|
519
|
+
sourceChunk: chunk.sourceChunk,
|
|
520
|
+
kind: chunk.kind,
|
|
521
|
+
page: chunk.page,
|
|
522
|
+
variables: chunk.variables,
|
|
523
|
+
part: chunk.part,
|
|
524
|
+
sizeBytes: chunk.sizeBytes,
|
|
525
|
+
oversized: chunk.oversized,
|
|
526
|
+
splitDueToSize: chunk.splitDueToSize,
|
|
527
|
+
embedded
|
|
528
|
+
});
|
|
529
|
+
if (chunk.splitDueToSize) {
|
|
530
|
+
diagnostics.sizeBudget.splitChunks += 1;
|
|
531
|
+
}
|
|
532
|
+
if (chunk.oversized) {
|
|
533
|
+
diagnostics.sizeBudget.oversizedChunks += 1;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (commonWasSplit) {
|
|
537
|
+
const imports = commonFileNames.map((fileName) => {
|
|
538
|
+
const relativePath = import_node_path2.posix.relative(
|
|
539
|
+
import_node_path2.posix.dirname(asset.name),
|
|
540
|
+
fileName
|
|
541
|
+
);
|
|
542
|
+
return `@import "./${relativePath}";`;
|
|
543
|
+
}).join("\n");
|
|
544
|
+
compilation.updateAsset(asset.name, new RawSource(imports));
|
|
545
|
+
} else {
|
|
546
|
+
compilation.updateAsset(
|
|
547
|
+
asset.name,
|
|
548
|
+
new RawSource(embeddedCommon?.css ?? "")
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
let pageSpecificSizeBytes = 0;
|
|
552
|
+
for (const chunk of pageChunks) {
|
|
553
|
+
const page = chunk.page;
|
|
554
|
+
if (!page) {
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
const fileName = emitCssChunk(chunk);
|
|
558
|
+
pageSpecificSizeBytes += chunk.sizeBytes;
|
|
559
|
+
const files = pageFiles.get(page) ?? [];
|
|
560
|
+
files.push(fileName);
|
|
561
|
+
pageFiles.set(page, files);
|
|
562
|
+
diagnostics.emittedPageChunks.push({
|
|
563
|
+
page,
|
|
564
|
+
fileName,
|
|
565
|
+
sizeBytes: chunk.sizeBytes
|
|
566
|
+
});
|
|
567
|
+
diagnostics.emittedCssChunks.push({
|
|
568
|
+
fileName,
|
|
569
|
+
sourceChunk: chunk.sourceChunk,
|
|
570
|
+
kind: chunk.kind,
|
|
571
|
+
page,
|
|
572
|
+
variables: chunk.variables,
|
|
573
|
+
part: chunk.part,
|
|
574
|
+
sizeBytes: chunk.sizeBytes,
|
|
575
|
+
oversized: chunk.oversized,
|
|
576
|
+
splitDueToSize: chunk.splitDueToSize,
|
|
577
|
+
embedded: false
|
|
578
|
+
});
|
|
579
|
+
if (chunk.splitDueToSize) {
|
|
580
|
+
diagnostics.sizeBudget.splitChunks += 1;
|
|
581
|
+
}
|
|
582
|
+
if (chunk.oversized) {
|
|
583
|
+
diagnostics.sizeBudget.oversizedChunks += 1;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
file.sizeAfterBytes = commonWasSplit ? commonSizeBytes : Buffer.byteLength(embeddedCommon?.css ?? "", "utf8");
|
|
587
|
+
file.split = {
|
|
588
|
+
associatedPages,
|
|
589
|
+
commonSizeBytes,
|
|
590
|
+
pageSpecificSizeBytes,
|
|
591
|
+
deadCodeCount: pipeline.deadCodeCount
|
|
592
|
+
};
|
|
593
|
+
for (const stat of pipeline.stats) {
|
|
594
|
+
const previous = matrix.get(stat.selector);
|
|
595
|
+
if (!previous || stat.totalOccurrences > previous.totalOccurrences) {
|
|
596
|
+
matrix.set(stat.selector, stat);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
diagnostics.deadCodeCount += pipeline.deadCodeCount;
|
|
600
|
+
diagnostics.commonCssBytes += commonSizeBytes;
|
|
601
|
+
diagnostics.pageSpecificCssBytes += pageSpecificSizeBytes;
|
|
602
|
+
} catch (error) {
|
|
603
|
+
const message = `failed to optimize CSS: ${describeError(error)}`;
|
|
604
|
+
diagnostics.errors.push(`${asset.name}: ${message}`);
|
|
605
|
+
logger.warn(`[css-doctor] ${asset.name}: ${message}`);
|
|
606
|
+
}
|
|
607
|
+
diagnostics.analyzedCssAssets += 1;
|
|
608
|
+
diagnostics.totalSizeBeforeBytes += file.sizeBeforeBytes;
|
|
609
|
+
diagnostics.totalSizeAfterBytes += file.sizeAfterBytes;
|
|
610
|
+
diagnostics.totalSelectors += file.selectorCount;
|
|
611
|
+
diagnostics.totalRules += file.ruleCount;
|
|
612
|
+
diagnostics.files.push(file);
|
|
613
|
+
}
|
|
614
|
+
if (pageFiles.size > 0) {
|
|
615
|
+
injectPageJsLoaders(compilation, pageFiles, pageByName, RawSource);
|
|
616
|
+
}
|
|
617
|
+
diagnostics.usageMatrix = [...matrix.values()].sort(
|
|
618
|
+
(left, right) => left.selector.localeCompare(right.selector)
|
|
619
|
+
);
|
|
620
|
+
diagnostics.durationMs = roundMs(import_node_perf_hooks.performance.now() - startedAt);
|
|
621
|
+
if (this.options.onDiagnostics) {
|
|
622
|
+
try {
|
|
623
|
+
await this.options.onDiagnostics(diagnostics);
|
|
624
|
+
} catch (error) {
|
|
625
|
+
const message = `onDiagnostics callback failed: ${describeError(
|
|
626
|
+
error
|
|
627
|
+
)}`;
|
|
628
|
+
diagnostics.errors.push(message);
|
|
629
|
+
logger.warn(`[css-doctor] ${message}`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (this.options.report !== false && (diagnostics.totalCssAssets > 0 || diagnostics.errors.length > 0)) {
|
|
633
|
+
try {
|
|
634
|
+
printDiagnostics(diagnostics);
|
|
635
|
+
} catch (error) {
|
|
636
|
+
logger.warn(`[css-doctor] reporter failed: ${describeError(error)}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
shouldSkipAsset(compilation, assetName, source) {
|
|
641
|
+
if (isCssModuleAsset(compilation, assetName, source)) {
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
if (this.include.length > 0 && !matchesPatterns(assetName, this.include)) {
|
|
645
|
+
return true;
|
|
646
|
+
}
|
|
647
|
+
return this.exclude.length > 0 && matchesPatterns(assetName, this.exclude);
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
function injectPageJsLoaders(compilation, cssByPage, pageByName, RawSource) {
|
|
651
|
+
const publicPath = typeof compilation.options.output.publicPath === "string" && compilation.options.output.publicPath.length > 0 ? compilation.options.output.publicPath : "/_next/";
|
|
652
|
+
const normalizedBase = publicPath.endsWith("/") ? publicPath : `${publicPath}/`;
|
|
653
|
+
for (const [page, files] of cssByPage) {
|
|
654
|
+
const jsFiles = pageByName.get(page)?.jsFiles;
|
|
655
|
+
if (!jsFiles || files.length === 0) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const hrefs = files.map((file) => `${normalizedBase}${file}`);
|
|
659
|
+
const snippet = `(()=>{const __cssDoctorPageStyles=${JSON.stringify(hrefs)};if(typeof document!=="undefined"){for(const __cssDoctorPageHref of __cssDoctorPageStyles){if(!Array.from(document.querySelectorAll("link[data-css-doctor-page]")).some((link)=>link.dataset.cssDoctorPage===__cssDoctorPageHref)){const __cssDoctorPageLink=document.createElement("link");__cssDoctorPageLink.rel="stylesheet";__cssDoctorPageLink.href=__cssDoctorPageHref;__cssDoctorPageLink.dataset.cssDoctorPage=__cssDoctorPageHref;document.head.append(__cssDoctorPageLink);}}}})();
|
|
660
|
+
`;
|
|
661
|
+
for (const jsFile of jsFiles) {
|
|
662
|
+
const asset = compilation.getAsset(jsFile);
|
|
663
|
+
if (!asset) {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
const source = sourceToString(asset.source.source());
|
|
667
|
+
compilation.updateAsset(jsFile, new RawSource(snippet + source));
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
function matchesPatterns(value, patterns) {
|
|
672
|
+
return patterns.some(
|
|
673
|
+
(pattern) => typeof pattern === "string" ? value.includes(pattern) : pattern.test(value)
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
function isCssModuleAsset(compilation, assetName, source) {
|
|
677
|
+
if (/\.module\.(css|scss|sass|less|styl|stylus)$/i.test(assetName)) {
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
if (/\.[A-Za-z0-9_-]+_[A-Za-z0-9_-]+__[A-Za-z0-9_-]+/.test(source)) {
|
|
681
|
+
return true;
|
|
682
|
+
}
|
|
683
|
+
for (const chunk of compilation.chunks) {
|
|
684
|
+
if (!chunk.files.has(assetName) && !chunk.auxiliaryFiles.has(assetName)) {
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
for (const module2 of compilation.chunkGraph.getChunkModulesIterable(
|
|
688
|
+
chunk
|
|
689
|
+
)) {
|
|
690
|
+
if (moduleHasCssModule(module2)) {
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
function moduleHasCssModule(module2) {
|
|
698
|
+
const nested = module2.modules;
|
|
699
|
+
if (nested) {
|
|
700
|
+
for (const child of nested) {
|
|
701
|
+
if (moduleHasCssModule(child)) {
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const resource = module2.resource;
|
|
707
|
+
return typeof resource === "string" && /\.module\.(css|scss|sass|less|styl|stylus)(\?|$)/i.test(resource);
|
|
708
|
+
}
|
|
709
|
+
function analyzeAsset(fileName, source) {
|
|
710
|
+
try {
|
|
711
|
+
const css = sourceToString(source);
|
|
712
|
+
const sizeBeforeBytes = Buffer.byteLength(css, "utf8");
|
|
713
|
+
const analysis = (0, import_css_doctor_binding.analyzeCss)(css);
|
|
714
|
+
const warnings = collectSyntaxWarnings(analysis);
|
|
715
|
+
if (sizeBeforeBytes >= LARGE_CSS_ASSET_BYTES) {
|
|
716
|
+
warnings.push({
|
|
717
|
+
kind: "large-asset",
|
|
718
|
+
message: `CSS asset is larger than ${formatBytes2(
|
|
719
|
+
LARGE_CSS_ASSET_BYTES
|
|
720
|
+
)}; consider route-level code splitting.`
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
fileName,
|
|
725
|
+
sizeBeforeBytes,
|
|
726
|
+
sizeAfterBytes: sizeBeforeBytes,
|
|
727
|
+
selectorCount: analysis.stats.selectorCount,
|
|
728
|
+
ruleCount: analysis.stats.ruleCount,
|
|
729
|
+
associatedPages: [],
|
|
730
|
+
warnings,
|
|
731
|
+
analysis
|
|
732
|
+
};
|
|
733
|
+
} catch (error) {
|
|
734
|
+
return {
|
|
735
|
+
fileName,
|
|
736
|
+
sizeBeforeBytes: 0,
|
|
737
|
+
sizeAfterBytes: 0,
|
|
738
|
+
selectorCount: 0,
|
|
739
|
+
ruleCount: 0,
|
|
740
|
+
associatedPages: [],
|
|
741
|
+
warnings: [],
|
|
742
|
+
error: describeError(error)
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
async function collectPages(compilation, directPageUsage) {
|
|
747
|
+
const entries = [...compilation.entrypoints.values()];
|
|
748
|
+
const pageEntries = [];
|
|
749
|
+
const sharedEntries = [];
|
|
750
|
+
const fallbackEntries = [];
|
|
751
|
+
for (const entry of entries) {
|
|
752
|
+
const name = entryName(entry);
|
|
753
|
+
if (!name) {
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
if (isPageEntry(name)) {
|
|
757
|
+
const route = routeNameForEntry(name) ?? name;
|
|
758
|
+
pageEntries.push({ entry, name: route, sourceName: name });
|
|
759
|
+
} else if (isSharedEntry(name)) {
|
|
760
|
+
sharedEntries.push(entry);
|
|
761
|
+
} else if (!isInternalEntry(name)) {
|
|
762
|
+
fallbackEntries.push({
|
|
763
|
+
entry,
|
|
764
|
+
name: routeNameForEntry(name) ?? name,
|
|
765
|
+
sourceName: name
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
const selectedPages = pageEntries.length > 0 ? pageEntries : fallbackEntries;
|
|
770
|
+
const direct = directPageUsage ? Object.keys(directPageUsage).length > 0 : false;
|
|
771
|
+
const pages = /* @__PURE__ */ new Map();
|
|
772
|
+
const pagesByCss = /* @__PURE__ */ new Map();
|
|
773
|
+
if (direct && directPageUsage) {
|
|
774
|
+
for (const [rawName, classes] of Object.entries(directPageUsage)) {
|
|
775
|
+
const name = rawName.trim();
|
|
776
|
+
if (!name) {
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
pages.set(name, {
|
|
780
|
+
name,
|
|
781
|
+
classes: new Set(classes.map((className) => className.toLowerCase())),
|
|
782
|
+
cssFiles: /* @__PURE__ */ new Set(),
|
|
783
|
+
jsFiles: /* @__PURE__ */ new Set()
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
} else {
|
|
787
|
+
const sourceCache = /* @__PURE__ */ new Map();
|
|
788
|
+
const sharedClasses = /* @__PURE__ */ new Set();
|
|
789
|
+
const projectRoot = process.cwd();
|
|
790
|
+
for (const entry of sharedEntries) {
|
|
791
|
+
for (const className of await collectEntryClasses(
|
|
792
|
+
compilation,
|
|
793
|
+
entry,
|
|
794
|
+
sourceCache
|
|
795
|
+
)) {
|
|
796
|
+
sharedClasses.add(className);
|
|
797
|
+
}
|
|
798
|
+
const name = entryName(entry);
|
|
799
|
+
if (name) {
|
|
800
|
+
for (const className of await collectNameSourceClasses(
|
|
801
|
+
name,
|
|
802
|
+
projectRoot,
|
|
803
|
+
sourceCache
|
|
804
|
+
)) {
|
|
805
|
+
sharedClasses.add(className);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
for (const { entry, name, sourceName } of selectedPages) {
|
|
810
|
+
const page = pages.get(name) ?? {
|
|
811
|
+
name,
|
|
812
|
+
classes: /* @__PURE__ */ new Set(),
|
|
813
|
+
cssFiles: /* @__PURE__ */ new Set(),
|
|
814
|
+
jsFiles: collectEntryJsFiles(entry)
|
|
815
|
+
};
|
|
816
|
+
for (const jsFile of collectEntryJsFiles(entry)) {
|
|
817
|
+
page.jsFiles.add(jsFile);
|
|
818
|
+
}
|
|
819
|
+
for (const className of sharedClasses) {
|
|
820
|
+
page.classes.add(className);
|
|
821
|
+
}
|
|
822
|
+
for (const className of await collectEntryClasses(
|
|
823
|
+
compilation,
|
|
824
|
+
entry,
|
|
825
|
+
sourceCache
|
|
826
|
+
)) {
|
|
827
|
+
page.classes.add(className);
|
|
828
|
+
}
|
|
829
|
+
for (const className of await collectPageSourceClasses(
|
|
830
|
+
sourceName,
|
|
831
|
+
projectRoot,
|
|
832
|
+
sourceCache
|
|
833
|
+
)) {
|
|
834
|
+
page.classes.add(className);
|
|
835
|
+
}
|
|
836
|
+
pages.set(name, page);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
const targetPages = [...pages.keys()];
|
|
840
|
+
for (const { entry, name } of selectedPages) {
|
|
841
|
+
const cssFiles = collectEntryCssFiles(entry);
|
|
842
|
+
if (direct) {
|
|
843
|
+
const directPage = pages.get(name);
|
|
844
|
+
if (directPage) {
|
|
845
|
+
for (const cssFile of cssFiles) {
|
|
846
|
+
directPage.cssFiles.add(cssFile);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
} else {
|
|
850
|
+
const page = pages.get(name);
|
|
851
|
+
if (page) {
|
|
852
|
+
for (const cssFile of cssFiles) {
|
|
853
|
+
page.cssFiles.add(cssFile);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
const sharedCssFiles = /* @__PURE__ */ new Set();
|
|
859
|
+
for (const entry of sharedEntries) {
|
|
860
|
+
for (const cssFile of collectEntryCssFiles(entry)) {
|
|
861
|
+
sharedCssFiles.add(cssFile);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
for (const page of pages.values()) {
|
|
865
|
+
for (const cssFile of page.cssFiles) {
|
|
866
|
+
addCssOwner(pagesByCss, cssFile, page.name);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
for (const cssFile of sharedCssFiles) {
|
|
870
|
+
for (const page of targetPages) {
|
|
871
|
+
addCssOwner(pagesByCss, cssFile, page);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return {
|
|
875
|
+
pages: [...pages.values()],
|
|
876
|
+
pagesByCss
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
async function collectPageSourceClasses(entryName2, projectRoot, sourceCache) {
|
|
880
|
+
const classes = /* @__PURE__ */ new Set();
|
|
881
|
+
const visited = /* @__PURE__ */ new Set();
|
|
882
|
+
const normalized = normalizeSlashes(entryName2);
|
|
883
|
+
const candidates = [];
|
|
884
|
+
if (normalized.startsWith("app/")) {
|
|
885
|
+
const path = normalized.slice(4);
|
|
886
|
+
if (path === "page" || path.endsWith("/page")) {
|
|
887
|
+
const pagePath = path === "page" ? "app/page" : `app/${path}`;
|
|
888
|
+
candidates.push((0, import_node_path2.resolve)(projectRoot, pagePath));
|
|
889
|
+
if (path === "_not-found/page") {
|
|
890
|
+
candidates.push((0, import_node_path2.resolve)(projectRoot, "app/not-found"));
|
|
891
|
+
}
|
|
892
|
+
const directories = path.split("/").slice(0, -1).filter(Boolean);
|
|
893
|
+
const layoutPaths = ["app"];
|
|
894
|
+
for (let index = 0; index < directories.length; index += 1) {
|
|
895
|
+
layoutPaths.push(`app/${directories.slice(0, index + 1).join("/")}`);
|
|
896
|
+
}
|
|
897
|
+
for (const layoutPath of layoutPaths) {
|
|
898
|
+
candidates.push((0, import_node_path2.resolve)(projectRoot, `${layoutPath}/layout`));
|
|
899
|
+
}
|
|
900
|
+
} else {
|
|
901
|
+
candidates.push((0, import_node_path2.resolve)(projectRoot, normalized));
|
|
902
|
+
}
|
|
903
|
+
} else if (normalized.startsWith("pages/")) {
|
|
904
|
+
candidates.push((0, import_node_path2.resolve)(projectRoot, normalized));
|
|
905
|
+
candidates.push((0, import_node_path2.resolve)(projectRoot, "pages/_app"));
|
|
906
|
+
} else {
|
|
907
|
+
candidates.push((0, import_node_path2.resolve)(projectRoot, normalized));
|
|
908
|
+
}
|
|
909
|
+
for (const candidate of candidates) {
|
|
910
|
+
const file = findSourceFile(candidate);
|
|
911
|
+
if (file) {
|
|
912
|
+
await scanSourceFile(file, classes, projectRoot, sourceCache, visited);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return classes;
|
|
916
|
+
}
|
|
917
|
+
async function collectNameSourceClasses(entryName2, projectRoot, sourceCache) {
|
|
918
|
+
const classes = /* @__PURE__ */ new Set();
|
|
919
|
+
const visited = /* @__PURE__ */ new Set();
|
|
920
|
+
const candidate = (0, import_node_path2.resolve)(projectRoot, normalizeSlashes(entryName2));
|
|
921
|
+
const file = findSourceFile(candidate);
|
|
922
|
+
if (file) {
|
|
923
|
+
await scanSourceFile(file, classes, projectRoot, sourceCache, visited);
|
|
924
|
+
}
|
|
925
|
+
return classes;
|
|
926
|
+
}
|
|
927
|
+
async function scanSourceFile(filePath, classes, projectRoot, sourceCache, visited) {
|
|
928
|
+
const absolutePath = (0, import_node_path2.resolve)(filePath);
|
|
929
|
+
if (visited.has(absolutePath)) {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
visited.add(absolutePath);
|
|
933
|
+
let pending = sourceCache.get(absolutePath);
|
|
934
|
+
if (!pending) {
|
|
935
|
+
pending = (0, import_promises.readFile)(absolutePath, "utf8").catch(() => void 0);
|
|
936
|
+
sourceCache.set(absolutePath, pending);
|
|
937
|
+
}
|
|
938
|
+
const source = await pending;
|
|
939
|
+
if (!source) {
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
scanClasses(source, classes);
|
|
943
|
+
for (const specifier of extractImportSpecifiers(source)) {
|
|
944
|
+
const resolved = resolveImportSpecifier(
|
|
945
|
+
specifier,
|
|
946
|
+
absolutePath,
|
|
947
|
+
projectRoot
|
|
948
|
+
);
|
|
949
|
+
if (resolved && !visited.has(resolved)) {
|
|
950
|
+
await scanSourceFile(
|
|
951
|
+
resolved,
|
|
952
|
+
classes,
|
|
953
|
+
projectRoot,
|
|
954
|
+
sourceCache,
|
|
955
|
+
visited
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
var SOURCE_EXTENSIONS = [
|
|
961
|
+
".tsx",
|
|
962
|
+
".ts",
|
|
963
|
+
".jsx",
|
|
964
|
+
".js",
|
|
965
|
+
".mjs",
|
|
966
|
+
".cjs",
|
|
967
|
+
".mts",
|
|
968
|
+
".cts",
|
|
969
|
+
".mdx"
|
|
970
|
+
];
|
|
971
|
+
function findSourceFile(basePath) {
|
|
972
|
+
if (isStyleFile(basePath)) {
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
if ((0, import_node_path2.extname)(basePath) && (0, import_node_fs2.existsSync)(basePath)) {
|
|
976
|
+
return basePath;
|
|
977
|
+
}
|
|
978
|
+
for (const extension of SOURCE_EXTENSIONS) {
|
|
979
|
+
const candidate = `${basePath}${extension}`;
|
|
980
|
+
if ((0, import_node_fs2.existsSync)(candidate)) {
|
|
981
|
+
return candidate;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
for (const extension of SOURCE_EXTENSIONS) {
|
|
985
|
+
const candidate = (0, import_node_path2.resolve)(basePath, `index${extension}`);
|
|
986
|
+
if ((0, import_node_fs2.existsSync)(candidate)) {
|
|
987
|
+
return candidate;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
function extractImportSpecifiers(source) {
|
|
993
|
+
const specifiers = /* @__PURE__ */ new Set();
|
|
994
|
+
const patterns = [
|
|
995
|
+
/(?:import|export)\s+(?:type\s+)?(?:[^'"]*?\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
996
|
+
/import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
997
|
+
/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
|
|
998
|
+
];
|
|
999
|
+
for (const pattern of patterns) {
|
|
1000
|
+
for (const match of source.matchAll(pattern)) {
|
|
1001
|
+
const specifier = match[1];
|
|
1002
|
+
if (specifier) {
|
|
1003
|
+
specifiers.add(specifier);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
return [...specifiers];
|
|
1008
|
+
}
|
|
1009
|
+
function resolveImportSpecifier(specifier, fromFile, projectRoot) {
|
|
1010
|
+
let basePath;
|
|
1011
|
+
if (specifier.startsWith("@/")) {
|
|
1012
|
+
basePath = (0, import_node_path2.resolve)(projectRoot, specifier.slice(2));
|
|
1013
|
+
} else if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
1014
|
+
basePath = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(fromFile), specifier);
|
|
1015
|
+
} else {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
if (isStyleFile(basePath)) {
|
|
1019
|
+
return null;
|
|
1020
|
+
}
|
|
1021
|
+
return findSourceFile(basePath);
|
|
1022
|
+
}
|
|
1023
|
+
async function collectEntryClasses(compilation, entry, sourceCache) {
|
|
1024
|
+
const classes = /* @__PURE__ */ new Set();
|
|
1025
|
+
const visitedModules = /* @__PURE__ */ new Set();
|
|
1026
|
+
for (const chunk of entry.chunks) {
|
|
1027
|
+
for (const module2 of compilation.chunkGraph.getChunkModulesIterable(
|
|
1028
|
+
chunk
|
|
1029
|
+
)) {
|
|
1030
|
+
await scanModule(module2, classes, sourceCache, visitedModules);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return classes;
|
|
1034
|
+
}
|
|
1035
|
+
async function scanModule(module2, classes, sourceCache, visited) {
|
|
1036
|
+
if (visited.has(module2)) {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
visited.add(module2);
|
|
1040
|
+
const nested = module2.modules;
|
|
1041
|
+
if (nested) {
|
|
1042
|
+
for (const child of nested) {
|
|
1043
|
+
await scanModule(child, classes, sourceCache, visited);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
const resource = module2.resource;
|
|
1047
|
+
if (typeof resource !== "string" || isStyleFile(resource)) {
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
const source = readModuleSource(module2);
|
|
1051
|
+
if (source) {
|
|
1052
|
+
scanClasses(source, classes);
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
if (!isScannableSource(resource)) {
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
const filePath = resource.split("?")[0] ?? resource;
|
|
1059
|
+
if (!filePath) {
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
let pending = sourceCache.get(filePath);
|
|
1063
|
+
if (!pending) {
|
|
1064
|
+
pending = (0, import_promises.readFile)(filePath, "utf8").catch(() => void 0);
|
|
1065
|
+
sourceCache.set(filePath, pending);
|
|
1066
|
+
}
|
|
1067
|
+
const fresh = await pending;
|
|
1068
|
+
if (fresh) {
|
|
1069
|
+
scanClasses(fresh, classes);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
function readModuleSource(module2) {
|
|
1073
|
+
try {
|
|
1074
|
+
const source = module2.originalSource();
|
|
1075
|
+
if (!source) {
|
|
1076
|
+
return void 0;
|
|
1077
|
+
}
|
|
1078
|
+
return sourceToString(source.source());
|
|
1079
|
+
} catch {
|
|
1080
|
+
return void 0;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
function collectEntryJsFiles(entry) {
|
|
1084
|
+
const files = /* @__PURE__ */ new Set();
|
|
1085
|
+
for (const file of entry.getFiles()) {
|
|
1086
|
+
if (file.endsWith(".js")) {
|
|
1087
|
+
files.add(file);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return files;
|
|
1091
|
+
}
|
|
1092
|
+
function collectEntryCssFiles(entry) {
|
|
1093
|
+
const cssFiles = /* @__PURE__ */ new Set();
|
|
1094
|
+
for (const file of entry.getFiles()) {
|
|
1095
|
+
if (file.endsWith(".css")) {
|
|
1096
|
+
cssFiles.add(file);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
for (const chunk of entry.chunks) {
|
|
1100
|
+
for (const file of chunk.files) {
|
|
1101
|
+
if (file.endsWith(".css")) {
|
|
1102
|
+
cssFiles.add(file);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
for (const file of chunk.auxiliaryFiles) {
|
|
1106
|
+
if (file.endsWith(".css")) {
|
|
1107
|
+
cssFiles.add(file);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return cssFiles;
|
|
1112
|
+
}
|
|
1113
|
+
function entryName(entry) {
|
|
1114
|
+
if (entry.name) {
|
|
1115
|
+
return entry.name;
|
|
1116
|
+
}
|
|
1117
|
+
const chunk = entry.getEntrypointChunk();
|
|
1118
|
+
return chunk.name ?? void 0;
|
|
1119
|
+
}
|
|
1120
|
+
function isPageEntry(name) {
|
|
1121
|
+
const normalized = normalizeSlashes(name);
|
|
1122
|
+
if (normalized.startsWith("app/")) {
|
|
1123
|
+
const path = normalized.slice(4);
|
|
1124
|
+
return path === "page" || path.endsWith("/page");
|
|
1125
|
+
}
|
|
1126
|
+
if (normalized.startsWith("pages/")) {
|
|
1127
|
+
const path = normalized.slice(6);
|
|
1128
|
+
return path.length > 0 && !path.split("/").some((segment) => segment.startsWith("_"));
|
|
1129
|
+
}
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
function isSharedEntry(name) {
|
|
1133
|
+
const normalized = normalizeSlashes(name);
|
|
1134
|
+
return normalized.startsWith("app/") && (normalized === "app/layout" || normalized.endsWith("/layout")) || normalized === "pages/_app" || normalized === "pages/_document";
|
|
1135
|
+
}
|
|
1136
|
+
function isInternalEntry(name) {
|
|
1137
|
+
const normalized = normalizeSlashes(name);
|
|
1138
|
+
const base = normalized.split("/").pop() ?? normalized;
|
|
1139
|
+
return base === "main" || base === "main-app" || base === "polyfills" || base === "webpack" || base === "app-pages-internals" || base === "middleware" || base === "instrumentation";
|
|
1140
|
+
}
|
|
1141
|
+
function routeNameForEntry(name) {
|
|
1142
|
+
const normalized = normalizeSlashes(name).replace(/^\.\//, "");
|
|
1143
|
+
if (normalized.startsWith("app/")) {
|
|
1144
|
+
let path = normalized.slice(4);
|
|
1145
|
+
if (path === "page") {
|
|
1146
|
+
return "/";
|
|
1147
|
+
}
|
|
1148
|
+
if (path.endsWith("/page")) {
|
|
1149
|
+
path = path.slice(0, -"/page".length);
|
|
1150
|
+
}
|
|
1151
|
+
if (!path || path.endsWith("layout")) {
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
return `/${path}`;
|
|
1155
|
+
}
|
|
1156
|
+
if (normalized.startsWith("pages/")) {
|
|
1157
|
+
let path = normalized.slice(6);
|
|
1158
|
+
if (!path || path.startsWith("_")) {
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
path = path.replace(/\/?index$/, "");
|
|
1162
|
+
return path.length > 0 ? `/${path}` : "/";
|
|
1163
|
+
}
|
|
1164
|
+
return normalized.length > 0 ? normalized : null;
|
|
1165
|
+
}
|
|
1166
|
+
function normalizeSlashes(value) {
|
|
1167
|
+
return value.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
1168
|
+
}
|
|
1169
|
+
function isStyleFile(resource) {
|
|
1170
|
+
return /\.(css|scss|sass|less|styl|stylus)(\?|$)/i.test(resource);
|
|
1171
|
+
}
|
|
1172
|
+
function isScannableSource(resource) {
|
|
1173
|
+
return /\.(js|jsx|ts|tsx|mjs|cjs|mts|cts|html?|vue|svelte|mdx)(\?|$)/i.test(
|
|
1174
|
+
resource
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
function scanClasses(source, classes) {
|
|
1178
|
+
const patterns = [
|
|
1179
|
+
/(?:class|className)\s*=\s*(?:"([^"]*)"|'([^']*)'|`([^`]*)`)/g,
|
|
1180
|
+
/(?:^|[,{;\s])(?:class|className)\s*:\s*(?:"([^"]*)"|'([^']*)'|`([^`]*)`)/g,
|
|
1181
|
+
/classList\.(?:add|remove|toggle|contains)\(\s*(?:"([^"]*)"|'([^']*)'|`([^`]*)`)/g
|
|
1182
|
+
];
|
|
1183
|
+
for (const pattern of patterns) {
|
|
1184
|
+
for (const match of source.matchAll(pattern)) {
|
|
1185
|
+
const value = match[1] ?? match[2] ?? match[3];
|
|
1186
|
+
if (!value) {
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
for (const token of value.split(/\s+/)) {
|
|
1190
|
+
if (/^-?[_a-zA-Z][\w-]*$/.test(token)) {
|
|
1191
|
+
classes.add(token.toLowerCase());
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
function addCssOwner(pagesByCss, cssFile, page) {
|
|
1198
|
+
const owners = pagesByCss.get(cssFile) ?? /* @__PURE__ */ new Set();
|
|
1199
|
+
owners.add(page);
|
|
1200
|
+
pagesByCss.set(cssFile, owners);
|
|
1201
|
+
}
|
|
1202
|
+
function collectSyntaxWarnings(analysis) {
|
|
1203
|
+
return analysis.warnings.map((warning) => ({
|
|
1204
|
+
kind: "syntax",
|
|
1205
|
+
message: warning.message,
|
|
1206
|
+
...warning.location ? {
|
|
1207
|
+
line: warning.location.line,
|
|
1208
|
+
column: warning.location.column
|
|
1209
|
+
} : {}
|
|
1210
|
+
}));
|
|
1211
|
+
}
|
|
1212
|
+
function sourceToString(source) {
|
|
1213
|
+
return typeof source === "string" ? source : Buffer.from(source).toString("utf8");
|
|
1214
|
+
}
|
|
1215
|
+
function formatBytes2(bytes) {
|
|
1216
|
+
if (bytes < 1024) {
|
|
1217
|
+
return `${bytes} B`;
|
|
1218
|
+
}
|
|
1219
|
+
if (bytes < 1024 * 1024) {
|
|
1220
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1221
|
+
}
|
|
1222
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1223
|
+
}
|
|
1224
|
+
function roundMs(value) {
|
|
1225
|
+
return Math.round(value * 1e3) / 1e3;
|
|
1226
|
+
}
|
|
1227
|
+
function describeError(error) {
|
|
1228
|
+
return error instanceof Error ? error.message : String(error);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// src/index.ts
|
|
1232
|
+
function withCssDoctor(nextConfig = {}, options = {}) {
|
|
1233
|
+
const pluginOptions = resolvePluginOptions(options);
|
|
1234
|
+
const originalWebpack = nextConfig.webpack;
|
|
1235
|
+
const webpack = (config, context) => {
|
|
1236
|
+
const result = originalWebpack ? originalWebpack(config, context) : config;
|
|
1237
|
+
if (isPromiseLike(result)) {
|
|
1238
|
+
return result.then(
|
|
1239
|
+
(resolvedConfig) => enableCssDoctor(resolvedConfig, context, pluginOptions)
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
return enableCssDoctor(result, context, pluginOptions);
|
|
1243
|
+
};
|
|
1244
|
+
return {
|
|
1245
|
+
...nextConfig,
|
|
1246
|
+
webpack
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
function enableCssDoctor(config, context, options) {
|
|
1250
|
+
if (!isRecord2(config)) {
|
|
1251
|
+
return config;
|
|
1252
|
+
}
|
|
1253
|
+
if (context.dev === true || context.isServer === true) {
|
|
1254
|
+
return config;
|
|
1255
|
+
}
|
|
1256
|
+
const webpackConfig = config;
|
|
1257
|
+
const plugins = Array.isArray(webpackConfig.plugins) ? [...webpackConfig.plugins] : [];
|
|
1258
|
+
const alreadyInstalled = plugins.some(
|
|
1259
|
+
(plugin) => plugin instanceof CssDoctorWebpackPlugin
|
|
1260
|
+
);
|
|
1261
|
+
if (!alreadyInstalled) {
|
|
1262
|
+
plugins.push(new CssDoctorWebpackPlugin(options));
|
|
1263
|
+
}
|
|
1264
|
+
webpackConfig.plugins = plugins;
|
|
1265
|
+
if (options.minify === false) {
|
|
1266
|
+
disableNextCssMinifier(webpackConfig);
|
|
1267
|
+
}
|
|
1268
|
+
return webpackConfig;
|
|
1269
|
+
}
|
|
1270
|
+
function disableNextCssMinifier(config) {
|
|
1271
|
+
const optimization = config.optimization;
|
|
1272
|
+
if (!optimization || !Array.isArray(optimization.minimizer)) {
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
optimization.minimizer = optimization.minimizer.filter((plugin) => {
|
|
1276
|
+
const source = typeof plugin === "function" ? Function.prototype.toString.call(plugin) : String(plugin);
|
|
1277
|
+
return !source.includes("CssMinimizerPlugin") && !source.includes("css-minimizer-plugin");
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
function resolvePluginOptions(options) {
|
|
1281
|
+
const { configFile, ...inlineOptions } = options;
|
|
1282
|
+
const hasInlineOptions = Object.keys(inlineOptions).length > 0;
|
|
1283
|
+
let loadedOptions = {};
|
|
1284
|
+
let configError;
|
|
1285
|
+
if (configFile) {
|
|
1286
|
+
const loaded = loadCssDoctorConfig({ configFile });
|
|
1287
|
+
loadedOptions = loaded.options;
|
|
1288
|
+
configError = loaded.error;
|
|
1289
|
+
} else if (!hasInlineOptions) {
|
|
1290
|
+
const loaded = loadCssDoctorConfig();
|
|
1291
|
+
loadedOptions = loaded.options;
|
|
1292
|
+
configError = loaded.error;
|
|
1293
|
+
}
|
|
1294
|
+
if (configError) {
|
|
1295
|
+
console.warn(
|
|
1296
|
+
`[css-doctor] ${configError}. Continuing with inline/default options.`
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
const merged = {
|
|
1300
|
+
...loadedOptions,
|
|
1301
|
+
...inlineOptions
|
|
1302
|
+
};
|
|
1303
|
+
const { configFile: _ignored, ...pluginOptions } = merged;
|
|
1304
|
+
return pluginOptions;
|
|
1305
|
+
}
|
|
1306
|
+
function isPromiseLike(value) {
|
|
1307
|
+
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
1308
|
+
}
|
|
1309
|
+
function isRecord2(value) {
|
|
1310
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1311
|
+
}
|
|
1312
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1313
|
+
0 && (module.exports = {
|
|
1314
|
+
findCssDoctorConfig,
|
|
1315
|
+
loadCssDoctorConfig,
|
|
1316
|
+
withCssDoctor
|
|
1317
|
+
});
|
|
1318
|
+
//# sourceMappingURL=index.cjs.map
|