@emulsify/core 3.3.2 → 3.4.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.
@@ -0,0 +1,36 @@
1
+
2
+ import resolveTemplate from './twig-resolver.js';
3
+
4
+ /**
5
+ * Twig `include()` polyfill.
6
+ * Mirrors Drupal behaviour inside Storybook.
7
+ * @param {string} templateName
8
+ * @param {Object} [variables]
9
+ * @param {boolean} [withContext=false]
10
+ * @return {string}
11
+ */
12
+ function twigInclude(Twig) {
13
+ Twig.extendFunction('include', (...args) => {
14
+ let [templateName, variables = {}, withContext = false] = args;
15
+ if (typeof withContext !== 'boolean' && variables && typeof variables.with_context !== 'undefined') {
16
+ withContext = variables.with_context;
17
+ delete variables.with_context;
18
+ }
19
+
20
+ try {
21
+ const templateFn = resolveTemplate(templateName);
22
+ if (!templateFn) return '';
23
+
24
+ const finalContext = withContext && typeof this === 'object'
25
+ ? { ...(this.context || {}), ...variables }
26
+ : variables;
27
+
28
+ return templateFn(finalContext);
29
+ } catch (err) {
30
+ console.error(`Twig include() failed for: ${templateName}`, err);
31
+ return '';
32
+ }
33
+ });
34
+ };
35
+
36
+ export default twigInclude;
@@ -0,0 +1,68 @@
1
+ import { getProjectMachineName } from '../utils';
2
+
3
+ const namespace = getProjectMachineName();
4
+
5
+ const twigComponents = require.context(
6
+ '../../../../../src/components/',
7
+ true,
8
+ /\.twig$/
9
+ );
10
+
11
+ /**
12
+ * Resolve template identifier to compiled Twig function.
13
+ * Supports: @component.twig, namespace:component, @namespace/component, namespace/component
14
+ * @param {string} name Template identifier
15
+ * @returns {Function|undefined} Compiled function or noop
16
+ */
17
+ function resolveTemplate(name) {
18
+ // namespace:icon, @namespace/icon.twig
19
+ if (name.startsWith(`${namespace}:`) || name.startsWith(`@${namespace}/`)) {
20
+ const part = name.startsWith(`${namespace}:`) ? name.split(':')[1] : name.replace(`${namespace}/`, '').replace('.twig', '');
21
+ const path = `./${part}/${part}.twig`;
22
+ try {
23
+ {
24
+ const mod = twigComponents(path);
25
+ return mod && mod.default ? mod.default : mod;
26
+ }
27
+ } catch (e) {
28
+ // eslint-disable-next-line no-console
29
+ console.error(`Cannot resolve Twig component for '${name}' at '${path}'`);
30
+ }
31
+ }
32
+
33
+ // @icon.twig → icon/icon.twig
34
+ if (name.startsWith('@') && name.endsWith('.twig')) {
35
+ const part = name.slice(1, -5); // remove leading @ and trailing .twig
36
+ const path = `./${part}/${part}.twig`;
37
+ try {
38
+ return twigComponents(path).default || twigComponents(path);
39
+ } catch (e) {
40
+ console.error(`Cannot resolve Twig shorthand template '${name}' at '${path}'`);
41
+ }
42
+ }
43
+
44
+ // namespace/icon.twig via webpack alias
45
+ if (name.startsWith(`${namespace}/`)) {
46
+ const part = name.replace(new RegExp(`^${namespace}/`), '').replace('.twig', '');
47
+ const path = `./${part}/${part}.twig`;
48
+ try {
49
+ return twigComponents(path).default || twigComponents(path);
50
+ } catch (e) {
51
+ console.error(`Cannot resolve Twig alias template '${name}' at '${path}'`);
52
+ }
53
+ }
54
+
55
+ try {
56
+ {
57
+ // eslint-disable-next-line import/no-dynamic-require, global-require
58
+ const mod = require(name);
59
+ return mod && mod.default ? mod.default : mod;
60
+ }
61
+ } catch (e) {
62
+ // eslint-disable-next-line no-console
63
+ console.error(`Cannot resolve Twig template '${name}'`, e);
64
+ return () => '';
65
+ }
66
+ };
67
+
68
+ export default resolveTemplate;
@@ -0,0 +1,54 @@
1
+ import { getProjectMachineName } from '../utils';
2
+
3
+ const namespace = getProjectMachineName();
4
+
5
+ // Constants used by the `source()` polyfill.
6
+ const PUBLIC_ASSET_BASE = (typeof window !== 'undefined' && window.location && window.location.hostname && window.location.hostname.endsWith('github.io'))
7
+ ? `/${namespace}/assets/`
8
+ : '/assets/';
9
+
10
+ const INLINE_ASSET_EXTS = new Set(['svg', 'html', 'twig', 'css', 'js', 'json', 'txt', 'md']);
11
+ const IMAGE_ASSET_EXTS = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'avif']);
12
+
13
+ /**
14
+ * Twig `source()` polyfill.
15
+ * Returns an <img> tag or URL for @assets paths.
16
+ * @param {string} assetPath
17
+ * @return {string}
18
+ */
19
+ function twigSource(Twig) {
20
+ Twig.extendFunction('source', (assetPath) => {
21
+ if (typeof assetPath !== 'string') return '';
22
+
23
+ // Strip Drupal-style alias and extract file extension.
24
+ const relPath = assetPath.replace(/^@assets\//, '');
25
+ const extension = relPath.split('.').pop().toLowerCase();
26
+
27
+ // Inline raw content for textual assets.
28
+ if (INLINE_ASSET_EXTS.has(extension)) {
29
+ try {
30
+ const xhr = new XMLHttpRequest();
31
+ xhr.open('GET', `${PUBLIC_ASSET_BASE}${relPath}`, false); // synchronous
32
+ xhr.send(null);
33
+ if (xhr.status >= 200 && xhr.status < 300) {
34
+ return xhr.responseText;
35
+ }
36
+ // eslint-disable-next-line no-console
37
+ console.error(`source(): ${xhr.status} while fetching ${relPath}`);
38
+ } catch (err) {
39
+ // eslint-disable-next-line no-console
40
+ console.error(`source(): failed to fetch ${relPath}`, err);
41
+ }
42
+ }
43
+
44
+ // Auto-render raster images.
45
+ if (IMAGE_ASSET_EXTS.has(extension)) {
46
+ return `<img src="${PUBLIC_ASSET_BASE}${relPath}" alt="" role="img">`;
47
+ }
48
+
49
+ // Fallback: return public URL.
50
+ return `${PUBLIC_ASSET_BASE}${relPath}`;
51
+ });
52
+ };
53
+
54
+ export default twigSource;
@@ -3,6 +3,8 @@ import twigDrupal from 'twig-drupal-filters';
3
3
  import twigBEM from 'bem-twig-extension';
4
4
  import twigAddAttributes from 'add-attributes-twig-extension';
5
5
  import emulsifyConfig from '../../../../project.emulsify.json' with { type: 'json' };
6
+ import twigInclude from './polyfills/twig-include';
7
+ import twigSource from './polyfills/twig-source';
6
8
 
7
9
  // Create __filename from import.meta.url without fileURLToPath
8
10
  let _filename = decodeURIComponent(new URL(import.meta.url).pathname);
@@ -55,6 +57,20 @@ const fetchCSSFiles = () => {
55
57
  }
56
58
  };
