@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/preview.js
CHANGED
|
@@ -1,31 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file Storybook preview configuration shared by Emulsify projects.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
5
|
import { getRules } from 'axe-core';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { defaultDecorateStory, useEffect } from 'storybook/preview-api';
|
|
8
|
+
import Twig from 'twig';
|
|
9
|
+
import { twigExtensionInstallers } from 'virtual:emulsify-twig-extension-installers';
|
|
10
|
+
import {
|
|
11
|
+
mergePreviewParameters,
|
|
12
|
+
normalizePreviewOverrideModule,
|
|
13
|
+
} from '../src/storybook/preview-parameters.js';
|
|
14
|
+
import {
|
|
15
|
+
renderHtmlStoryResult,
|
|
16
|
+
withLegacyStoryToString,
|
|
17
|
+
} from '../src/storybook/render-twig.js';
|
|
18
|
+
import {
|
|
19
|
+
attachStorybookBehaviors,
|
|
20
|
+
fetchCSSFiles,
|
|
21
|
+
getStorybookPlatformAdapter,
|
|
22
|
+
setupTwig,
|
|
23
|
+
} from './utils.js';
|
|
24
|
+
|
|
25
|
+
const previewOverrideModules = import.meta.glob(
|
|
26
|
+
[
|
|
27
|
+
// Installed package path: node_modules/@emulsify/core/.storybook -> project root.
|
|
28
|
+
'../../../../config/emulsify-core/storybook/preview.js',
|
|
29
|
+
// Local development path: repo .storybook -> repo root.
|
|
30
|
+
'../config/emulsify-core/storybook/preview.js',
|
|
31
|
+
],
|
|
32
|
+
{ eager: true },
|
|
33
|
+
);
|
|
34
|
+
const [previewOverrideModule] = Object.values(previewOverrideModules);
|
|
35
|
+
const externalOverrides = normalizePreviewOverrideModule(previewOverrideModule);
|
|
6
36
|
|
|
7
37
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
38
|
+
* Active platform behavior used by the shared preview decorators.
|
|
39
|
+
*
|
|
40
|
+
* @type {ReturnType<typeof getStorybookPlatformAdapter>}
|
|
10
41
|
*/
|
|
11
|
-
|
|
42
|
+
const platformAdapter = getStorybookPlatformAdapter();
|
|
12
43
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
externalOverrides = {};
|
|
25
|
-
}
|
|
44
|
+
/**
|
|
45
|
+
* Deferred Drupal behavior shim import.
|
|
46
|
+
*
|
|
47
|
+
* The decorator awaits this promise when Drupal behaviors are enabled so that
|
|
48
|
+
* `attachBehaviors()` is available before a story asks for it.
|
|
49
|
+
*
|
|
50
|
+
* @type {Promise<*>}
|
|
51
|
+
*/
|
|
52
|
+
const platformBehaviorShimReady = platformAdapter.loadDrupalBehaviorShim
|
|
53
|
+
? import('./_drupal.js')
|
|
54
|
+
: Promise.resolve();
|
|
26
55
|
|
|
27
|
-
|
|
28
|
-
|
|
56
|
+
/** @type {Array<Function>} Configured Twig.js extension installers. */
|
|
57
|
+
const configuredTwigExtensions = twigExtensionInstallers;
|
|
29
58
|
|
|
30
59
|
/**
|
|
31
60
|
* Filters accessibility rules by matching tags.
|
|
@@ -34,10 +63,10 @@ import './_drupal.js';
|
|
|
34
63
|
*/
|
|
35
64
|
function enableRulesByTag(tags = []) {
|
|
36
65
|
const allRules = getRules();
|
|
37
|
-
return allRules.map(rule =>
|
|
38
|
-
tags.some(t => rule.tags.includes(t))
|
|
66
|
+
return allRules.map((rule) =>
|
|
67
|
+
tags.some((t) => rule.tags.includes(t))
|
|
39
68
|
? { id: rule.ruleId, enabled: true }
|
|
40
|
-
: { id: rule.ruleId, enabled: false }
|
|
69
|
+
: { id: rule.ruleId, enabled: false },
|
|
41
70
|
);
|
|
42
71
|
}
|
|
43
72
|
|
|
@@ -54,26 +83,49 @@ const AxeRules = enableRulesByTag([
|
|
|
54
83
|
'best-practice',
|
|
55
84
|
]);
|
|
56
85
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Storybook React wraps story functions in React elements before decorators run.
|
|
88
|
+
* Preserve that React-safe behavior while giving old stringifying decorators a
|
|
89
|
+
* useful string result for legacy Twig stories.
|
|
90
|
+
*
|
|
91
|
+
* @param {Function} storyFn Storybook story function.
|
|
92
|
+
* @param {Function[]} decorators Storybook decorators.
|
|
93
|
+
* @returns {Function} Decorated story function.
|
|
94
|
+
*/
|
|
95
|
+
export const applyDecorators = (storyFn, decorators) =>
|
|
96
|
+
defaultDecorateStory(
|
|
97
|
+
(context) =>
|
|
98
|
+
withLegacyStoryToString(React.createElement(storyFn, context), () =>
|
|
99
|
+
storyFn(context),
|
|
100
|
+
),
|
|
101
|
+
decorators,
|
|
102
|
+
);
|
|
60
103
|
|
|
61
104
|
/**
|
|
62
|
-
* Storybook decorators to apply
|
|
105
|
+
* Storybook decorators to apply platform-specific behavior after each story render.
|
|
63
106
|
* @type {Array<import('@storybook/react').Decorator>}
|
|
64
107
|
*/
|
|
65
108
|
export const decorators = [
|
|
66
109
|
/**
|
|
67
|
-
* Decorator that attaches
|
|
110
|
+
* Decorator that attaches platform behavior on story mount and args updates.
|
|
111
|
+
* Legacy Twig stories that return HTML strings are wrapped so React
|
|
112
|
+
* Storybook renders them as markup while projects migrate to renderTwig().
|
|
113
|
+
*
|
|
68
114
|
* @param {Function} Story The story component to render.
|
|
69
115
|
* @param {object} context Story context including args.
|
|
70
|
-
* @returns {
|
|
116
|
+
* @returns {*} Rendered story.
|
|
71
117
|
*/
|
|
72
118
|
(Story, { args }) => {
|
|
73
119
|
useEffect(() => {
|
|
74
|
-
|
|
120
|
+
void attachStorybookBehaviors({
|
|
121
|
+
adapter: platformAdapter,
|
|
122
|
+
behaviorShimReady: platformBehaviorShimReady,
|
|
123
|
+
});
|
|
75
124
|
}, [args]);
|
|
76
|
-
|
|
125
|
+
|
|
126
|
+
return renderHtmlStoryResult(Story({ args }), {
|
|
127
|
+
platformAdapter,
|
|
128
|
+
});
|
|
77
129
|
},
|
|
78
130
|
];
|
|
79
131
|
|
|
@@ -97,7 +149,11 @@ const defaultParams = {
|
|
|
97
149
|
* Merged Storybook parameters including external overrides.
|
|
98
150
|
* @type {object}
|
|
99
151
|
*/
|
|
100
|
-
export const parameters =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
152
|
+
export const parameters = mergePreviewParameters(
|
|
153
|
+
defaultParams,
|
|
154
|
+
externalOverrides,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Initialize platform-agnostic Twig helpers and eager-load story CSS.
|
|
158
|
+
setupTwig(Twig, { extensions: configuredTwigExtensions });
|
|
159
|
+
await fetchCSSFiles(parameters);
|
package/.storybook/utils.js
CHANGED
|
@@ -1,58 +1,77 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import twigAddAttributes from 'add-attributes-twig-extension';
|
|
5
|
-
import emulsifyConfig from '../../../../project.emulsify.json' with { type: 'json' };
|
|
6
|
-
import twigInclude from './polyfills/twig-include';
|
|
7
|
-
import twigSource from './polyfills/twig-source';
|
|
1
|
+
/**
|
|
2
|
+
* @file Shared Storybook runtime helpers.
|
|
3
|
+
*/
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import {
|
|
6
|
+
attachStorybookBehaviors,
|
|
7
|
+
genericStorybookAdapter,
|
|
8
|
+
normalizeStorybookPlatformAdapter,
|
|
9
|
+
} from '../src/storybook/platform-behaviors.js';
|
|
10
|
+
import { setupTwig } from '../src/storybook/twig/setup.js';
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const emulsifyEnv =
|
|
13
|
+
(typeof __EMULSIFY_ENV__ !== 'undefined' && __EMULSIFY_ENV__) || {};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the normalized Emulsify environment injected by Storybook's Vite config.
|
|
17
|
+
*
|
|
18
|
+
* @returns {object} Normalized Emulsify environment.
|
|
19
|
+
*/
|
|
20
|
+
export function getEmulsifyEnvironment() {
|
|
21
|
+
return emulsifyEnv;
|
|
15
22
|
}
|
|
16
23
|
|
|
17
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Get Storybook platform behavior flags from the active adapter.
|
|
26
|
+
*
|
|
27
|
+
* @returns {object} Storybook adapter flags.
|
|
28
|
+
*/
|
|
29
|
+
export function getStorybookPlatformAdapter() {
|
|
30
|
+
return normalizeStorybookPlatformAdapter(
|
|
31
|
+
emulsifyEnv.platformAdapter?.storybook,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
18
34
|
|
|
19
35
|
/**
|
|
20
|
-
*
|
|
21
|
-
* exists, returns default values as a flat component structure.
|
|
36
|
+
* Determine whether Storybook should eagerly load all compiled CSS.
|
|
22
37
|
*
|
|
23
|
-
* @
|
|
38
|
+
* @param {object} [parameters={}] - Storybook parameters.
|
|
39
|
+
* @returns {boolean} TRUE unless `parameters.emulsify.loadAllCSS` is false.
|
|
24
40
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} catch (e) {
|
|
29
|
-
return [
|
|
30
|
-
{
|
|
31
|
-
name: 'components',
|
|
32
|
-
directory: '../../../../components',
|
|
33
|
-
},
|
|
34
|
-
];
|
|
35
|
-
}
|
|
36
|
-
};
|
|
41
|
+
export function getLoadAllCSS(parameters = {}) {
|
|
42
|
+
return parameters?.emulsify?.loadAllCSS !== false;
|
|
43
|
+
}
|
|
37
44
|
|
|
38
45
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
46
|
+
* Eagerly load CSS from the active Storybook render path.
|
|
47
|
+
*
|
|
48
|
+
* Drupal-style mirrored component CSS uses the root `components` CSS tree for
|
|
49
|
+
* component styles and keeps shared compiled CSS from `dist` excluding
|
|
50
|
+
* `dist/components`; all other projects use the compiled `dist` CSS tree. The
|
|
51
|
+
* eager globs live in separate dynamically imported modules so Vite cannot
|
|
52
|
+
* hoist both render paths into the same preview bundle. Projects with very
|
|
53
|
+
* large CSS libraries can set `parameters.emulsify.loadAllCSS = false` and
|
|
54
|
+
* import their own CSS from a preview override.
|
|
41
55
|
*
|
|
42
|
-
* @
|
|
56
|
+
* @param {object} [parameters={}] - Storybook parameters.
|
|
57
|
+
* @returns {Promise<undefined>} Resolves when the selected CSS path is loaded.
|
|
43
58
|
*/
|
|
44
|
-
const fetchCSSFiles = () => {
|
|
59
|
+
const fetchCSSFiles = async (parameters = {}) => {
|
|
45
60
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
if (!getLoadAllCSS(parameters)) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const adapter = getStorybookPlatformAdapter();
|
|
49
66
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
drupalCSSFiles.keys().forEach((file) => drupalCSSFiles(file));
|
|
67
|
+
if (adapter.loadMirroredComponentCss) {
|
|
68
|
+
await import('./css-components.js');
|
|
69
|
+
return undefined;
|
|
54
70
|
}
|
|
55
|
-
|
|
71
|
+
|
|
72
|
+
await import('./css-dist.js');
|
|
73
|
+
return undefined;
|
|
74
|
+
} catch {
|
|
56
75
|
return undefined;
|
|
57
76
|
}
|
|
58
77
|
};
|
|
@@ -64,34 +83,16 @@ const fetchCSSFiles = () => {
|
|
|
64
83
|
* @returns {string|undefined} Project machine name string, or undefined if not available
|
|
65
84
|
*/
|
|
66
85
|
export function getProjectMachineName() {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return undefined;
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
// Build namespaces mapping.
|
|
75
|
-
export const namespaces = {};
|
|
76
|
-
for (const { name, directory } of fetchVariantConfig()) {
|
|
77
|
-
namespaces[name] = resolve(_dirname, '../../../../', directory);
|
|
86
|
+
return typeof emulsifyEnv.machineName === 'string'
|
|
87
|
+
? emulsifyEnv.machineName
|
|
88
|
+
: undefined;
|
|
78
89
|
}
|
|
79
90
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
twigDrupal(twig);
|
|
89
|
-
twigBEM(twig);
|
|
90
|
-
twigAddAttributes(twig);
|
|
91
|
-
twigInclude(twig);
|
|
92
|
-
twigSource(twig);
|
|
93
|
-
return twig;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Export the fetchCSSFiles function.
|
|
97
|
-
export { fetchCSSFiles };
|
|
91
|
+
// Keep these named exports stable for preview.js and downstream overrides.
|
|
92
|
+
export {
|
|
93
|
+
attachStorybookBehaviors,
|
|
94
|
+
fetchCSSFiles,
|
|
95
|
+
genericStorybookAdapter,
|
|
96
|
+
normalizeStorybookPlatformAdapter,
|
|
97
|
+
setupTwig,
|
|
98
|
+
};
|
package/README.md
CHANGED
|
@@ -4,93 +4,144 @@
|
|
|
4
4
|
|
|
5
5
|
An open-source toolset for creating and implementing design systems.
|
|
6
6
|
|
|
7
|
-
**Emulsify Core** provides
|
|
7
|
+
**Emulsify Core** provides shared [Vite](https://vite.dev/) build configuration and a [Storybook](https://storybook.js.org/) component library setup for component-driven development. Twig-based components and React components are both supported authoring models. A project can be Twig-first, React-first, or intentionally mixed.
|
|
8
8
|
|
|
9
|
-
##
|
|
10
|
-
Installation and configuration is setup by the provided base theme project(s). As of this writing, Emulsify Drupal is the only base theme project [with this integration](https://github.com/emulsify-ds/emulsify-drupal/blob/main/whisk/package.json#L36).
|
|
9
|
+
## How Emulsify Core Works
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
11
|
+
- Vite builds project JavaScript, Sass/CSS, Twig templates, component metadata, and static component assets.
|
|
12
|
+
- Storybook uses the React/Vite framework.
|
|
13
|
+
- Twig files can render in React-based Storybook through `renderTwig()`.
|
|
14
|
+
- React components render through Storybook's React/Vite support.
|
|
15
|
+
- Twig and React stories can coexist in the same Storybook instance.
|
|
16
|
+
- `project.emulsify.json` is the source of truth for platform and structure configuration.
|
|
17
|
+
- Platform-specific behavior is controlled by adapters instead of being assumed globally.
|
|
18
|
+
- Node.js 24 or later is required.
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
## Project Evolution
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
(Each is prefixed with `npm run `)
|
|
22
|
+
Emulsify Core has grown through each major release while keeping the same practical goal: make component-library tooling easier to share across real projects.
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
- `1.x` established Emulsify Core as a reusable package for Storybook, Webpack, linting, a11y checks, project overrides, and asset handling.
|
|
25
|
+
- `2.x` expanded component structure support, improved Drupal SDC compatibility, upgraded Storybook, and made more project files configurable from consuming projects.
|
|
26
|
+
- `3.x` modernized the runtime around ESM and Node 24, continued Storybook and dependency upgrades, improved component asset copying, and strengthened compatibility for existing Drupal-oriented builds.
|
|
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`.
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
Lints all JS/SCSS within your components and reports any violations.
|
|
29
|
+
The latest version is the next evolution of that work: faster builds, clearer public APIs, less global Drupal assumption, and a broader foundation for CMS themes, standalone UI libraries, and mixed component systems.
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
Automatically fixes any simple violations.
|
|
31
|
+
See [Version Evolution](docs/version-evolution.md) for more release history.
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
Outputs any code formatting violations.
|
|
33
|
+
## Authoring Models
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
Automatically fixes any simple code formatting violations.
|
|
35
|
+
Twig and React are equally valid ways to build component libraries with Emulsify Core. The right authoring model depends on the consuming project:
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
- Use Twig for CMS themes and server-rendered template systems. Drupal has a dedicated adapter today; Craft CMS and WordPress + Timber can use the generic adapter unless a project adds platform-specific behavior.
|
|
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.
|
|
39
40
|
|
|
41
|
+
See [Component Authoring](docs/component-authoring.md) for Twig, React, mixed Storybook, and shared Sass examples.
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
## Basic Usage
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
Installation and project scripts are usually provided by a starter or platform integration. Manual setup starts with:
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
```sh
|
|
48
|
+
npm install @emulsify/core
|
|
49
|
+
```
|
|
46
50
|
|
|
47
|
-
|
|
51
|
+
Every project should provide a `project.emulsify.json` file at the project root:
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"project": {
|
|
56
|
+
"platform": "generic",
|
|
57
|
+
"name": "example",
|
|
58
|
+
"machineName": "example"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
50
62
|
|
|
51
|
-
|
|
63
|
+
Common project scripts call the shared Emulsify Core Vite and Storybook config:
|
|
52
64
|
|
|
53
|
-
|
|
65
|
+
- `storybook`: starts Storybook development.
|
|
66
|
+
- `storybook-build`: builds static Storybook output.
|
|
67
|
+
- `build`: runs the Vite build for JS, CSS, copied Twig templates, component metadata, and static component assets.
|
|
68
|
+
- `lint`: lints maintained project source.
|
|
54
69
|
|
|
55
|
-
|
|
70
|
+
## Documentation
|
|
56
71
|
|
|
57
|
-
|
|
72
|
+
The documentation is split by task:
|
|
58
73
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
74
|
+
| Topic | Use This When |
|
|
75
|
+
| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
76
|
+
| [Version Evolution](docs/version-evolution.md) | Understanding how Emulsify Core has evolved across major releases. |
|
|
77
|
+
| [Component Authoring](docs/component-authoring.md) | Choosing Twig, React, or mixed Storybook authoring and comparing component examples. |
|
|
78
|
+
| [Storybook](docs/storybook.md) | Rendering Twig stories, using `renderTwig()`, understanding Twig runtime helpers, and mixing Twig with React stories. |
|
|
79
|
+
| [Project Structure And Output](docs/project-structure.md) | Configuring `src/components`, root `./components`, `variant.structureImplementations`, and expected output paths. |
|
|
80
|
+
| [Platform Adapters](docs/platform-adapters.md) | Understanding `generic`, `drupal`, platform resolution order, and Drupal SDC behavior. |
|
|
81
|
+
| [Extension Points](docs/extension-points.md) | Adding Vite plugins, Tailwind CSS, Storybook preview overrides, and other framework tooling. |
|
|
82
|
+
| [Performance](docs/performance.md) | Understanding sourcemaps, eager Twig imports, Tailwind scanning, copied files, and fixture validation. |
|
|
83
|
+
| [Native Twig Extensions](docs/native-twig-extensions.md) | Using `bem()`, `add_attributes()`, and `switch/case/default/endswitch` in Twig.js. |
|
|
84
|
+
| [Release Verification](docs/release.md) | Running 4.x release checks, tarball smoke tests, and semantic-release dry runs before publishing. |
|
|
85
|
+
| [Migration](docs/migration-4x.md) | Upgrading from earlier versions while preserving existing structures. |
|
|
62
86
|
|
|
63
|
-
|
|
87
|
+
## Known Limitations
|
|
64
88
|
|
|
65
|
-
|
|
89
|
+
- Implemented platform adapters are currently `generic` and `drupal`. WordPress + Timber and Craft CMS are supported as Twig-oriented use cases through the generic adapter today; dedicated adapters are future opportunities. See [Platform Adapters](docs/platform-adapters.md).
|
|
90
|
+
- Storybook's Twig resolver eagerly imports Twig modules and raw Twig source. This is reliable for `include()` and `source()`, but large Twig libraries should keep Storybook source roots intentional. See [Performance](docs/performance.md).
|
|
91
|
+
- Production sourcemaps are enabled by default unless a project overrides Vite config through `config/emulsify-core/vite/plugins.*`. See [Performance](docs/performance.md).
|
|
92
|
+
- Project extensions use the public `config/emulsify-core` directory: `config/emulsify-core/vite/plugins.*` for Vite, `config/emulsify-core/storybook/...` for Storybook, and `config/emulsify-core/a11y.config.js` for a11y. See [Extension Points](docs/extension-points.md).
|
|
93
|
+
- Webpack-specific customizations must be migrated manually to Vite plugins or `extendConfig()`. See [Migration](docs/migration-4x.md).
|
|
94
|
+
- Drupal SDC mirroring only applies when the Drupal adapter and SDC settings are enabled. Generic projects should expect output to remain in `dist/`. See [Platform Adapters](docs/platform-adapters.md).
|
|
66
95
|
|
|
67
|
-
|
|
68
|
-
2. Run the `commit` script via `yarn commit` or `npm run commit` and follow the prompts to craft the perfect commit message.
|
|
69
|
-
3. Your commit message will be used to create the changelog for the next version that includes that commit.
|
|
96
|
+
## Supported Project Shapes
|
|
70
97
|
|
|
71
|
-
|
|
98
|
+
Release-readiness coverage validates:
|
|
72
99
|
|
|
73
|
-
|
|
100
|
+
- Drupal SDC projects using `src/components`.
|
|
101
|
+
- Generic Twig projects using `src/components`.
|
|
102
|
+
- Root `./components` projects.
|
|
103
|
+
- Projects using multiple `variant.structureImplementations`.
|
|
104
|
+
- Mixed Twig + React Storybook projects.
|
|
74
105
|
|
|
75
|
-
|
|
106
|
+
WordPress + Timber and Craft CMS are Twig-based project use cases that can use the `generic` adapter today. Dedicated adapters for those platforms are future opportunities. The implemented adapters in this package are currently `generic` and `drupal`.
|
|
76
107
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
108
|
+
## Public Imports
|
|
109
|
+
|
|
110
|
+
Emulsify Core exposes stable public package paths:
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
import { renderTwig } from '@emulsify/core/storybook';
|
|
114
|
+
import { registerTwigExtensions } from '@emulsify/core/extensions/twig';
|
|
115
|
+
import { defineReactExtension } from '@emulsify/core/extensions/react';
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`defineReactExtension` is reserved for future React extension support. It currently returns the input unchanged. Adopting the import path is safe; the runtime is intentionally a no-op until the registry lands. See [Extension Points](docs/extension-points.md#public-imports).
|
|
119
|
+
|
|
120
|
+
Vite consumers can import the shared config from `@emulsify/core/vite` and public Vite plugin helpers from `@emulsify/core/vite/plugins`.
|
|
121
|
+
|
|
122
|
+
## Contributing
|
|
90
123
|
|
|
91
|
-
|
|
92
|
-
<!-- prettier-ignore-end -->
|
|
124
|
+
Maintained JavaScript source, config, scripts, and tests should use consistent comments:
|
|
93
125
|
|
|
94
|
-
|
|
126
|
+
- Start each maintained JS file with a short JSDoc file block that explains the file's responsibility.
|
|
127
|
+
- Use JSDoc blocks for exported functions, complex helpers, and public contracts.
|
|
128
|
+
- Use `//` comments for local intent, compatibility behavior, and non-obvious edge cases.
|
|
129
|
+
- Keep comments concise and factual. Prefer explaining why behavior exists instead of restating the code.
|
|
130
|
+
- Use YAML or shell comments in workflow, hook, and fixture files where the format supports comments.
|
|
95
131
|
|
|
96
|
-
|
|
132
|
+
Do not add comments to JSON files, lockfiles, binary assets, generated output, legal documents, or dependency files. Those formats either do not support comments or should remain exact artifacts.
|
|
133
|
+
|
|
134
|
+
Please also follow the issue template and pull request templates provided. See below for the correct places to post issues:
|
|
135
|
+
|
|
136
|
+
1. [Emulsify Drupal](https://github.com/emulsify-ds/emulsify-drupal/issues)
|
|
137
|
+
2. [Emulsify Tools (Drupal module)](https://www.drupal.org/project/issues/emulsify_tools)
|
|
138
|
+
|
|
139
|
+
## Links
|
|
140
|
+
|
|
141
|
+
- [Emulsify Homepage](https://www.emulsify.info/)
|
|
142
|
+
- [Storybook Demo](http://storybook.emulsify.info/)
|
|
143
|
+
- [Code of Conduct](https://github.com/emulsify-ds/emulsify-drupal/blob/master/CODE_OF_CONDUCT.md)
|
|
144
|
+
|
|
145
|
+
## Author
|
|
146
|
+
|
|
147
|
+
Emulsify® is a product of [Four Kitchens](https://fourkitchens.com).
|
package/config/.stylelintrc.json
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": [
|
|
3
|
-
|
|
4
|
-
],
|
|
5
|
-
"plugins": [
|
|
6
|
-
"stylelint-prettier"
|
|
7
|
-
],
|
|
2
|
+
"extends": ["stylelint-config-standard-scss"],
|
|
3
|
+
"plugins": ["stylelint-prettier"],
|
|
8
4
|
"customSyntax": "postcss-scss",
|
|
9
5
|
"rules": {
|
|
10
6
|
"at-rule-empty-line-before": [
|
package/config/a11y.config.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file Shared accessibility linting configuration.
|
|
3
|
+
*
|
|
4
|
+
* These defaults are consumed by the pa11y script and can be extended by
|
|
5
|
+
* consuming projects through their local Emulsify config.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export default {
|
|
2
9
|
storybookBuildDir: '../../../../.out',
|
|
3
10
|
pa11y: {
|
|
4
11
|
includeNotices: false,
|
|
5
12
|
includeWarnings: false,
|
|
6
13
|
runners: ['axe'],
|
|
7
14
|
},
|
|
8
|
-
//
|
|
9
|
-
// basis, which results in the linter reporting some errors that
|
|
10
|
-
// should be ignored. These codes and descriptions allow for those
|
|
11
|
-
// errors to be targeted specifically.
|
|
15
|
+
// Ignore rules that are noisy for isolated component pages.
|
|
12
16
|
ignore: {
|
|
13
17
|
codes: ['landmark-one-main', 'page-has-heading-one'],
|
|
14
18
|
descriptions: ['Ensures all page content is contained by landmarks'],
|
package/config/babel.config.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Babel configuration for test and legacy transpilation paths.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
export default (api) => {
|
|
2
6
|
api.cache(true);
|
|
3
7
|
|
|
8
|
+
// Disable Babel's generated comments so minified output stays compact.
|
|
4
9
|
const presets = [['minify', { builtIns: false }]];
|
|
5
10
|
const comments = false;
|
|
6
11
|
|
package/config/eslint.config.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file ESLint flat configuration for Emulsify Core.
|
|
3
|
+
*/
|
|
4
|
+
|
|
2
5
|
import js from '@eslint/js';
|
|
3
6
|
import babelParser from '@babel/eslint-parser';
|
|
4
7
|
import importPlugin from 'eslint-plugin-import';
|
|
@@ -6,10 +9,9 @@ import pluginSecurity from 'eslint-plugin-security';
|
|
|
6
9
|
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
|
7
10
|
|
|
8
11
|
export default [
|
|
9
|
-
//
|
|
12
|
+
// Start with core and plugin recommendations before project overrides.
|
|
10
13
|
js.configs.recommended,
|
|
11
14
|
|
|
12
|
-
// Plugin configurations
|
|
13
15
|
importPlugin.flatConfigs.recommended,
|
|
14
16
|
pluginSecurity.configs.recommended,
|
|
15
17
|
eslintPluginPrettierRecommended,
|
|
@@ -39,6 +41,7 @@ export default [
|
|
|
39
41
|
ignores: ['**/*.min.js', '**/node_modules/**/*'],
|
|
40
42
|
|
|
41
43
|
rules: {
|
|
44
|
+
// Keep historical project conventions while warning on risky patterns.
|
|
42
45
|
strict: 0,
|
|
43
46
|
'consistent-return': 'off',
|
|
44
47
|
'no-underscore-dangle': 'off',
|
package/config/postcss.config.js
CHANGED