@emulsify/core 3.5.0 → 4.0.1
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 +9 -6
- package/.storybook/main.js +397 -106
- package/.storybook/manager.js +9 -16
- package/.storybook/preview.js +88 -110
- package/.storybook/utils.js +69 -74
- package/README.md +110 -59
- package/config/.stylelintrc.json +2 -6
- package/config/a11y.config.js +9 -5
- package/config/babel.config.js +6 -11
- package/config/eslint.config.js +31 -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 +164 -75
- 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 +95 -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 -40
- package/.storybook/polyfills/twig-resolver.js +0 -70
- package/.storybook/polyfills/twig-source.js +0 -65
- package/.storybook/webpack.config.js +0 -269
- 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 -26
- 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 -272
- 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,20 +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
|
|
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
|
+
*/
|
|
16
55
|
|
|
17
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Storybook config type used for editor hints in this plain JS file.
|
|
58
|
+
* @typedef {import('@storybook/core-common').StorybookConfig} StorybookConfig
|
|
59
|
+
*/
|
|
18
60
|
|
|
19
61
|
/**
|
|
20
62
|
* The full path to the current file (ESM compatible).
|
|
@@ -29,114 +71,227 @@ const _filename = fileURLToPath(import.meta.url);
|
|
|
29
71
|
const _dirname = path.dirname(_filename);
|
|
30
72
|
|
|
31
73
|
/**
|
|
32
|
-
*
|
|
33
|
-
* "storybook/theming" when needed.
|
|
74
|
+
* Reads an optional HTML fragment relative to this config file.
|
|
34
75
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
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.
|
|
37
81
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
82
|
+
function readOptionalHtmlFragment(relativePath) {
|
|
83
|
+
const fragmentPath = resolve(_dirname, relativePath);
|
|
84
|
+
|
|
85
|
+
if (!fs.existsSync(fragmentPath)) {
|
|
86
|
+
return '';
|
|
87
|
+
}
|
|
44
88
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
89
|
+
return fs.readFileSync(fragmentPath, 'utf8');
|
|
90
|
+
}
|
|
48
91
|
|
|
49
|
-
|
|
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;
|
|
50
105
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
106
|
+
return directory && fs.existsSync(directory);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
54
109
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
+
);
|
|
59
165
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
+
);
|
|
65
192
|
}
|
|
66
|
-
};
|
|
67
193
|
|
|
68
|
-
|
|
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
|
+
}
|
|
69
211
|
|
|
70
212
|
/**
|
|
71
213
|
* Safely apply any user-provided overrides or fall back to an empty object.
|
|
72
214
|
* @type {object}
|
|
73
215
|
*/
|
|
74
|
-
const safeConfigOverrides = (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
})();
|
|
216
|
+
const safeConfigOverrides = await loadConfigOverrides();
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Environment details shared across this Storybook config load.
|
|
220
|
+
* @type {StorybookEnvironment}
|
|
221
|
+
*/
|
|
222
|
+
const resolvedStorybookEnv = resolveEnvironment();
|
|
82
223
|
|
|
83
224
|
/**
|
|
84
225
|
* Primary Storybook configuration object.
|
|
85
|
-
* @type {
|
|
226
|
+
* @type {StorybookConfig}
|
|
86
227
|
*/
|
|
87
|
-
const
|
|
228
|
+
const baseConfig = {
|
|
88
229
|
/**
|
|
89
|
-
*
|
|
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
|
+
*
|
|
90
235
|
* @type {string[]}
|
|
91
236
|
*/
|
|
92
|
-
stories:
|
|
237
|
+
stories: buildStoryGlobs(resolvedStorybookEnv),
|
|
93
238
|
|
|
94
239
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
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}>}
|
|
97
246
|
*/
|
|
98
247
|
staticDirs: [
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
248
|
+
...existingStaticDirs([
|
|
249
|
+
{
|
|
250
|
+
from: path.resolve(process.cwd(), 'assets'),
|
|
251
|
+
to: '/assets',
|
|
252
|
+
},
|
|
253
|
+
path.resolve(process.cwd(), 'dist'),
|
|
254
|
+
]),
|
|
103
255
|
],
|
|
104
256
|
|
|
105
257
|
/**
|
|
106
|
-
*
|
|
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
|
+
*
|
|
107
263
|
* @type {string[]}
|
|
108
264
|
*/
|
|
109
265
|
addons: [
|
|
110
266
|
'@storybook/addon-a11y',
|
|
111
267
|
'@storybook/addon-links',
|
|
112
268
|
'@storybook/addon-themes',
|
|
113
|
-
'@storybook/addon-styling-webpack',
|
|
114
269
|
],
|
|
115
270
|
|
|
116
271
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* builder must be declared explicitly instead of relying on html-webpack5.
|
|
120
|
-
* @type {{builder: {name: string}, disableTelemetry: boolean}}
|
|
272
|
+
* Force the Vite builder and disable Storybook telemetry for shared usage.
|
|
273
|
+
* @type {{builder: string, disableTelemetry: boolean}}
|
|
121
274
|
*/
|
|
122
275
|
core: {
|
|
123
|
-
builder:
|
|
124
|
-
name: '@storybook/builder-webpack5',
|
|
125
|
-
},
|
|
276
|
+
builder: '@storybook/builder-vite',
|
|
126
277
|
disableTelemetry: true,
|
|
127
278
|
},
|
|
128
279
|
|
|
129
280
|
/**
|
|
130
|
-
*
|
|
281
|
+
* Tell Storybook to use the React + Vite framework package.
|
|
131
282
|
* @type {{name: string, options: object}}
|
|
132
283
|
*/
|
|
133
284
|
framework: {
|
|
134
|
-
name: '@storybook/
|
|
285
|
+
name: '@storybook/react-vite',
|
|
135
286
|
options: {},
|
|
136
287
|
},
|
|
137
288
|
|
|
138
289
|
/**
|
|
139
|
-
*
|
|
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
|
+
*
|
|
140
295
|
* @type {{autodocs: boolean}}
|
|
141
296
|
*/
|
|
142
297
|
docs: {
|
|
@@ -144,13 +299,17 @@ const config = {
|
|
|
144
299
|
},
|
|
145
300
|
|
|
146
301
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
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.
|
|
151
309
|
*/
|
|
152
310
|
managerHead: (head) => {
|
|
153
|
-
// inline
|
|
311
|
+
// Keep the manager styling inline so consumers inherit the branded UI
|
|
312
|
+
// without having to maintain a separate manager-only stylesheet.
|
|
154
313
|
const inlineStyles = `
|
|
155
314
|
<style>
|
|
156
315
|
:root {
|
|
@@ -167,7 +326,7 @@ const config = {
|
|
|
167
326
|
--colors-purple: #8B1E7E;
|
|
168
327
|
}
|
|
169
328
|
.sidebar-container {
|
|
170
|
-
background:
|
|
329
|
+
background-color: var(--colors-emulsify-blue-900);
|
|
171
330
|
}
|
|
172
331
|
.sidebar-container .sidebar-subheading {
|
|
173
332
|
color: var(--colors-emulsify-blue-200);
|
|
@@ -177,7 +336,7 @@ const config = {
|
|
|
177
336
|
.sidebar-container .sidebar-subheading button:focus {
|
|
178
337
|
color: var(--colors-emulsify-blue-300);
|
|
179
338
|
}
|
|
180
|
-
|
|
339
|
+
/* Triangle icon. */
|
|
181
340
|
.sidebar-container .sidebar-subheading button span {
|
|
182
341
|
color: var(--colors-emulsify-blue-300);
|
|
183
342
|
}
|
|
@@ -252,54 +411,186 @@ const config = {
|
|
|
252
411
|
}
|
|
253
412
|
</style>
|
|
254
413
|
`;
|
|
255
|
-
|
|
256
|
-
// load external manager-head.html if present
|
|
257
|
-
const externalManagerHeadPath = resolve(
|
|
258
|
-
_dirname,
|
|
414
|
+
const externalManagerHtml = readOptionalHtmlFragment(
|
|
259
415
|
'../../../../config/emulsify-core/storybook/manager-head.html',
|
|
260
416
|
);
|
|
261
|
-
let externalManagerHtml = '';
|
|
262
|
-
if (fs.existsSync(externalManagerHeadPath)) {
|
|
263
|
-
externalManagerHtml = fs.readFileSync(externalManagerHeadPath, 'utf8');
|
|
264
|
-
}
|
|
265
417
|
|
|
266
418
|
return `${head}
|
|
267
|
-
${inlineStyles}
|
|
268
|
-
${externalManagerHtml}`;
|
|
419
|
+
${inlineStyles}
|
|
420
|
+
${externalManagerHtml}`;
|
|
269
421
|
},
|
|
270
422
|
|
|
271
423
|
/**
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
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.
|
|
275
431
|
*/
|
|
276
432
|
previewHead: (head) => {
|
|
277
|
-
const
|
|
278
|
-
_dirname,
|
|
433
|
+
const externalHtml = readOptionalHtmlFragment(
|
|
279
434
|
'../../../../config/emulsify-core/storybook/preview-head.html',
|
|
280
435
|
);
|
|
281
436
|
|
|
282
|
-
let externalHtml = '';
|
|
283
|
-
if (fs.existsSync(externalHeadPath)) {
|
|
284
|
-
externalHtml = fs.readFileSync(externalHeadPath, 'utf8');
|
|
285
|
-
}
|
|
286
|
-
|
|
287
437
|
return `${head}
|
|
288
|
-
${externalHtml}`;
|
|
438
|
+
${externalHtml}`;
|
|
289
439
|
},
|
|
290
440
|
|
|
291
441
|
/**
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
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.
|
|
297
450
|
*/
|
|
298
|
-
|
|
299
|
-
|
|
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
|
+
);
|
|
300
506
|
|
|
301
|
-
|
|
302
|
-
|
|
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
|
+
},
|
|
303
582
|
};
|
|
304
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
|
+
|
|
305
596
|
export default config;
|
package/.storybook/manager.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file Storybook manager bootstrap and theme selection.
|
|
3
|
+
*/
|
|
2
4
|
|
|
3
5
|
import { addons } from 'storybook/manager-api';
|
|
4
|
-
import emulsifyTheme from './emulsifyTheme
|
|
6
|
+
import emulsifyTheme from './emulsifyTheme';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Dynamically import the user-provided Storybook theme override.
|
|
@@ -9,37 +11,28 @@ import emulsifyTheme from './emulsifyTheme.js';
|
|
|
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
22
|
(typeof customTheme === 'object' &&
|
|
23
23
|
Object.keys(customTheme).length === 0);
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* Apply the chosen theme to Storybook’s manager UI configuration.
|
|
27
|
-
* @type {{ theme: object }}
|
|
28
|
-
*/
|
|
29
25
|
addons.setConfig({
|
|
30
26
|
theme: isEmptyObject ? emulsifyTheme : customTheme,
|
|
31
27
|
});
|
|
32
28
|
})
|
|
33
29
|
/**
|
|
34
|
-
*
|
|
30
|
+
* Fall back to the default theme when the project override is absent.
|
|
31
|
+
*
|
|
35
32
|
* @returns {void}
|
|
36
33
|
*/
|
|
37
34
|
.catch(() => {
|
|
38
35
|
addons.setConfig({
|
|
39
|
-
/**
|
|
40
|
-
* Fallback to the default Emulsify theme on import error.
|
|
41
|
-
* @type {{ theme: object }}
|
|
42
|
-
*/
|
|
43
36
|
theme: emulsifyTheme,
|
|
44
37
|
});
|
|
45
38
|
});
|