57
59
 
60
+ /**
61
+ * Fetches the project machine name from Emulsify configuration.
62
+ * Returns undefined if the config is unavailable or machineName is not set.
63
+ *
64
+ * @returns {string|undefined} Project machine name string, or undefined if not available
65
+ */
66
+ export function getProjectMachineName() {
67
+ try {
68
+ return emulsifyConfig.project.machineName;
69
+ } catch (e) {
70
+ return undefined;
71
+ }
72
+ };
73
+
58
74
  // Build namespaces mapping.
59
75
  export const namespaces = {};
60
76
  for (const { name, directory } of fetchVariantConfig()) {
@@ -72,6 +88,8 @@ export function setupTwig(twig) {
72
88
  twigDrupal(twig);
73
89
  twigBEM(twig);
74
90
  twigAddAttributes(twig);
91
+ twigInclude(twig);
92
+ twigSource(twig);
75
93
  return twig;
76
94
  }
77
95
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emulsify/core",
3
- "version": "3.3.2",
3
+ "version": "3.4.1",
4
4
  "description": "Bundled tooling for Storybook development + Webpack Build",
5
5
  "keywords": [
6
6
  "component library",
@@ -50,11 +50,11 @@
50
50
  "twatch": "jest --no-coverage --watch --verbose"
51
51
  },
52
52
  "dependencies": {
53
- "@babel/core": "^7.28.4",
54
- "@babel/eslint-parser": "^7.28.4",
55
- "@babel/preset-env": "^7.28.3",
53
+ "@babel/core": "^7.28.5",
54
+ "@babel/eslint-parser": "^7.28.5",
55
+ "@babel/preset-env": "^7.28.5",
56
56
  "@emulsify/cli": "^1.11.4",
57
- "@eslint/js": "^9.35.0",
57
+ "@eslint/js": "^9.38.0",
58
58
  "@storybook/addon-a11y": "^8.6.14",
59
59
  "@storybook/addon-actions": "^8.6.14",
60
60
  "@storybook/addon-essentials": "^8.6.14",
@@ -76,7 +76,7 @@
76
76
  "concurrently": "^9.2.1",
77
77
  "copy-webpack-plugin": "^13.0.1",
78
78
  "css-loader": "^7.1.1",
79
- "eslint": "^9.35.0",
79
+ "eslint": "^9.38.0",
80
80
  "eslint-config-prettier": "^10.1.8",
81
81
  "eslint-plugin-import": "^2.32.0",
82
82
  "eslint-plugin-jest": "^29.0.1",
@@ -85,7 +85,7 @@
85
85
  "eslint-plugin-storybook": "^0.12.0",
86
86
  "eslint-webpack-plugin": "^5.0.2",
87
87
  "file-loader": "^6.2.0",
88
- "fs-extra": "^11.3.1",
88
+ "fs-extra": "^11.3.2",
89
89
  "glob": "^11.0.3",
90
90
  "graceful-fs": "^4.2.11",
91
91
  "html-webpack-plugin": "^5.6.4",
@@ -93,52 +93,52 @@
93
93
  "imagemin": "^9.0.1",
94
94
  "imagemin-jpegtran": "^8.0.0",
95
95
  "imagemin-optipng": "^8.0.0",
96
- "jest": "^30.1.3",
97
- "jest-environment-jsdom": "^30.1.2",
96
+ "jest": "^30.2.0",
97
+ "jest-environment-jsdom": "^30.2.0",
98
98
  "js-yaml": "^4.1.0",
99
99
  "js-yaml-loader": "^1.2.2",
100
100
  "mini-css-extract-plugin": "^2.9.4",
101
101
  "node-sass-glob-importer": "^5.3.3",
102
102
  "normalize.css": "^8.0.1",
103
103
  "open-cli": "^8.0.0",
104
- "pa11y": "^9.0.0",
104
+ "pa11y": "^9.0.1",
105
105
  "postcss": "^8.5.6",
106
106
  "postcss-loader": "^8.2.0",
107
107
  "postcss-scss": "^4.0.9",
108
- "ramda": "^0.31.3",
108
+ "ramda": "^0.32.0",
109
109
  "regenerator-runtime": "^0.14.1",
110
- "sass": "^1.92.1",
111
- "sass-loader": "^16.0.5",
110
+ "sass": "^1.93.2",
111
+ "sass-loader": "^16.0.6",
112
112
  "storybook": "^8.6.14",
113
- "style-dictionary": "^5.0.4",
114
- "stylelint": "^16.24.0",
115
- "stylelint-config-standard-scss": "^15.0.1",
113
+ "style-dictionary": "^5.1.1",
114
+ "stylelint": "^16.25.0",
115
+ "stylelint-config-standard-scss": "^16.0.0",
116
116
  "stylelint-prettier": "^5.0.3",
117
117
  "stylelint-selector-bem-pattern": "^4.0.1",
118
118
  "stylelint-webpack-plugin": "^5.0.1",
119
- "svg-spritemap-webpack-plugin": "^5.0.1",
119
+ "svg-spritemap-webpack-plugin": "^5.0.3",
120
120
  "token-transformer": "^0.0.33",
121
121
  "twig-drupal-filters": "^3.2.0",
122
122
  "twig-testing-library": "^1.2.0",
123
123
  "twigjs-loader": "^1.0.3",
124
- "webpack": "^5.101.3",
124
+ "webpack": "^5.102.1",
125
125
  "webpack-cli": "^6.0.1",
126
126
  "webpack-merge": "^6.0.1",
127
127
  "webpack-remove-empty-scripts": "^1.1.1",
128
128
  "yaml": "^2.8.1"
129
129
  },
130
130
  "devDependencies": {
131
- "@commitlint/cli": "^19.8.1",
132
- "@commitlint/config-conventional": "^19.8.1",
131
+ "@commitlint/cli": "^20.1.0",
132
+ "@commitlint/config-conventional": "^20.0.0",
133
133
  "@semantic-release/changelog": "^6.0.2",
134
134
  "@semantic-release/commit-analyzer": "^13.0.1",
135
135
  "@semantic-release/git": "^10.0.1",
136
- "@semantic-release/github": "^11.0.5",
136
+ "@semantic-release/github": "^11.0.6",
137
137
  "@semantic-release/release-notes-generator": "^14.1.0",
138
138
  "all-contributors-cli": "^6.26.1",
139
139
  "husky": "^9.1.7",
140
- "lint-staged": "^16.1.6",
141
- "semantic-release": "^24.2.7"
140
+ "lint-staged": "^16.2.6",
141
+ "semantic-release": "^24.2.9"
142
142
  },
143
143
  "overrides": {
144
144
  "inflight": "^1.0.7",