@@ -11,7 +11,10 @@ import { resolve } from 'path';
1111import fs from 'fs' ;
1212import path from 'path' ;
1313import { fileURLToPath } from 'url' ;
14- import configOverrides from '../../../../config/emulsify-core/storybook/main.js' ;
14+ import { createRequire } from 'module' ;
15+ import extendWebpackConfig from './webpack.config.js' ;
16+
17+ const require = createRequire ( import . meta. url ) ;
1518
1619/**
1720 * The full path to the current file (ESM compatible).
@@ -23,26 +26,70 @@ const _filename = fileURLToPath(import.meta.url);
2326 * The directory name of the current module file.
2427 * @type {string }
2528 */
26- const _dirname = path . dirname ( _filename ) ;
29+ const _dirname = path . dirname ( _filename ) ;
30+
31+ /**
32+ * Migrate the consumer Storybook theme import from "@storybook/theming" to
33+ * "storybook/theming" when needed.
34+ *
35+ * This runs opportunistically during startup and never throws so Storybook
36+ * startup is resilient across all projects.
37+ */
38+ const migrateConsumerThemeImport = ( ) => {
39+ try {
40+ const themeConfigPath = resolve (
41+ _dirname ,
42+ '../../../../config/emulsify-core/storybook/theme.js' ,
43+ ) ;
44+
45+ if ( ! fs . existsSync ( themeConfigPath ) ) {
46+ return ;
47+ }
48+
49+ const originalThemeConfig = fs . readFileSync ( themeConfigPath , 'utf8' ) ;
50+
51+ if ( ! originalThemeConfig . includes ( '@storybook/theming' ) ) {
52+ return ;
53+ }
54+
55+ const migratedThemeConfig = originalThemeConfig . replace (
56+ / ( [ ' " ] ) @ s t o r y b o o k \/ t h e m i n g \1/ g,
57+ '$1storybook/theming$1' ,
58+ ) ;
59+
60+ if ( migratedThemeConfig !== originalThemeConfig ) {
61+ fs . writeFileSync ( themeConfigPath , migratedThemeConfig , 'utf8' ) ;
62+ }
63+ } catch {
64+ // Ignore migration failures so Storybook startup is never blocked.
65+ }
66+ } ;
67+
68+ migrateConsumerThemeImport ( ) ;
2769
2870/**
2971 * Safely apply any user-provided overrides or fall back to an empty object.
3072 * @type {object }
3173 */
32- const safeConfigOverrides = configOverrides || { } ;
74+ const safeConfigOverrides = ( ( ) => {
75+ try {
76+ const overridesModule = require ( '../../../../config/emulsify-core/storybook/main.js' ) ;
77+ return overridesModule . default || overridesModule || { } ;
78+ } catch {
79+ return { } ;
80+ }
81+ } ) ( ) ;
3382
3483/**
3584 * Primary Storybook configuration object.
36- * @type {import('@ storybook/core-common ').StorybookConfig }
85+ * @type {import('storybook/internal/types ').StorybookConfig }
3786 */
3887const config = {
3988 /**
4089 * Patterns for locating story files under src or components directories.
4190 * @type {string[] }
4291 */
43- stories : [
44- '../../../../(src|components)/**/*.stories.@(js|jsx|ts|tsx)' ,
45- ] ,
92+ stories : [ '../../../../@(src|components)/**/*.stories.@(js|jsx|ts|tsx)' ] ,
4693
4794 /**
4895 * Directories to serve as static assets in the Storybook build.
@@ -52,35 +99,39 @@ const config = {
5299 '../../../../assets/images' ,
53100 '../../../../assets/icons' ,
54101 '../../../../dist' ,
102+ '../../../../assets/videos' ,
55103 ] ,
56104
57105 /**
58106 * List of Storybook addons to enable various features.
59107 * @type {string[] }
60108 */
61109 addons : [
62- '../../../@storybook/addon-a11y' ,
63- '../../../@storybook/addon-links' ,
64- '../../../@storybook/addon-essentials' ,
65- '../../../@storybook/addon-themes' ,
66- '../../../@storybook/addon-styling-webpack' ,
110+ '@storybook/addon-a11y' ,
111+ '@storybook/addon-links' ,
112+ '@storybook/addon-themes' ,
113+ '@storybook/addon-styling-webpack' ,
67114 ] ,
68115
69116 /**
70117 * Core builder configuration for Storybook.
71- * @type {{builder: string, disableTelemetry: boolean} }
118+ * Storybook 9 splits the HTML renderer from the webpack builder, so the
119+ * builder must be declared explicitly instead of relying on html-webpack5.
120+ * @type {{builder: {name: string}, disableTelemetry: boolean} }
72121 */
73122 core : {
74- builder : 'webpack5' ,
123+ builder : {
124+ name : '@storybook/builder-webpack5' ,
125+ } ,
75126 disableTelemetry : true ,
76127 } ,
77128
78129 /**
79- * Framework specification for Storybook ( HTML + Webpack5) .
130+ * Framework specification for Storybook's HTML renderer .
80131 * @type {{name: string, options: object} }
81132 */
82133 framework : {
83- name : '@storybook/html -webpack5' ,
134+ name : '@storybook/server -webpack5' ,
84135 options : { } ,
85136 } ,
86137
@@ -205,7 +256,7 @@ const config = {
205256 // load external manager-head.html if present
206257 const externalManagerHeadPath = resolve (
207258 _dirname ,
208- '../../../../config/emulsify-core/storybook/manager-head.html'
259+ '../../../../config/emulsify-core/storybook/manager-head.html' ,
209260 ) ;
210261 let externalManagerHtml = '' ;
211262 if ( fs . existsSync ( externalManagerHeadPath ) ) {
@@ -225,7 +276,7 @@ ${externalManagerHtml}`;
225276 previewHead : ( head ) => {
226277 const externalHeadPath = resolve (
227278 _dirname ,
228- '../../../../config/emulsify-core/storybook/preview-head.html'
279+ '../../../../config/emulsify-core/storybook/preview-head.html' ,
229280 ) ;
230281
231282 let externalHtml = '' ;
@@ -237,6 +288,16 @@ ${externalManagerHtml}`;
237288${ externalHtml } `;
238289 } ,
239290
291+ /**
292+ * Forward Storybook 9's webpack hook to the existing shared webpack helper so
293+ * custom Twig, Sass, YAML, and resolver behavior still applies.
294+ * @param {object } storybookConfig - Storybook's generated webpack config.
295+ * @param {object } options - Storybook webpack hook options.
296+ * @returns {Promise<object> } The merged webpack config.
297+ */
298+ webpackFinal : async ( storybookConfig , options ) =>
299+ extendWebpackConfig ( { config : storybookConfig , ...options } ) ,
300+
240301 // Merge in user overrides without modifying original logic
241302 ...safeConfigOverrides ,
242303} ;
0 commit comments