@emulsify/core 4.3.1 → 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.
Files changed (44) hide show
  1. package/.storybook/main-static-assets.js +5 -8
  2. package/.storybook/main-vite.js +11 -3
  3. package/README.md +4 -5
  4. package/config/vite/entries.js +7 -2
  5. package/config/vite/environment.js +4 -0
  6. package/config/vite/plugins/assets/asset-url-rebase.js +241 -0
  7. package/config/vite/plugins/assets/copy-src-assets.js +82 -12
  8. package/config/vite/plugins/assets/copy-twig-files.js +96 -25
  9. package/config/vite/plugins/assets/css-asset-rebase.js +306 -0
  10. package/config/vite/plugins/assets/css-asset-relativizer.js +301 -21
  11. package/config/vite/plugins/assets/development-source-maps.js +273 -0
  12. package/config/vite/plugins/assets/mirror-components.js +98 -82
  13. package/config/vite/plugins/assets/output-freshness.js +235 -0
  14. package/config/vite/plugins/assets/source-file-index.js +7 -1
  15. package/config/vite/plugins/assets/stable-watch-output.js +165 -0
  16. package/config/vite/plugins/assets/storybook-output.js +27 -0
  17. package/config/vite/plugins/index.js +95 -9
  18. package/config/vite/plugins/reporter/asset-resolver.js +34 -6
  19. package/config/vite/plugins/reporter/build-errors.js +7 -3
  20. package/config/vite/plugins/reporter/diagnostics.js +140 -10
  21. package/config/vite/plugins/reporter/index.js +380 -75
  22. package/config/vite/plugins/reporter/render.js +297 -44
  23. package/config/vite/plugins/reporter/sass-logger.js +30 -0
  24. package/config/vite/plugins/reporter/source-roots.js +101 -21
  25. package/config/vite/plugins/reporter/strict-mode.js +99 -0
  26. package/config/vite/plugins/reporter/vite-logger.js +220 -8
  27. package/config/vite/plugins/reporter/watch-mode.js +6 -2
  28. package/config/vite/plugins/twig/virtual-twig-asset-sources.js +48 -49
  29. package/config/vite/project-config.js +121 -21
  30. package/config/vite/project-structure.js +6 -0
  31. package/config/vite/utils/asset-roots.js +205 -0
  32. package/config/vite/utils/css-urls.js +350 -0
  33. package/config/vite/utils/fs-safe.js +38 -1
  34. package/config/vite/utils/source-maps.js +88 -0
  35. package/config/vite/vite.config.js +106 -42
  36. package/package.json +40 -29
  37. package/scripts/audit/checks/css-asset-references.js +256 -24
  38. package/scripts/audit/fix.js +836 -0
  39. package/scripts/audit/index.js +10 -2
  40. package/scripts/audit/lib/css.js +41 -35
  41. package/scripts/audit/lib/twig.js +11 -29
  42. package/scripts/audit/report.js +83 -5
  43. package/scripts/audit.js +87 -2
  44. package/src/storybook/twig/source-function.js +14 -10
@@ -13,18 +13,13 @@
13
13
  * parts of it by returning a patch object from `extendConfig(...)`.
14
14
  *
15
15
  * Notes:
