@emulsify/core 4.2.1 → 4.3.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-vite.js +91 -36
- package/README.md +185 -56
- package/config/a11y.config.js +2 -1
- package/config/vite/entries.js +4 -4
- package/config/vite/plugins/{copy-src-assets.js → assets/copy-src-assets.js} +1 -1
- package/config/vite/plugins/{copy-twig-files.js → assets/copy-twig-files.js} +1 -1
- package/config/vite/plugins/{css-asset-relativizer.js → assets/css-asset-relativizer.js} +9 -0
- package/config/vite/plugins/{mirror-components.js → assets/mirror-components.js} +2 -28
- package/config/vite/plugins/{source-file-index.js → assets/source-file-index.js} +1 -1
- package/config/vite/plugins/{svg-sprite.js → assets/svg-sprite.js} +2 -2
- package/config/vite/plugins/index.js +23 -14
- package/config/vite/plugins/reporter/asset-resolver.js +541 -0
- package/config/vite/plugins/reporter/build-errors.js +284 -0
- package/config/vite/plugins/reporter/diagnostics.js +366 -0
- package/config/vite/plugins/reporter/format.js +199 -0
- package/config/vite/plugins/reporter/index.js +308 -0
- package/config/vite/plugins/reporter/render.js +758 -0
- package/config/vite/plugins/reporter/sass-logger.js +237 -0
- package/config/vite/plugins/reporter/vite-logger.js +188 -0
- package/config/vite/plugins/reporter/watch-mode.js +40 -0
- package/config/vite/plugins/{twig-extension-installers.js → twig/extension-installers.js} +1 -1
- package/config/vite/{twig-extensions.js → plugins/twig/extensions.js} +2 -2
- package/config/vite/plugins/{twig-module.js → twig/twig-module.js} +197 -85
- package/config/vite/plugins/{virtual-twig-asset-sources.js → twig/virtual-twig-asset-sources.js} +37 -136
- package/config/vite/plugins/{virtual-twig-globs.js → twig/virtual-twig-globs.js} +3 -32
- package/config/vite/plugins/{vituum-patch.js → twig/vituum-patch.js} +3 -3
- package/config/vite/plugins.js +1 -1
- package/config/vite/project-config.js +1 -1
- package/config/vite/project-structure.js +1 -1
- package/config/vite/utils/lru.js +77 -0
- package/config/vite/utils/package-version.js +42 -0
- package/config/vite/utils/paths.js +1 -9
- package/config/vite/utils/react-singleton.js +1 -1
- package/config/vite/vite.config.js +51 -5
- package/package.json +93 -58
- package/scripts/a11y.js +115 -23
- package/scripts/audit/checks/core-imports.js +78 -0
- package/scripts/audit/checks/css-asset-references.js +99 -0
- package/scripts/audit/checks/drupal-assumptions.js +48 -0
- package/scripts/audit/checks/files-outside-roots.js +53 -0
- package/scripts/audit/checks/generated-package-scripts.js +113 -0
- package/scripts/audit/checks/legacy-twig-stories.js +33 -0
- package/scripts/audit/checks/package-overrides.js +91 -0
- package/scripts/audit/checks/project-config.js +71 -0
- package/scripts/audit/checks/story-discovery.js +35 -0
- package/scripts/audit/checks/twig-references.js +69 -0
- package/scripts/audit/checks/twig-volume.js +54 -0
- package/scripts/audit/checks/webpack-patterns.js +86 -0
- package/scripts/audit/index.js +177 -0
- package/scripts/audit/lib/css.js +165 -0
- package/scripts/audit/lib/files.js +168 -0
- package/scripts/audit/lib/findings.js +31 -0
- package/scripts/audit/lib/package-json.js +65 -0
- package/scripts/audit/lib/twig.js +227 -0
- package/scripts/audit/report.js +273 -0
- package/scripts/audit-twig-stories.js +115 -78
- package/scripts/audit.js +150 -1632
- package/scripts/check-node-version.js +136 -10
- package/scripts/inspect-components.js +456 -0
- package/scripts/lib/cli.js +179 -0
- package/scripts/lib/fs.js +31 -0
- package/scripts/lib/proc.js +78 -0
- package/scripts/lib/text.js +14 -0
- package/scripts/loadYaml.js +2 -2
- package/src/extensions/shared/attributes.js +3 -3
- package/src/extensions/shared/lists.js +2 -6
- package/src/extensions/shared/root-relative.js +38 -0
- package/src/storybook/index.js +4 -0
- package/src/storybook/render-twig.js +1 -1
- package/src/storybook/render-web-component.js +459 -0
- package/src/storybook/twig/asset-source-runtime.js +193 -0
- package/src/storybook/twig/{source-extensions.js → constants.js} +3 -1
- package/src/storybook/twig/reference-paths.js +2 -13
- package/src/storybook/twig/setup.js +12 -2
- package/src/storybook/twig/source-function.js +5 -2
- package/config/vite/utils/unique.js +0 -36
- package/src/storybook/twig/include.js +0 -28
- package/src/storybook/twig/source-events.js +0 -5
package/.storybook/main-vite.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import viteConfig from '../config/vite/vite.config.js';
|
|
3
|
-
import { twigExtensionModuleSpecifiers } from '../config/vite/twig
|
|
3
|
+
import { twigExtensionModuleSpecifiers } from '../config/vite/plugins/twig/extensions.js';
|
|
4
4
|
import {
|
|
5
5
|
mergeReactSingletonOptimizeDeps,
|
|
6
6
|
mergeReactSingletonResolve,
|
|
7
7
|
} from '../config/vite/utils/react-singleton.js';
|
|
8
8
|
import { makeGeneratedDistFilesPlugin } from './main-static-assets.js';
|
|
9
9
|
|
|
10
|
-
// Twig glob maps are provided by config/vite/plugins/virtual-twig-globs.js.
|
|
10
|
+
// Twig glob maps are provided by config/vite/plugins/twig/virtual-twig-globs.js.
|
|
11
11
|
const twigVirtualModuleIds = [
|
|
12
12
|
'virtual:emulsify-twig-globs',
|
|
13
13
|
'virtual:emulsify-twig-asset-sources',
|
|
14
|
+
'virtual:emulsify-twig-asset-source-runtime',
|
|
14
15
|
'virtual:emulsify-twig-extension-installers',
|
|
15
16
|
];
|
|
16
17
|
|
|
@@ -52,22 +53,64 @@ function mergeTwigRuntimeOptimizeDepsExcludes(...excludeLists) {
|
|
|
52
53
|
);
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Drop Vite's legacy `rollupOptions` alias from an `optimizeDeps` object.
|
|
58
|
+
*
|
|
59
|
+
* Vite 8 installs a compat shim on resolved `optimizeDeps` that defines
|
|
60
|
+
* `rollupOptions` as an *enumerable accessor* forwarding to `rolldownOptions`.
|
|
61
|
+
* Spreading such an object — which is how Storybook's config is merged here —
|
|
62
|
+
* copies the alias out as an ordinary data property. Vite then sees an
|
|
63
|
+
* `optimizeDeps` carrying two distinct objects and logs:
|
|
64
|
+
*
|
|
65
|
+
* `optimizeDeps.rollupOptions` / `ssr.optimizeDeps.rollupOptions` is
|
|
66
|
+
* deprecated. Use `optimizeDeps.rolldownOptions` instead.
|
|
67
|
+
*
|
|
68
|
+
* `ssr.optimizeDeps` inherits from `optimizeDeps`, so clearing the alias here
|
|
69
|
+
* settles both. Anything the alias was carrying is folded into
|
|
70
|
+
* `rolldownOptions` rather than discarded, so behavior is unchanged.
|
|
71
|
+
*
|
|
72
|
+
* That deprecation is emitted with `console.warn` rather than through Vite's
|
|
73
|
+
* logger, so it cannot be intercepted downstream — it has to be prevented.
|
|
74
|
+
*
|
|
75
|
+
* @param {object} [optimizeDeps] - Resolved or partial optimizeDeps config.
|
|
76
|
+
* @returns {object} Copy with the alias resolved into `rolldownOptions`.
|
|
77
|
+
*/
|
|
78
|
+
export function stripRollupOptionsAlias(optimizeDeps) {
|
|
79
|
+
if (!optimizeDeps) return {};
|
|
80
|
+
|
|
81
|
+
const { rollupOptions, rolldownOptions, ...rest } = optimizeDeps;
|
|
82
|
+
const resolved = rolldownOptions || rollupOptions;
|
|
83
|
+
|
|
84
|
+
return resolved ? { ...rest, rolldownOptions: resolved } : rest;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Virtual module IDs that must stay external during dependency optimization.
|
|
89
|
+
*
|
|
90
|
+
* @type {RegExp}
|
|
91
|
+
*/
|
|
92
|
+
const TWIG_VIRTUAL_MODULE_PATTERN =
|
|
93
|
+
/^virtual:emulsify-twig-(?:globs|asset-sources|asset-source-runtime)$/;
|
|
94
|
+
|
|
55
95
|
/**
|
|
56
96
|
* Keep Emulsify Twig virtual imports out of Storybook dependency prebundles.
|
|
57
97
|
*
|
|
58
|
-
*
|
|
98
|
+
* Vite 8 optimizes dependencies with Rolldown rather than esbuild, so this is a
|
|
99
|
+
* Rolldown plugin using the Rollup-compatible `resolveId` hook. It replaces the
|
|
100
|
+
* previous esbuild `onResolve` plugin, which reached Vite through the
|
|
101
|
+
* deprecated `optimizeDeps.esbuildOptions` escape hatch.
|
|
102
|
+
*
|
|
103
|
+
* @returns {import('rolldown').Plugin} Rolldown plugin for optimizeDeps.
|
|
59
104
|
*/
|
|
60
105
|
function makeTwigVirtualModuleOptimizerPlugin() {
|
|
61
106
|
return {
|
|
62
107
|
name: 'emulsify-twig-virtual-modules',
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}),
|
|
70
|
-
);
|
|
108
|
+
resolveId(source) {
|
|
109
|
+
if (!TWIG_VIRTUAL_MODULE_PATTERN.test(source)) return null;
|
|
110
|
+
|
|
111
|
+
// Marking these external leaves them in the module graph for Emulsify's
|
|
112
|
+
// virtual plugins to resolve during the normal Vite pipeline.
|
|
113
|
+
return { id: source, external: true };
|
|
71
114
|
},
|
|
72
115
|
};
|
|
73
116
|
}
|
|
@@ -125,9 +168,16 @@ export function createViteFinal(resolvedStorybookEnv) {
|
|
|
125
168
|
'**/*.twig',
|
|
126
169
|
]),
|
|
127
170
|
);
|
|
171
|
+
// Clear Vite's legacy `rollupOptions` alias before either object is
|
|
172
|
+
// spread, so the merged result never carries both it and `rolldownOptions`.
|
|
173
|
+
const baseOptimizeDeps = stripRollupOptionsAlias(
|
|
174
|
+
baseViteConfig?.optimizeDeps,
|
|
175
|
+
);
|
|
176
|
+
const storybookOptimizeDeps = stripRollupOptionsAlias(config?.optimizeDeps);
|
|
177
|
+
|
|
128
178
|
const optimizeDepsInclude = mergeReactSingletonOptimizeDeps(
|
|
129
|
-
|
|
130
|
-
|
|
179
|
+
baseOptimizeDeps.include,
|
|
180
|
+
storybookOptimizeDeps.include,
|
|
131
181
|
[
|
|
132
182
|
'twig',
|
|
133
183
|
'@emulsify/core/extensions/twig',
|
|
@@ -166,32 +216,37 @@ export function createViteFinal(resolvedStorybookEnv) {
|
|
|
166
216
|
exclude: [],
|
|
167
217
|
},
|
|
168
218
|
optimizeDeps: {
|
|
169
|
-
...
|
|
170
|
-
...
|
|
219
|
+
...baseOptimizeDeps,
|
|
220
|
+
...storybookOptimizeDeps,
|
|
171
221
|
include: optimizeDepsInclude,
|
|
172
222
|
exclude: mergeTwigRuntimeOptimizeDepsExcludes(
|
|
173
|
-
|
|
174
|
-
|
|
223
|
+
baseOptimizeDeps.exclude,
|
|
224
|
+
storybookOptimizeDeps.exclude,
|
|
175
225
|
),
|
|
176
|
-
|
|
177
|
-
...(
|
|
178
|
-
...(
|
|
226
|
+
rolldownOptions: {
|
|
227
|
+
...(baseOptimizeDeps.rolldownOptions || {}),
|
|
228
|
+
...(storybookOptimizeDeps.rolldownOptions || {}),
|
|
179
229
|
plugins: [
|
|
180
|
-
...(
|
|
181
|
-
...(
|
|
230
|
+
...(baseOptimizeDeps.rolldownOptions?.plugins || []),
|
|
231
|
+
...(storybookOptimizeDeps.rolldownOptions?.plugins || []),
|
|
182
232
|
makeTwigVirtualModuleOptimizerPlugin(),
|
|
183
233
|
],
|
|
184
|
-
|
|
185
|
-
...(
|
|
186
|
-
...(
|
|
187
|
-
// Pre-bundle `.js` dependencies
|
|
188
|
-
//
|
|
234
|
+
moduleTypes: {
|
|
235
|
+
...(baseOptimizeDeps.rolldownOptions?.moduleTypes || {}),
|
|
236
|
+
...(storybookOptimizeDeps.rolldownOptions?.moduleTypes || {}),
|
|
237
|
+
// Pre-bundle `.js` dependencies as JSX for packages that ship JSX
|
|
238
|
+
// without a `.jsx` extension. Rolldown's `moduleTypes` is the
|
|
239
|
+
// successor to esbuild's `loader` map.
|
|
189
240
|
'.js': 'jsx',
|
|
190
241
|
},
|
|
191
242
|
},
|
|
192
243
|
},
|
|
193
244
|
});
|
|
194
245
|
|
|
246
|
+
const mergedOptimizeDeps = stripRollupOptionsAlias(
|
|
247
|
+
mergedConfig.optimizeDeps,
|
|
248
|
+
);
|
|
249
|
+
|
|
195
250
|
return {
|
|
196
251
|
...mergedConfig,
|
|
197
252
|
build: {
|
|
@@ -204,17 +259,17 @@ export function createViteFinal(resolvedStorybookEnv) {
|
|
|
204
259
|
},
|
|
205
260
|
resolve: mergeReactSingletonResolve(mergedConfig),
|
|
206
261
|
optimizeDeps: {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
),
|
|
262
|
+
// `mergeConfig` walks enumerable keys, so the alias can reappear on the
|
|
263
|
+
// merged result even though both inputs were cleaned above.
|
|
264
|
+
...mergedOptimizeDeps,
|
|
265
|
+
include: mergeReactSingletonOptimizeDeps(mergedOptimizeDeps.include),
|
|
211
266
|
exclude: mergeTwigRuntimeOptimizeDepsExcludes(
|
|
212
|
-
|
|
267
|
+
mergedOptimizeDeps.exclude,
|
|
213
268
|
),
|
|
214
|
-
|
|
215
|
-
...(
|
|
216
|
-
|
|
217
|
-
...(
|
|
269
|
+
rolldownOptions: {
|
|
270
|
+
...(mergedOptimizeDeps.rolldownOptions || {}),
|
|
271
|
+
moduleTypes: {
|
|
272
|
+
...(mergedOptimizeDeps.rolldownOptions?.moduleTypes || {}),
|
|
218
273
|
'.js': 'jsx',
|
|
219
274
|
},
|
|
220
275
|
},
|
package/README.md
CHANGED
|
@@ -2,47 +2,85 @@
|
|
|
2
2
|
|
|
3
3
|
# Emulsify Core
|
|
4
4
|
|
|
5
|
-
An open-source
|
|
5
|
+
An open-source foundation for building design systems across component
|
|
6
|
+
libraries, CMS themes, Storybook, and the handoff between design and
|
|
7
|
+
development.
|
|
6
8
|
|
|
7
|
-
**Emulsify Core** provides shared [Vite](https://vite.dev/) build configuration
|
|
9
|
+
**Emulsify Core** provides shared [Vite](https://vite.dev/) build configuration
|
|
10
|
+
and a [Storybook](https://storybook.js.org/) workspace for component-driven
|
|
11
|
+
development with Twig, React, and autonomous custom elements.
|
|
8
12
|
|
|
9
13
|
## How Emulsify Core Works
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
15
|
+
Emulsify Core is the shared frontend layer for Emulsify projects. It gives teams
|
|
16
|
+
one place to build, document, and test components while leaving platform-specific
|
|
17
|
+
implementation details where they belong.
|
|
18
|
+
|
|
19
|
+
- Vite builds project JavaScript, Sass/CSS, Twig templates, component metadata,
|
|
20
|
+
and static component assets.
|
|
21
|
+
- Storybook runs on the React/Vite framework.
|
|
22
|
+
- Twig files render in React-based Storybook through `renderTwig()`.
|
|
23
|
+
- React components render through Storybook's standard React support.
|
|
24
|
+
- Autonomous custom elements render in Storybook through
|
|
25
|
+
`renderWebComponent()`.
|
|
26
|
+
- Twig, React, and custom element stories can live together in one Storybook
|
|
27
|
+
workspace.
|
|
28
|
+
- `project.emulsify.json` is the source of truth for platform and structure
|
|
29
|
+
configuration.
|
|
30
|
+
- Platform adapters control CMS-specific behavior instead of assuming it
|
|
31
|
+
globally.
|
|
32
|
+
|
|
33
|
+
## Node.js Runtime Policy
|
|
34
|
+
|
|
35
|
+
- Consumers are supported on Node.js 24.13.0 or later. The strictest published
|
|
36
|
+
toolchain dependency, `stylelint-selector-bem-pattern` 5, requires that patch.
|
|
37
|
+
- Contributors should use Node.js 24.18.0, the exact version pinned in `.nvmrc`.
|
|
38
|
+
- CI also uses Node.js 24.18.0 by reading `.nvmrc`, so local development and
|
|
39
|
+
automated checks share the same recommended runtime.
|
|
19
40
|
|
|
20
41
|
## Project Evolution
|
|
21
42
|
|
|
22
|
-
Emulsify Core
|
|
43
|
+
Emulsify Core began as shared Webpack and Storybook tooling, adopted ESM in
|
|
44
|
+
3.x, and moved to Vite and React/Vite Storybook in 4.x. The current project
|
|
45
|
+
model supports CMS themes, standalone component libraries, and mixed Twig,
|
|
46
|
+
React, and custom element Storybook workspaces.
|
|
23
47
|
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- The current release moves the build system to Vite, runs Storybook on React/Vite, supports Twig and React stories side by side, and normalizes platform and project-structure behavior through `project.emulsify.json`.
|
|
48
|
+
See [Version Evolution](docs/version-evolution.md) for major-version history
|
|
49
|
+
and the [4.3.0 release notes](docs/releases/4.3.0.md) for the compatibility
|
|
50
|
+
changes and additions in that release.
|
|
28
51
|
|
|
29
|
-
|
|
52
|
+
## Authoring Models
|
|
30
53
|
|
|
31
|
-
|
|
54
|
+
Emulsify Core supports Twig and React authoring workflows plus a focused
|
|
55
|
+
Storybook adapter for web components built with autonomous custom elements. The
|
|
56
|
+
right choice depends on how the design system will be used.
|
|
32
57
|
|
|
33
|
-
|
|
58
|
+
- Use Twig for CMS themes and server-rendered template systems. Drupal has a
|
|
59
|
+
Drupal-specific adapter, and WordPress/Timber projects can use the
|
|
60
|
+
intentionally neutral `wordpress` adapter. WordPress runtime integration
|
|
61
|
+
belongs in `emulsify-wordpress-theme`.
|
|
62
|
+
- Use React for standalone UI libraries, application components, or projects
|
|
63
|
+
that already use React.
|
|
64
|
+
- Use web components built with autonomous custom elements for
|
|
65
|
+
framework-neutral browser components that fit the adapter's documented
|
|
66
|
+
property, attribute, default-slot, and native event boundaries.
|
|
67
|
+
- Use mixed authoring when a design system needs to document CMS-rendered,
|
|
68
|
+
framework-rendered, and framework-neutral components in the same Storybook
|
|
69
|
+
instance.
|
|
34
70
|
|
|
35
|
-
|
|
71
|
+
See [Component Authoring](docs/component-authoring.md) for Twig, React, custom
|
|
72
|
+
element, mixed Storybook, and shared Sass examples.
|
|
36
73
|
|
|
37
|
-
|
|
38
|
-
- Use React for standalone UI libraries, application components, or projects that already use React.
|
|
39
|
-
- Use mixed Twig and React when a design system needs to document both CMS-rendered and JavaScript-rendered components in the same Storybook instance.
|
|
74
|
+
## Installation And Project Setup
|
|
40
75
|
|
|
41
|
-
|
|
76
|
+
Emulsify Core can enter a project in two common ways. Both are supported and both
|
|
77
|
+
are valid. The right setup path depends on whether you are wiring Core into a
|
|
78
|
+
project yourself or starting from an Emulsify starter.
|
|
42
79
|
|
|
43
|
-
|
|
80
|
+
### Manual Setup With npm
|
|
44
81
|
|
|
45
|
-
|
|
82
|
+
Use npm when you want to add Emulsify Core to an existing project, a custom
|
|
83
|
+
starter, or a project that owns its own setup decisions.
|
|
46
84
|
|
|
47
85
|
```sh
|
|
48
86
|
npm install @emulsify/core
|
|
@@ -63,74 +101,163 @@ Every project should provide a `project.emulsify.json` file at the project root:
|
|
|
63
101
|
}
|
|
64
102
|
```
|
|
65
103
|
|
|
66
|
-
Asset files are discovered from the default asset roots and any additional
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
locations.
|
|
104
|
+
Asset files are discovered from the default asset roots and any additional roots
|
|
105
|
+
configured in `project.emulsify.json`. Use asset roots when a project stores
|
|
106
|
+
fonts, images, icons, or other static files outside the default locations.
|
|
70
107
|
|
|
71
108
|
Common project scripts call the shared Emulsify Core Vite and Storybook config:
|
|
72
109
|
|
|
73
110
|
- `storybook`: starts Storybook development.
|
|
74
111
|
- `storybook-build`: builds static Storybook output.
|
|
75
|
-
- `build`: runs the Vite build for JS, CSS, copied Twig templates, component
|
|
112
|
+
- `build`: runs the Vite build for JS, CSS, copied Twig templates, component
|
|
113
|
+
metadata, and static component assets.
|
|
76
114
|
- `lint`: lints maintained project source.
|
|
77
115
|
|
|
116
|
+
### Streamlined Setup With Emulsify CLI
|
|
117
|
+
|
|
118
|
+
Use the [Emulsify CLI](https://github.com/emulsify-ds/emulsify-cli) when you
|
|
119
|
+
want a starter-driven setup with project scaffolding, starter hooks, system
|
|
120
|
+
installation, and component generation.
|
|
121
|
+
|
|
122
|
+
Install the CLI globally:
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
npm install -g @emulsify/cli
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Then initialize a project from a starter:
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
emulsify init "My Project" ./path/to/projects --platform none
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Global or direct CLI installation is still the supported way to run
|
|
135
|
+
`emulsify init` before a project exists. For Core 4.x compatibility, projects
|
|
136
|
+
that install `@emulsify/core` also receive a project-local `emulsify` binary
|
|
137
|
+
through Core's `@emulsify/cli` dependency. That local binary can be invoked with
|
|
138
|
+
`npx --no-install emulsify --help`; it is a compatibility bridge for existing
|
|
139
|
+
Core consumers, not a replacement for choosing an explicit CLI installation
|
|
140
|
+
when scaffolding a new project.
|
|
141
|
+
|
|
142
|
+
The CLI supports built-in `drupal`, `wordpress`, and `none` platforms. It can
|
|
143
|
+
create starter projects, install component systems, list available components,
|
|
144
|
+
install system components, and generate local components. The starter owns the
|
|
145
|
+
project structure and scripts; Emulsify Core still provides the shared build and
|
|
146
|
+
Storybook foundation underneath.
|
|
147
|
+
|
|
78
148
|
## Documentation
|
|
79
149
|
|
|
80
150
|
The documentation is split by task:
|
|
81
151
|
|
|
82
|
-
| Topic | Use This When
|
|
83
|
-
| --------------------------------------------------------- |
|
|
84
|
-
| [Version Evolution](docs/version-evolution.md) | Understanding how Emulsify Core has evolved across major releases.
|
|
85
|
-
| [Component Authoring](docs/component-authoring.md) | Choosing Twig, React, or mixed Storybook authoring and comparing component examples.
|
|
86
|
-
| [Storybook](docs/storybook.md) | Rendering Twig stories, using
|
|
87
|
-
| [Project Structure And Output](docs/project-structure.md) | Configuring `src/components`, root `./components`, `variant.structureImplementations`, and expected output paths.
|
|
88
|
-
| [Platform Adapters](docs/platform-adapters.md) | Understanding `none`, `wordpress`, `drupal`, platform resolution order, and Drupal SDC behavior.
|
|
89
|
-
| [Extension Points](docs/extension-points.md) | Adding Vite plugins, Tailwind CSS, Storybook preview overrides, and other framework tooling.
|
|
90
|
-
| [
|
|
91
|
-
| [
|
|
92
|
-
| [
|
|
93
|
-
| [
|
|
152
|
+
| Topic | Use This When |
|
|
153
|
+
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
154
|
+
| [Version Evolution](docs/version-evolution.md) | Understanding how Emulsify Core has evolved across major releases. |
|
|
155
|
+
| [Component Authoring](docs/component-authoring.md) | Choosing Twig, React, custom element, or mixed Storybook authoring and comparing component examples. |
|
|
156
|
+
| [Storybook](docs/storybook.md) | Rendering Twig and custom element stories, using Storybook helpers, and mixing authoring models. |
|
|
157
|
+
| [Project Structure And Output](docs/project-structure.md) | Configuring `src/components`, root `./components`, `variant.structureImplementations`, and expected output paths. |
|
|
158
|
+
| [Platform Adapters](docs/platform-adapters.md) | Understanding `none`, `wordpress`, `drupal`, platform resolution order, and Drupal SDC behavior. |
|
|
159
|
+
| [Extension Points](docs/extension-points.md) | Adding Vite plugins, Tailwind CSS, Storybook preview overrides, and other framework tooling. |
|
|
160
|
+
| [Dependency Contract](docs/dependency-contract.md) | Understanding why generated themes rely on Core runtime dependencies and npm hoisting. |
|
|
161
|
+
| [Performance](docs/performance.md) | Understanding sourcemaps, eager Twig imports, Tailwind scanning, copied files, and fixture validation. |
|
|
162
|
+
| [Native Twig Extensions](docs/native-twig-extensions.md) | Using `bem()`, `add_attributes()`, and `switch/case/default/endswitch` in Twig.js. |
|
|
163
|
+
| [Component Inspector](docs/component-inspector.md) | Listing recognized Twig component templates, exact references, shorthand collisions, and expected output paths. |
|
|
164
|
+
| [Project Audit](docs/audit.md) | Running human or versioned JSON project audits and configuring CI failure thresholds. |
|
|
165
|
+
| [Release Verification](docs/release.md) | Running 4.x release checks, tarball smoke tests, and semantic-release dry runs before publishing. |
|
|
166
|
+
| [Release Pull Request Review](docs/release-review.md) | Reviewing large release pull requests by independent API, runtime, consumer, accessibility, and release risks. |
|
|
167
|
+
| [Migration To 4.x](docs/migration-4x.md) | Upgrading a pre-4.x/Webpack project while preserving existing structures. |
|
|
168
|
+
| [4.3.0 Release Notes](docs/releases/4.3.0.md) | Reviewing the 4.3.0 scope, compatibility changes, public APIs, limitations, and verification evidence. |
|
|
94
169
|
|
|
95
170
|
## Known Limitations
|
|
96
171
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
|
|
172
|
+
Emulsify Core is intentionally focused on the shared build and Storybook layer.
|
|
173
|
+
Some integration work still belongs to the project, starter, theme, or platform
|
|
174
|
+
package using it.
|
|
175
|
+
|
|
176
|
+
- Implemented platform adapters are `none`, `wordpress`, and `drupal`. The
|
|
177
|
+
`wordpress` adapter is intentionally neutral: it supports Core Twig authoring,
|
|
178
|
+
Storybook, Vite, `bem()`, `add_attributes()`, `include()`, and `source()`, but
|
|
179
|
+
it does not emulate WordPress or Timber PHP runtime behavior. Runtime
|
|
180
|
+
integration belongs in `emulsify-wordpress-theme`. See
|
|
181
|
+
[Platform Adapters](docs/platform-adapters.md).
|
|
182
|
+
- Storybook eagerly imports compiled Twig modules for synchronous rendering,
|
|
183
|
+
while raw Twig and text asset sources load lazily when `source()` requests
|
|
184
|
+
them. Large Twig libraries should still keep Storybook source roots
|
|
185
|
+
intentional. See [Performance](docs/performance.md).
|
|
186
|
+
- Production sourcemaps are enabled by default unless a project overrides Vite
|
|
187
|
+
config through `config/emulsify-core/vite/plugins.*`. See
|
|
188
|
+
[Performance](docs/performance.md).
|
|
189
|
+
- Project extensions use the public `config/emulsify-core` directory:
|
|
190
|
+
`config/emulsify-core/vite/plugins.*` for Vite,
|
|
191
|
+
`config/emulsify-core/storybook/...` for Storybook, and
|
|
192
|
+
`config/emulsify-core/a11y.config.js` for a11y. See
|
|
193
|
+
[Extension Points](docs/extension-points.md).
|
|
194
|
+
- Webpack-specific customizations must be migrated manually to Vite plugins or
|
|
195
|
+
`extendConfig()`. See [Migration](docs/migration-4x.md).
|
|
196
|
+
- Drupal SDC mirroring only applies when the Drupal adapter and SDC settings are
|
|
197
|
+
enabled. `none` and `wordpress` projects should expect output to remain in
|
|
198
|
+
`dist/`. See [Platform Adapters](docs/platform-adapters.md).
|
|
199
|
+
- Generated themes that depend only on `@emulsify/core` assume npm's flat
|
|
200
|
+
`node_modules` hoisting for script binaries and shared config packages. pnpm's
|
|
201
|
+
isolated linker and Yarn Plug'n'Play are unsupported for that one-dependency
|
|
202
|
+
generated-theme pattern unless the consuming project declares each tool
|
|
203
|
+
package itself. See [Dependency Contract](docs/dependency-contract.md).
|
|
204
|
+
- The Storybook renderer for custom elements supports autonomous custom elements,
|
|
205
|
+
light DOM children for an unnamed default slot, and explicitly mapped native
|
|
206
|
+
events. It does not provide a named-slot composition API or support
|
|
207
|
+
customized built-in elements. See
|
|
208
|
+
[Storybook](docs/storybook.md#known-limitations).
|
|
103
209
|
|
|
104
210
|
## Supported Project Shapes
|
|
105
211
|
|
|
106
|
-
Core
|
|
212
|
+
Emulsify Core is designed to meet projects where they are. These project shapes
|
|
213
|
+
are supported:
|
|
107
214
|
|
|
108
215
|
- Drupal SDC projects using `src/components`.
|
|
109
216
|
- `none` platform Twig projects using `src/components`.
|
|
110
217
|
- `wordpress` platform Twig projects using `src/components`.
|
|
111
218
|
- Root `./components` projects.
|
|
112
219
|
- Projects using multiple `variant.structureImplementations`.
|
|
113
|
-
- Mixed Twig
|
|
220
|
+
- Mixed Storybook projects with Twig, React, and custom elements.
|
|
114
221
|
|
|
115
|
-
WordPress and Timber projects should use `platform: "wordpress"` when they want
|
|
222
|
+
WordPress and Timber projects should use `platform: "wordpress"` when they want
|
|
223
|
+
Core's neutral WordPress adapter. The adapter keeps output in `dist/`, loads
|
|
224
|
+
Storybook CSS from `dist/**/*.css`, and leaves WordPress runtime behavior to
|
|
225
|
+
`emulsify-wordpress-theme`.
|
|
116
226
|
|
|
117
227
|
## Public Imports
|
|
118
228
|
|
|
119
|
-
Emulsify Core exposes stable public package paths
|
|
229
|
+
Emulsify Core exposes stable public package paths so consuming projects do not
|
|
230
|
+
need to reach into internal files:
|
|
120
231
|
|
|
121
232
|
```js
|
|
122
233
|
import { renderTwig } from '@emulsify/core/storybook';
|
|
234
|
+
import {
|
|
235
|
+
defineCustomElement,
|
|
236
|
+
renderWebComponent,
|
|
237
|
+
} from '@emulsify/core/storybook';
|
|
123
238
|
import { registerTwigExtensions } from '@emulsify/core/extensions/twig';
|
|
124
239
|
import { defineReactExtension } from '@emulsify/core/extensions/react';
|
|
125
240
|
```
|
|
126
241
|
|
|
127
|
-
`defineReactExtension` is reserved for future React extension support. It
|
|
242
|
+
`defineReactExtension` is reserved for future React extension support. It
|
|
243
|
+
currently returns the input unchanged. Adopting the import path is safe; the
|
|
244
|
+
runtime is intentionally a no-op until the registry lands. See
|
|
245
|
+
[Extension Points](docs/extension-points.md#public-imports).
|
|
246
|
+
|
|
247
|
+
Vite consumers can import the shared config from `@emulsify/core/vite`, public
|
|
248
|
+
Vite plugin helpers from `@emulsify/core/vite/plugins`, and platform adapter
|
|
249
|
+
helpers from `@emulsify/core/vite/platforms`.
|
|
128
250
|
|
|
129
|
-
|
|
251
|
+
`defineCustomElement()` and `renderWebComponent()` use the existing
|
|
252
|
+
`@emulsify/core/storybook` entry point; they do not require a dedicated package
|
|
253
|
+
subpath. The generated Twig asset-source runtime is internal and is not a
|
|
254
|
+
public package export.
|
|
130
255
|
|
|
131
256
|
## Contributing
|
|
132
257
|
|
|
133
|
-
|
|
258
|
+
Contributions should keep the codebase clear for the next person working in it.
|
|
259
|
+
Maintained JavaScript source, config, scripts, and tests should use consistent
|
|
260
|
+
comments:
|
|
134
261
|
|
|
135
262
|
- Start each maintained JS file with a short JSDoc file block that explains the file's responsibility.
|
|
136
263
|
- Use JSDoc blocks for exported functions, complex helpers, and public contracts.
|
|
@@ -138,7 +265,9 @@ Maintained JavaScript source, config, scripts, and tests should use consistent c
|
|
|
138
265
|
- Keep comments concise and factual. Prefer explaining why behavior exists instead of restating the code.
|
|
139
266
|
- Use YAML or shell comments in workflow, hook, and fixture files where the format supports comments.
|
|
140
267
|
|
|
141
|
-
Do not add comments to JSON files, lockfiles, binary assets, generated output,
|
|
268
|
+
Do not add comments to JSON files, lockfiles, binary assets, generated output,
|
|
269
|
+
legal documents, or dependency files. Those formats either do not support
|
|
270
|
+
comments or should remain exact artifacts.
|
|
142
271
|
|
|
143
272
|
Please also follow the issue template and pull request templates provided. See below for the correct places to post issues:
|
|
144
273
|
|
package/config/a11y.config.js
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export default {
|
|
9
|
-
storybookBuildDir: '
|
|
9
|
+
storybookBuildDir: '.out',
|
|
10
10
|
discoverStories: true,
|
|
11
11
|
pa11y: {
|
|
12
|
+
actions: ['wait for element #storybook-root > * to be visible'],
|
|
12
13
|
includeNotices: false,
|
|
13
14
|
includeWarnings: false,
|
|
14
15
|
runners: ['axe'],
|
package/config/vite/entries.js
CHANGED
|
@@ -24,10 +24,10 @@ import {
|
|
|
24
24
|
resolveProjectStructure,
|
|
25
25
|
storybookStyleOutputPath,
|
|
26
26
|
} from './project-structure.js';
|
|
27
|
-
import { createSourceFileIndex } from './plugins/source-file-index.js';
|
|
28
|
-
import { replaceLastSlash,
|
|
27
|
+
import { createSourceFileIndex } from './plugins/assets/source-file-index.js';
|
|
28
|
+
import { replaceLastSlash, toPosixPath } from './utils/paths.js';
|
|
29
29
|
|
|
30
|
-
export { replaceLastSlash,
|
|
30
|
+
export { replaceLastSlash, toPosixPath };
|
|
31
31
|
|
|
32
32
|
/** Remove characters that would confuse Rollup naming or file systems. */
|
|
33
33
|
export const sanitizePath = (s) => s.replace(/[^a-zA-Z0-9/_-]/g, '');
|
|
@@ -123,7 +123,7 @@ export function buildInputs(ctx) {
|
|
|
123
123
|
*/
|
|
124
124
|
const add = (key, abs) => {
|
|
125
125
|
if (!key) return;
|
|
126
|
-
const clean = sanitizePath(
|
|
126
|
+
const clean = sanitizePath(toPosixPath(key)).replace(/^\/+/, '');
|
|
127
127
|
if (!clean) return;
|
|
128
128
|
safeSetKey(inputs, clean, abs);
|
|
129
129
|
};
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Rewrites emitted CSS references to root assets so nested CSS files can keep
|
|
5
5
|
* resolving copied assets correctly from their final output directories.
|
|
6
|
+
*
|
|
7
|
+
* Sourcemap warning: this rewrites emitted CSS in `generateBundle` without
|
|
8
|
+
* adjusting positions, and every replacement changes the length of the line it
|
|
9
|
+
* sits on. Vite does not emit sourcemaps for extracted CSS today, so there is
|
|
10
|
+
* nothing to invalidate. If CSS sourcemaps are ever added, this plugin has to
|
|
11
|
+
* shift mappings as it rewrites (for example via MagicString) or each mapping
|
|
12
|
+
* after the first rewritten `url()` will silently resolve to the wrong column.
|
|
6
13
|
*/
|
|
7
14
|
|
|
8
15
|
import { posix as pathPosix } from 'path';
|
|
@@ -26,6 +33,8 @@ export function cssAssetUrlRelativizer({ assetsRoot = 'assets' } = {}) {
|
|
|
26
33
|
|
|
27
34
|
const fromDir = pathPosix.dirname(fileName);
|
|
28
35
|
|
|
36
|
+
// Length-changing rewrite: read the sourcemap warning in the file
|
|
37
|
+
// header before pairing this plugin with CSS sourcemaps.
|
|
29
38
|
chunk.source = chunk.source.replace(
|
|
30
39
|
/url\((['"]?)\/?assets\/([^)'"]+)\1\)/g,
|
|
31
40
|
(match, quote = '', rest) => {
|
|
@@ -22,39 +22,13 @@ import {
|
|
|
22
22
|
} from 'fs';
|
|
23
23
|
import { basename, dirname, join, resolve } from 'path';
|
|
24
24
|
|
|
25
|
-
import { safeExists, safeReadJson } from '
|
|
25
|
+
import { safeExists, safeReadJson } from '../../utils/fs-safe.js';
|
|
26
|
+
import { resolvePackageVersion } from '../../utils/package-version.js';
|
|
26
27
|
import { walkFiles } from './source-file-index.js';
|
|
27
28
|
|
|
28
29
|
const MIRROR_STATE_FILE = '.emulsify-mirror-state.json';
|
|
29
30
|
const FILE_COMPARE_CHUNK_SIZE = 64 * 1024;
|
|
30
31
|
|
|
31
|
-
/**
|
|
32
|
-
* Resolve the installed Core package version without relying on import.meta so
|
|
33
|
-
* Jest's CommonJS transform can load this Vite plugin module.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} projectDir - Project directory running the build.
|
|
36
|
-
* @returns {string} Emulsify Core package version.
|
|
37
|
-
*/
|
|
38
|
-
const resolvePackageVersion = (projectDir) => {
|
|
39
|
-
const candidates = [
|
|
40
|
-
join(projectDir, 'node_modules/@emulsify/core/package.json'),
|
|
41
|
-
join(process.cwd(), 'node_modules/@emulsify/core/package.json'),
|
|
42
|
-
join(process.cwd(), 'package.json'),
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
for (const candidate of candidates) {
|
|
46
|
-
const candidatePackage = safeReadJson(candidate).data;
|
|
47
|
-
if (
|
|
48
|
-
candidatePackage?.name === '@emulsify/core' &&
|
|
49
|
-
candidatePackage.version
|
|
50
|
-
) {
|
|
51
|
-
return candidatePackage.version;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return '0.0.0';
|
|
56
|
-
};
|
|
57
|
-
|
|
58
32
|
/**
|
|
59
33
|
* Remove empty parent directories from a start directory up to, but not including,
|
|
60
34
|
* a stopping boundary directory.
|