@emulsify/core 4.3.2 → 4.4.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/.storybook/main-static-assets.js +5 -8
- package/.storybook/main-vite.js +11 -3
- package/README.md +4 -5
- package/config/vite/entries.js +7 -2
- package/config/vite/environment.js +4 -0
- package/config/vite/plugins/assets/asset-url-rebase.js +241 -0
- package/config/vite/plugins/assets/copy-src-assets.js +82 -12
- package/config/vite/plugins/assets/copy-twig-files.js +85 -16
- package/config/vite/plugins/assets/css-asset-rebase.js +306 -0
- package/config/vite/plugins/assets/css-asset-relativizer.js +301 -21
- package/config/vite/plugins/assets/development-source-maps.js +273 -0
- package/config/vite/plugins/assets/mirror-components.js +98 -82
- package/config/vite/plugins/assets/output-freshness.js +235 -0
- package/config/vite/plugins/assets/source-file-index.js +7 -1
- package/config/vite/plugins/assets/stable-watch-output.js +165 -0
- package/config/vite/plugins/assets/storybook-output.js +27 -0
- package/config/vite/plugins/index.js +95 -9
- package/config/vite/plugins/reporter/asset-resolver.js +34 -6
- package/config/vite/plugins/reporter/build-errors.js +7 -3
- package/config/vite/plugins/reporter/diagnostics.js +140 -10
- package/config/vite/plugins/reporter/index.js +380 -75
- package/config/vite/plugins/reporter/render.js +297 -44
- package/config/vite/plugins/reporter/sass-logger.js +30 -0
- package/config/vite/plugins/reporter/source-roots.js +101 -21
- package/config/vite/plugins/reporter/strict-mode.js +99 -0
- package/config/vite/plugins/reporter/vite-logger.js +220 -8
- package/config/vite/plugins/reporter/watch-mode.js +6 -2
- package/config/vite/plugins/twig/virtual-twig-asset-sources.js +48 -49
- package/config/vite/project-config.js +121 -21
- package/config/vite/project-structure.js +6 -0
- package/config/vite/utils/asset-roots.js +205 -0
- package/config/vite/utils/css-urls.js +350 -0
- package/config/vite/utils/fs-safe.js +38 -1
- package/config/vite/utils/source-maps.js +88 -0
- package/config/vite/vite.config.js +106 -42
- package/package.json +40 -29
- package/scripts/audit/checks/css-asset-references.js +256 -24
- package/scripts/audit/fix.js +836 -0
- package/scripts/audit/index.js +10 -2
- package/scripts/audit/lib/css.js +41 -35
- package/scripts/audit/lib/twig.js +11 -29
- package/scripts/audit/report.js +83 -5
- package/scripts/audit.js +87 -2
- package/src/storybook/twig/source-function.js +14 -10
|
@@ -16,13 +16,12 @@
|
|
|
16
16
|
* - `apply: 'build'` excludes Storybook's dev server, which resolves with
|
|
17
17
|
* `command: 'serve'`. This mirrors the existing guard on the component
|
|
18
18
|
* mirror plugin.
|
|
19
|
-
* - A `config.build.watch` check
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* byte for byte.
|
|
19
|
+
* - A `config.build.watch` check selects the full develop summary. One-shot
|
|
20
|
+
* production builds remain silent unless the collector has an asset problem
|
|
21
|
+
* or a Sass tally that would otherwise be hidden.
|
|
23
22
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* Storybook dev stays out of the reporter path and keeps its own ready box;
|
|
24
|
+
* standalone Storybook builds receive the compact diagnostics they need.
|
|
26
25
|
*/
|
|
27
26
|
|
|
28
27
|
import { join, relative } from 'node:path';
|
|
@@ -37,17 +36,37 @@ import {
|
|
|
37
36
|
sharedMissingDirectory,
|
|
38
37
|
} from './asset-resolver.js';
|
|
39
38
|
import { classifyBuildError } from './build-errors.js';
|
|
40
|
-
import {
|
|
41
|
-
|
|
39
|
+
import {
|
|
40
|
+
hasCycleFailure,
|
|
41
|
+
renderAssetSummary,
|
|
42
|
+
renderBanner,
|
|
43
|
+
renderDeprecationSummary,
|
|
44
|
+
renderRebuild,
|
|
45
|
+
renderSummary,
|
|
46
|
+
} from './render.js';
|
|
47
|
+
import {
|
|
48
|
+
createStyler,
|
|
49
|
+
displayPath,
|
|
50
|
+
pluralize,
|
|
51
|
+
supportsColor,
|
|
52
|
+
supportsUnicode,
|
|
53
|
+
} from './format.js';
|
|
42
54
|
import {
|
|
43
55
|
buildInputFileRows,
|
|
44
56
|
buildInputRows,
|
|
45
57
|
buildOutputFileRows,
|
|
58
|
+
buildOutputSummaryRows,
|
|
46
59
|
diffFingerprints,
|
|
47
60
|
fingerprintBundle,
|
|
61
|
+
isReportableOutput,
|
|
48
62
|
summarizeBundle,
|
|
49
63
|
watchedRootLabel,
|
|
50
64
|
} from './source-roots.js';
|
|
65
|
+
import {
|
|
66
|
+
STRICTNESS,
|
|
67
|
+
countStrictAssetFailures,
|
|
68
|
+
resolveAssetStrictness,
|
|
69
|
+
} from './strict-mode.js';
|
|
51
70
|
import { isDetailed } from './verbosity.js';
|
|
52
71
|
import { resolvePackageVersion } from '../../utils/package-version.js';
|
|
53
72
|
|
|
@@ -85,6 +104,132 @@ export function countEntries(input) {
|
|
|
85
104
|
return undefined;
|
|
86
105
|
}
|
|
87
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Render one strict-mode asset failure with the stylesheet that imported it.
|
|
109
|
+
*
|
|
110
|
+
* @param {{url: string, rewritten?: string, importer?: string}} entry - Asset diagnostic.
|
|
111
|
+
* @param {string} [projectDir] - Project root used to shorten the importer.
|
|
112
|
+
* @returns {string} One actionable failure line.
|
|
113
|
+
*/
|
|
114
|
+
const strictAssetFailureLine = (entry, projectDir) => {
|
|
115
|
+
const rewrite = entry.rewritten ? ` -> ${entry.rewritten}` : '';
|
|
116
|
+
return ` ${entry.url}${rewrite} (imported by ${displayPath(entry.importer, projectDir)})`;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Explain every CSS asset URL that strict mode is rejecting.
|
|
121
|
+
*
|
|
122
|
+
* Level 1 includes only URLs that remained unresolved. Level 2 also includes
|
|
123
|
+
* URLs repaired by the build and ambiguous asset-root matches; keeping those
|
|
124
|
+
* in separate sections avoids describing a successful repair as unresolved.
|
|
125
|
+
*
|
|
126
|
+
* @param {{unresolvedAssets?: object[], assetRebases?: object[]}} snapshot - Diagnostics snapshot.
|
|
127
|
+
* @param {string} strictness - One of {@link STRICTNESS}.
|
|
128
|
+
* @param {string} [projectDir] - Project root used to shorten importers.
|
|
129
|
+
* @param {Array<{where?: string, url?: string}>} [assetRows] - Resolved source locations for notices without an importer.
|
|
130
|
+
* @returns {string} Complete build failure message.
|
|
131
|
+
*/
|
|
132
|
+
const strictAssetFailureMessage = (
|
|
133
|
+
snapshot,
|
|
134
|
+
strictness,
|
|
135
|
+
projectDir,
|
|
136
|
+
assetRows = [],
|
|
137
|
+
) => {
|
|
138
|
+
const unresolved = snapshot.unresolvedAssets || [];
|
|
139
|
+
const rebases =
|
|
140
|
+
strictness === STRICTNESS.all ? snapshot.assetRebases || [] : [];
|
|
141
|
+
const repaired = rebases.filter((entry) => entry.status === 'rebased');
|
|
142
|
+
const ambiguous = rebases.filter((entry) => entry.status === 'ambiguous');
|
|
143
|
+
const lines = ['Emulsify: strict CSS asset checks failed.'];
|
|
144
|
+
|
|
145
|
+
if (unresolved.length) {
|
|
146
|
+
const unresolvedLines = unresolved.flatMap((entry) => {
|
|
147
|
+
if (entry.importer) return [strictAssetFailureLine(entry, projectDir)];
|
|
148
|
+
|
|
149
|
+
const sourceRows = assetRows.filter(
|
|
150
|
+
(row) => row.url === entry.url && row.where && row.where !== '—',
|
|
151
|
+
);
|
|
152
|
+
if (!sourceRows.length) {
|
|
153
|
+
return [strictAssetFailureLine(entry, projectDir)];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return sourceRows.map((row) =>
|
|
157
|
+
strictAssetFailureLine({ ...entry, importer: row.where }, projectDir),
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
lines.push(
|
|
162
|
+
`${pluralize(unresolved.length, 'CSS asset URL')} did not resolve:`,
|
|
163
|
+
...unresolvedLines,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (repaired.length) {
|
|
168
|
+
lines.push(
|
|
169
|
+
`${pluralize(repaired.length, 'CSS asset URL')} ${repaired.length === 1 ? 'was' : 'were'} repaired during the build:`,
|
|
170
|
+
...repaired.map((entry) => strictAssetFailureLine(entry, projectDir)),
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (ambiguous.length) {
|
|
175
|
+
lines.push(
|
|
176
|
+
`${pluralize(ambiguous.length, 'CSS asset URL')} matched multiple asset roots:`,
|
|
177
|
+
...ambiguous.map((entry) => strictAssetFailureLine(entry, projectDir)),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
lines.push('Set EMULSIFY_STRICT_ASSETS=0 to report without failing.');
|
|
182
|
+
return lines.join('\n');
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Merge filesystem copy events into Rollup's bundle-derived output diff.
|
|
187
|
+
*
|
|
188
|
+
* Twig, component metadata, and static source assets are written by plugins
|
|
189
|
+
* after Rollup has produced its bundle, so they can never appear in that
|
|
190
|
+
* bundle's fingerprints. The event map is last-write-wins per path: a future
|
|
191
|
+
* safe prune can mark a path removed, while a later write in the same cycle
|
|
192
|
+
* can restore it.
|
|
193
|
+
*
|
|
194
|
+
* @param {Array<{fileName: string, bytes?: number, gzipBytes?: number}>} changed - Bundle outputs whose bytes changed.
|
|
195
|
+
* @param {string[]} removed - Bundle outputs no longer present.
|
|
196
|
+
* @param {Map<string, {kind: 'written'|'removed', bytes?: number}>} copyChanges - Successful copied-output mutations.
|
|
197
|
+
* @returns {{changedOutputs: Array<{fileName: string, bytes?: number, gzipBytes?: number}>, removedOutputs: string[]}} Combined output diff.
|
|
198
|
+
*/
|
|
199
|
+
const mergeCopiedOutputChanges = (changed, removed, copyChanges) => {
|
|
200
|
+
const changedByName = new Map(
|
|
201
|
+
changed.map((entry) => [entry.fileName, entry]),
|
|
202
|
+
);
|
|
203
|
+
const removedNames = new Set(removed);
|
|
204
|
+
|
|
205
|
+
for (const [fileName, change] of copyChanges) {
|
|
206
|
+
if (!isReportableOutput(fileName)) continue;
|
|
207
|
+
|
|
208
|
+
if (change?.kind === 'removed') {
|
|
209
|
+
changedByName.delete(fileName);
|
|
210
|
+
removedNames.add(fileName);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (change?.kind !== 'written') continue;
|
|
215
|
+
|
|
216
|
+
removedNames.delete(fileName);
|
|
217
|
+
changedByName.set(fileName, {
|
|
218
|
+
fileName,
|
|
219
|
+
bytes: Number.isFinite(change.bytes) ? change.bytes : undefined,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
changedOutputs: [...changedByName.values()].sort((a, b) => {
|
|
225
|
+
const aBytes = Number.isFinite(a.bytes) ? a.bytes : -1;
|
|
226
|
+
const bBytes = Number.isFinite(b.bytes) ? b.bytes : -1;
|
|
227
|
+
return bBytes - aBytes || a.fileName.localeCompare(b.fileName, 'en');
|
|
228
|
+
}),
|
|
229
|
+
removedOutputs: [...removedNames].sort((a, b) => a.localeCompare(b, 'en')),
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
|
|
88
233
|
/**
|
|
89
234
|
* Create the Emulsify develop reporter plugin.
|
|
90
235
|
*
|
|
@@ -96,8 +241,13 @@ export function countEntries(input) {
|
|
|
96
241
|
* clock?: () => Date,
|
|
97
242
|
* colorEnabled?: boolean,
|
|
98
243
|
* detailed?: boolean,
|
|
244
|
+
* unchangedOutputs?: Set<string>,
|
|
245
|
+
* copiedOutputChanges?: Map<string, {kind: 'written'|'removed', bytes?: number}>,
|
|
99
246
|
* version?: string
|
|
100
|
-
* }} options - Plugin options.
|
|
247
|
+
* }} options - Plugin options. `unchangedOutputs` carries the files the stable
|
|
248
|
+
* output plugin dropped from this cycle's bundle because the bytes on disk
|
|
249
|
+
* already matched. `copiedOutputChanges` carries successful writes and
|
|
250
|
+
* removals that never enter Rollup's bundle.
|
|
101
251
|
* @returns {import('vite').PluginOption} Develop reporter plugin.
|
|
102
252
|
*/
|
|
103
253
|
export function developReporterPlugin({
|
|
@@ -109,6 +259,9 @@ export function developReporterPlugin({
|
|
|
109
259
|
colorEnabled,
|
|
110
260
|
unicodeEnabled,
|
|
111
261
|
detailed,
|
|
262
|
+
strictness,
|
|
263
|
+
unchangedOutputs = new Set(),
|
|
264
|
+
copiedOutputChanges = new Map(),
|
|
112
265
|
version,
|
|
113
266
|
} = {}) {
|
|
114
267
|
const styler = createStyler(
|
|
@@ -117,21 +270,29 @@ export function developReporterPlugin({
|
|
|
117
270
|
const unicode =
|
|
118
271
|
unicodeEnabled === undefined ? supportsUnicode() : unicodeEnabled;
|
|
119
272
|
const verbose = detailed === undefined ? isDetailed() : detailed;
|
|
273
|
+
const strictLevel =
|
|
274
|
+
strictness === undefined ? resolveAssetStrictness() : strictness;
|
|
120
275
|
|
|
121
276
|
let watching = false;
|
|
277
|
+
let oneShot = false;
|
|
278
|
+
let oneShotPrinted = false;
|
|
279
|
+
let oneShotAssetRows = [];
|
|
122
280
|
let outDir = 'dist';
|
|
281
|
+
let outputRows = [];
|
|
123
282
|
let inputRows = [];
|
|
124
283
|
let inputFiles = [];
|
|
125
284
|
let watchLabel;
|
|
126
285
|
let cycleStart = 0;
|
|
127
286
|
let cyclePrinted = true;
|
|
128
287
|
let firstCycleComplete = false;
|
|
288
|
+
let previousCycleFailed = false;
|
|
129
289
|
let changedFiles = [];
|
|
130
290
|
let writeTally;
|
|
131
291
|
let outputFiles = [];
|
|
132
292
|
let changedOutputs = [];
|
|
133
293
|
let removedOutputs = [];
|
|
134
294
|
let fingerprints = new Map();
|
|
295
|
+
let outputNames = new Set();
|
|
135
296
|
let transformedModules = new Set();
|
|
136
297
|
|
|
137
298
|
/**
|
|
@@ -191,6 +352,47 @@ export function developReporterPlugin({
|
|
|
191
352
|
|
|
192
353
|
const snapshot = diagnostics.snapshot();
|
|
193
354
|
const durationMs = now() - cycleStart;
|
|
355
|
+
const failed = hasCycleFailure(snapshot);
|
|
356
|
+
|
|
357
|
+
// Enriching costs a filesystem read per stylesheet, so it only runs when
|
|
358
|
+
// the cycle actually produced something to attribute. A resolver is built
|
|
359
|
+
// per cycle so its read cache never serves stale contents after an edit.
|
|
360
|
+
//
|
|
361
|
+
// Rebuilds need this too, not just the first build: a rebuild that fails on
|
|
362
|
+
// a missing Sass import has to name the import, and that attribution is
|
|
363
|
+
// what turns "rebuild failed" into something actionable.
|
|
364
|
+
const needsResolver =
|
|
365
|
+
snapshot.unresolvedAssets.length > 0 ||
|
|
366
|
+
snapshot.importErrors.length > 0 ||
|
|
367
|
+
snapshot.syntaxErrors.length > 0;
|
|
368
|
+
const resolver = needsResolver ? createAssetResolver(env) : undefined;
|
|
369
|
+
|
|
370
|
+
const assetRows = resolver
|
|
371
|
+
? buildAssetRows(snapshot.unresolvedAssets, resolver)
|
|
372
|
+
: [];
|
|
373
|
+
|
|
374
|
+
const importRows = resolver
|
|
375
|
+
? buildImportRows(snapshot.importErrors, resolver, env.projectDir)
|
|
376
|
+
: [];
|
|
377
|
+
const sharedDirectory = sharedMissingDirectory(importRows, env.projectDir);
|
|
378
|
+
const importErrors = {
|
|
379
|
+
rows: importRows,
|
|
380
|
+
sharedDirectory,
|
|
381
|
+
directoryExists: Boolean(
|
|
382
|
+
sharedDirectory &&
|
|
383
|
+
env.projectDir &&
|
|
384
|
+
existsSync(join(env.projectDir, sharedDirectory)),
|
|
385
|
+
),
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// The minifier reports against generated CSS, so the only route back to
|
|
389
|
+
// a source file is searching for the literals in the offending rule.
|
|
390
|
+
const syntaxErrors = resolver
|
|
391
|
+
? snapshot.syntaxErrors.map((error) => ({
|
|
392
|
+
...error,
|
|
393
|
+
lead: findLikelySource(error.declaration, resolver),
|
|
394
|
+
}))
|
|
395
|
+
: snapshot.syntaxErrors;
|
|
194
396
|
|
|
195
397
|
if (firstCycleComplete) {
|
|
196
398
|
emit(
|
|
@@ -199,64 +401,35 @@ export function developReporterPlugin({
|
|
|
199
401
|
durationMs,
|
|
200
402
|
changedFiles,
|
|
201
403
|
projectDir: env.projectDir,
|
|
404
|
+
outDir,
|
|
405
|
+
sourceGlob: resolveSourceGlob(env),
|
|
406
|
+
assetRows,
|
|
407
|
+
importErrors,
|
|
408
|
+
syntaxErrors,
|
|
409
|
+
recovered: previousCycleFailed && !failed,
|
|
202
410
|
moduleCount: verbose ? transformedModules.size : undefined,
|
|
203
411
|
changedOutputs,
|
|
204
412
|
removedOutputs,
|
|
205
413
|
detailed: verbose,
|
|
414
|
+
unicode,
|
|
206
415
|
styler,
|
|
207
416
|
now: clock(),
|
|
208
417
|
}),
|
|
209
418
|
);
|
|
210
419
|
} else {
|
|
211
420
|
firstCycleComplete = true;
|
|
212
|
-
// Enriching costs a filesystem read per stylesheet, so it only runs when
|
|
213
|
-
// the build actually produced unresolved URLs. A resolver is built per
|
|
214
|
-
// cycle so its read cache never serves stale contents after an edit.
|
|
215
|
-
const needsResolver =
|
|
216
|
-
snapshot.unresolvedAssets.length > 0 ||
|
|
217
|
-
snapshot.importErrors.length > 0 ||
|
|
218
|
-
snapshot.syntaxErrors.length > 0;
|
|
219
|
-
const resolver = needsResolver ? createAssetResolver(env) : undefined;
|
|
220
|
-
|
|
221
|
-
const assetRows = resolver
|
|
222
|
-
? buildAssetRows(snapshot.unresolvedAssets, resolver)
|
|
223
|
-
: [];
|
|
224
|
-
|
|
225
|
-
const importRows = resolver
|
|
226
|
-
? buildImportRows(snapshot.importErrors, resolver, env.projectDir)
|
|
227
|
-
: [];
|
|
228
|
-
const sharedDirectory = sharedMissingDirectory(
|
|
229
|
-
importRows,
|
|
230
|
-
env.projectDir,
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
// The minifier reports against generated CSS, so the only route back to
|
|
234
|
-
// a source file is searching for the literals in the offending rule.
|
|
235
|
-
const syntaxErrors = resolver
|
|
236
|
-
? snapshot.syntaxErrors.map((error) => ({
|
|
237
|
-
...error,
|
|
238
|
-
lead: findLikelySource(error.declaration, resolver),
|
|
239
|
-
}))
|
|
240
|
-
: snapshot.syntaxErrors;
|
|
241
421
|
|
|
242
422
|
emit(
|
|
243
423
|
renderSummary({
|
|
244
424
|
snapshot,
|
|
245
425
|
durationMs,
|
|
246
426
|
outDir,
|
|
427
|
+
outputRows,
|
|
247
428
|
projectDir: env.projectDir,
|
|
248
429
|
sourceGlob: resolveSourceGlob(env),
|
|
249
430
|
assetRows,
|
|
250
431
|
syntaxErrors,
|
|
251
|
-
importErrors
|
|
252
|
-
rows: importRows,
|
|
253
|
-
sharedDirectory,
|
|
254
|
-
directoryExists: Boolean(
|
|
255
|
-
sharedDirectory &&
|
|
256
|
-
env.projectDir &&
|
|
257
|
-
existsSync(join(env.projectDir, sharedDirectory)),
|
|
258
|
-
),
|
|
259
|
-
},
|
|
432
|
+
importErrors,
|
|
260
433
|
platform: env.platform,
|
|
261
434
|
inputRows,
|
|
262
435
|
watchLabel,
|
|
@@ -269,9 +442,59 @@ export function developReporterPlugin({
|
|
|
269
442
|
);
|
|
270
443
|
}
|
|
271
444
|
|
|
445
|
+
previousCycleFailed = failed;
|
|
272
446
|
changedFiles = [];
|
|
273
447
|
};
|
|
274
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Report collected diagnostics after a one-shot build.
|
|
451
|
+
*
|
|
452
|
+
* Watch builds get the full per-cycle summary; a one-shot `vite build` or
|
|
453
|
+
* `storybook build` gets this and nothing else, and only when there is
|
|
454
|
+
* something to say. It covers both captured asset diagnostics and Sass
|
|
455
|
+
* deprecations swallowed by Storybook's quiet logger.
|
|
456
|
+
*
|
|
457
|
+
* @returns {void}
|
|
458
|
+
*/
|
|
459
|
+
const reportOneShot = () => {
|
|
460
|
+
if (!oneShot || oneShotPrinted) return;
|
|
461
|
+
oneShotPrinted = true;
|
|
462
|
+
|
|
463
|
+
const snapshot = diagnostics.snapshot();
|
|
464
|
+
const rebases = snapshot.assetRebases || [];
|
|
465
|
+
const deprecations = snapshot.deprecations || [];
|
|
466
|
+
if (
|
|
467
|
+
!snapshot.unresolvedAssets.length &&
|
|
468
|
+
!rebases.length &&
|
|
469
|
+
!deprecations.length
|
|
470
|
+
) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Enriching walks the project, so it only runs when there is an unresolved
|
|
475
|
+
// URL to attribute. Rebase records already carry their own importer.
|
|
476
|
+
const resolver = snapshot.unresolvedAssets.length
|
|
477
|
+
? createAssetResolver(env)
|
|
478
|
+
: undefined;
|
|
479
|
+
oneShotAssetRows = resolver
|
|
480
|
+
? buildAssetRows(snapshot.unresolvedAssets, resolver)
|
|
481
|
+
: [];
|
|
482
|
+
|
|
483
|
+
emit([
|
|
484
|
+
...renderAssetSummary({
|
|
485
|
+
assetRows: oneShotAssetRows,
|
|
486
|
+
rebases,
|
|
487
|
+
styler,
|
|
488
|
+
}),
|
|
489
|
+
...renderDeprecationSummary({
|
|
490
|
+
snapshot,
|
|
491
|
+
projectDir: env.projectDir,
|
|
492
|
+
sourceGlob: resolveSourceGlob(env),
|
|
493
|
+
styler,
|
|
494
|
+
}),
|
|
495
|
+
]);
|
|
496
|
+
};
|
|
497
|
+
|
|
275
498
|
return {
|
|
276
499
|
name: 'emulsify-develop-reporter',
|
|
277
500
|
|
|
@@ -284,9 +507,17 @@ export function developReporterPlugin({
|
|
|
284
507
|
|
|
285
508
|
configResolved(config) {
|
|
286
509
|
watching = Boolean(config.build?.watch);
|
|
510
|
+
// Everything below is watch-only setup. A one-shot build stays silent
|
|
511
|
+
// unless it has an asset problem or a collected Sass tally to report.
|
|
512
|
+
oneShot = !watching;
|
|
287
513
|
if (!watching) return;
|
|
288
514
|
|
|
289
515
|
outDir = config.build?.outDir || 'dist';
|
|
516
|
+
outputRows = buildOutputSummaryRows({
|
|
517
|
+
outDir,
|
|
518
|
+
mirrorComponentOutput: env.projectStructure?.mirrorComponentOutput,
|
|
519
|
+
componentOutput: env.projectStructure?.output?.components,
|
|
520
|
+
});
|
|
290
521
|
|
|
291
522
|
// Attribution is resolved here, from the entry map the config already
|
|
292
523
|
// carries, so the summary can name each source root and what it
|
|
@@ -331,6 +562,7 @@ export function developReporterPlugin({
|
|
|
331
562
|
transformedModules = new Set();
|
|
332
563
|
changedOutputs = [];
|
|
333
564
|
removedOutputs = [];
|
|
565
|
+
copiedOutputChanges.clear();
|
|
334
566
|
},
|
|
335
567
|
|
|
336
568
|
// Rolldown's own `N modules transformed.` count is unavailable here by
|
|
@@ -375,43 +607,116 @@ export function developReporterPlugin({
|
|
|
375
607
|
// only hook that receives it. Raising `logLevel` to quiet the develop loop
|
|
376
608
|
// discards Rolldown's per-file asset table, and this recovers the three
|
|
377
609
|
// facts from it worth keeping.
|
|
378
|
-
writeBundle
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
610
|
+
writeBundle: {
|
|
611
|
+
// Copy and mirror hooks publish outside Rollup's bundle. Waiting for all
|
|
612
|
+
// normal-order hooks makes their filesystem events and diagnostics a
|
|
613
|
+
// hard input to this cycle's verdict, even if a producer later becomes
|
|
614
|
+
// asynchronous.
|
|
615
|
+
order: 'post',
|
|
616
|
+
sequential: true,
|
|
617
|
+
handler(_options, bundle) {
|
|
618
|
+
if (watching) {
|
|
619
|
+
writeTally = summarizeBundle(bundle);
|
|
620
|
+
outputRows = buildOutputSummaryRows({
|
|
621
|
+
bundle,
|
|
622
|
+
outDir,
|
|
623
|
+
mirrorComponentOutput: env.projectStructure?.mirrorComponentOutput,
|
|
624
|
+
componentOutput: env.projectStructure?.output?.components,
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// Quiet mode does not hash output contents, but it still keeps the
|
|
628
|
+
// path set needed to identify removals. Carry stable skipped outputs
|
|
629
|
+
// forward just as the detailed fingerprint diff does below.
|
|
630
|
+
if (!verbose) {
|
|
631
|
+
const currentOutputNames = new Set(
|
|
632
|
+
Object.keys(bundle || {}).filter(isReportableOutput),
|
|
400
633
|
);
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
634
|
+
for (const fileName of unchangedOutputs) {
|
|
635
|
+
if (!isReportableOutput(fileName)) continue;
|
|
636
|
+
if (outputNames.has(fileName)) currentOutputNames.add(fileName);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (firstCycleComplete) {
|
|
640
|
+
removedOutputs = [...outputNames].filter(
|
|
641
|
+
(fileName) => !currentOutputNames.has(fileName),
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
outputNames = currentOutputNames;
|
|
404
645
|
}
|
|
405
646
|
|
|
406
|
-
|
|
647
|
+
if (verbose) {
|
|
648
|
+
// Rollup regenerates the whole bundle every cycle, so the first
|
|
649
|
+
// build lists everything and later cycles list only what came out
|
|
650
|
+
// different. Gzip is computed for the full listing once, then only
|
|
651
|
+
// for the files a rebuild changed — which is what keeps the watch
|
|
652
|
+
// loop from paying Rolldown's `computing gzip size...` pause on
|
|
653
|
+
// every keystroke.
|
|
654
|
+
const current = fingerprintBundle(bundle);
|
|
655
|
+
|
|
656
|
+
// A file the stable-output plugin dropped is still on disk with the
|
|
657
|
+
// same bytes, so carry its fingerprint forward. Without this the
|
|
658
|
+
// diff below sees it missing from the bundle and calls it removed.
|
|
659
|
+
for (const fileName of unchangedOutputs) {
|
|
660
|
+
if (!isReportableOutput(fileName)) continue;
|
|
661
|
+
const previous = fingerprints.get(fileName);
|
|
662
|
+
if (previous !== undefined) current.set(fileName, previous);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (firstCycleComplete) {
|
|
666
|
+
const diff = diffFingerprints(fingerprints, current);
|
|
667
|
+
const changed = new Set(diff.changed);
|
|
668
|
+
|
|
669
|
+
changedOutputs = buildOutputFileRows(
|
|
670
|
+
Object.fromEntries(
|
|
671
|
+
Object.entries(bundle).filter(([fileName]) =>
|
|
672
|
+
changed.has(fileName),
|
|
673
|
+
),
|
|
674
|
+
),
|
|
675
|
+
);
|
|
676
|
+
removedOutputs = diff.removed;
|
|
677
|
+
} else {
|
|
678
|
+
outputFiles = buildOutputFileRows(bundle);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
fingerprints = current;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
({ changedOutputs, removedOutputs } = mergeCopiedOutputChanges(
|
|
685
|
+
changedOutputs,
|
|
686
|
+
removedOutputs,
|
|
687
|
+
copiedOutputChanges,
|
|
688
|
+
));
|
|
407
689
|
}
|
|
408
|
-
}
|
|
409
690
|
|
|
410
|
-
|
|
691
|
+
reportCycle();
|
|
692
|
+
},
|
|
411
693
|
},
|
|
412
694
|
|
|
413
695
|
closeBundle() {
|
|
414
696
|
reportCycle();
|
|
697
|
+
// Printed from here, not writeBundle, so the block lands after Rolldown's
|
|
698
|
+
// own asset table rather than in the middle of it.
|
|
699
|
+
reportOneShot();
|
|
700
|
+
|
|
701
|
+
const snapshot = diagnostics.snapshot();
|
|
702
|
+
const strictFailures = countStrictAssetFailures(snapshot, strictLevel);
|
|
703
|
+
if (!oneShot || strictLevel === STRICTNESS.off || !strictFailures) return;
|
|
704
|
+
|
|
705
|
+
// Not `process.exitCode`: `storybook build` ends with `process.exit(0)`
|
|
706
|
+
// in a commander postAction hook, which discards it. Rejecting the build
|
|
707
|
+
// is the only signal both runners honor. `closeBundle` also runs after
|
|
708
|
+
// mirrorComponentsToRoot's writeBundle, so a strict failure can never
|
|
709
|
+
// leave a half-mirrored tree behind.
|
|
710
|
+
const error = new Error(
|
|
711
|
+
strictAssetFailureMessage(
|
|
712
|
+
snapshot,
|
|
713
|
+
strictLevel,
|
|
714
|
+
env.projectDir,
|
|
715
|
+
oneShotAssetRows,
|
|
716
|
+
),
|
|
717
|
+
);
|
|
718
|
+
error.stack = error.message;
|
|
719
|
+
throw error;
|
|
415
720
|
},
|
|
416
721
|
};
|
|
417
722
|
}
|