16
- * - JS sourcemaps come from `build.sourcemap`. Extracted CSS gets no map from
17
- * `vite build`: `vite:css-post` emits CSS through
18
- * `this.emitFile({ type: 'asset' })`, Rollup/Rolldown assets carry no map,
19
- * and `finalizeCss()` -> `minifyCSS()` returns code only. To trace a rule
20
- * back to its `.scss` partial, let Vite compile the SCSS in Storybook: set
21
- * `parameters.emulsify.loadAllCSS = false` in
22
- * `config/emulsify-core/storybook/preview.js` and import the SCSS entry
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 { createSassOptions } from './plugins/reporter/sass-logger.js';
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
- export default defineConfig(async () => {
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 reporter takes over output only for `vite build --watch`, the
67
- // watcher `npm run develop` runs. One-shot builds, Storybook, and the release
68
- // fixture verifications keep their existing output untouched, so no warning
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
- const diagnostics = watching ? createDiagnosticsCollector() : undefined;
72
- const envWithSourceFileIndex = { ...env, sourceFileIndex, diagnostics };
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({ env });
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
- ...(diagnostics
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
- // Generate CSS sourcemaps in dev; JS sourcemaps are set in `build.sourcemap`.
154
- // These map only what Vite itself compiles. A preview that imports
155
- // already-compiled CSS gets an identity map pointing at that `.css` file,
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
- // During a watch build, route Sass warnings into the diagnostics
161
- // collector instead of letting Dart Sass print a formatted block per
162
- // occurrence. The reporter prints one deduplicated tally per cycle, so
163
- // the deprecation debt stays visible without the repetition.
164
- ...(diagnostics
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
- // Emit JS sourcemaps. Extracted CSS is not covered; see the file header.
177
- sourcemap: true,
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
- // Vite cannot map extracted CSS, so during `vite build --watch` the
180
- // readable stylesheet is the debugging aid: keep it unminified so
181
- // devtools shows one declaration per line instead of a single long line.
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, extendConfig(base, { env }) || {})
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emulsify/core",
3
- "version": "4.3.1",
3
+ "version": "4.4.0",
4
4
  "description": "Bundled tooling for Storybook development + Vite Build",
5
5
  "keywords": [
6
6
  "component library",
@@ -59,11 +59,18 @@
59
59
  "config/vite/entries.js",
60
60
  "config/vite/environment.js",
61
61
  "config/vite/platforms.js",
62
+ "config/vite/plugins.js",
63
+ "config/vite/plugins/assets/asset-url-rebase.js",
62
64
  "config/vite/plugins/assets/copy-src-assets.js",
63
65
  "config/vite/plugins/assets/copy-twig-files.js",
66
+ "config/vite/plugins/assets/css-asset-rebase.js",
64
67
  "config/vite/plugins/assets/css-asset-relativizer.js",
68
+ "config/vite/plugins/assets/development-source-maps.js",
65
69
  "config/vite/plugins/assets/mirror-components.js",
70
+ "config/vite/plugins/assets/output-freshness.js",
66
71
  "config/vite/plugins/assets/source-file-index.js",
72
+ "config/vite/plugins/assets/stable-watch-output.js",
73
+ "config/vite/plugins/assets/storybook-output.js",
67
74
  "config/vite/plugins/assets/svg-sprite.js",
68
75
  "config/vite/plugins/index.js",
69
76
  "config/vite/plugins/reporter/asset-resolver.js",
@@ -74,6 +81,7 @@
74
81
  "config/vite/plugins/reporter/render.js",
75
82
  "config/vite/plugins/reporter/sass-logger.js",
76
83
  "config/vite/plugins/reporter/source-roots.js",
84
+ "config/vite/plugins/reporter/strict-mode.js",
77
85
  "config/vite/plugins/reporter/verbosity.js",
78
86
  "config/vite/plugins/reporter/vite-logger.js",
79
87
  "config/vite/plugins/reporter/watch-mode.js",
@@ -85,23 +93,22 @@
85
93
  "config/vite/plugins/twig/virtual-twig-globs.js",
86
94
  "config/vite/plugins/twig/vituum-patch.js",
87
95
  "config/vite/plugins/yaml-module.js",
88
- "config/vite/plugins.js",
89
96
  "config/vite/project-config.js",
90
97
  "config/vite/project-extensions.js",
91
98
  "config/vite/project-structure.js",
99
+ "config/vite/utils/asset-roots.js",
100
+ "config/vite/utils/css-urls.js",
92
101
  "config/vite/utils/fs-safe.js",
93
102
  "config/vite/utils/lru.js",
94
103
  "config/vite/utils/package-version.js",
95
104
  "config/vite/utils/paths.js",
96
105
  "config/vite/utils/react-singleton.js",
106
+ "config/vite/utils/source-maps.js",
97
107
  "config/vite/vite.config.js",
98
108
  "scripts/a11y.js",
109
+ "scripts/audit-twig-stories.js",
99
110
  "scripts/audit.js",
100
111
  "scripts/audit/**/*.js",
101
- "!scripts/audit/**/*.test.js",
102
- "!scripts/audit/**/__snapshots__/**",
103
- "!scripts/audit/test-utils.js",
104
- "scripts/audit-twig-stories.js",
105
112
  "scripts/check-node-version.js",
106
113
  "scripts/inspect-components.js",
107
114
  "scripts/lib/cli.js",
@@ -139,7 +146,10 @@
139
146
  "src/storybook/twig/resolver.js",
140
147
  "src/storybook/twig/setup.js",
141
148
  "src/storybook/twig/source-function.js",
142
- "src/storybook/twig/source.js"
149
+ "src/storybook/twig/source.js",
150
+ "!scripts/audit/**/*.test.js",
151
+ "!scripts/audit/**/__snapshots__/**",
152
+ "!scripts/audit/test-utils.js"
143
153
  ],
144
154
  "exports": {
145
155
  ".": "./src/extensions/index.js",
@@ -157,7 +167,8 @@
157
167
  "./package.json": "./package.json"
158
168
  },
159
169
  "publishConfig": {
160
- "access": "public"
170
+ "access": "public",
171
+ "provenance": true
161
172
  },
162
173
  "config": {
163
174
  "scripts-prepend-node-path": "auto"
@@ -201,57 +212,55 @@
201
212
  "@emulsify/cli": "^2.2.0",
202
213
  "@eslint/js": "^9.39.5",
203
214
  "@mlnop/vite-plugin-sass-glob-import": "^6.2.0",
204
- "@storybook/addon-a11y": "^10.5.4",
205
- "@storybook/addon-links": "^10.5.4",
206
- "@storybook/addon-themes": "^10.5.4",
207
- "@storybook/react": "^10.5.4",
208
- "@storybook/react-vite": "^10.5.4",
215
+ "@storybook/addon-a11y": "^10.5.8",
216
+ "@storybook/addon-links": "^10.5.8",
217
+ "@storybook/addon-themes": "^10.5.8",
218
+ "@storybook/react": "^10.5.8",
219
+ "@storybook/react-vite": "^10.5.8",
209
220
  "@vituum/vite-plugin-twig": "^2.0.1",
210
221
  "autoprefixer": "^10.5.4",
211
- "axe-core": "^4.12.1",
222
+ "axe-core": "^4.13.0",
212
223
  "babel-preset-minify": "^0.5.2",
213
- "concurrently": "^10.0.4",
224
+ "concurrently": "^10.0.5",
214
225
  "eslint": "^9.39.5",
215
226
  "eslint-config-prettier": "^10.1.8",
216
227
  "eslint-plugin-import": "^2.32.0",
217
- "eslint-plugin-jest": "^29.16.0",
228
+ "eslint-plugin-jest": "^29.16.1",
218
229
  "eslint-plugin-prettier": "^5.5.6",
219
230
  "eslint-plugin-security": "^4.0.1",
220
- "eslint-plugin-storybook": "^10.5.4",
231
+ "eslint-plugin-storybook": "^10.5.8",
221
232
  "glob": "^13.0.6",
222
233
  "jest": "^30.4.2",
223
234
  "jest-environment-jsdom": "^30.4.1",
224
- "js-yaml": "^5.2.2",
235
+ "js-yaml": "^5.3.0",
225
236
  "normalize.css": "^8.0.1",
226
237
  "open-cli": "^9.0.0",
227
238
  "pa11y": "^9.1.1",
228
- "postcss": "^8.5.23",
239
+ "postcss": "^8.5.26",
229
240
  "postcss-scss": "^4.0.9",
230
241
  "sass": "^1.102.0",
231
- "storybook": "^10.5.4",
242
+ "storybook": "^10.5.8",
232
243
  "stylelint": "^17.14.1",
233
244
  "stylelint-config-standard-scss": "^17.0.0",
234
245
  "stylelint-prettier": "^5.0.3",
235
246
  "stylelint-selector-bem-pattern": "^5.0.0",
236
247
  "twig": "^3.0.0",
237
248
  "twig-drupal-filters": "^3.2.0",
238
- "vite": "^8.1.5"
249
+ "vite": "^8.2.1"
239
250
  },
240
251
  "devDependencies": {
241
- "@commitlint/cli": "^21.2.1",
242
- "@commitlint/config-conventional": "^21.2.0",
243
- "@semantic-release/changelog": "^7.0.0",
252
+ "@commitlint/cli": "^21.2.2",
253
+ "@commitlint/config-conventional": "^21.2.2",
244
254
  "@semantic-release/commit-analyzer": "^13.0.1",
245
- "@semantic-release/git": "^11.0.1",
246
255
  "@semantic-release/github": "^12.0.9",
247
256
  "@semantic-release/npm": "^13.1.5",
248
257
  "@semantic-release/release-notes-generator": "^14.1.1",
249
258
  "husky": "^9.1.7",
250
- "lint-staged": "^17.2.0",
251
- "puppeteer": "^25.3.0",
259
+ "lint-staged": "^17.3.0",
260
+ "puppeteer": "^25.7.0",
252
261
  "react": "^19.2.8",
253
262
  "react-dom": "^19.2.8",
254
- "semantic-release": "^25.0.8"
263
+ "semantic-release": "^25.0.9"
255
264
  },
256
265
  "peerDependencies": {
257
266
  "react": "^18.0.0 || ^19.0.0",
@@ -268,6 +277,8 @@
268
277
  "fsevents@2.3.3": true,
269
278
  "puppeteer@24.43.1": true,
270
279
  "unrs-resolver@1.12.2": true,
271
- "puppeteer@25.4.0": true
280
+ "puppeteer@25.4.0": true,
281
+ "puppeteer@25.5.0": true,
282
+ "puppeteer@25.7.0": true
272
283
  }
273
284
  }