@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
|
@@ -152,6 +152,7 @@ const FACT_LABELS = {
|
|
|
152
152
|
* platform?: string,
|
|
153
153
|
* inputRows?: Array<{name: string, path: string, count: number}>,
|
|
154
154
|
* outDir?: string,
|
|
155
|
+
* outputRows?: Array<{path: string, write?: {fileCount: number, totalBytes: number, largest?: {fileName: string, bytes: number}}}>,
|
|
155
156
|
* write?: {fileCount: number, totalBytes: number, largest?: {fileName: string, bytes: number}},
|
|
156
157
|
* styler: (format: string|string[], text: string) => string
|
|
157
158
|
* }} options - Facts inputs.
|
|
@@ -161,6 +162,7 @@ export function renderFacts({
|
|
|
161
162
|
platform,
|
|
162
163
|
inputRows = [],
|
|
163
164
|
outDir = 'dist',
|
|
165
|
+
outputRows = [],
|
|
164
166
|
write,
|
|
165
167
|
styler,
|
|
166
168
|
}) {
|
|
@@ -211,24 +213,59 @@ export function renderFacts({
|
|
|
211
213
|
});
|
|
212
214
|
}
|
|
213
215
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Render the numeric facts following one output path.
|
|
218
|
+
*
|
|
219
|
+
* @param {{fileCount: number, totalBytes: number, largest?: {fileName: string, bytes: number}}|undefined} summary - Bundle tally.
|
|
220
|
+
* @param {boolean} includeLargest - Whether to name the largest file.
|
|
221
|
+
* @returns {string} Styled facts with their leading column gap.
|
|
222
|
+
*/
|
|
223
|
+
const outputSuffix = (summary, includeLargest = true) => {
|
|
224
|
+
if (!summary) return '';
|
|
225
|
+
|
|
226
|
+
const facts = [
|
|
227
|
+
pluralize(summary.fileCount, 'file'),
|
|
228
|
+
formatBytes(summary.totalBytes),
|
|
229
|
+
];
|
|
218
230
|
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
`largest ${
|
|
231
|
+
if (includeLargest && summary.largest) {
|
|
232
|
+
facts.push(
|
|
233
|
+
`largest ${summary.largest.fileName} ${formatBytes(summary.largest.bytes)}`,
|
|
222
234
|
);
|
|
223
235
|
}
|
|
224
|
-
}
|
|
225
236
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
? styler('gray', ` ${outputFacts.join(SEPARATOR)}`)
|
|
229
|
-
: '';
|
|
237
|
+
return styler('gray', ` ${facts.join(SEPARATOR)}`);
|
|
238
|
+
};
|
|
230
239
|
|
|
231
|
-
|
|
240
|
+
const destinations =
|
|
241
|
+
outputRows.length > 0 ? outputRows : [{ path: outDir, write }];
|
|
242
|
+
const hasTotal = destinations.length > 1 && Boolean(write);
|
|
243
|
+
const pathWidth = Math.max(
|
|
244
|
+
...destinations.map((entry) => entry.path.length),
|
|
245
|
+
hasTotal ? 'total'.length : 0,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
destinations.forEach((entry, index) => {
|
|
249
|
+
const destinationWrite =
|
|
250
|
+
destinations.length === 1 && !entry.write ? write : entry.write;
|
|
251
|
+
const path = styler('cyan', entry.path.padEnd(pathWidth));
|
|
252
|
+
|
|
253
|
+
lines.push(
|
|
254
|
+
row(
|
|
255
|
+
index === 0 ? FACT_LABELS.output : '',
|
|
256
|
+
`${path}${outputSuffix(destinationWrite)}`,
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (hasTotal) {
|
|
262
|
+
lines.push(
|
|
263
|
+
row(
|
|
264
|
+
'',
|
|
265
|
+
`${styler('gray', 'total'.padEnd(pathWidth))}${outputSuffix(write, false)}`,
|
|
266
|
+
),
|
|
267
|
+
);
|
|
268
|
+
}
|
|
232
269
|
|
|
233
270
|
return lines;
|
|
234
271
|
}
|
|
@@ -405,7 +442,7 @@ function renderDetailRows(entries, projectDir, styler) {
|
|
|
405
442
|
const hidden = entries.length - MAX_DETAIL_ROWS;
|
|
406
443
|
if (hidden > 0) {
|
|
407
444
|
lines.push(
|
|
408
|
-
`${DETAIL_INDENT}${styler('gray', `+${pluralize(hidden, 'more')}`)}`,
|
|
445
|
+
`${DETAIL_INDENT}${styler('gray', `+${pluralize(hidden, 'more', 'more')}`)}`,
|
|
409
446
|
);
|
|
410
447
|
}
|
|
411
448
|
|
|
@@ -432,16 +469,27 @@ function renderDeprecations(snapshot, projectDir, styler, sourceGlob) {
|
|
|
432
469
|
const { deprecations, deprecationsByFile, deprecationTotal } = snapshot;
|
|
433
470
|
if (deprecations.length === 0) return [];
|
|
434
471
|
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
pluralize(deprecationsByFile.length
|
|
438
|
-
|
|
472
|
+
const total = pluralize(deprecationTotal, 'sass deprecation');
|
|
473
|
+
const headline = deprecationsByFile.length
|
|
474
|
+
? [total, pluralize(deprecationsByFile.length, 'file')].join(SEPARATOR)
|
|
475
|
+
: total;
|
|
439
476
|
|
|
440
477
|
const lines = [
|
|
441
478
|
`${INDENT}${styler('yellow', SYMBOLS.warning)} ${styler('yellow', headline)}`,
|
|
442
|
-
'',
|
|
443
479
|
];
|
|
444
480
|
|
|
481
|
+
// Sass can report a deprecation without a span. It still belongs in the
|
|
482
|
+
// total, but there is no file to open and therefore no worklist to render.
|
|
483
|
+
// Omitting the empty table also avoids presenting "0 files" as actionable
|
|
484
|
+
// detail when the collector simply had no location to group.
|
|
485
|
+
if (deprecationsByFile.length === 0) {
|
|
486
|
+
const command = renderMigratorCommand(deprecations, sourceGlob, styler);
|
|
487
|
+
if (command) lines.push('', command);
|
|
488
|
+
return lines;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
lines.push('');
|
|
492
|
+
|
|
445
493
|
const shownFiles = deprecationsByFile.slice(0, MAX_DEPRECATION_FILES);
|
|
446
494
|
|
|
447
495
|
// Column widths are measured across every row that will be printed so the
|
|
@@ -512,6 +560,27 @@ function renderDeprecations(snapshot, projectDir, styler, sourceGlob) {
|
|
|
512
560
|
return lines;
|
|
513
561
|
}
|
|
514
562
|
|
|
563
|
+
/**
|
|
564
|
+
* Render the Sass deprecation worklist for a one-shot build.
|
|
565
|
+
*
|
|
566
|
+
* The watch summary supplies its own section spacing. A standalone build has
|
|
567
|
+
* no surrounding report, so this wrapper adds the leading blank line that
|
|
568
|
+
* keeps the tally separate from Vite's output.
|
|
569
|
+
*
|
|
570
|
+
* @param {{snapshot: object, projectDir?: string, sourceGlob?: string, styler: Function}} options - Render inputs.
|
|
571
|
+
* @returns {string[]} Report lines.
|
|
572
|
+
*/
|
|
573
|
+
export function renderDeprecationSummary({
|
|
574
|
+
snapshot,
|
|
575
|
+
projectDir = '',
|
|
576
|
+
sourceGlob = 'src/**/*.scss',
|
|
577
|
+
styler,
|
|
578
|
+
}) {
|
|
579
|
+
const lines = renderDeprecations(snapshot, projectDir, styler, sourceGlob);
|
|
580
|
+
|
|
581
|
+
return lines.length > 0 ? ['', ...lines] : [];
|
|
582
|
+
}
|
|
583
|
+
|
|
515
584
|
/**
|
|
516
585
|
* Render the `sass-migrator` invocation that resolves most of the debt.
|
|
517
586
|
*
|
|
@@ -664,7 +733,7 @@ function renderSyntaxErrors(errors, styler) {
|
|
|
664
733
|
const hidden = matches.length - MAX_SOURCE_LEADS;
|
|
665
734
|
if (hidden > 0) {
|
|
666
735
|
lines.push(
|
|
667
|
-
`${DETAIL_INDENT}${styler('gray', `+${pluralize(hidden, 'more')}`)}`,
|
|
736
|
+
`${DETAIL_INDENT}${styler('gray', `+${pluralize(hidden, 'more', 'more')}`)}`,
|
|
668
737
|
);
|
|
669
738
|
}
|
|
670
739
|
}
|
|
@@ -734,7 +803,7 @@ function renderImportErrors(rows, sharedDirectory, directoryExists, styler) {
|
|
|
734
803
|
const hidden = rows.length - MAX_ASSET_ROWS;
|
|
735
804
|
if (hidden > 0) {
|
|
736
805
|
lines.push(
|
|
737
|
-
`${DETAIL_INDENT}${styler('gray', `+${pluralize(hidden, 'more')}`)}`,
|
|
806
|
+
`${DETAIL_INDENT}${styler('gray', `+${pluralize(hidden, 'more', 'more')}`)}`,
|
|
738
807
|
);
|
|
739
808
|
}
|
|
740
809
|
|
|
@@ -800,7 +869,7 @@ function renderUnresolvedAssets(rows, styler) {
|
|
|
800
869
|
const hidden = rows.length - MAX_ASSET_ROWS;
|
|
801
870
|
if (hidden > 0) {
|
|
802
871
|
lines.push(
|
|
803
|
-
`${DETAIL_INDENT}${styler('gray', `+${pluralize(hidden, 'more')}`)}`,
|
|
872
|
+
`${DETAIL_INDENT}${styler('gray', `+${pluralize(hidden, 'more', 'more')}`)}`,
|
|
804
873
|
);
|
|
805
874
|
}
|
|
806
875
|
|
|
@@ -815,6 +884,61 @@ function renderUnresolvedAssets(rows, styler) {
|
|
|
815
884
|
return lines;
|
|
816
885
|
}
|
|
817
886
|
|
|
887
|
+
/**
|
|
888
|
+
* Render the tally of modules Vite externalized for browser compatibility.
|
|
889
|
+
*
|
|
890
|
+
* A dependency reaching for a Node builtin is a property of that dependency,
|
|
891
|
+
* not of the edit just made, so this belongs with the inherited debt rather
|
|
892
|
+
* than in "needs attention". Vite repeats the notice once per importing file on
|
|
893
|
+
* every cycle; one line per module, with its occurrence count, says the same
|
|
894
|
+
* thing without the repetition.
|
|
895
|
+
*
|
|
896
|
+
* @param {Array<{module: string, importer?: string, count: number}>} modules - Externalized modules.
|
|
897
|
+
* @param {(format: string|string[], text: string) => string} styler - Styling function.
|
|
898
|
+
* @returns {string[]} Report lines.
|
|
899
|
+
*/
|
|
900
|
+
function renderExternalizedModules(modules = [], styler) {
|
|
901
|
+
if (modules.length === 0) return [];
|
|
902
|
+
|
|
903
|
+
const lines = [
|
|
904
|
+
`${INDENT}${styler('gray', `${pluralize(modules.length, 'module')} externalized for the browser`)}`,
|
|
905
|
+
'',
|
|
906
|
+
];
|
|
907
|
+
|
|
908
|
+
for (const entry of modules.slice(0, MAX_ASSET_ROWS)) {
|
|
909
|
+
const times = entry.count > 1 ? ` (${entry.count}\u00d7)` : '';
|
|
910
|
+
lines.push(`${DETAIL_INDENT}${styler('gray', `${entry.module}${times}`)}`);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const hidden = modules.length - MAX_ASSET_ROWS;
|
|
914
|
+
if (hidden > 0) {
|
|
915
|
+
lines.push(
|
|
916
|
+
`${DETAIL_INDENT}${styler('gray', `+${pluralize(hidden, 'more', 'more')}`)}`,
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return lines;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Determine whether a build cycle failed.
|
|
925
|
+
*
|
|
926
|
+
* Import and syntax errors live in their own buckets rather than in
|
|
927
|
+
* `snapshot.errors`, so a check against errors alone reads a stylesheet that
|
|
928
|
+
* never compiled as a success. That is precisely how a mistyped `@use` used to
|
|
929
|
+
* report "rebuilt in 1.69s" while dist/ kept the previous CSS.
|
|
930
|
+
*
|
|
931
|
+
* @param {{errors?: object[], importErrors?: object[], syntaxErrors?: object[]}} snapshot - Diagnostics snapshot.
|
|
932
|
+
* @returns {boolean} TRUE when the cycle produced no usable output.
|
|
933
|
+
*/
|
|
934
|
+
export function hasCycleFailure(snapshot = {}) {
|
|
935
|
+
return (
|
|
936
|
+
(snapshot.errors?.length || 0) > 0 ||
|
|
937
|
+
(snapshot.importErrors?.length || 0) > 0 ||
|
|
938
|
+
(snapshot.syntaxErrors?.length || 0) > 0
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
818
942
|
/**
|
|
819
943
|
* Render the problem blocks shared by first builds and rebuilds.
|
|
820
944
|
*
|
|
@@ -834,6 +958,7 @@ function renderProblems(
|
|
|
834
958
|
importErrors,
|
|
835
959
|
syntaxErrors,
|
|
836
960
|
unicode = true,
|
|
961
|
+
includeDebt = true,
|
|
837
962
|
) {
|
|
838
963
|
const attention = [];
|
|
839
964
|
|
|
@@ -876,7 +1001,22 @@ function renderProblems(
|
|
|
876
1001
|
attention.push(...assetLines);
|
|
877
1002
|
}
|
|
878
1003
|
|
|
879
|
-
|
|
1004
|
+
// Rebuilds pass includeDebt: false. Restating 190 inherited deprecations on
|
|
1005
|
+
// every keystroke is the noise this reporter exists to remove.
|
|
1006
|
+
const debt = includeDebt
|
|
1007
|
+
? renderDeprecations(snapshot, projectDir, styler, sourceGlob)
|
|
1008
|
+
: [];
|
|
1009
|
+
|
|
1010
|
+
if (includeDebt) {
|
|
1011
|
+
const externalized = renderExternalizedModules(
|
|
1012
|
+
snapshot.externalizedModules,
|
|
1013
|
+
styler,
|
|
1014
|
+
);
|
|
1015
|
+
if (externalized.length > 0) {
|
|
1016
|
+
if (debt.length > 0) debt.push('');
|
|
1017
|
+
debt.push(...externalized);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
880
1020
|
|
|
881
1021
|
const lines = [];
|
|
882
1022
|
|
|
@@ -1105,6 +1245,69 @@ function renderSizeTable(rows, styler) {
|
|
|
1105
1245
|
return lines;
|
|
1106
1246
|
}
|
|
1107
1247
|
|
|
1248
|
+
/**
|
|
1249
|
+
* Render the standalone CSS asset block a one-shot build prints.
|
|
1250
|
+
*
|
|
1251
|
+
* One-shot builds are silent unless something is wrong, so this omits the
|
|
1252
|
+
* banner and project facts. The caller may compose it with a collected Sass
|
|
1253
|
+
* deprecation tally; a clean project keeps its output byte for byte.
|
|
1254
|
+
*
|
|
1255
|
+
* @param {{assetRows?: Array<object>, rebases?: Array<object>, styler: Function}} options - Render inputs.
|
|
1256
|
+
* @returns {string[]} Report lines.
|
|
1257
|
+
*/
|
|
1258
|
+
export function renderAssetSummary({ assetRows = [], rebases = [], styler }) {
|
|
1259
|
+
const repaired = rebases.filter((entry) => entry.status === 'rebased');
|
|
1260
|
+
const ambiguous = rebases.filter((entry) => entry.status === 'ambiguous');
|
|
1261
|
+
|
|
1262
|
+
if (!assetRows.length && !repaired.length && !ambiguous.length) return [];
|
|
1263
|
+
|
|
1264
|
+
const lines = [''];
|
|
1265
|
+
|
|
1266
|
+
if (repaired.length) {
|
|
1267
|
+
lines.push(
|
|
1268
|
+
`${INDENT}${styler(
|
|
1269
|
+
'gray',
|
|
1270
|
+
`${pluralize(repaired.length, 'css asset url')} rebased to /assets/`,
|
|
1271
|
+
)}`,
|
|
1272
|
+
'',
|
|
1273
|
+
);
|
|
1274
|
+
|
|
1275
|
+
for (const entry of repaired.slice(0, MAX_ASSET_ROWS)) {
|
|
1276
|
+
lines.push(
|
|
1277
|
+
`${DETAIL_INDENT}${styler('gray', `${entry.url} -> ${entry.rewritten}`)}`,
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
const hidden = repaired.length - MAX_ASSET_ROWS;
|
|
1282
|
+
if (hidden > 0) {
|
|
1283
|
+
lines.push(
|
|
1284
|
+
`${DETAIL_INDENT}${styler('gray', `+${pluralize(hidden, 'more', 'more')}`)}`,
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
lines.push(
|
|
1289
|
+
'',
|
|
1290
|
+
`${DETAIL_INDENT}${styler(
|
|
1291
|
+
'gray',
|
|
1292
|
+
'run `emulsify-audit --fix` to write these canonically in source',
|
|
1293
|
+
)}`,
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
for (const entry of ambiguous) {
|
|
1298
|
+
lines.push(
|
|
1299
|
+
`${INDENT}${styler('yellow', SYMBOLS.warning)} ${styler(
|
|
1300
|
+
'yellow',
|
|
1301
|
+
`${entry.url} matches more than one asset root`,
|
|
1302
|
+
)}`,
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
lines.push(...renderUnresolvedAssets(assetRows, styler));
|
|
1307
|
+
|
|
1308
|
+
return lines;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1108
1311
|
/**
|
|
1109
1312
|
* Render the summary printed after the first successful watch build.
|
|
1110
1313
|
*
|
|
@@ -1116,6 +1319,7 @@ function renderSizeTable(rows, styler) {
|
|
|
1116
1319
|
* snapshot: object,
|
|
1117
1320
|
* durationMs: number,
|
|
1118
1321
|
* outDir?: string,
|
|
1322
|
+
* outputRows?: Array<{path: string, write?: {fileCount: number, totalBytes: number, largest?: {fileName: string, bytes: number}}}>,
|
|
1119
1323
|
* projectDir?: string,
|
|
1120
1324
|
* sourceGlob?: string,
|
|
1121
1325
|
* assetRows?: Array<object>,
|
|
@@ -1135,6 +1339,7 @@ export function renderSummary({
|
|
|
1135
1339
|
snapshot,
|
|
1136
1340
|
durationMs,
|
|
1137
1341
|
outDir = 'dist',
|
|
1342
|
+
outputRows = [],
|
|
1138
1343
|
projectDir = '',
|
|
1139
1344
|
sourceGlob = 'src/**/*.scss',
|
|
1140
1345
|
assetRows = [],
|
|
@@ -1149,10 +1354,7 @@ export function renderSummary({
|
|
|
1149
1354
|
unicode = true,
|
|
1150
1355
|
styler,
|
|
1151
1356
|
}) {
|
|
1152
|
-
const failed =
|
|
1153
|
-
snapshot.errors.length > 0 ||
|
|
1154
|
-
(importErrors.rows || []).length > 0 ||
|
|
1155
|
-
syntaxErrors.length > 0;
|
|
1357
|
+
const failed = hasCycleFailure(snapshot);
|
|
1156
1358
|
const symbol = failed
|
|
1157
1359
|
? styler('red', SYMBOLS.error)
|
|
1158
1360
|
: styler('green', SYMBOLS.ok);
|
|
@@ -1181,7 +1383,7 @@ export function renderSummary({
|
|
|
1181
1383
|
'',
|
|
1182
1384
|
renderDivider('project', unicode, styler),
|
|
1183
1385
|
'',
|
|
1184
|
-
...renderFacts({ platform, inputRows, outDir, write, styler }),
|
|
1386
|
+
...renderFacts({ platform, inputRows, outDir, outputRows, write, styler }),
|
|
1185
1387
|
// The verbose listings expand the two rows above them, so they sit directly
|
|
1186
1388
|
// under the totals they itemize rather than after the build result.
|
|
1187
1389
|
...renderInputFiles(inputFiles, unicode, styler),
|
|
@@ -1220,7 +1422,7 @@ export function renderSummary({
|
|
|
1220
1422
|
* changedFiles?: string[],
|
|
1221
1423
|
* projectDir?: string,
|
|
1222
1424
|
* moduleCount?: number,
|
|
1223
|
-
* changedOutputs?: Array<{fileName: string, bytes
|
|
1425
|
+
* changedOutputs?: Array<{fileName: string, bytes?: number, gzipBytes?: number}>,
|
|
1224
1426
|
* removedOutputs?: string[],
|
|
1225
1427
|
* detailed?: boolean,
|
|
1226
1428
|
* styler: (format: string|string[], text: string) => string,
|
|
@@ -1233,14 +1435,21 @@ export function renderRebuild({
|
|
|
1233
1435
|
durationMs,
|
|
1234
1436
|
changedFiles = [],
|
|
1235
1437
|
projectDir = '',
|
|
1438
|
+
outDir = 'dist',
|
|
1439
|
+
sourceGlob,
|
|
1440
|
+
assetRows = [],
|
|
1441
|
+
importErrors = {},
|
|
1442
|
+
syntaxErrors = [],
|
|
1443
|
+
recovered = false,
|
|
1236
1444
|
moduleCount,
|
|
1237
1445
|
changedOutputs = [],
|
|
1238
1446
|
removedOutputs = [],
|
|
1239
1447
|
detailed = false,
|
|
1448
|
+
unicode = true,
|
|
1240
1449
|
styler,
|
|
1241
1450
|
now = new Date(),
|
|
1242
1451
|
}) {
|
|
1243
|
-
const failed = snapshot
|
|
1452
|
+
const failed = hasCycleFailure(snapshot);
|
|
1244
1453
|
const [firstChange] = changedFiles;
|
|
1245
1454
|
const changeLabel =
|
|
1246
1455
|
changedFiles.length > 1
|
|
@@ -1251,27 +1460,71 @@ export function renderRebuild({
|
|
|
1251
1460
|
|
|
1252
1461
|
const outcome = failed
|
|
1253
1462
|
? styler('red', `rebuild failed after ${formatDuration(durationMs)}`)
|
|
1254
|
-
:
|
|
1255
|
-
|
|
1463
|
+
: recovered
|
|
1464
|
+
? styler(
|
|
1465
|
+
'green',
|
|
1466
|
+
`recovered${SEPARATOR}rebuilt in ${formatDuration(durationMs)}`,
|
|
1467
|
+
)
|
|
1468
|
+
: styler('gray', `rebuilt in ${formatDuration(durationMs)}`);
|
|
1469
|
+
|
|
1470
|
+
// A recovered rebuild gets the success tick rather than the change caret, so
|
|
1471
|
+
// the save that fixed a broken build is distinguishable from an ordinary one.
|
|
1256
1472
|
const symbol = failed
|
|
1257
1473
|
? styler('red', SYMBOLS.error)
|
|
1258
|
-
:
|
|
1474
|
+
: recovered
|
|
1475
|
+
? styler('green', SYMBOLS.ok)
|
|
1476
|
+
: styler('gray', SYMBOLS.change);
|
|
1259
1477
|
|
|
1260
1478
|
const lines = [
|
|
1261
1479
|
`${INDENT}${styler('gray', formatClockTime(now))} ${symbol} ${changeLabel}${styler('gray', SEPARATOR)}${outcome}`,
|
|
1262
1480
|
];
|
|
1263
1481
|
|
|
1264
|
-
// Repeating the deprecation tally on every keystroke would recreate the noise
|
|
1265
|
-
// this reporter exists to remove, so rebuilds only surface hard failures.
|
|
1266
1482
|
if (failed) {
|
|
1267
|
-
|
|
1483
|
+
// Compile failures occur before output is written, but copy and mirror
|
|
1484
|
+
// failures happen after Rollup has already published some files. Preserve
|
|
1485
|
+
// that distinction so a partially updated theme is never described as the
|
|
1486
|
+
// last wholly successful build.
|
|
1487
|
+
const outputIncomplete = snapshot.errors?.some(
|
|
1488
|
+
(error) => error.outputState === 'incomplete',
|
|
1489
|
+
);
|
|
1490
|
+
const outputStatus = outputIncomplete
|
|
1491
|
+
? `output may be incomplete${SEPARATOR}some output may already have changed`
|
|
1492
|
+
: `output not updated${SEPARATOR}${outDir} still holds the last successful build`;
|
|
1493
|
+
lines.push(`${DETAIL_INDENT}${styler('yellow', outputStatus)}`);
|
|
1494
|
+
|
|
1495
|
+
// The same blocks the first build renders, minus the inherited deprecation
|
|
1496
|
+
// debt, so a rebuild failure names its cause instead of only its verdict.
|
|
1497
|
+
lines.push(
|
|
1498
|
+
...renderProblems(
|
|
1499
|
+
snapshot,
|
|
1500
|
+
projectDir,
|
|
1501
|
+
styler,
|
|
1502
|
+
sourceGlob,
|
|
1503
|
+
assetRows,
|
|
1504
|
+
importErrors,
|
|
1505
|
+
syntaxErrors,
|
|
1506
|
+
unicode,
|
|
1507
|
+
false,
|
|
1508
|
+
),
|
|
1509
|
+
);
|
|
1510
|
+
|
|
1511
|
+
if (removedOutputs.length > 0) {
|
|
1512
|
+
lines.push(...renderRebuildDetail({ removedOutputs }, styler));
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1268
1515
|
return lines;
|
|
1269
1516
|
}
|
|
1270
1517
|
|
|
1271
|
-
|
|
1518
|
+
// Writes follow the existing detailed-mode contract. Removals are always
|
|
1519
|
+
// named: a destructive cycle must never collapse to a green one-line result.
|
|
1520
|
+
if (detailed || removedOutputs.length > 0)
|
|
1272
1521
|
lines.push(
|
|
1273
1522
|
...renderRebuildDetail(
|
|
1274
|
-
{
|
|
1523
|
+
{
|
|
1524
|
+
moduleCount: detailed ? moduleCount : undefined,
|
|
1525
|
+
changedOutputs: detailed ? changedOutputs : [],
|
|
1526
|
+
removedOutputs,
|
|
1527
|
+
},
|
|
1275
1528
|
styler,
|
|
1276
1529
|
),
|
|
1277
1530
|
);
|
|
@@ -1284,7 +1537,7 @@ export function renderRebuild({
|
|
|
1284
1537
|
*
|
|
1285
1538
|
* @param {{
|
|
1286
1539
|
* moduleCount?: number,
|
|
1287
|
-
* changedOutputs?: Array<{fileName: string, bytes
|
|
1540
|
+
* changedOutputs?: Array<{fileName: string, bytes?: number, gzipBytes?: number}>,
|
|
1288
1541
|
* removedOutputs?: string[]
|
|
1289
1542
|
* }} cycle - What the rebuild produced.
|
|
1290
1543
|
* @param {(format: string|string[], text: string) => string} styler - Styling function.
|
|
@@ -1299,11 +1552,11 @@ function renderRebuildDetail(
|
|
|
1299
1552
|
facts.push(`${pluralize(moduleCount, 'module')} transformed`);
|
|
1300
1553
|
}
|
|
1301
1554
|
|
|
1302
|
-
|
|
1303
|
-
changedOutputs.length
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1555
|
+
if (changedOutputs.length > 0) {
|
|
1556
|
+
facts.push(`${pluralize(changedOutputs.length, 'output')} changed`);
|
|
1557
|
+
} else if (removedOutputs.length === 0) {
|
|
1558
|
+
facts.push('no output changed');
|
|
1559
|
+
}
|
|
1307
1560
|
|
|
1308
1561
|
if (removedOutputs.length > 0) {
|
|
1309
1562
|
facts.push(`${pluralize(removedOutputs.length, 'output')} removed`);
|
|
@@ -235,3 +235,33 @@ export function createSassOptions(collector, options = {}) {
|
|
|
235
235
|
verbose: true,
|
|
236
236
|
};
|
|
237
237
|
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Decide whether Emulsify should replace Dart Sass's own console output.
|
|
241
|
+
*
|
|
242
|
+
* Three callers resolve the shared Vite config and each needs a different
|
|
243
|
+
* answer, so the rule lives here rather than as a condition inside the config:
|
|
244
|
+
*
|
|
245
|
+
* - **The develop watcher** (`vite build --watch`) always takes the logger. It
|
|
246
|
+
* prints the deduplicated tally, once per session rather than once per
|
|
247
|
+
* rebuild, which is the whole point. Raw mode does not turn this off: the
|
|
248
|
+
* reporter is still what prints the summary, and the firehose would bury it.
|
|
249
|
+
* - **Storybook** resolves with `command: 'serve'` for both `storybook dev`
|
|
250
|
+
* and `storybook build`. During development it compiles the same source tree
|
|
251
|
+
* the watcher already tallied; a standalone static build prints the
|
|
252
|
+
* collector's deduplicated tally at completion. Without this it printed Dart
|
|
253
|
+
* Sass's full formatted block per occurrence, at startup and after saves.
|
|
254
|
+
* - **A one-shot `vite build`** keeps Dart Sass's output. Nothing else is
|
|
255
|
+
* running to report the debt, so that output is the only report there is.
|
|
256
|
+
*
|
|
257
|
+
* Raw verbose mode hands Storybook's firehose back, which is what asking for
|
|
258
|
+
* raw output means.
|
|
259
|
+
*
|
|
260
|
+
* @param {{watching?: boolean, command?: string, verbose?: boolean}} options - Invocation.
|
|
261
|
+
* @returns {boolean} TRUE when the quiet logger should be installed.
|
|
262
|
+
*/
|
|
263
|
+
export function shouldQuietSass({ watching, command, verbose } = {}) {
|
|
264
|
+
if (watching) return true;
|
|
265
|
+
|
|
266
|
+
return command === 'serve' && !verbose;
|
|
267
|
+
}
|