@emulsify/core 3.4.1 → 4.0.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/.cli/init.js +40 -31
- package/.storybook/_drupal.js +129 -8
- package/.storybook/css-components.js +13 -0
- package/.storybook/css-dist.js +5 -0
- package/.storybook/emulsifyTheme.js +10 -7
- package/.storybook/main.js +417 -65
- package/.storybook/manager.js +11 -18
- package/.storybook/preview.js +93 -37
- package/.storybook/utils.js +70 -69
- package/README.md +110 -59
- package/config/.stylelintrc.json +2 -6
- package/config/a11y.config.js +9 -5
- package/config/babel.config.js +5 -0
- package/config/eslint.config.js +6 -3
- package/config/postcss.config.js +5 -0
- package/config/vite/entries.js +227 -0
- package/config/vite/environment.js +39 -0
- package/config/vite/platforms.js +70 -0
- package/config/vite/plugins/copy-src-assets.js +76 -0
- package/config/vite/plugins/copy-twig-files.js +84 -0
- package/config/vite/plugins/css-asset-relativizer.js +40 -0
- package/config/vite/plugins/index.js +105 -0
- package/config/vite/plugins/mirror-components.js +358 -0
- package/config/vite/plugins/require-context.js +311 -0
- package/config/vite/plugins/source-file-index.js +184 -0
- package/config/vite/plugins/svg-sprite.js +117 -0
- package/config/vite/plugins/twig-extension-installers.js +36 -0
- package/config/vite/plugins/twig-module.js +1251 -0
- package/config/vite/plugins/virtual-twig-asset-sources.js +404 -0
- package/config/vite/plugins/virtual-twig-globs.js +136 -0
- package/config/vite/plugins/vituum-patch.js +167 -0
- package/config/vite/plugins/yaml-module.js +133 -0
- package/config/vite/plugins.js +12 -0
- package/config/vite/project-config.js +192 -0
- package/config/vite/project-extensions.js +177 -0
- package/config/vite/project-structure.js +447 -0
- package/config/vite/twig-extensions.js +109 -0
- package/config/vite/utils/fs-safe.js +66 -0
- package/config/vite/utils/paths.js +40 -0
- package/config/vite/utils/react-singleton.js +85 -0
- package/config/vite/utils/unique.js +36 -0
- package/config/vite/vite.config.js +161 -0
- package/package.json +168 -88
- package/scripts/a11y.js +70 -16
- package/scripts/audit-twig-stories.js +378 -0
- package/scripts/audit.js +1602 -0
- package/scripts/check-node-version.js +18 -0
- package/scripts/loadYaml.js +5 -1
- package/src/extensions/index.js +8 -0
- package/src/extensions/react/index.js +12 -0
- package/src/extensions/react/register.js +45 -0
- package/src/extensions/shared/attributes.js +308 -0
- package/src/extensions/shared/html.js +41 -0
- package/src/extensions/shared/lists.js +38 -0
- package/src/extensions/shared/object.js +22 -0
- package/src/extensions/twig/function-map.js +20 -0
- package/src/extensions/twig/functions/add-attributes.js +39 -0
- package/src/extensions/twig/functions/bem.js +166 -0
- package/src/extensions/twig/index.js +13 -0
- package/src/extensions/twig/register.js +52 -0
- package/src/extensions/twig/tag-map.js +16 -0
- package/src/extensions/twig/tags/switch.js +266 -0
- package/src/storybook/index.js +14 -0
- package/src/storybook/main-config.js +132 -0
- package/src/storybook/platform-behaviors.js +60 -0
- package/src/storybook/preview-parameters.js +81 -0
- package/src/storybook/render-twig.js +295 -0
- package/src/storybook/twig/drupal-filters.js +7 -0
- package/src/storybook/twig/include-function.js +109 -0
- package/src/storybook/twig/include.js +28 -0
- package/src/storybook/twig/reference-paths.js +294 -0
- package/src/storybook/twig/resolver.js +318 -0
- package/src/storybook/twig/setup.js +39 -0
- package/src/storybook/twig/source-events.js +5 -0
- package/src/storybook/twig/source-extensions.js +24 -0
- package/src/storybook/twig/source-function.js +239 -0
- package/src/storybook/twig/source.js +39 -0
- package/.all-contributorsrc +0 -45
- package/.editorconfig +0 -5
- package/.github/ISSUE_TEMPLATE/BUG_REPORT_TEMPLATE.md +0 -18
- package/.github/ISSUE_TEMPLATE/FEATURE_REQUEST_TEMPLATE.md +0 -11
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -19
- package/.github/dependabot.yml +0 -6
- package/.github/workflows/addtoprojects.yml +0 -21
- package/.github/workflows/contributors.yml +0 -37
- package/.github/workflows/lint.yml +0 -22
- package/.github/workflows/semantic-release.yml +0 -24
- package/.husky/commit-msg +0 -2
- package/.husky/pre-commit +0 -2
- package/.nvmrc +0 -1
- package/.prettierignore +0 -4
- package/.storybook/polyfills/twig-include.js +0 -36
- package/.storybook/polyfills/twig-resolver.js +0 -68
- package/.storybook/polyfills/twig-source.js +0 -54
- package/.storybook/webpack.config.js +0 -193
- package/CODE_OF_CONDUCT.md +0 -56
- package/commitlint.config.js +0 -5
- package/config/jest.config.js +0 -19
- package/config/webpack/app.js +0 -1
- package/config/webpack/loaders.js +0 -167
- package/config/webpack/optimizers.js +0 -17
- package/config/webpack/plugins.js +0 -283
- package/config/webpack/resolves.js +0 -157
- package/config/webpack/sdc-loader.js +0 -16
- package/config/webpack/webpack.common.js +0 -268
- package/config/webpack/webpack.dev.js +0 -41
- package/config/webpack/webpack.prod.js +0 -6
- package/release.config.cjs +0 -30
- package/scripts/a11y.test.js +0 -172
- package/scripts/loadYaml.test.js +0 -30
package/.storybook/main.js
CHANGED
|
@@ -1,17 +1,62 @@
|
|
|
1
|
-
// .storybook/main.js
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* Storybook
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Central Storybook configuration for Emulsify.
|
|
3
|
+
*
|
|
4
|
+
* This shared config defines the default Storybook behavior for consumers of
|
|
5
|
+
* the package, then lets a project layer local overrides on top at the end.
|
|
6
|
+
* The main custom behavior here is:
|
|
7
|
+
* - injecting manager/preview head markup
|
|
8
|
+
* - adapting the shared Vite config for Storybook
|
|
9
|
+
* - wiring Twig template discovery into the Storybook build
|
|
10
|
+
*
|
|
7
11
|
* @module .storybook/main
|
|
8
12
|
*/
|
|
9
13
|
|
|
10
|
-
import { resolve } from 'path';
|
|
11
14
|
import fs from 'fs';
|
|
12
|
-
import path from 'path';
|
|
13
|
-
import { fileURLToPath } from 'url';
|
|
14
|
-
import
|
|
15
|
+
import path, { resolve } from 'path';
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
17
|
+
import viteConfig from '../config/vite/vite.config.js';
|
|
18
|
+
import { resolveEnvironment } from '../config/vite/environment.js';
|
|
19
|
+
import {
|
|
20
|
+
mergeReactSingletonOptimizeDeps,
|
|
21
|
+
mergeReactSingletonResolve,
|
|
22
|
+
} from '../config/vite/utils/react-singleton.js';
|
|
23
|
+
import { twigExtensionModuleSpecifiers } from '../config/vite/twig-extensions.js';
|
|
24
|
+
import {
|
|
25
|
+
applyStorybookConfigOverrides,
|
|
26
|
+
normalizeStorybookConfigOverrideModule,
|
|
27
|
+
} from '../src/storybook/main-config.js';
|
|
28
|
+
|
|
29
|
+
// Twig glob maps are provided by config/vite/plugins/virtual-twig-globs.js.
|
|
30
|
+
|
|
31
|
+
const twigVirtualModuleIds = [
|
|
32
|
+
'virtual:emulsify-twig-globs',
|
|
33
|
+
'virtual:emulsify-twig-asset-sources',
|
|
34
|
+
'virtual:emulsify-twig-extension-installers',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const twigRuntimeOptimizeDepsExclude = [
|
|
38
|
+
...twigVirtualModuleIds,
|
|
39
|
+
'@emulsify/core/storybook/twig/source-function',
|
|
40
|
+
'@emulsify/core/storybook/twig/source',
|
|
41
|
+
'@emulsify/core/storybook/twig/resolver',
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Minimal subset of the resolved Emulsify environment used by this file.
|
|
46
|
+
*
|
|
47
|
+
* @typedef {object} StorybookEnvironment
|
|
48
|
+
* @property {string} projectDir - Absolute path to the consuming project root.
|
|
49
|
+
* @property {boolean} [structureOverrides] - Whether custom structure roots are enabled.
|
|
50
|
+
* @property {string[]} [structureRoots] - Absolute component root paths when overrides are active.
|
|
51
|
+
* @property {string[]} [componentRoots] - Absolute component roots in resolution order.
|
|
52
|
+
* @property {Record<string, string>} [namespaceRoots] - Twig namespace roots.
|
|
53
|
+
* @property {string} [srcDir] - Absolute path to the project's `src` directory when present.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Storybook config type used for editor hints in this plain JS file.
|
|
58
|
+
* @typedef {import('@storybook/core-common').StorybookConfig} StorybookConfig
|
|
59
|
+
*/
|
|
15
60
|
|
|
16
61
|
/**
|
|
17
62
|
* The full path to the current file (ESM compatible).
|
|
@@ -23,69 +68,230 @@ const _filename = fileURLToPath(import.meta.url);
|
|
|
23
68
|
* The directory name of the current module file.
|
|
24
69
|
* @type {string}
|
|
25
70
|
*/
|
|
26
|
-
const _dirname
|
|
71
|
+
const _dirname = path.dirname(_filename);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Reads an optional HTML fragment relative to this config file.
|
|
75
|
+
*
|
|
76
|
+
* Missing files are treated as empty content so downstream projects can opt in
|
|
77
|
+
* to extra markup without making Storybook fail on startup.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} relativePath - Relative path from this file to the HTML fragment.
|
|
80
|
+
* @returns {string} File contents when the fragment exists, otherwise an empty string.
|
|
81
|
+
*/
|
|
82
|
+
function readOptionalHtmlFragment(relativePath) {
|
|
83
|
+
const fragmentPath = resolve(_dirname, relativePath);
|
|
84
|
+
|
|
85
|
+
if (!fs.existsSync(fragmentPath)) {
|
|
86
|
+
return '';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return fs.readFileSync(fragmentPath, 'utf8');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Keeps Storybook static directory config aligned to the consuming project.
|
|
94
|
+
*
|
|
95
|
+
* Storybook errors when a declared static directory is absent, so only expose
|
|
96
|
+
* project asset directories that exist in the current workspace.
|
|
97
|
+
*
|
|
98
|
+
* @param {Array<string|{from: string, to: string}>} staticDirs - Static directory entries.
|
|
99
|
+
* @returns {Array<string|{from: string, to: string}>} Existing static directory entries.
|
|
100
|
+
*/
|
|
101
|
+
function existingStaticDirs(staticDirs) {
|
|
102
|
+
return staticDirs.filter((staticDir) => {
|
|
103
|
+
const directory =
|
|
104
|
+
typeof staticDir === 'string' ? staticDir : staticDir.from;
|
|
105
|
+
|
|
106
|
+
return directory && fs.existsSync(directory);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Merge Storybook and project optimizeDeps excludes with Core Twig runtime IDs.
|
|
112
|
+
*
|
|
113
|
+
* Storybook's dependency optimizer runs before normal Vite virtual module
|
|
114
|
+
* resolution. Core Twig runtime modules import virtual IDs that must stay in
|
|
115
|
+
* the Vite module graph so Emulsify's virtual plugins can resolve them.
|
|
116
|
+
*
|
|
117
|
+
* @param {...string[]} excludeLists - Existing optimizeDeps exclude arrays.
|
|
118
|
+
* @returns {string[]} Merged exclude list.
|
|
119
|
+
*/
|
|
120
|
+
function mergeTwigRuntimeOptimizeDepsExcludes(...excludeLists) {
|
|
121
|
+
return Array.from(
|
|
122
|
+
new Set([
|
|
123
|
+
...excludeLists.flatMap((excludeList) =>
|
|
124
|
+
Array.isArray(excludeList) ? excludeList : [],
|
|
125
|
+
),
|
|
126
|
+
...twigRuntimeOptimizeDepsExclude,
|
|
127
|
+
]),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Keep Emulsify Twig virtual imports out of Storybook dependency prebundles.
|
|
133
|
+
*
|
|
134
|
+
* @returns {import('esbuild').Plugin} Esbuild plugin for optimizeDeps.
|
|
135
|
+
*/
|
|
136
|
+
function makeTwigVirtualModuleOptimizerPlugin() {
|
|
137
|
+
return {
|
|
138
|
+
name: 'emulsify-twig-virtual-modules',
|
|
139
|
+
setup(build) {
|
|
140
|
+
build.onResolve(
|
|
141
|
+
{ filter: /^virtual:emulsify-twig-(?:globs|asset-sources)$/ },
|
|
142
|
+
(args) => ({
|
|
143
|
+
path: args.path,
|
|
144
|
+
external: true,
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Reads optional project-level Storybook overrides.
|
|
153
|
+
*
|
|
154
|
+
* Downstream projects can provide this file, but the shared config also needs
|
|
155
|
+
* to load in package-level smoke tests where that project file is absent.
|
|
156
|
+
*
|
|
157
|
+
* @returns {Promise<{ config: object|Function, extendConfig?: Function, replaceAddons: boolean }>}
|
|
158
|
+
* Consumer overrides.
|
|
159
|
+
*/
|
|
160
|
+
async function loadConfigOverrides() {
|
|
161
|
+
const overridePath = resolve(
|
|
162
|
+
_dirname,
|
|
163
|
+
'../../../../config/emulsify-core/storybook/main.js',
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (!fs.existsSync(overridePath)) {
|
|
167
|
+
return normalizeStorybookConfigOverrideModule();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const configOverrides = await import(pathToFileURL(overridePath).href);
|
|
171
|
+
return normalizeStorybookConfigOverrideModule(configOverrides);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Builds Storybook story globs from normalized project roots.
|
|
176
|
+
*
|
|
177
|
+
* Stories remain colocated with components, whether the project uses the
|
|
178
|
+
* recommended `src/components` layout, legacy root `components`, or explicit
|
|
179
|
+
* structure implementation directories.
|
|
180
|
+
*
|
|
181
|
+
* @param {StorybookEnvironment} env - Resolved project paths used by Storybook.
|
|
182
|
+
* @returns {string[]} Storybook story globs.
|
|
183
|
+
*/
|
|
184
|
+
function buildStoryGlobs(env) {
|
|
185
|
+
if (Array.isArray(env.projectStructure?.storyRoots)) {
|
|
186
|
+
return env.projectStructure.storyRoots.map((root) =>
|
|
187
|
+
path
|
|
188
|
+
.resolve(root, '**/*.stories.@(js|jsx|ts|tsx)')
|
|
189
|
+
.split(path.sep)
|
|
190
|
+
.join('/'),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const roots =
|
|
195
|
+
env.structureOverrides &&
|
|
196
|
+
Array.isArray(env.structureRoots) &&
|
|
197
|
+
env.structureRoots.length
|
|
198
|
+
? env.structureRoots
|
|
199
|
+
: [
|
|
200
|
+
path.resolve(env.projectDir, 'src'),
|
|
201
|
+
path.resolve(env.projectDir, 'components'),
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
return Array.from(new Set(roots.filter(Boolean))).map((root) =>
|
|
205
|
+
path
|
|
206
|
+
.resolve(root, '**/*.stories.@(js|jsx|ts|tsx)')
|
|
207
|
+
.split(path.sep)
|
|
208
|
+
.join('/'),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
27
211
|
|
|
28
212
|
/**
|
|
29
213
|
* Safely apply any user-provided overrides or fall back to an empty object.
|
|
30
214
|
* @type {object}
|
|
31
215
|
*/
|
|
32
|
-
const safeConfigOverrides =
|
|
216
|
+
const safeConfigOverrides = await loadConfigOverrides();
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Environment details shared across this Storybook config load.
|
|
220
|
+
* @type {StorybookEnvironment}
|
|
221
|
+
*/
|
|
222
|
+
const resolvedStorybookEnv = resolveEnvironment();
|
|
33
223
|
|
|
34
224
|
/**
|
|
35
225
|
* Primary Storybook configuration object.
|
|
36
|
-
* @type {
|
|
226
|
+
* @type {StorybookConfig}
|
|
37
227
|
*/
|
|
38
|
-
const
|
|
228
|
+
const baseConfig = {
|
|
39
229
|
/**
|
|
40
|
-
*
|
|
230
|
+
* Discover stories from both supported component roots.
|
|
231
|
+
*
|
|
232
|
+
* This shared config supports projects that keep stories under `src` as well
|
|
233
|
+
* as projects that expose a top-level `components` directory.
|
|
234
|
+
*
|
|
41
235
|
* @type {string[]}
|
|
42
236
|
*/
|
|
43
|
-
stories:
|
|
44
|
-
'../../../../(src|components)/**/*.stories.@(js|jsx|ts|tsx)',
|
|
45
|
-
],
|
|
237
|
+
stories: buildStoryGlobs(resolvedStorybookEnv),
|
|
46
238
|
|
|
47
239
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
240
|
+
* Mount shared assets into Storybook's static file server.
|
|
241
|
+
*
|
|
242
|
+
* Anything referenced by URL inside stories should live in one of these
|
|
243
|
+
* directories so it works in both `storybook dev` and static builds.
|
|
244
|
+
*
|
|
245
|
+
* @type {Array<string|{from: string, to: string}>}
|
|
50
246
|
*/
|
|
51
247
|
staticDirs: [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
248
|
+
...existingStaticDirs([
|
|
249
|
+
{
|
|
250
|
+
from: path.resolve(process.cwd(), 'assets'),
|
|
251
|
+
to: '/assets',
|
|
252
|
+
},
|
|
253
|
+
path.resolve(process.cwd(), 'dist'),
|
|
254
|
+
]),
|
|
55
255
|
],
|
|
56
256
|
|
|
57
257
|
/**
|
|
58
|
-
*
|
|
258
|
+
* Enable the default addon set used by Emulsify.
|
|
259
|
+
*
|
|
260
|
+
* `a11y` adds accessibility tooling, `links` supports story-to-story
|
|
261
|
+
* navigation, and `themes` exposes theme switching in the Storybook UI.
|
|
262
|
+
*
|
|
59
263
|
* @type {string[]}
|
|
60
264
|
*/
|
|
61
265
|
addons: [
|
|
62
|
-
'
|
|
63
|
-
'
|
|
64
|
-
'
|
|
65
|
-
'../../../@storybook/addon-themes',
|
|
66
|
-
'../../../@storybook/addon-styling-webpack',
|
|
266
|
+
'@storybook/addon-a11y',
|
|
267
|
+
'@storybook/addon-links',
|
|
268
|
+
'@storybook/addon-themes',
|
|
67
269
|
],
|
|
68
270
|
|
|
69
271
|
/**
|
|
70
|
-
*
|
|
272
|
+
* Force the Vite builder and disable Storybook telemetry for shared usage.
|
|
71
273
|
* @type {{builder: string, disableTelemetry: boolean}}
|
|
72
274
|
*/
|
|
73
275
|
core: {
|
|
74
|
-
builder: '
|
|
276
|
+
builder: '@storybook/builder-vite',
|
|
75
277
|
disableTelemetry: true,
|
|
76
278
|
},
|
|
77
279
|
|
|
78
280
|
/**
|
|
79
|
-
*
|
|
281
|
+
* Tell Storybook to use the React + Vite framework package.
|
|
80
282
|
* @type {{name: string, options: object}}
|
|
81
283
|
*/
|
|
82
284
|
framework: {
|
|
83
|
-
name: '@storybook/
|
|
285
|
+
name: '@storybook/react-vite',
|
|
84
286
|
options: {},
|
|
85
287
|
},
|
|
86
288
|
|
|
87
289
|
/**
|
|
88
|
-
*
|
|
290
|
+
* Disable automatic docs generation.
|
|
291
|
+
*
|
|
292
|
+
* Storybook will only render documentation pages that are authored
|
|
293
|
+
* explicitly instead of generating them from component metadata.
|
|
294
|
+
*
|
|
89
295
|
* @type {{autodocs: boolean}}
|
|
90
296
|
*/
|
|
91
297
|
docs: {
|
|
@@ -93,13 +299,17 @@ const config = {
|
|
|
93
299
|
},
|
|
94
300
|
|
|
95
301
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
302
|
+
* Appends Emulsify branding to the Storybook manager UI.
|
|
303
|
+
*
|
|
304
|
+
* This only affects Storybook's chrome, such as the sidebar, toolbar, and
|
|
305
|
+
* addon panels. It does not affect the iframe where stories actually render.
|
|
306
|
+
*
|
|
307
|
+
* @param {string} head - Existing manager head markup provided by Storybook.
|
|
308
|
+
* @returns {string} Manager head markup with Emulsify additions appended.
|
|
100
309
|
*/
|
|
101
310
|
managerHead: (head) => {
|
|
102
|
-
// inline
|
|
311
|
+
// Keep the manager styling inline so consumers inherit the branded UI
|
|
312
|
+
// without having to maintain a separate manager-only stylesheet.
|
|
103
313
|
const inlineStyles = `
|
|
104
314
|
<style>
|
|
105
315
|
:root {
|
|
@@ -116,7 +326,7 @@ const config = {
|
|
|
116
326
|
--colors-purple: #8B1E7E;
|
|
117
327
|
}
|
|
118
328
|
.sidebar-container {
|
|
119
|
-
background:
|
|
329
|
+
background-color: var(--colors-emulsify-blue-900);
|
|
120
330
|
}
|
|
121
331
|
.sidebar-container .sidebar-subheading {
|
|
122
332
|
color: var(--colors-emulsify-blue-200);
|
|
@@ -126,7 +336,7 @@ const config = {
|
|
|
126
336
|
.sidebar-container .sidebar-subheading button:focus {
|
|
127
337
|
color: var(--colors-emulsify-blue-300);
|
|
128
338
|
}
|
|
129
|
-
|
|
339
|
+
/* Triangle icon. */
|
|
130
340
|
.sidebar-container .sidebar-subheading button span {
|
|
131
341
|
color: var(--colors-emulsify-blue-300);
|
|
132
342
|
}
|
|
@@ -201,44 +411,186 @@ const config = {
|
|
|
201
411
|
}
|
|
202
412
|
</style>
|
|
203
413
|
`;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const externalManagerHeadPath = resolve(
|
|
207
|
-
_dirname,
|
|
208
|
-
'../../../../config/emulsify-core/storybook/manager-head.html'
|
|
414
|
+
const externalManagerHtml = readOptionalHtmlFragment(
|
|
415
|
+
'../../../../config/emulsify-core/storybook/manager-head.html',
|
|
209
416
|
);
|
|
210
|
-
let externalManagerHtml = '';
|
|
211
|
-
if (fs.existsSync(externalManagerHeadPath)) {
|
|
212
|
-
externalManagerHtml = fs.readFileSync(externalManagerHeadPath, 'utf8');
|
|
213
|
-
}
|
|
214
417
|
|
|
215
418
|
return `${head}
|
|
216
|
-
${inlineStyles}
|
|
217
|
-
${externalManagerHtml}`;
|
|
419
|
+
${inlineStyles}
|
|
420
|
+
${externalManagerHtml}`;
|
|
218
421
|
},
|
|
219
422
|
|
|
220
423
|
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
424
|
+
* Appends project-level head markup to the story preview iframe.
|
|
425
|
+
*
|
|
426
|
+
* This is the place for preview-only fonts, scripts, or meta tags that the
|
|
427
|
+
* rendered component output depends on.
|
|
428
|
+
*
|
|
429
|
+
* @param {string} head - Existing preview head markup provided by Storybook.
|
|
430
|
+
* @returns {string} Preview head markup with optional project HTML appended.
|
|
224
431
|
*/
|
|
225
432
|
previewHead: (head) => {
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
'../../../../config/emulsify-core/storybook/preview-head.html'
|
|
433
|
+
const externalHtml = readOptionalHtmlFragment(
|
|
434
|
+
'../../../../config/emulsify-core/storybook/preview-head.html',
|
|
229
435
|
);
|
|
230
436
|
|
|
231
|
-
let externalHtml = '';
|
|
232
|
-
if (fs.existsSync(externalHeadPath)) {
|
|
233
|
-
externalHtml = fs.readFileSync(externalHeadPath, 'utf8');
|
|
234
|
-
}
|
|
235
|
-
|
|
236
437
|
return `${head}
|
|
237
|
-
${externalHtml}`;
|
|
438
|
+
${externalHtml}`;
|
|
238
439
|
},
|
|
239
440
|
|
|
240
|
-
|
|
241
|
-
|
|
441
|
+
/**
|
|
442
|
+
* Merges Storybook's generated Vite config with Emulsify's shared Vite config.
|
|
443
|
+
*
|
|
444
|
+
* Storybook supplies a baseline config, but Emulsify still needs to expose
|
|
445
|
+
* the resolved environment, expand filesystem access, and expose the Twig
|
|
446
|
+
* virtual glob module used by the runtime resolver.
|
|
447
|
+
*
|
|
448
|
+
* @param {import('vite').UserConfig} config - Storybook's generated Vite config.
|
|
449
|
+
* @returns {Promise<import('vite').UserConfig>} Final Vite config used by Storybook.
|
|
450
|
+
*/
|
|
451
|
+
async viteFinal(config) {
|
|
452
|
+
const { mergeConfig } = await import('vite');
|
|
453
|
+
/** @type {StorybookEnvironment} */
|
|
454
|
+
const env = resolvedStorybookEnv;
|
|
455
|
+
|
|
456
|
+
// Keep using the `serve` branch of the shared Vite config here. Storybook
|
|
457
|
+
// has historically consumed that branch, while `mode` still reflects
|
|
458
|
+
// whether Storybook is running in development or production.
|
|
459
|
+
const mode = config?.mode || 'development';
|
|
460
|
+
const baseViteConfig =
|
|
461
|
+
typeof viteConfig === 'function'
|
|
462
|
+
? await viteConfig({ command: 'serve', mode })
|
|
463
|
+
: viteConfig;
|
|
464
|
+
const existingDefine = (config && config.define) || {};
|
|
465
|
+
const viteDefine = (baseViteConfig && baseViteConfig.define) || {};
|
|
466
|
+
|
|
467
|
+
// Allow Storybook's dev server to read component sources from the project
|
|
468
|
+
// root and any structure override paths used by Emulsify consumers.
|
|
469
|
+
const allowList = new Set([
|
|
470
|
+
...(config?.server?.fs?.allow || []),
|
|
471
|
+
env.projectDir,
|
|
472
|
+
path.resolve(env.projectDir, 'src'),
|
|
473
|
+
path.resolve(env.projectDir, 'components'),
|
|
474
|
+
path.resolve(env.projectDir, 'dist'),
|
|
475
|
+
...(Array.isArray(env.projectStructure?.sourceRoots)
|
|
476
|
+
? env.projectStructure.sourceRoots
|
|
477
|
+
: []),
|
|
478
|
+
...(Array.isArray(env.componentRoots) ? env.componentRoots : []),
|
|
479
|
+
...(Array.isArray(env.structureRoots) ? env.structureRoots : []),
|
|
480
|
+
...(env.namespaceRoots && typeof env.namespaceRoots === 'object'
|
|
481
|
+
? Object.values(env.namespaceRoots)
|
|
482
|
+
: []),
|
|
483
|
+
...(Array.isArray(env.projectStructure?.assetRoots)
|
|
484
|
+
? env.projectStructure.assetRoots
|
|
485
|
+
: []),
|
|
486
|
+
]);
|
|
487
|
+
|
|
488
|
+
// Twig files are loaded through custom resolvers/plugins, so they need to
|
|
489
|
+
// be treated as importable assets by Storybook's Vite pipeline.
|
|
490
|
+
const assetsInclude = Array.from(
|
|
491
|
+
new Set([
|
|
492
|
+
...(config.assetsInclude || []),
|
|
493
|
+
...(baseViteConfig.assetsInclude || []),
|
|
494
|
+
'**/*.twig',
|
|
495
|
+
]),
|
|
496
|
+
);
|
|
497
|
+
const optimizeDepsInclude = mergeReactSingletonOptimizeDeps(
|
|
498
|
+
baseViteConfig?.optimizeDeps?.include,
|
|
499
|
+
config?.optimizeDeps?.include,
|
|
500
|
+
[
|
|
501
|
+
'twig',
|
|
502
|
+
'@emulsify/core/extensions/twig',
|
|
503
|
+
...twigExtensionModuleSpecifiers(env),
|
|
504
|
+
],
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
const mergedConfig = mergeConfig(config, {
|
|
508
|
+
...baseViteConfig,
|
|
509
|
+
resolve: mergeReactSingletonResolve(baseViteConfig, config),
|
|
510
|
+
define: {
|
|
511
|
+
// Preserve shared and Storybook-provided constants, then publish the
|
|
512
|
+
// resolved Emulsify environment to client-side code.
|
|
513
|
+
...viteDefine,
|
|
514
|
+
...existingDefine,
|
|
515
|
+
__EMULSIFY_ENV__: JSON.stringify(env),
|
|
516
|
+
'globalThis.__EMULSIFY_ENV__': JSON.stringify(env),
|
|
517
|
+
},
|
|
518
|
+
server: {
|
|
519
|
+
...(baseViteConfig?.server || {}),
|
|
520
|
+
fs: {
|
|
521
|
+
allow: Array.from(allowList),
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
assetsInclude,
|
|
525
|
+
plugins: [...(baseViteConfig?.plugins || [])],
|
|
526
|
+
esbuild: {
|
|
527
|
+
// Some downstream code is authored as `.js` files containing JSX, so
|
|
528
|
+
// keep Storybook's esbuild settings aligned with the shared Vite config.
|
|
529
|
+
jsx: 'automatic',
|
|
530
|
+
loader: 'jsx',
|
|
531
|
+
include: /.*\.jsx?$/,
|
|
532
|
+
exclude: [],
|
|
533
|
+
},
|
|
534
|
+
optimizeDeps: {
|
|
535
|
+
...(baseViteConfig?.optimizeDeps || {}),
|
|
536
|
+
...(config?.optimizeDeps || {}),
|
|
537
|
+
include: optimizeDepsInclude,
|
|
538
|
+
exclude: mergeTwigRuntimeOptimizeDepsExcludes(
|
|
539
|
+
baseViteConfig?.optimizeDeps?.exclude,
|
|
540
|
+
config?.optimizeDeps?.exclude,
|
|
541
|
+
),
|
|
542
|
+
esbuildOptions: {
|
|
543
|
+
...(baseViteConfig?.optimizeDeps?.esbuildOptions || {}),
|
|
544
|
+
...(config?.optimizeDeps?.esbuildOptions || {}),
|
|
545
|
+
plugins: [
|
|
546
|
+
...(baseViteConfig?.optimizeDeps?.esbuildOptions?.plugins || []),
|
|
547
|
+
...(config?.optimizeDeps?.esbuildOptions?.plugins || []),
|
|
548
|
+
makeTwigVirtualModuleOptimizerPlugin(),
|
|
549
|
+
],
|
|
550
|
+
loader: {
|
|
551
|
+
...(baseViteConfig?.optimizeDeps?.esbuildOptions?.loader || {}),
|
|
552
|
+
...(config?.optimizeDeps?.esbuildOptions?.loader || {}),
|
|
553
|
+
// Pre-bundle `.js` dependencies with the JSX loader for packages
|
|
554
|
+
// that ship JSX without a `.jsx` extension.
|
|
555
|
+
'.js': 'jsx',
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
return {
|
|
562
|
+
...mergedConfig,
|
|
563
|
+
resolve: mergeReactSingletonResolve(mergedConfig),
|
|
564
|
+
optimizeDeps: {
|
|
565
|
+
...(mergedConfig.optimizeDeps || {}),
|
|
566
|
+
include: mergeReactSingletonOptimizeDeps(
|
|
567
|
+
mergedConfig.optimizeDeps?.include,
|
|
568
|
+
),
|
|
569
|
+
exclude: mergeTwigRuntimeOptimizeDepsExcludes(
|
|
570
|
+
mergedConfig.optimizeDeps?.exclude,
|
|
571
|
+
),
|
|
572
|
+
esbuildOptions: {
|
|
573
|
+
...(mergedConfig.optimizeDeps?.esbuildOptions || {}),
|
|
574
|
+
loader: {
|
|
575
|
+
...(mergedConfig.optimizeDeps?.esbuildOptions?.loader || {}),
|
|
576
|
+
'.js': 'jsx',
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
};
|
|
581
|
+
},
|
|
242
582
|
};
|
|
243
583
|
|
|
584
|
+
/**
|
|
585
|
+
* Primary Storybook configuration after project overrides have been applied.
|
|
586
|
+
* Project `addons` append to Emulsify defaults unless replacement is requested.
|
|
587
|
+
*
|
|
588
|
+
* @type {StorybookConfig}
|
|
589
|
+
*/
|
|
590
|
+
const config = await applyStorybookConfigOverrides(
|
|
591
|
+
baseConfig,
|
|
592
|
+
safeConfigOverrides,
|
|
593
|
+
{ env: resolvedStorybookEnv },
|
|
594
|
+
);
|
|
595
|
+
|
|
244
596
|
export default config;
|
package/.storybook/manager.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file Storybook manager bootstrap and theme selection.
|
|
3
|
+
*/
|
|
2
4
|
|
|
3
|
-
import { addons } from '
|
|
5
|
+
import { addons } from 'storybook/manager-api';
|
|
4
6
|
import emulsifyTheme from './emulsifyTheme';
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -9,37 +11,28 @@ import emulsifyTheme from './emulsifyTheme';
|
|
|
9
11
|
*/
|
|
10
12
|
import('../../../../config/emulsify-core/storybook/theme')
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
14
|
+
* Apply a project theme override when one exists.
|
|
15
|
+
*
|
|
13
16
|
* @param {{ default: object }} module - The imported theme module.
|
|
14
17
|
*/
|
|
15
18
|
.then(({ default: customTheme }) => {
|
|
16
|
-
|
|
17
|
-
* Determine if the imported theme object is empty or not.
|
|
18
|
-
* @type {boolean}
|
|
19
|
-
*/
|
|
19
|
+
// Empty override files should still fall back to the package theme.
|
|
20
20
|
const isEmptyObject =
|
|
21
21
|
!customTheme ||
|
|
22
|
-
(typeof customTheme === 'object' &&
|
|
22
|
+
(typeof customTheme === 'object' &&
|
|
23
|
+
Object.keys(customTheme).length === 0);
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
-
* Apply the chosen theme to Storybook’s manager UI configuration.
|
|
26
|
-
* @type {{ theme: object }}
|
|
27
|
-
*/
|
|
28
25
|
addons.setConfig({
|
|
29
26
|
theme: isEmptyObject ? emulsifyTheme : customTheme,
|
|
30
27
|
});
|
|
31
28
|
})
|
|
32
29
|
/**
|
|
33
|
-
*
|
|
30
|
+
* Fall back to the default theme when the project override is absent.
|
|
31
|
+
*
|
|
34
32
|
* @returns {void}
|
|
35
33
|
*/
|
|
36
34
|
.catch(() => {
|
|
37
35
|
addons.setConfig({
|
|
38
|
-
/**
|
|
39
|
-
* Fallback to the default Emulsify theme on import error.
|
|
40
|
-
* @type {{ theme: object }}
|
|
41
|
-
*/
|
|
42
36
|
theme: emulsifyTheme,
|
|
43
37
|
});
|
|
44
38
|
});
|
|
45
|
-
|