@emulsify/core 2.7.0 → 3.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.
@@ -1,30 +1,67 @@
1
- const path = require('path');
2
- const globImporter = require('node-sass-glob-importer');
1
+ import { dirname, resolve } from 'path';
2
+ import globImporter from 'node-sass-glob-importer';
3
+ import _StyleLintPlugin from 'stylelint-webpack-plugin';
4
+ import ESLintPlugin from 'eslint-webpack-plugin';
5
+ import resolves from '../config/webpack/resolves.js';
6
+ import emulsifyConfig from '../../../../project.emulsify.json' with { type: 'json' };
3
7
 
4
- const _StyleLintPlugin = require('stylelint-webpack-plugin');
5
- const ESLintPlugin = require('eslint-webpack-plugin');
6
- const resolves = require('../config/webpack/resolves');
8
+ // Create __filename from import.meta.url without fileURLToPath
9
+ let _filename = decodeURIComponent(new URL(import.meta.url).pathname);
7
10
 
8
- // Emulsify project configuration.
9
- const emulsifyConfig = require('../../../../project.emulsify.json');
11
+ // On Windows, remove the leading slash (e.g. "/C:/path" -> "C:/path")
12
+ if (process.platform === 'win32' && _filename.startsWith('/')) {
13
+ _filename = _filename.slice(1);
14
+ }
15
+
16
+ /**
17
+ * Directory name of the current file.
18
+ * @type {string}
19
+ */
20
+ const _dirname = dirname(_filename);
10
21
 
11
22
  /**
12
- * Transforms namespace:component to @namespace/template/path
23
+ * Absolute path to the project root directory.
24
+ * @type {string}
25
+ */
26
+ const projectDir = resolve(_dirname, '../../../../..');
27
+
28
+ /**
29
+ * Webpack plugin to resolve custom namespace imports.
30
+ * Transforms `<prefix>:<component>` into `<prefix>/<component>` paths.
13
31
  */
