@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
|
@@ -12,9 +12,12 @@ import { resolveProjectStructure } from '../project-structure.js';
|
|
|
12
12
|
import { toPosixPath } from '../utils/paths.js';
|
|
13
13
|
import { copyAllSrcAssetsPlugin } from './assets/copy-src-assets.js';
|
|
14
14
|
import { copyTwigFilesPlugin } from './assets/copy-twig-files.js';
|
|
15
|
+
import { cssAssetRebasePlugin } from './assets/css-asset-rebase.js';
|
|
15
16
|
import { cssAssetUrlRelativizer } from './assets/css-asset-relativizer.js';
|
|
17
|
+
import { developmentCssSourceMapPlugins } from './assets/development-source-maps.js';
|
|
16
18
|
import { mirrorComponentsToRoot } from './assets/mirror-components.js';
|
|
17
19
|
import { createSourceFileIndex } from './assets/source-file-index.js';
|
|
20
|
+
import { stableWatchOutputPlugin } from './assets/stable-watch-output.js';
|
|
18
21
|
import { svgSpriteFilePlugin } from './assets/svg-sprite.js';
|
|
19
22
|
import { developReporterPlugin } from './reporter/index.js';
|
|
20
23
|
import { requireContextCompatPlugin } from './require-context.js';
|
|
@@ -37,9 +40,10 @@ import { yamlModulePlugin } from './yaml-module.js';
|
|
|
37
40
|
* srcDir: string,
|
|
38
41
|
* srcExists: boolean,
|
|
39
42
|
* structureOverrides?: boolean,
|
|
43
|
+
* developmentBuild?: boolean,
|
|
40
44
|
* diagnostics?: object
|
|
41
|
-
* }} env - Project environment. When `diagnostics` is present the
|
|
42
|
-
*
|
|
45
|
+
* }} env - Project environment. When `diagnostics` is present the reporter is
|
|
46
|
+
* appended for watch summaries and actionable one-shot diagnostics.
|
|
43
47
|
* @returns {import('vite').PluginOption[]} Emulsify Vite plugins.
|
|
44
48
|
*/
|
|
45
49
|
export function makePlugins(env) {
|
|
@@ -56,6 +60,35 @@ export function makePlugins(env) {
|
|
|
56
60
|
const sourceFileIndex =
|
|
57
61
|
env.sourceFileIndex || createSourceFileIndex(structure);
|
|
58
62
|
|
|
63
|
+
// In lean-output mode, filled by the rebase plugin and read by the
|
|
64
|
+
// relativizer: published asset path -> where that file actually lives,
|
|
65
|
+
// relative to the project root. It stays empty for self-contained output.
|
|
66
|
+
/** @type {Map<string, string>} */
|
|
67
|
+
const publishedAssetSources = new Map();
|
|
68
|
+
// The subset above that came from Vite copies. An actual CSS rewrite plus
|
|
69
|
+
// membership here authorizes the relativizer to remove a redundant copy.
|
|
70
|
+
/** @type {Set<string>} */
|
|
71
|
+
const removablePublishedAssets = new Set();
|
|
72
|
+
|
|
73
|
+
// Filled by the stable-output plugin, read by the reporter: emitted files it
|
|
74
|
+
// dropped this cycle because the bytes on disk already match. The reporter
|
|
75
|
+
// diffs one cycle's bundle against the last, so without this a skipped file
|
|
76
|
+
// would be listed as a deleted one.
|
|
77
|
+
/** @type {Set<string>} */
|
|
78
|
+
const unchangedOutputs = new Set();
|
|
79
|
+
|
|
80
|
+
// Filled by the source copy plugins, read and reset by the reporter: copied
|
|
81
|
+
// files never enter Rollup's bundle, so its fingerprint diff cannot see
|
|
82
|
+
// them. A Map deduplicates paths when more than one producer touches the
|
|
83
|
+
// same destination and leaves room for safe pruning to report removals if it
|
|
84
|
+
// is reintroduced later.
|
|
85
|
+
/** @type {Map<string, {kind: 'written'|'removed', bytes?: number}>|undefined} */
|
|
86
|
+
const copiedOutputChanges = env.diagnostics ? new Map() : undefined;
|
|
87
|
+
const developmentCssMaps = developmentCssSourceMapPlugins({
|
|
88
|
+
projectDir,
|
|
89
|
+
developmentBuild: env.developmentBuild,
|
|
90
|
+
});
|
|
91
|
+
|
|
59
92
|
const basePlugins = [
|
|
60
93
|
virtualTwigExtensionInstallersPlugin(envWithStructure),
|
|
61
94
|
virtualTwigGlobsPlugin(envWithStructure),
|
|
@@ -86,29 +119,82 @@ export function makePlugins(env) {
|
|
|
86
119
|
// Legacy Storybook stories may still enumerate assets with require.context.
|
|
87
120
|
requireContextCompatPlugin(),
|
|
88
121
|
|
|
89
|
-
//
|
|
90
|
-
|
|
122
|
+
// Capture Vite's combined Sass/PostCSS map before Core changes asset URLs.
|
|
123
|
+
// Vite's extracted-CSS build path discards this map unless Core retains it.
|
|
124
|
+
developmentCssMaps.capture,
|
|
125
|
+
|
|
126
|
+
// Repair CSS asset URLs Vite could not resolve. Ordering against the
|
|
127
|
+
// relativizer below is load-bearing: this normalizes URLs to `/assets/...`
|
|
128
|
+
// and either emits an output asset or records its source-tree location;
|
|
129
|
+
// only then can the relativizer select and calculate the final target.
|
|
130
|
+
cssAssetRebasePlugin({
|
|
131
|
+
env: envWithStructure,
|
|
132
|
+
diagnostics: env.diagnostics,
|
|
133
|
+
publishedAssetSources,
|
|
134
|
+
removablePublishedAssets,
|
|
135
|
+
}),
|
|
136
|
+
|
|
137
|
+
// Point CSS asset URLs at the file each one names, relative to the
|
|
138
|
+
// stylesheet's own location on disk.
|
|
139
|
+
cssAssetUrlRelativizer({
|
|
140
|
+
assetsRoot: 'assets',
|
|
141
|
+
env: envWithStructure,
|
|
142
|
+
publishedAssetSources,
|
|
143
|
+
removablePublishedAssets,
|
|
144
|
+
}),
|
|
145
|
+
|
|
146
|
+
// Pair each direct stylesheet entry with its finalized watch-build asset.
|
|
147
|
+
// This must run after URL rewriting and before stable-output comparison.
|
|
148
|
+
developmentCssMaps.emit,
|
|
149
|
+
|
|
150
|
+
// Last of the CSS chain: once the text is final, an unchanged stylesheet is
|
|
151
|
+
// dropped rather than rewritten, so a watch rebuild does not send HMR
|
|
152
|
+
// updates for stylesheets the edit never touched.
|
|
153
|
+
stableWatchOutputPlugin({
|
|
154
|
+
projectDir,
|
|
155
|
+
mirrorComponentOutput: structure.mirrorComponentOutput,
|
|
156
|
+
unchangedOutputs,
|
|
157
|
+
}),
|
|
91
158
|
];
|
|
92
159
|
|
|
93
160
|
return [
|
|
94
161
|
...basePlugins,
|
|
95
162
|
|
|
96
163
|
// Copy Twig templates and component metadata beside compiled assets.
|
|
97
|
-
copyTwigFilesPlugin({
|
|
164
|
+
copyTwigFilesPlugin({
|
|
165
|
+
structure,
|
|
166
|
+
sourceFileIndex,
|
|
167
|
+
diagnostics: env.diagnostics,
|
|
168
|
+
outputChanges: copiedOutputChanges,
|
|
169
|
+
}),
|
|
98
170
|
|
|
99
171
|
// Copy every non-code asset under src with the same routing.
|
|
100
|
-
copyAllSrcAssetsPlugin({
|
|
172
|
+
copyAllSrcAssetsPlugin({
|
|
173
|
+
structure,
|
|
174
|
+
sourceFileIndex,
|
|
175
|
+
diagnostics: env.diagnostics,
|
|
176
|
+
outputChanges: copiedOutputChanges,
|
|
177
|
+
}),
|
|
101
178
|
|
|
102
179
|
// Drupal projects with src mirror dist/components back to ./components.
|
|
103
180
|
mirrorComponentsToRoot({
|
|
104
181
|
enabled: structure.mirrorComponentOutput,
|
|
105
182
|
projectDir,
|
|
183
|
+
developmentBuild: env.developmentBuild,
|
|
184
|
+
diagnostics: env.diagnostics,
|
|
106
185
|
}),
|
|
107
186
|
|
|
108
|
-
// Summarize
|
|
109
|
-
//
|
|
187
|
+
// Summarize `npm run develop`, and report actionable diagnostics collected
|
|
188
|
+
// during one-shot Vite or Storybook builds.
|
|
110
189
|
...(env.diagnostics
|
|
111
|
-
? [
|
|
190
|
+
? [
|
|
191
|
+
developReporterPlugin({
|
|
192
|
+
env,
|
|
193
|
+
diagnostics: env.diagnostics,
|
|
194
|
+
unchangedOutputs,
|
|
195
|
+
copiedOutputChanges,
|
|
196
|
+
}),
|
|
197
|
+
]
|
|
112
198
|
: []),
|
|
113
199
|
];
|
|
114
200
|
}
|
|
@@ -97,7 +97,7 @@ const lineAt = (source, index) => source.slice(0, index).split('\n').length;
|
|
|
97
97
|
* @param {{projectDir?: string}} env - Project environment.
|
|
98
98
|
* @returns {{
|
|
99
99
|
* locate: (url: string) => {status: string, label: string},
|
|
100
|
-
* references: (url: string) => Array<{file: string, line: number}>
|
|
100
|
+
* references: (url: string) => Array<{file: string, sourceFile: string, line: number}>
|
|
101
101
|
* }} Resolver.
|
|
102
102
|
*/
|
|
103
103
|
export function createAssetResolver({ projectDir = '' } = {}) {
|
|
@@ -246,7 +246,7 @@ export function createAssetResolver({ projectDir = '' } = {}) {
|
|
|
246
246
|
* Find the stylesheets that write this URL, with line numbers.
|
|
247
247
|
*
|
|
248
248
|
* @param {string} url - Unresolved asset URL.
|
|
249
|
-
* @returns {Array<{file: string, line: number}>} References, in file order.
|
|
249
|
+
* @returns {Array<{file: string, sourceFile: string, line: number}>} References, in file order.
|
|
250
250
|
*/
|
|
251
251
|
references(url) {
|
|
252
252
|
const files = allFiles();
|
|
@@ -257,7 +257,7 @@ export function createAssetResolver({ projectDir = '' } = {}) {
|
|
|
257
257
|
*
|
|
258
258
|
* @param {string[]} stylesheets - Files to search.
|
|
259
259
|
* @param {(source: string) => Array<number>} findOffsets - Offset finder.
|
|
260
|
-
* @returns {Array<{file: string, line: number}>} Matches.
|
|
260
|
+
* @returns {Array<{file: string, sourceFile: string, line: number}>} Matches.
|
|
261
261
|
*/
|
|
262
262
|
const scan = (stylesheets, findOffsets) => {
|
|
263
263
|
const found = [];
|
|
@@ -265,17 +265,19 @@ export function createAssetResolver({ projectDir = '' } = {}) {
|
|
|
265
265
|
for (const absPath of stylesheets) {
|
|
266
266
|
const source = read(absPath);
|
|
267
267
|
if (!source) continue;
|
|
268
|
+
const sourceFile = toProjectPath(absPath, projectDir);
|
|
268
269
|
|
|
269
270
|
for (const offset of findOffsets(source)) {
|
|
270
271
|
found.push({
|
|
271
|
-
file: tailSegments(
|
|
272
|
+
file: tailSegments(sourceFile),
|
|
273
|
+
sourceFile,
|
|
272
274
|
line: lineAt(source, offset),
|
|
273
275
|
});
|
|
274
276
|
}
|
|
275
277
|
}
|
|
276
278
|
|
|
277
279
|
return found.sort(
|
|
278
|
-
(a, b) => a.
|
|
280
|
+
(a, b) => a.sourceFile.localeCompare(b.sourceFile) || a.line - b.line,
|
|
279
281
|
);
|
|
280
282
|
};
|
|
281
283
|
|
|
@@ -514,7 +516,7 @@ export function sharedMissingDirectory(rows, projectDir = '') {
|
|
|
514
516
|
* every row has to be somewhere to go. A URL written in three stylesheets
|
|
515
517
|
* becomes three rows rather than one row with a repeat count.
|
|
516
518
|
*
|
|
517
|
-
* @param {Array<{url: string}>} assets - Unresolved assets from the collector.
|
|
519
|
+
* @param {Array<{url: string, importer?: string}>} assets - Unresolved assets from the collector.
|
|
518
520
|
* @param {ReturnType<createAssetResolver>} resolver - Asset resolver.
|
|
519
521
|
* @returns {Array<{where: string, url: string, status: string, label: string}>} Table rows.
|
|
520
522
|
*/
|
|
@@ -523,6 +525,32 @@ export function buildAssetRows(assets, resolver) {
|
|
|
523
525
|
const location = resolver.locate(asset.url);
|
|
524
526
|
const references = resolver.references(asset.url);
|
|
525
527
|
|
|
528
|
+
if (asset.importer) {
|
|
529
|
+
const importer = cleanUrl(asset.importer).replaceAll('\\', '/');
|
|
530
|
+
const matchingReference = references.find((reference) => {
|
|
531
|
+
const sourceFile = reference.sourceFile || reference.file;
|
|
532
|
+
return sourceFile === importer || importer.endsWith(`/${sourceFile}`);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
if (!matchingReference) {
|
|
536
|
+
return [
|
|
537
|
+
{
|
|
538
|
+
where: tailSegments(importer),
|
|
539
|
+
url: asset.url,
|
|
540
|
+
...location,
|
|
541
|
+
},
|
|
542
|
+
];
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return [
|
|
546
|
+
{
|
|
547
|
+
where: `${matchingReference.file}:${matchingReference.line}`,
|
|
548
|
+
url: asset.url,
|
|
549
|
+
...location,
|
|
550
|
+
},
|
|
551
|
+
];
|
|
552
|
+
}
|
|
553
|
+
|
|
526
554
|
if (references.length === 0) {
|
|
527
555
|
return [{ where: '—', url: asset.url, ...location }];
|
|
528
556
|
}
|
|
@@ -134,7 +134,7 @@ export function parseCssSyntaxError(error) {
|
|
|
134
134
|
/**
|
|
135
135
|
* Flatten a rolldown build error into the individual errors it wraps.
|
|
136
136
|
*
|
|
137
|
-
* @param {Error & {errors?: Array<
|
|
137
|
+
* @param {Error & {errors?: Array<unknown>}} error - Build error.
|
|
138
138
|
* @returns {Array<object>} Individual errors.
|
|
139
139
|
*/
|
|
140
140
|
export function flattenBuildErrors(error) {
|
|
@@ -143,7 +143,11 @@ export function flattenBuildErrors(error) {
|
|
|
143
143
|
const nested = error.errors;
|
|
144
144
|
if (!Array.isArray(nested) || nested.length === 0) return [error];
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
const flattened = nested.flatMap((entry) => flattenBuildErrors(entry));
|
|
147
|
+
|
|
148
|
+
// A malformed aggregate can contain only falsy entries. Keep its wrapper so
|
|
149
|
+
// the failed hook still records an error instead of turning the cycle green.
|
|
150
|
+
return flattened.length > 0 ? flattened : [error];
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
/**
|
|
@@ -250,7 +254,7 @@ export function describeBuildError(error) {
|
|
|
250
254
|
* line, and a path that does not resolve — and because one deleted partial
|
|
251
255
|
* commonly produces a dozen of them.
|
|
252
256
|
*
|
|
253
|
-
* @param {Error & {errors?: Array<
|
|
257
|
+
* @param {Error & {errors?: Array<unknown>}} error - Build error.
|
|
254
258
|
* @returns {{
|
|
255
259
|
* importErrors: Array<object>,
|
|
256
260
|
* syntaxErrors: Array<object>,
|
|
@@ -35,6 +35,18 @@ const locationKey = (file, line) =>
|
|
|
35
35
|
const entryKey = (entry) =>
|
|
36
36
|
`${locationKey(entry.file, entry.line)}|${entry.message || ''}`;
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Build the identity of one CSS asset reference site.
|
|
40
|
+
*
|
|
41
|
+
* The same URL text in two stylesheets represents two separate edits, while
|
|
42
|
+
* repeat notices for the same stylesheet and URL are one problem to tally.
|
|
43
|
+
*
|
|
44
|
+
* @param {string|undefined} importer - Referencing stylesheet.
|
|
45
|
+
* @param {string|undefined} url - Referenced asset URL.
|
|
46
|
+
* @returns {string} Stable asset-reference key.
|
|
47
|
+
*/
|
|
48
|
+
const assetReferenceKey = (importer, url) => `${importer || ''}\0${url || ''}`;
|
|
49
|
+
|
|
38
50
|
/**
|
|
39
51
|
* Record one occurrence against a location map, incrementing when repeated.
|
|
40
52
|
*
|
|
@@ -138,8 +150,10 @@ const groupDeprecationsByFile = (deprecationList) => {
|
|
|
138
150
|
* @returns {{
|
|
139
151
|
* recordDeprecation: (entry: {id?: string, file?: string, line?: number}) => void,
|
|
140
152
|
* recordWarning: (entry: {message?: string, file?: string, line?: number}) => void,
|
|
141
|
-
* recordError: (entry: {message?: string, file?: string, line?: number}) => void,
|
|
153
|
+
* recordError: (entry: {message?: string, file?: string, line?: number, outputState?: 'incomplete'}) => void,
|
|
142
154
|
* recordUnresolvedAsset: (entry: {url?: string, importer?: string}) => void,
|
|
155
|
+
* recordAssetRebase: (entry: {status?: string, url?: string, rewritten?: string, importer?: string, resolvedAsset?: string, candidates?: string[]}) => void,
|
|
156
|
+
* recordExternalizedModule: (entry: {module?: string, importer?: string}) => void,
|
|
143
157
|
* recordImportError: (entry: {file?: string, line?: number, specifier?: string}) => void,
|
|
144
158
|
* recordSyntaxError: (entry: {minifier?: string, message?: string, declaration?: string}) => void,
|
|
145
159
|
* hasCapturedBuildErrors: () => boolean,
|
|
@@ -147,10 +161,11 @@ const groupDeprecationsByFile = (deprecationList) => {
|
|
|
147
161
|
* deprecations: Array<{id: string, occurrences: number, locations: Array<{file: string|undefined, line: number|undefined, count: number}>}>,
|
|
148
162
|
* deprecationsByFile: Array<{file: string, occurrences: number, entries: Array<{id: string, count: number, lines: number[]}>}>,
|
|
149
163
|
* unresolvedAssets: Array<{url: string, importer: string|undefined, count: number}>,
|
|
164
|
+
* assetRebases: Array<{status: string, url: string, rewritten: string|undefined, importer: string|undefined, resolvedAsset: string|undefined, candidates: string[]|undefined, count: number}>,
|
|
150
165
|
* importErrors: Array<{file: string|undefined, line: number|undefined, specifier: string, count: number}>,
|
|
151
166
|
* syntaxErrors: Array<{minifier: string|undefined, message: string, declaration: string|undefined, count: number}>,
|
|
152
167
|
* warnings: Array<{message: string|undefined, file: string|undefined, line: number|undefined, count: number}>,
|
|
153
|
-
* errors: Array<{message: string|undefined, file: string|undefined, line: number|undefined, count: number}>,
|
|
168
|
+
* errors: Array<{message: string|undefined, file: string|undefined, line: number|undefined, outputState?: 'incomplete', count: number}>,
|
|
154
169
|
* deprecationTotal: number,
|
|
155
170
|
* deprecationFileCount: number,
|
|
156
171
|
* hasProblems: boolean
|
|
@@ -168,6 +183,10 @@ export function createDiagnosticsCollector() {
|
|
|
168
183
|
/** @type {Map<string, {url: string, importer: string|undefined, count: number}>} */
|
|
169
184
|
let unresolvedAssets = new Map();
|
|
170
185
|
/** @type {Map<string, object>} */
|
|
186
|
+
let assetRebases = new Map();
|
|
187
|
+
/** @type {Map<string, {module: string, importer: string|undefined, count: number}>} */
|
|
188
|
+
let externalizedModules = new Map();
|
|
189
|
+
/** @type {Map<string, object>} */
|
|
171
190
|
let importErrors = new Map();
|
|
172
191
|
/** @type {Map<string, object>} */
|
|
173
192
|
let syntaxErrors = new Map();
|
|
@@ -266,8 +285,8 @@ export function createDiagnosticsCollector() {
|
|
|
266
285
|
/**
|
|
267
286
|
* Record one CSS `url()` that Vite could not resolve at build time.
|
|
268
287
|
*
|
|
269
|
-
* Keyed by URL, because the same
|
|
270
|
-
*
|
|
288
|
+
* Keyed by importer and URL, because the same spelling in two stylesheets
|
|
289
|
+
* is two separate source sites for an author to fix.
|
|
271
290
|
*
|
|
272
291
|
* @param {{url?: string, importer?: string}} entry - Unresolved asset.
|
|
273
292
|
* @returns {void}
|
|
@@ -275,14 +294,76 @@ export function createDiagnosticsCollector() {
|
|
|
275
294
|
recordUnresolvedAsset({ url, importer } = {}) {
|
|
276
295
|
if (!url) return;
|
|
277
296
|
|
|
278
|
-
const
|
|
297
|
+
const key = assetReferenceKey(importer, url);
|
|
298
|
+
const existing = unresolvedAssets.get(key);
|
|
299
|
+
if (existing) {
|
|
300
|
+
existing.count += 1;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
unresolvedAssets.set(key, { url, importer, count: 1 });
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Record one CSS `url()` the build repaired, or could not choose for.
|
|
309
|
+
*
|
|
310
|
+
* A separate channel from `recordUnresolvedAsset` on purpose: Vite already
|
|
311
|
+
* warned about every one of these URLs, and folding them into that map
|
|
312
|
+
* would double the occurrence count it reports. `snapshot()` subtracts
|
|
313
|
+
* repaired URLs from the unresolved list instead.
|
|
314
|
+
*
|
|
315
|
+
* @param {{status?: string, url?: string, rewritten?: string, importer?: string, resolvedAsset?: string, candidates?: string[]}} entry - Rebase record.
|
|
316
|
+
* @returns {void}
|
|
317
|
+
*/
|
|
318
|
+
recordAssetRebase({
|
|
319
|
+
status = 'rebased',
|
|
320
|
+
url,
|
|
321
|
+
rewritten,
|
|
322
|
+
importer,
|
|
323
|
+
resolvedAsset,
|
|
324
|
+
candidates,
|
|
325
|
+
} = {}) {
|
|
326
|
+
if (!url) return;
|
|
327
|
+
|
|
328
|
+
const key = assetReferenceKey(importer, url);
|
|
329
|
+
const existing = assetRebases.get(key);
|
|
330
|
+
if (existing) {
|
|
331
|
+
existing.count += 1;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
assetRebases.set(key, {
|
|
336
|
+
status,
|
|
337
|
+
url,
|
|
338
|
+
rewritten,
|
|
339
|
+
importer,
|
|
340
|
+
resolvedAsset,
|
|
341
|
+
candidates,
|
|
342
|
+
count: 1,
|
|
343
|
+
});
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Record one module Vite externalized for browser compatibility.
|
|
348
|
+
*
|
|
349
|
+
* Vite emits this once per importing file per cycle, so a single dependency
|
|
350
|
+
* that reaches for a Node builtin prints on every keystroke. The identity
|
|
351
|
+
* that matters is the module, not the importer, so occurrences are tallied
|
|
352
|
+
* against the module name.
|
|
353
|
+
*
|
|
354
|
+
* @param {{module?: string, importer?: string}} entry - Externalized module.
|
|
355
|
+
* @returns {void}
|
|
356
|
+
*/
|
|
357
|
+
recordExternalizedModule({ module, importer } = {}) {
|
|
358
|
+
if (!module) return;
|
|
359
|
+
|
|
360
|
+
const existing = externalizedModules.get(module);
|
|
279
361
|
if (existing) {
|
|
280
362
|
existing.count += 1;
|
|
281
|
-
existing.importer = existing.importer || importer;
|
|
282
363
|
return;
|
|
283
364
|
}
|
|
284
365
|
|
|
285
|
-
|
|
366
|
+
externalizedModules.set(module, { module, importer, count: 1 });
|
|
286
367
|
},
|
|
287
368
|
|
|
288
369
|
snapshot() {
|
|
@@ -306,7 +387,10 @@ export function createDiagnosticsCollector() {
|
|
|
306
387
|
const errorList = [...errors.values()];
|
|
307
388
|
const warningList = [...warnings.values()];
|
|
308
389
|
const unresolvedAssetList = [...unresolvedAssets.values()].sort(
|
|
309
|
-
(a, b) =>
|
|
390
|
+
(a, b) =>
|
|
391
|
+
b.count - a.count ||
|
|
392
|
+
a.url.localeCompare(b.url) ||
|
|
393
|
+
String(a.importer || '').localeCompare(String(b.importer || '')),
|
|
310
394
|
);
|
|
311
395
|
|
|
312
396
|
const importErrorList = [...importErrors.values()].sort(
|
|
@@ -315,10 +399,54 @@ export function createDiagnosticsCollector() {
|
|
|
315
399
|
(a.line ?? 0) - (b.line ?? 0),
|
|
316
400
|
);
|
|
317
401
|
|
|
402
|
+
const assetRebaseList = [...assetRebases.values()];
|
|
403
|
+
const handledReferences = new Set(
|
|
404
|
+
assetRebaseList
|
|
405
|
+
.filter(
|
|
406
|
+
(entry) => entry.status === 'aliased' || entry.status === 'rebased',
|
|
407
|
+
)
|
|
408
|
+
.map((entry) => assetReferenceKey(entry.importer, entry.url)),
|
|
409
|
+
);
|
|
410
|
+
const handledUrls = new Set(
|
|
411
|
+
assetRebaseList
|
|
412
|
+
.filter(
|
|
413
|
+
(entry) => entry.status === 'aliased' || entry.status === 'rebased',
|
|
414
|
+
)
|
|
415
|
+
.map((entry) => entry.url),
|
|
416
|
+
);
|
|
417
|
+
// Vite warns about a URL before the rebase plugin repairs it, so without
|
|
418
|
+
// this a repaired reference or accepted alias is reported as an
|
|
419
|
+
// outstanding problem. The importer remains part of the identity:
|
|
420
|
+
// handling one stylesheet must not hide the same URL spelling in another
|
|
421
|
+
// stylesheet. When Vite's notice lacks an importer, fall back to URL
|
|
422
|
+
// matching because it sometimes reports the URL itself in the importer
|
|
423
|
+
// position.
|
|
424
|
+
const outstandingAssets = unresolvedAssetList.filter((asset) => {
|
|
425
|
+
if (
|
|
426
|
+
handledReferences.has(assetReferenceKey(asset.importer, asset.url))
|
|
427
|
+
) {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (!asset.importer) return !handledUrls.has(asset.url);
|
|
432
|
+
return true;
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// `@assets/...` is an accepted authoring form. It is tracked internally
|
|
436
|
+
// only to suppress Vite's pre-normalization unresolved notice and must
|
|
437
|
+
// not appear as a repair in summaries or strict-mode failures.
|
|
438
|
+
const reportedAssetRebases = assetRebaseList.filter(
|
|
439
|
+
(entry) => entry.status !== 'aliased',
|
|
440
|
+
);
|
|
441
|
+
|
|
318
442
|
return {
|
|
319
443
|
deprecations: deprecationList,
|
|
320
444
|
deprecationsByFile: groupDeprecationsByFile(deprecationList),
|
|
321
|
-
unresolvedAssets:
|
|
445
|
+
unresolvedAssets: outstandingAssets,
|
|
446
|
+
assetRebases: reportedAssetRebases,
|
|
447
|
+
externalizedModules: [...externalizedModules.values()].sort(
|
|
448
|
+
(a, b) => b.count - a.count || a.module.localeCompare(b.module),
|
|
449
|
+
),
|
|
322
450
|
importErrors: importErrorList,
|
|
323
451
|
syntaxErrors: [...syntaxErrors.values()],
|
|
324
452
|
warnings: warningList,
|
|
@@ -332,7 +460,7 @@ export function createDiagnosticsCollector() {
|
|
|
332
460
|
errorList.length > 0 ||
|
|
333
461
|
warningList.length > 0 ||
|
|
334
462
|
deprecationList.length > 0 ||
|
|
335
|
-
|
|
463
|
+
outstandingAssets.length > 0 ||
|
|
336
464
|
importErrorList.length > 0 ||
|
|
337
465
|
syntaxErrors.size > 0,
|
|
338
466
|
};
|
|
@@ -360,6 +488,8 @@ export function createDiagnosticsCollector() {
|
|
|
360
488
|
warnings = new Map();
|
|
361
489
|
errors = new Map();
|
|
362
490
|
unresolvedAssets = new Map();
|
|
491
|
+
assetRebases = new Map();
|
|
492
|
+
externalizedModules = new Map();
|
|
363
493
|
importErrors = new Map();
|
|
364
494
|
syntaxErrors = new Map();
|
|
365
495
|
},
|