@emulsify/core 4.3.2 → 4.5.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 +14 -6
- package/config/a11y-wcag22.js +11 -0
- 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 +13 -13
- 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/twig-module.js +35 -258
- 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-directory-skips.js +13 -0
- package/config/vite/utils/source-maps.js +88 -0
- package/config/vite/utils/twig-component-resolver.js +316 -0
- package/config/vite/vite.config.js +106 -42
- package/package.json +54 -40
- package/scripts/a11y.js +88 -9
- package/scripts/audit/checks/css-asset-references.js +256 -24
- package/scripts/audit/checks/twig-references.js +16 -5
- 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/story-ast.js +392 -0
- package/scripts/audit/lib/story-render-paths.js +600 -0
- package/scripts/audit/lib/story-selection.js +190 -0
- package/scripts/audit/lib/twig.js +372 -80
- package/scripts/audit/report.js +83 -5
- package/scripts/audit-twig-stories.js +73 -3
- package/scripts/audit.js +87 -2
- package/src/storybook/twig/source-function.js +14 -10
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Node-safe component template resolution shared by Twig compilation and audits.
|
|
3
|
+
*
|
|
4
|
+
* Callers own the grouping-directory cache so a build can invalidate it on
|
|
5
|
+
* filesystem changes and an audit can discard it after each pass.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import { basename, isAbsolute, relative, resolve } from 'node:path';
|
|
10
|
+
import { toPosixPath } from './paths.js';
|
|
11
|
+
import { DEFAULT_SKIP_DIRS } from './source-directory-skips.js';
|
|
12
|
+
import { unique } from '../../../src/extensions/shared/lists.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build likely filesystem candidates for a Twig template reference.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} baseDir - Directory used as the resolution root.
|
|
18
|
+
* @param {string} templatePath - Template path from Twig source.
|
|
19
|
+
* @returns {string[]} Candidate absolute paths.
|
|
20
|
+
*/
|
|
21
|
+
export const buildTemplateFileCandidates = (baseDir, templatePath) => {
|
|
22
|
+
const normalizedTemplatePath = toPosixPath(templatePath);
|
|
23
|
+
const withoutTwigExt = normalizedTemplatePath.replace(/\.twig$/i, '');
|
|
24
|
+
const stem = basename(withoutTwigExt);
|
|
25
|
+
|
|
26
|
+
return unique(
|
|
27
|
+
[
|
|
28
|
+
resolve(baseDir, normalizedTemplatePath),
|
|
29
|
+
resolve(baseDir, `${normalizedTemplatePath}.twig`),
|
|
30
|
+
resolve(baseDir, `${normalizedTemplatePath}.html.twig`),
|
|
31
|
+
resolve(baseDir, withoutTwigExt, `${stem}.twig`),
|
|
32
|
+
resolve(baseDir, withoutTwigExt, `${stem}.html.twig`),
|
|
33
|
+
].filter(Boolean),
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Determine whether a file path is equal to or below a candidate root.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} root - Absolute root path.
|
|
41
|
+
* @param {string} filePath - Absolute file path.
|
|
42
|
+
* @returns {boolean} TRUE when the file belongs to the root.
|
|
43
|
+
*/
|
|
44
|
+
export const isWithinRoot = (root, filePath) => {
|
|
45
|
+
const rootRelativePath = relative(root, filePath);
|
|
46
|
+
return (
|
|
47
|
+
rootRelativePath === '' ||
|
|
48
|
+
(!!rootRelativePath &&
|
|
49
|
+
!rootRelativePath.startsWith('..') &&
|
|
50
|
+
!isAbsolute(rootRelativePath))
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Return the first component template candidate contained by its configured root.
|
|
56
|
+
*
|
|
57
|
+
* Both lexical and real paths are checked so `..` segments and symlinks cannot
|
|
58
|
+
* escape the component root.
|
|
59
|
+
*
|
|
60
|
+
* @param {Iterable<string>} paths - Candidate absolute paths in precedence order.
|
|
61
|
+
* @param {string} componentRoot - Absolute component root path.
|
|
62
|
+
* @returns {string|undefined} Existing component template path.
|
|
63
|
+
*/
|
|
64
|
+
const findExistingComponentTemplateFile = (paths, componentRoot) => {
|
|
65
|
+
const absoluteRoot = resolve(componentRoot);
|
|
66
|
+
let realRoot;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
realRoot = fs.realpathSync(absoluteRoot);
|
|
70
|
+
} catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const filePath of paths) {
|
|
75
|
+
if (!filePath) continue;
|
|
76
|
+
const absoluteFilePath = resolve(filePath);
|
|
77
|
+
if (!isWithinRoot(absoluteRoot, absoluteFilePath)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
if (
|
|
83
|
+
fs.statSync(absoluteFilePath).isFile() &&
|
|
84
|
+
isWithinRoot(realRoot, fs.realpathSync(absoluteFilePath))
|
|
85
|
+
) {
|
|
86
|
+
return filePath;
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
// A missing or unreadable candidate does not prevent later matches.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Resolve Twig namespace syntax to a namespace root and relative path.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} templatePath - Template reference from Twig source.
|
|
99
|
+
* @param {Record<string, string>} [namespaces={}] - Namespace root map.
|
|
100
|
+
* @returns {{ namespace: string, root: string, path: string }|null}
|
|
101
|
+
* Namespace lookup result.
|
|
102
|
+
*/
|
|
103
|
+
export const parseTwigNamespaceReference = (templatePath, namespaces = {}) => {
|
|
104
|
+
const namespaceNames = Object.keys(namespaces);
|
|
105
|
+
const atNamespace = templatePath.match(/^@([^/]+)\/(.+)$/);
|
|
106
|
+
if (atNamespace && namespaces[atNamespace[1]]) {
|
|
107
|
+
return {
|
|
108
|
+
namespace: atNamespace[1],
|
|
109
|
+
root: namespaces[atNamespace[1]],
|
|
110
|
+
path: atNamespace[2],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const doubleColon = templatePath.match(/^([^:]+)::(.+)$/);
|
|
115
|
+
if (doubleColon && namespaces[doubleColon[1]]) {
|
|
116
|
+
return {
|
|
117
|
+
namespace: doubleColon[1],
|
|
118
|
+
root: namespaces[doubleColon[1]],
|
|
119
|
+
path: doubleColon[2],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const singleColon = templatePath.match(/^([^:/.]+):(.+)$/);
|
|
124
|
+
if (singleColon && namespaces[singleColon[1]]) {
|
|
125
|
+
return {
|
|
126
|
+
namespace: singleColon[1],
|
|
127
|
+
root: namespaces[singleColon[1]],
|
|
128
|
+
path: singleColon[2],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const slashNamespace = namespaceNames.find((namespace) =>
|
|
133
|
+
templatePath.startsWith(`${namespace}/`),
|
|
134
|
+
);
|
|
135
|
+
if (slashNamespace) {
|
|
136
|
+
return {
|
|
137
|
+
namespace: slashNamespace,
|
|
138
|
+
// Namespace names come from the normalized Twig namespace map.
|
|
139
|
+
root: namespaces[slashNamespace],
|
|
140
|
+
path: templatePath.slice(slashNamespace.length + 1),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return null;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Return grouping directories below the configured component root.
|
|
149
|
+
*
|
|
150
|
+
* Breadth-first traversal preserves direct and one-level behavior before
|
|
151
|
+
* searching deeper groups. Siblings use UTF-16 code-unit order so duplicate
|
|
152
|
+
* shorthand names resolve consistently across filesystems.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} componentRoot - Absolute component root path.
|
|
155
|
+
* @param {Map<string, string[]>} componentGroupRootsCache - Caller-scoped directory cache.
|
|
156
|
+
* @returns {string[]} Absolute grouping directory paths.
|
|
157
|
+
*/
|
|
158
|
+
const componentGroupRoots = (componentRoot, componentGroupRootsCache) => {
|
|
159
|
+
if (!componentRoot) return [];
|
|
160
|
+
|
|
161
|
+
const absoluteRoot = resolve(componentRoot);
|
|
162
|
+
if (componentGroupRootsCache.has(absoluteRoot)) {
|
|
163
|
+
return componentGroupRootsCache.get(absoluteRoot);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const groupRoots = [];
|
|
167
|
+
const pendingDirectories = [absoluteRoot];
|
|
168
|
+
|
|
169
|
+
for (let index = 0; index < pendingDirectories.length; index += 1) {
|
|
170
|
+
const directory = pendingDirectories[index];
|
|
171
|
+
let entries;
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
175
|
+
} catch {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const childDirectories = entries
|
|
180
|
+
.filter(
|
|
181
|
+
(entry) =>
|
|
182
|
+
entry.isDirectory() && !DEFAULT_SKIP_DIRS.includes(entry.name),
|
|
183
|
+
)
|
|
184
|
+
.sort(({ name: left }, { name: right }) =>
|
|
185
|
+
left === right ? 0 : left < right ? -1 : 1,
|
|
186
|
+
)
|
|
187
|
+
.map((entry) => resolve(directory, entry.name))
|
|
188
|
+
.filter((childDirectory) => isWithinRoot(absoluteRoot, childDirectory));
|
|
189
|
+
|
|
190
|
+
groupRoots.push(...childDirectories);
|
|
191
|
+
pendingDirectories.push(...childDirectories);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
componentGroupRootsCache.set(absoluteRoot, groupRoots);
|
|
195
|
+
return groupRoots;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Resolve a component reference through recursively grouped directories.
|
|
200
|
+
*
|
|
201
|
+
* Project-scoped component IDs can use the component name (`project:button`)
|
|
202
|
+
* even when projects organize components under grouping paths such as
|
|
203
|
+
* `atoms/text`.
|
|
204
|
+
*
|
|
205
|
+
* @param {string} templatePath - Component-relative template reference.
|
|
206
|
+
* @param {string} componentRoot - Absolute component root path.
|
|
207
|
+
* @param {Map<string, string[]>} componentGroupRootsCache - Caller-scoped directory cache.
|
|
208
|
+
* @returns {string|null} Existing template path when found.
|
|
209
|
+
*/
|
|
210
|
+
const resolveGroupedComponentTemplate = (
|
|
211
|
+
templatePath,
|
|
212
|
+
componentRoot,
|
|
213
|
+
componentGroupRootsCache,
|
|
214
|
+
) => {
|
|
215
|
+
const groupRoots = componentGroupRoots(
|
|
216
|
+
componentRoot,
|
|
217
|
+
componentGroupRootsCache,
|
|
218
|
+
);
|
|
219
|
+
function* candidates() {
|
|
220
|
+
for (const groupRoot of groupRoots) {
|
|
221
|
+
yield* buildTemplateFileCandidates(groupRoot, templatePath);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return findExistingComponentTemplateFile(candidates(), componentRoot) || null;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Resolve shorthand component references against the components namespace.
|
|
230
|
+
*
|
|
231
|
+
* @param {string} templatePath - Template reference from Twig source.
|
|
232
|
+
* @param {string} componentRoot - Absolute component root path.
|
|
233
|
+
* @param {Map<string, string[]>} componentGroupRootsCache - Caller-scoped directory cache.
|
|
234
|
+
* @returns {string|null} Existing template path when found.
|
|
235
|
+
*/
|
|
236
|
+
const resolveComponentShorthandReference = (
|
|
237
|
+
templatePath,
|
|
238
|
+
componentRoot,
|
|
239
|
+
componentGroupRootsCache,
|
|
240
|
+
) => {
|
|
241
|
+
if (!componentRoot || templatePath.startsWith('.')) return null;
|
|
242
|
+
|
|
243
|
+
const shorthandPath =
|
|
244
|
+
templatePath.startsWith('@') && !templatePath.includes('/')
|
|
245
|
+
? templatePath.slice(1)
|
|
246
|
+
: templatePath;
|
|
247
|
+
const directComponentPath = findExistingComponentTemplateFile(
|
|
248
|
+
buildTemplateFileCandidates(componentRoot, shorthandPath),
|
|
249
|
+
componentRoot,
|
|
250
|
+
);
|
|
251
|
+
if (directComponentPath) {
|
|
252
|
+
return directComponentPath;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// A bare directory path keeps every segment; only explicit namespace syntax
|
|
256
|
+
// can drop its prefix before searching component groups.
|
|
257
|
+
const genericNamespace = templatePath.match(/^(?:@[^/:]+\/|@?[^/:]+:)(.+)$/);
|
|
258
|
+
if (!genericNamespace) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const genericComponentPath = genericNamespace[1];
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
findExistingComponentTemplateFile(
|
|
266
|
+
buildTemplateFileCandidates(componentRoot, genericComponentPath),
|
|
267
|
+
componentRoot,
|
|
268
|
+
) ||
|
|
269
|
+
resolveGroupedComponentTemplate(
|
|
270
|
+
genericComponentPath,
|
|
271
|
+
componentRoot,
|
|
272
|
+
componentGroupRootsCache,
|
|
273
|
+
)
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Resolve component namespace paths and project-scoped component shorthand.
|
|
279
|
+
*
|
|
280
|
+
* Configured non-component namespaces remain scoped to their own roots. Direct
|
|
281
|
+
* candidates precede grouped candidates, which retain breadth-first/UTF-16
|
|
282
|
+
* order for duplicate component names.
|
|
283
|
+
*
|
|
284
|
+
* @param {string} templatePath - Template reference from Twig source.
|
|
285
|
+
* @param {Record<string, string>} namespaces - Normalized namespace root map.
|
|
286
|
+
* @param {Map<string, string[]>} componentGroupRootsCache - Caller-scoped directory cache.
|
|
287
|
+
* @returns {string|null} Existing component template path when found.
|
|
288
|
+
*/
|
|
289
|
+
export const resolveComponentReference = (
|
|
290
|
+
templatePath,
|
|
291
|
+
namespaces,
|
|
292
|
+
componentGroupRootsCache,
|
|
293
|
+
) => {
|
|
294
|
+
const namespaced = parseTwigNamespaceReference(templatePath, namespaces);
|
|
295
|
+
if (namespaced) {
|
|
296
|
+
if (namespaced.namespace !== 'components') return null;
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
findExistingComponentTemplateFile(
|
|
300
|
+
buildTemplateFileCandidates(namespaced.root, namespaced.path),
|
|
301
|
+
namespaced.root,
|
|
302
|
+
) ||
|
|
303
|
+
resolveGroupedComponentTemplate(
|
|
304
|
+
namespaced.path,
|
|
305
|
+
namespaced.root,
|
|
306
|
+
componentGroupRootsCache,
|
|
307
|
+
)
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return resolveComponentShorthandReference(
|
|
312
|
+
templatePath,
|
|
313
|
+
namespaces?.components,
|
|
314
|
+
componentGroupRootsCache,
|
|
315
|
+
);
|
|
316
|
+
};
|
|
@@ -13,18 +13,13 @@
|
|
|
13
13
|
* parts of it by returning a patch object from `extendConfig(...)`.
|
|
14
14
|
*
|
|
15
15
|
* Notes:
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* there, so `css.devSourcemap` can chain the map to source. Loading the
|
|
24
|
-
* compiled CSS instead yields an identity map whose only source is the
|
|
25
|
-
* compiled `.css` file.
|
|
26
|
-
* - CSS is left unminified during `vite build --watch` so the develop loop
|
|
27
|
-
* stays readable; one-shot builds keep minification.
|
|
16
|
+
* - `vite build --watch` emits external JS and CSS maps while one-shot
|
|
17
|
+
* production builds ship neither. Vite discards maps when it extracts CSS as
|
|
18
|
+
* an asset, so Core captures the Sass/PostCSS map before URL rewriting and
|
|
19
|
+
* attaches it to the finalized stylesheet. URL rewrites preserve source
|
|
20
|
+
* lines, though a changed URL length can shift columns inside that value.
|
|
21
|
+
* - JS and CSS stay unminified during `vite build --watch` so generated output
|
|
22
|
+
* remains readable. One-shot builds keep Vite's production minification.
|
|
28
23
|
* - CSS assets keep their path and drop the internal `__style` suffix if present.
|
|
29
24
|
*/
|
|
30
25
|
|
|
@@ -36,7 +31,10 @@ import { makePlugins } from './plugins.js';
|
|
|
36
31
|
import { buildInputs } from './entries.js';
|
|
37
32
|
import { createSourceFileIndex } from './plugins/assets/source-file-index.js';
|
|
38
33
|
import { createDiagnosticsCollector } from './plugins/reporter/diagnostics.js';
|
|
39
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
createSassOptions,
|
|
36
|
+
shouldQuietSass,
|
|
37
|
+
} from './plugins/reporter/sass-logger.js';
|
|
40
38
|
import {
|
|
41
39
|
createReporterLogger,
|
|
42
40
|
isVerbose,
|
|
@@ -45,7 +43,7 @@ import { isWatchInvocation } from './plugins/reporter/watch-mode.js';
|
|
|
45
43
|
import { loadProjectExtensions } from './project-extensions.js';
|
|
46
44
|
import { mergeReactSingletonResolve } from './utils/react-singleton.js';
|
|
47
45
|
|
|
48
|
-
|
|
46
|
+
async function createViteConfig({ command, isStorybookBuild = false } = {}) {
|
|
49
47
|
/**
|
|
50
48
|
* Environment details for this build (project paths, platform, flags).
|
|
51
49
|
* @typedef {Object} EmulsifyEnv
|
|
@@ -56,20 +54,42 @@ export default defineConfig(async () => {
|
|
|
56
54
|
* @property {boolean} [SDC] - Single Directory Components toggle, if available.
|
|
57
55
|
* @property {boolean} [structureOverrides] - Whether component structure overrides are enabled.
|
|
58
56
|
* @property {string[]} [structureRoots] - Override roots, if provided.
|
|
57
|
+
* @property {boolean} [assetRebase] - Whether unresolved CSS asset URLs are repaired.
|
|
58
|
+
* @property {boolean} [selfContainedOutput] - Whether project assets remain in the output.
|
|
59
59
|
* @property {object} [platformAdapter] - Active platform behavior adapter.
|
|
60
|
+
* @property {boolean} [developmentBuild] - Whether this is the long-running develop build.
|
|
60
61
|
*/
|
|
61
62
|
|
|
62
63
|
/** @type {EmulsifyEnv} */
|
|
63
64
|
const env = resolveEnvironment();
|
|
64
65
|
const sourceFileIndex = createSourceFileIndex(env.projectStructure);
|
|
65
66
|
|
|
66
|
-
// The develop
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// is ever collected without also being reported.
|
|
67
|
+
// The full develop summary runs only for `vite build --watch`, the watcher
|
|
68
|
+
// `npm run develop` starts. One-shot builds keep their normal output and add
|
|
69
|
+
// a compact diagnostic block only when the collector has something to say.
|
|
70
70
|
const watching = isWatchInvocation();
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
|
|
72
|
+
// The collector itself is a handful of Maps, and one-shot builds need one
|
|
73
|
+
// too: an unresolved CSS asset URL used to print a single raw Vite line and
|
|
74
|
+
// exit 0, so a broken asset path shipped through CI unnoticed. The reporter
|
|
75
|
+
// plugin decides whether to speak, and for a one-shot build it stays silent
|
|
76
|
+
// unless there is an asset problem or a collected Sass deprecation tally —
|
|
77
|
+
// a clean project's output is unchanged.
|
|
78
|
+
const diagnostics = createDiagnosticsCollector();
|
|
79
|
+
const envWithSourceFileIndex = {
|
|
80
|
+
...env,
|
|
81
|
+
sourceFileIndex,
|
|
82
|
+
diagnostics,
|
|
83
|
+
developmentBuild: watching,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// `vite build` and `vite build --watch` both resolve `command: 'build'`.
|
|
87
|
+
// Storybook pins `serve` for both of its commands, so its Vite adapter
|
|
88
|
+
// supplies the separate static-build signal. Raw verbose output still needs
|
|
89
|
+
// the wrapper: it passes the notice through while retaining a copy for
|
|
90
|
+
// strict asset mode.
|
|
91
|
+
const captureViteNotices =
|
|
92
|
+
!watching && (command === 'build' || isStorybookBuild);
|
|
73
93
|
|
|
74
94
|
// Build the Rollup/Vite entry map: keys encode output paths, values source files.
|
|
75
95
|
/** @type {Record<string, string>} */
|
|
@@ -93,7 +113,9 @@ export default defineConfig(async () => {
|
|
|
93
113
|
* extendConfig?: (base: import('vite').UserConfig, ctx: { env: EmulsifyEnv }) => import('vite').UserConfig
|
|
94
114
|
* }}
|
|
95
115
|
*/
|
|
96
|
-
const { projectPlugins, extendConfig } = await loadProjectExtensions({
|
|
116
|
+
const { projectPlugins, extendConfig } = await loadProjectExtensions({
|
|
117
|
+
env,
|
|
118
|
+
});
|
|
97
119
|
|
|
98
120
|
// Assemble the base config before applying project extensions.
|
|
99
121
|
/** @type {import('vite').UserConfig} */
|
|
@@ -137,7 +159,15 @@ export default defineConfig(async () => {
|
|
|
137
159
|
// build reporter consults the level and never consults the logger.
|
|
138
160
|
// `build.reportCompressedSize` is deliberately left alone; it suppresses
|
|
139
161
|
// only the gzip column, and the table it belongs to is already gone.
|
|
140
|
-
|
|
162
|
+
//
|
|
163
|
+
// A one-shot build takes only the second switch. `logLevel: 'warn'` is what
|
|
164
|
+
// stops Rolldown instrumenting transforms, so setting it there would delete
|
|
165
|
+
// the module count and the per-file asset table from `npm run build` — the
|
|
166
|
+
// one command whose output people actually read. The comment above already
|
|
167
|
+
// establishes the two are independent, and this relies on that: the logger
|
|
168
|
+
// captures the unresolved-URL notices, the reporter prints them back as one
|
|
169
|
+
// block, and Rolldown's report is untouched.
|
|
170
|
+
...(watching
|
|
141
171
|
? {
|
|
142
172
|
logLevel: isVerbose() ? 'info' : 'warn',
|
|
143
173
|
customLogger: createReporterLogger(
|
|
@@ -145,42 +175,52 @@ export default defineConfig(async () => {
|
|
|
145
175
|
createLogger(isVerbose() ? 'info' : 'warn'),
|
|
146
176
|
),
|
|
147
177
|
}
|
|
148
|
-
:
|
|
178
|
+
: captureViteNotices
|
|
179
|
+
? { customLogger: createReporterLogger(diagnostics, createLogger()) }
|
|
180
|
+
: {}),
|
|
149
181
|
|
|
150
182
|
// Keep React-based story helpers on the consumer project's React singleton.
|
|
151
183
|
resolve: mergeReactSingletonResolve(),
|
|
152
184
|
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
// so import SCSS entries when styles need to resolve to their partials.
|
|
185
|
+
// Ask Sass/PostCSS for maps. Vite uses them directly in its dev server;
|
|
186
|
+
// Core's development map plugins retain them for extracted watch-build CSS.
|
|
187
|
+
// JS sourcemaps are controlled by `build.sourcemap` below.
|
|
157
188
|
css: {
|
|
158
189
|
devSourcemap: true,
|
|
159
190
|
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
// the
|
|
164
|
-
|
|
191
|
+
// Route Sass warnings into the diagnostics collector instead of letting
|
|
192
|
+
// Dart Sass print a formatted block per occurrence. The reporter prints
|
|
193
|
+
// one deduplicated tally per develop session or standalone Storybook
|
|
194
|
+
// build, so the debt stays visible without the repetition.
|
|
195
|
+
// `shouldQuietSass` owns which invocations get this.
|
|
196
|
+
...(shouldQuietSass({ watching, command, verbose: isVerbose() })
|
|
165
197
|
? { preprocessorOptions: { scss: createSassOptions(diagnostics) } }
|
|
166
198
|
: {}),
|
|
167
199
|
},
|
|
168
200
|
|
|
169
201
|
build: {
|
|
170
|
-
// Clean the output directory before building.
|
|
202
|
+
// Clean the output directory before building. Vite re-empties it on every
|
|
203
|
+
// watch rebuild, not just the first, which rewrites stylesheets no edit
|
|
204
|
+
// touched; `stableWatchOutputPlugin` turns that off once the develop
|
|
205
|
+
// loop's first cycle has produced a clean tree.
|
|
171
206
|
emptyOutDir: true,
|
|
172
207
|
|
|
173
208
|
// All outputs are written into ./dist/
|
|
174
209
|
outDir: 'dist/',
|
|
175
210
|
|
|
176
|
-
//
|
|
177
|
-
|
|
211
|
+
// Keep source maps available to the develop watcher without shipping
|
|
212
|
+
// them in one-shot production builds. Core bridges the extracted-CSS gap
|
|
213
|
+
// left by Vite; see the file header.
|
|
214
|
+
sourcemap: watching,
|
|
215
|
+
|
|
216
|
+
// Readable generated JavaScript plus its source map makes the watch
|
|
217
|
+
// output useful on both sides of devtools. Production retains Vite's
|
|
218
|
+
// default minification behavior through the explicit TRUE value.
|
|
219
|
+
minify: !watching,
|
|
178
220
|
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
// One-shot `vite build`, `storybook build`, and the release fixture
|
|
183
|
-
// verifications still minify, so nothing a platform ships changes.
|
|
221
|
+
// Keep development styles readable as well as mapped. One-shot
|
|
222
|
+
// `vite build`, `storybook build`, and release fixtures still minify, so
|
|
223
|
+
// nothing a platform ships changes.
|
|
184
224
|
cssMinify: !watching,
|
|
185
225
|
|
|
186
226
|
rollupOptions: {
|
|
@@ -238,10 +278,34 @@ export default defineConfig(async () => {
|
|
|
238
278
|
|
|
239
279
|
// Let project extensions patch the final Vite config.
|
|
240
280
|
/** @type {import('vite').UserConfig} */
|
|
281
|
+
const extensionPatch =
|
|
282
|
+
typeof extendConfig === 'function' ? extendConfig(base, { env }) || {} : {};
|
|
241
283
|
const patched =
|
|
242
284
|
typeof extendConfig === 'function'
|
|
243
|
-
? mergeConfig(base,
|
|
285
|
+
? mergeConfig(base, extensionPatch)
|
|
244
286
|
: base;
|
|
245
287
|
|
|
288
|
+
// A project extension can enable watch mode without a CLI flag. Apply the
|
|
289
|
+
// same development defaults in that case while preserving any explicit
|
|
290
|
+
// sourcemap or minification choices in the extension itself.
|
|
291
|
+
if (!watching && patched.build?.watch) {
|
|
292
|
+
const extensionBuild = extensionPatch.build || {};
|
|
293
|
+
return {
|
|
294
|
+
...patched,
|
|
295
|
+
build: {
|
|
296
|
+
...patched.build,
|
|
297
|
+
...(!Object.hasOwn(extensionBuild, 'sourcemap')
|
|
298
|
+
? { sourcemap: true }
|
|
299
|
+
: {}),
|
|
300
|
+
...(!Object.hasOwn(extensionBuild, 'minify') ? { minify: false } : {}),
|
|
301
|
+
...(!Object.hasOwn(extensionBuild, 'cssMinify')
|
|
302
|
+
? { cssMinify: false }
|
|
303
|
+
: {}),
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
246
308
|
return patched;
|
|
247
|
-
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export default defineConfig(createViteConfig);
|