14
32
  class ProjectNameResolverPlugin {
33
+ /**
34
+ * @param {object} options - Plugin options.
35
+ * @param {string} options.projectName - Prefix for the project namespace.
36
+ */
15
37
  constructor(options = {}) {
16
38
  this.prefix = options.projectName;
17
39
  }
18
40
 
41
+ /**
42
+ * Apply the webpack resolver hook.
43
+ * @param {object} resolver - The webpack resolver instance.
44
+ */
19
45
  apply(resolver) {
20
46
  const target = resolver.ensureHook('resolve');
21
- resolver
22
- .getHook('before-resolve')
23
- .tapAsync('ProjectNameResolverPlugin', (request, resolveContext, callback) => {
47
+ resolver.getHook('before-resolve').tapAsync(
48
+ 'ProjectNameResolverPlugin',
49
+ /**
50
+ * @param {object} request - The resolve request object.
51
+ * @param {object} resolveContext - Context for resolving.
52
+ * @param {Function} callback - Callback to continue resolution.
53
+ */
54
+ (request, resolveContext, callback) => {
24
55
  const requestPath = request.request;
25
56
 
26
- if (requestPath && requestPath.startsWith(`${this.prefix}:`)) {
27
- const newRequestPath = requestPath.replace(`${this.prefix}:`, `${this.prefix}/`);
57
+ if (
58
+ requestPath &&
59
+ requestPath.startsWith(`${this.prefix}:`)
60
+ ) {
61
+ const newRequestPath = requestPath.replace(
62
+ `${this.prefix}:`,
63
+ `${this.prefix}/`
64
+ );
28
65
  const newRequest = {
29
66
  ...request,
30
67
  request: newRequestPath,
@@ -40,31 +77,53 @@ class ProjectNameResolverPlugin {
40
77
  } else {
41
78
  callback();
42
79
  }
43
- });
80
+ }
81
+ );
44
82
  }
45
83
  }
46
84
 
47
- module.exports = async ({ config }) => {
85
+ /**
86
+ * Export a function to customize the Webpack config for Storybook.
87
+ * @param {object} param0 - The Storybook configuration object.
88
+ * @param {object} param0.config - The existing webpack config to modify.
89
+ * @returns {object} The updated webpack config.
90
+ */
91
+ export default async function ({ config }) {
48
92
  // Alias
49
93
  Object.assign(config.resolve.alias, resolves.TwigResolve.alias);
50
94
 
51
- // Twig
95
+ // Twig loader
52
96
  config.module.rules.push({
97
+ /**
98
+ * @type {RegExp}
99
+ */
53
100
  test: /\.twig$/,
54
101
  use: [
55
102
  {
56
- loader: path.resolve(__dirname, '../config/webpack/sdc-loader.js'),
103
+ /**
104
+ * Custom loader for svg/spritemap integration.
105
+ * @type {string}
106
+ */
107
+ loader: resolve(_dirname, '../config/webpack/sdc-loader.js'),
57
108
  options: {
109
+ /**
110
+ * Name of the Emulsify project for resolving.
111
+ * @type {string}
112
+ */
58
113
  projectName: emulsifyConfig.project.name,
59
114
  },
60
115
  },
61
116
  {
117
+ /**
118
+ * Standard Twig JS loader.
119
+ * @type {string}
120
+ */
62
121
  loader: 'twigjs-loader',
63
122
  },
64
123
  ],
65
124
  });
66
125
 
67
- // SCSS
126
+ // SCSS Loader configuration
68
127
  config.module.rules.push({
69
128
  test: /\.s[ac]ss$/i,
70
129
  use: [
@@ -72,6 +131,10 @@ module.exports = async ({ config }) => {
72
131
  {
73
132
  loader: 'css-loader',
74
133
  options: {
134
+ /**
135
+ * Enable source maps for CSS.
136
+ * @type {boolean}
137
+ */
75
138
  sourceMap: true,
76
139
  },
77
140
  },
@@ -87,38 +150,44 @@ module.exports = async ({ config }) => {
87
150
  ],
88
151
  });
89
152
 
90
- // YAML
153
+ // YAML loader
91
154
  config.module.rules.push({
155
+ /**
156
+ * @type {RegExp}
157
+ */
92
158
  test: /\.ya?ml$/,
93
159
  loader: 'js-yaml-loader',
94
160
  });
95
161
 
96
- // Plugins
162
+ // StyleLint and ESLint plugins
97
163
  config.plugins.push(
98
164
  new _StyleLintPlugin({
99
- configFile: path.resolve(__dirname, '../', '.stylelintrc.json'),
100
- context: path.resolve(__dirname, '../', 'src'),
165
+ configFile: resolve(projectDir, '../', '.stylelintrc.json'),
166
+ context: resolve(projectDir, '../', 'src'),
101
167
  files: '**/*.scss',
102
168
  failOnError: false,
103
169
  quiet: false,
104
170
  }),
105
171
  new ESLintPlugin({
106
- context: path.resolve(__dirname, '../', 'src'),
172
+ context: resolve(projectDir, '../', 'src'),
107
173
  extensions: ['js'],
108
174
  }),
109
175
  );
110
176
 
111
- // Resolver Plugins
177
+ // Custom resolver plugin for namespaced imports
112
178
  config.resolve.plugins = [
113
179
  new ProjectNameResolverPlugin({
114
180
  projectName: emulsifyConfig.project.name,
115
181
  }),
116
182
  ];
117
183
 
118
- // Configure fallback for optional modules that may not be present
184
+ // Fallback for optional modules
119
185
  config.resolve.fallback = {
186
+ /**
187
+ * Prevent resolution of components directory if missing.
188
+ */
120
189
  '../../../../components': false,
121
190
  };
122
191
 
123
192
  return config;
124
- };
193
+ }
@@ -1,3 +1,5 @@
1
- module.exports = {
2
- extends: ['@commitlint/config-conventional'],
1
+ const Configuration = {
2
+ extends: ["@commitlint/config-conventional"],
3
3
  };
4
+
5
+ export default Configuration;
@@ -1,8 +1,7 @@
1
- module.exports = (api) => {
1
+ export default (api) => {
2
2
  api.cache(true);
3
3
 
4
4
  const presets = [['minify', { builtIns: false }]];
5
-
6
5
  const comments = false;
7
6
 
8
7
  return { presets, comments };
@@ -0,0 +1,74 @@
1
+ // Import ESLint Flat Config and required plugins
2
+ import js from '@eslint/js';
3
+ import babelParser from '@babel/eslint-parser';
4
+ import importPlugin from 'eslint-plugin-import';
5
+ import pluginSecurity from 'eslint-plugin-security';
6
+ import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
7
+
8
+ export default [
9
+ // Base ESLint recommended rules
10
+ js.configs.recommended,
11
+
12
+ // Plugin configurations
13
+ importPlugin.flatConfigs.recommended,
14
+ pluginSecurity.configs.recommended,
15
+ eslintPluginPrettierRecommended,
16
+
17
+ {
18
+ name: 'emulsify-core-config',
19
+ languageOptions: {
20
+ parser: babelParser,
21
+ parserOptions: {
22
+ requireConfigFile: false,
23
+ babelOptions: {
24
+ babelrc: false,
25
+ configFile: false,
26
+ },
27
+ },
28
+ sourceType: 'module',
29
+ ecmaVersion: 'latest',
30
+ globals: {
31
+ expect: true,
32
+ it: true,
33
+ describe: true,
34
+ },
35
+ },
36
+
37
+ files: ['**/*.{js,mjs,cjs}'],
38
+
39
+ ignores: ['**/*.min.js', '**/node_modules/**/*'],
40
+
41
+ rules: {
42
+ strict: 0,
43
+ 'consistent-return': 'off',
44
+ 'no-underscore-dangle': 'off',
45
+ 'max-nested-callbacks': ['warn', 3],
46
+ 'import/extensions': 'off',
47
+ 'import/no-unresolved': 'off',
48
+ 'import/no-extraneous-dependencies': 'warn',
49
+ 'import/no-mutable-exports': 'warn',
50
+ 'no-plusplus': ['warn', { allowForLoopAfterthoughts: true }],
51
+ 'no-param-reassign': 'off',
52
+ 'no-prototype-builtins': 'off',
53
+ 'prettier/prettier': ['error', { singleQuote: true }],
54
+ 'no-unused-vars': 'warn',
55
+ 'no-undef': 'off',
56
+ 'operator-linebreak': [
57
+ 'error',
58
+ 'after',
59
+ { overrides: { '?': 'ignore', ':': 'ignore' } },
60
+ ],
61
+ quotes: ['error', 'single'],
62
+ },
63
+
64
+ settings: {
65
+ 'import/ignore': ['\\.(scss|less|css)$'],
66
+ 'import/resolver': {
67
+ node: {
68
+ extensions: ['.js', '.jsx'],
69
+ moduleDirectory: ['src', 'node_modules'],
70
+ },
71
+ },
72
+ },
73
+ },
74
+ ];
@@ -1,5 +1,5 @@
1
- const autoPrefixer = require('autoprefixer');
1
+ import autoPrefixer from 'autoprefixer';
2
2
 
3
- module.exports = {
3
+ export default {
4
4
  plugins: [autoPrefixer()],
5
5
  };
@@ -1,6 +1,10 @@
1
- const MiniCssExtractPlugin = require('mini-css-extract-plugin');
2
- const globImporter = require('node-sass-glob-importer');
3
- const fs = require('fs-extra');
1
+ /**
2
+ * @fileoverview Configures Webpack loaders.
3
+ */
4
+
5
+ import MiniCssExtractPlugin from 'mini-css-extract-plugin';
6
+ import globImporter from 'node-sass-glob-importer';
7
+ import fs from 'fs-extra';
4
8
 
5
9
  let babelConfig;
6
10
  let postcssConfig;
@@ -71,14 +75,19 @@ const CSSLoader = {
71
75
  };
72
76
 
73
77
  const SVGSpriteLoader = {
74
- test: /icons\/.*\.svg$/, // your icons directory
75
- loader: 'svg-sprite-loader',
76
- options: {
77
- extract: true,
78
- runtimeCompat: true,
79
- outputPath: 'dist/',
80
- spriteFilename: './icons.svg',
81
- },
78
+ test: /icons\/.*\.svg$/,
79
+ use: [
80
+ {
81
+ loader: 'svg-sprite-loader',
82
+ options: {
83
+ extract: true,
84
+ esModule: true,
85
+ runtimeCompat: true,
86
+ outputPath: 'dist/',
87
+ spriteFilename: './icons.svg',
88
+ },
89
+ },
90
+ ],
82
91
  };
83
92
 
84
93
  const TwigLoader = {
@@ -88,7 +97,7 @@ const TwigLoader = {
88
97
  },
89
98
  };
90
99
 
91
- module.exports = {
100
+ export default {
92
101
  JSLoader,
93
102
  CSSLoader,
94
103
  SVGSpriteLoader,
@@ -1,4 +1,4 @@
1
- const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
1
+ import ImageMinimizerPlugin from 'image-minimizer-webpack-plugin';
2
2
 
3
3
  const ImageMinimizer = new ImageMinimizerPlugin({
4
4
  minimizer: {
@@ -13,6 +13,6 @@ const ImageMinimizer = new ImageMinimizerPlugin({
13
13
  },
14
14
  });
15
15
 
16
- module.exports = {
16
+ export default {
17
17
  minimizer: [ImageMinimizer],
18
18
  };
@@ -1,45 +1,52 @@
1
- /* eslint-disable no-underscore-dangle */
2
- const path = require('path');
3
- const webpack = require('webpack');
4
- const { CleanWebpackPlugin } = require('clean-webpack-plugin');
5
- const _MiniCssExtractPlugin = require('mini-css-extract-plugin');
6
- const _SpriteLoaderPlugin = require('svg-sprite-loader/plugin');
7
- const CopyPlugin = require('copy-webpack-plugin');
8
- const glob = require('glob');
9
- const fs = require('fs-extra');
1
+ /**
2
+ * @fileoverview Configures Webpack plugins.
3
+ */
4
+
5
+ import { resolve, dirname } from 'path';
6
+ import webpack from 'webpack';
7
+ import { CleanWebpackPlugin } from 'clean-webpack-plugin';
8
+ import _MiniCssExtractPlugin from 'mini-css-extract-plugin';
9
+ import _SpriteLoaderPlugin from 'svg-sprite-loader/plugin.js';
10
+ import CopyPlugin from 'copy-webpack-plugin';
11
+ import { sync as globSync } from 'glob';
12
+ import fs from 'fs-extra';
13
+ import emulsifyConfig from '../../../../../project.emulsify.json' with { type: 'json' };
14
+
15
+ // Create __filename from import.meta.url without fileURLToPath
16
+ let _filename = decodeURIComponent(new URL(import.meta.url).pathname);
10
17
 
11
- // Get directories for file contexts.
12
- const projectDir = path.resolve(__dirname, '../../../../..');
13
- const srcDir = path.resolve(projectDir, 'src');
18
+ // On Windows, remove the leading slash (e.g. "/C:/path" -> "C:/path")
19
+ if (process.platform === 'win32' && _filename.startsWith('/')) {
20
+ _filename = _filename.slice(1);
21
+ }
22
+
23
+ const _dirname = dirname(_filename);
14
24
 
15
- // Emulsify project configuration.
16
- const emulsifyConfig = require('../../../../../project.emulsify.json');
25
+ const projectDir = resolve(_dirname, '../../../../..');
26
+ const srcDir = resolve(projectDir, 'src');
17
27
 
18
- // Compress images plugin.
19
28
  const MiniCssExtractPlugin = new _MiniCssExtractPlugin({
20
29
  filename: '[name].css',
21
30
  chunkFilename: '[id].css',
22
31
  });
23
32
 
24
- // Create SVG sprite.
25
33
  const SpriteLoaderPlugin = new _SpriteLoaderPlugin({
26
34
  plainSprite: true,
27
35
  });
28
36
 
29
- // Enable Webpack progress plugin.
30
37
  const ProgressPlugin = new webpack.ProgressPlugin();
31
38
 
32
- // Glob pattern for markup files.
33
- const componentFilesPattern = path.resolve(srcDir, '**/*.{twig,component.yml}');
39
+ const componentFilesPattern = resolve(srcDir, '**/*.{twig,component.yml}');
34
40
 
35
41
  /**
36
- * Prepare list of twig files to copy to "compiled" directories.
37
- * @constructor
38
- * @param {string} filesMatcher - Glob pattern.
42
+ * Prepare a list of patterns for copying Twig and component files.
43
+ *
44
+ * @param {string} filesMatcher - Glob pattern for matching files.
45
+ * @returns {Array<Object>} Array of objects with `from` and `to` properties.
39
46
  */
40
47
  function getPatterns(filesMatcher) {
41
48
  const patterns = [];
42
- glob.sync(filesMatcher).forEach((file) => {
49
+ globSync(filesMatcher).forEach((file) => {
43
50
  const projectPath = file.split('/src/')[0];
44
51
  const srcStructure = file.split(`${srcDir}/`)[1];
45
52
  const parentDir = srcStructure.split('/')[0];
@@ -57,32 +64,29 @@ function getPatterns(filesMatcher) {
57
64
  to: newfilePath,
58
65
  });
59
66
  });
60
-
61
67
  return patterns;
62
68
  }
63
69
 
64
- // Copy twig files from src directory.
65
- const CopyTwigPlugin = fs.existsSync(path.resolve(projectDir, 'src'))
66
- ? new CopyPlugin({
67
- patterns: getPatterns(componentFilesPattern),
68
- })
70
+ const CopyTwigPlugin = fs.pathExistsSync(resolve(projectDir, 'src'))
71
+ ? new CopyPlugin({ patterns: getPatterns(componentFilesPattern) })
69
72
  : '';
70
73
 
71
- // Export plugin configuration.
72
- module.exports = {
74
+ const pluginConfig = {
73
75
  ProgressPlugin,
74
76
  MiniCssExtractPlugin,
75
77
  SpriteLoaderPlugin,
76
78
  CopyTwigPlugin,
77
79
  CleanWebpackPlugin: new CleanWebpackPlugin({
78
- protectWebpackAssets: false, // Required for removal of extra, unwanted dist/css/*.js files.
80
+ protectWebpackAssets: false,
79
81
  cleanOnceBeforeBuildPatterns: ['!*.{png,jpg,gif,svg}'],
80
82
  cleanAfterEveryBuildPatterns: [
81
83
  'remove/**',
82
84
  '!js',
83
- 'css/**/*.js', // Remove all unwanted, auto generated JS files from dist/css folder.
85
+ 'css/**/*.js',
84
86
  'css/**/*.js.map',
85
87
  '!*.{png,jpg,gif,svg}',
86
88
  ],
87
89
  }),
88
90
  };
91
+
92
+ export default pluginConfig;
@@ -1,37 +1,51 @@
1
- const path = require('path');
2
- const glob = require('glob');
3
- const fs = require('fs-extra');
1
+ /**
2
+ * @fileoverview Configures Twig alias resolution for the project.
3
+ */
4
+
5
+ import { basename, dirname, resolve } from 'path';
6
+ import { sync as globSync } from 'glob';
7
+ import fs from 'fs-extra';
8
+ import emulsifyConfig from '../../../../../project.emulsify.json' with { type: 'json' };
4
9
 
5
- // Emulsify project configuration.
6
- const emulsifyConfig = require('../../../../../project.emulsify.json');
10
+ // Create __filename from import.meta.url without fileURLToPath
11
+ let _filename = decodeURIComponent(new URL(import.meta.url).pathname);
12
+
13
+ // On Windows, remove the leading slash (e.g. "/C:/path" -> "C:/path")
14
+ if (process.platform === 'win32' && _filename.startsWith('/')) {
15
+ _filename = _filename.slice(1);
16
+ }
7
17
 
8
- // Get directories for file contexts.
9
- const projectDir = path.resolve(__dirname, '../../../../..');
18
+ const _dirname = dirname(_filename);
19
+
20
+ const projectDir = resolve(_dirname, '../../../../..');
10
21
  const projectName = emulsifyConfig.project.name;
11
- const srcDir = fs.existsSync(path.resolve(projectDir, 'src'))
12
- ? path.resolve(projectDir, 'src')
13
- : path.resolve(projectDir, 'components');
22
+ const srcDir = fs.pathExistsSync(resolve(projectDir, 'src'))
23
+ ? resolve(projectDir, 'src')
24
+ : resolve(projectDir, 'components');
14
25
 
15
- // Glob pattern for twig aliases.
16
- const aliasPattern = path.resolve(srcDir, '**/!(_*).twig');
26
+ const aliasPattern = resolve(srcDir, '**/!(_*).twig');
17
27
 
18
28
  /**
19
- * Return all top-level directories from the projects source directory.
20
- * @constructor
21
- * @param {string} source - Path to source directory.
29
+ * Get all top-level directory names from a source directory.
30
+ *
31
+ * @param {string} source - The source directory path.
32
+ * @returns {string[]} Array of directory names.
22
33
  */
23
34
  function getDirectories(source) {
35
+ /* eslint-disable security/detect-non-literal-fs-filename */
24
36
  const dirs = fs
25
- .readdirSync(source, { withFileTypes: true }) // Read contents of the directory
26
- .filter((dirent) => dirent.isDirectory()) // Filter only directories
37
+ .readdirSync(source, { withFileTypes: true })
38
+ .filter((dirent) => dirent.isDirectory())
27
39
  .map((dirent) => dirent.name);
40
+ /* eslint-enable security/detect-non-literal-fs-filename */
28
41
  return dirs;
29
42
  }
30
43
 
31
44
  /**
32
- * Return clean directory names if numbering is used for sorting.
33
- * @constructor
34
- * @param {string} dir - Given directory name.
45
+ * Remove numbering from a directory name if present.
46
+ *
47
+ * @param {string} dir - The original directory name.
48
+ * @returns {string} The cleaned directory name.
35
49
  */
36
50
  function cleanDirectoryName(dir) {
37
51
  if (/^\d{2}/.test(dir)) {
@@ -41,40 +55,33 @@ function cleanDirectoryName(dir) {
41
55
  }
42
56
 
43
57
  /**
44
- * Return a list of twig file file paths from the project source directory.
45
- * @constructor
46
- * @param {string} aliasMatcher - Glob pattern.
58
+ * Generate a set of Twig aliases from a glob pattern.
59
+ *
60
+ * @param {string} aliasMatcher - The glob pattern to match Twig files.
61
+ * @returns {Object} An object containing Twig aliases.
47
62
  */
48
63
  function getAliases(aliasMatcher) {
49
- // Create default aliases
50
64
  let aliases = {};
51
- // Add SDC compatible aliases.
52
- glob.sync(aliasMatcher).forEach((file) => {
65
+ globSync(aliasMatcher).forEach((file) => {
53
66
  const filePath = file.split(`${srcDir}/`)[1];
54
- const fileName = path.basename(filePath);
55
-
67
+ const fileName = basename(filePath);
56
68
  if (emulsifyConfig.project.platform === 'drupal') {
57
69
  aliases[`${projectName}/${fileName.replace('.twig', '')}`] = file;
58
70
  }
59
71
  });
60
- // Add typical @namespace (path to directory) aliases for twig partials.
61
72
  const dirs = getDirectories(srcDir);
62
73
  dirs.forEach((dir) => {
63
74
  const name = cleanDirectoryName(dir);
64
75
  Object.assign(aliases, {
65
- [`@${name}`]: `${projectDir}/${path.basename(srcDir)}/${dir}`,
76
+ [`@${name}`]: `${projectDir}/${basename(srcDir)}/${dir}`,
66
77
  });
67
78
  });
68
-
69
79
  return aliases;
70
80
  }
71
81
 
72
- // Alias twig namespaces.
73
82
  const TwigResolve = {
74
83
  extensions: ['.twig'],
75
84
  alias: getAliases(aliasPattern),
76
85
  };
77
86
 
78
- module.exports = {
79
- TwigResolve,
80
- };
87
+ export default { TwigResolve };
@@ -1,8 +1,16 @@
1
- module.exports = function (source) {
1
+ /**
2
+ * A loader function that replaces occurrences of "projectName:" with "projectName/".
3
+ *
4
+ * @param {string} source - The source string to process.
5
+ * @returns {string} The transformed source.
6
+ */
7
+ export default function (source) {
2
8
  const projectName = this.getOptions().projectName || '';
9
+ /* eslint-disable security/detect-non-literal-regexp */
3
10
  const result = source.replace(
4
11
  new RegExp(`${projectName}:`, 'g'),
5
12
  `${projectName}/`,
6
13
  );
14
+ /* eslint-enable security/detect-non-literal-regexp */
7
15
  return result;
8
- };
16
+ }