@shopgate/webpack 7.29.1-beta.2 → 7.30.0-alpha.3

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,9 +1,13 @@
1
1
  const importFresh = require('import-fresh');
2
2
 
3
+ /**
4
+ * @typedef {import('../../../themes/theme-ios11/config/app.json')} AppSettings
5
+ */
6
+
3
7
  /**
4
8
  * Returns the app settings from the remote project.
5
9
  * @param {string} themePath The path to the theme.
6
- * @return {Object} The app settings.
10
+ * @return {AppSettings} The app settings.
7
11
  */
8
12
  module.exports = function getAppSettings(themePath) {
9
13
  try {
@@ -1,6 +1,16 @@
1
+ /**
2
+ * @typedef {Object} DevConfig
3
+ * @property {string} ip The IP address.
4
+ * @property {number} port The port number.
5
+ * @property {number} apiPort The API port number.
6
+ * @property {number} hmrPort The HMR port number.
7
+ * @property {number} remotePort The remote port number.
8
+ * @property {string} sourceMap The source map type.
9
+ */
10
+
1
11
  /**
2
12
  * Returns the development configuration.
3
- * @return {Object} The development configuration.
13
+ * @return {DevConfig} The development configuration.
4
14
  */
5
15
  module.exports = function getDevConfig() {
6
16
  const defaultConfig = {
@@ -9,7 +19,7 @@ module.exports = function getDevConfig() {
9
19
  apiPort: 9666,
10
20
  hmrPort: 3000,
11
21
  remotePort: 8000,
12
- sourceMap: 'cheap-module-eval-source-map',
22
+ sourceMap: 'eval-cheap-module-source-map',
13
23
  };
14
24
 
15
25
  try {
@@ -29,7 +39,12 @@ module.exports = function getDevConfig() {
29
39
  apiPort,
30
40
  hmrPort,
31
41
  remotePort,
32
- sourceMap: sourceMapsType,
42
+ // The source map type 'cheap-module-eval-source-map' is renamed to
43
+ // 'eval-cheap-module-source-map' in webpack 5. Since it is the default type created by
44
+ // the platform-sdk, we need to map it here.
45
+ sourceMap: sourceMapsType === 'cheap-module-eval-source-map'
46
+ ? 'eval-cheap-module-source-map'
47
+ : defaultConfig.sourceMap,
33
48
  };
34
49
  } catch (e) {
35
50
  return defaultConfig;
package/lib/helpers.js ADDED
@@ -0,0 +1,41 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Resolves the absolute path to a given package, taking into account different development
6
+ * and deployment contexts (theme, monorepo, or globally hoisted dependencies).
7
+ *
8
+ * The resolution strategy tries multiple locations in order:
9
+ * 1. The theme’s local `node_modules` (used in deployment or external developer mode).
10
+ * 2. The global monorepo root’s `node_modules` (used in monorepo development mode).
11
+ * 3. The current working directory’s `node_modules`.
12
+ * 4. Falls back to Node's `require.resolve()` if no path exists, allowing Yarn workspaces
13
+ * or hoisting to handle resolution.
14
+ *
15
+ * @param {string} pkgName - The name of the package to resolve.
16
+ * @param {string} [extraSubPath=''] - Optional subpath inside the package.
17
+ * @param {string} [themePath=process.cwd()] - Base path of the theme. Defaults to the current
18
+ * working directory.
19
+ * @returns {string} The resolved absolute path to the requested package or subpath.
20
+ *
21
+ */
22
+ const resolveForAliasPackage = (pkgName, extraSubPath = '', themePath = process.cwd()) => {
23
+ const tryPaths = [
24
+ path.resolve(themePath, 'node_modules', pkgName + extraSubPath),
25
+ path.resolve(themePath, '..', '..', 'node_modules', pkgName + extraSubPath),
26
+ path.resolve(process.cwd(), 'node_modules', pkgName + extraSubPath),
27
+ ];
28
+
29
+ const hit = tryPaths.find(candidate => fs.existsSync(candidate)) || null;
30
+
31
+ if (!hit) {
32
+ // fallback to Node's own algorithm (which will follow Yarn workspaces / hoisting)
33
+ return require.resolve(pkgName + extraSubPath);
34
+ }
35
+
36
+ return hit;
37
+ };
38
+
39
+ module.exports = {
40
+ resolveForAliasPackage,
41
+ };
package/lib/i18n.js CHANGED
@@ -1,62 +1,162 @@
1
1
  const path = require('path');
2
- const fsEx = require('fs-extra');
3
- const MessageFormat = require('messageformat');
4
- const Messages = require('messageformat/messages');
2
+ const fs = require('fs');
5
3
 
6
4
  const rootDirectory = path.resolve(__dirname, '..');
7
5
  const localesDirectory = path.resolve(rootDirectory, 'locales');
8
6
 
9
7
  /**
10
- * The i18n class.
8
+ * Reads and parses a JSON file with friendly error handling.
9
+ *
10
+ * @private
11
+ * @param {string} file - Absolute path to the JSON file.
12
+ * @returns {Object} Parsed JSON object.
13
+ * @throws {Error} When the file cannot be read or parsed.
14
+ */
15
+ function readJSON(file) {
16
+ try {
17
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
18
+ } catch (err) {
19
+ throw new Error(`Failed to read locale file "${file}": ${err.message}`);
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Flattens nested objects to dot-separated key paths.
25
+ *
26
+ * Example:
27
+ * ```js
28
+ * flatten({ a: { b: 'c' } }) // -> { 'a.b': 'c' }
29
+ * ```
30
+ *
31
+ * @private
32
+ * @param {Object} obj - The source object.
33
+ * @param {string} [prefix=''] - The prefix for the keys (used for recursion).
34
+ * @param {Object} [out={}] - The accumulator for flattened entries.
35
+ * @returns {Object} A flattened key-value map.
36
+ */
37
+ function flatten(obj, prefix = '', out = {}) {
38
+ const result = out;
39
+ // eslint-disable-next-line no-restricted-syntax
40
+ for (const [key, value] of Object.entries(obj)) {
41
+ const fullKey = prefix ? `${prefix}.${key}` : key;
42
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
43
+ flatten(value, fullKey, result);
44
+ } else {
45
+ result[fullKey] = String(value);
46
+ }
47
+ }
48
+ return result;
49
+ }
50
+
51
+ /**
52
+ * Replaces placeholders of the form `{var}` in a message string.
53
+ *
54
+ * Example:
55
+ * ```js
56
+ * format("Hello {name}", { name: "World" }); // -> "Hello World"
57
+ * ```
58
+ *
59
+ * @private
60
+ * @param {string} template - Message string with `{var}` placeholders.
61
+ * @param {Record<string, string|number>} [values={}] - Replacement values.
62
+ * @returns {string} Formatted string.
63
+ */
64
+ function format(template, values = {}) {
65
+ return template.replace(/\{(\w+)\}/g, (_, k) =>
66
+ (values[k] != null ? String(values[k]) : `{${k}}`));
67
+ }
68
+
69
+ /**
70
+ * Provides localized message lookup and interpolation for a given locale.
71
+ *
72
+ * @class
11
73
  */
12
74
  class I18n {
13
75
  /**
14
- * @param {string} [locale='en'] The desired locale for the module.
76
+ * Creates a new I18n instance.
77
+ *
78
+ * @param {string} [locale='en'] - The locale to load (e.g. "en", "de").
15
79
  */
16
80
  constructor(locale = 'en') {
17
81
  const locales = [locale];
82
+ if (!locales.includes('en')) locales.unshift('en');
18
83
 
19
- if (!locales.includes('en')) {
20
- locales.unshift('en');
21
- }
22
-
23
- const messageSet = {};
84
+ let merged = {};
24
85
  locales.forEach((entry) => {
25
- const localeFilePath = path.resolve(rootDirectory, localesDirectory, `${entry}.json`);
26
- messageSet[entry] = fsEx.readJSONSync(localeFilePath);
86
+ const localeFilePath = path.join(localesDirectory, `${entry}.json`);
87
+ if (fs.existsSync(localeFilePath)) {
88
+ const data = flatten(readJSON(localeFilePath));
89
+ merged = {
90
+ ...merged,
91
+ ...data,
92
+ };
93
+ }
27
94
  });
28
95
 
29
- const messageFormat = new MessageFormat(locales);
30
- this.messages = new Messages(messageFormat.compile(messageSet));
31
- this.messages.locale = locale;
96
+ /**
97
+ * The flattened dictionary of messages for this locale.
98
+ * @type {Record<string, string>}
99
+ */
100
+ this.messages = merged;
101
+
102
+ /**
103
+ * The current locale string.
104
+ * @type {string}
105
+ */
106
+ this.locale = locale;
32
107
  }
33
108
 
34
109
  /**
35
- * @param {Array} keyPath The key path of the translation.
36
- * @param {Object} data Additional data for the translation.
37
- * @returns {string|Array}
110
+ * Returns the translated message for the given key path.
111
+ *
112
+ * @param {string[]|string} keyPath - Key path or array of keys representing the translation key.
113
+ * @param {Record<string, string|number>} [data={}] - Optional interpolation data.
114
+ * @returns {string} The translated and formatted message, or the key if not found.
38
115
  */
39
116
  get(keyPath, data = {}) {
40
- if (this.messages.hasObject(keyPath)) {
41
- return keyPath;
117
+ const id = Array.isArray(keyPath) ? keyPath.join('.') : keyPath;
118
+ const message = this.messages[id];
119
+ if (message == null) {
120
+ return Array.isArray(keyPath) ? keyPath[keyPath.length - 1] : keyPath;
42
121
  }
43
-
44
- return this.messages.get(keyPath, data);
122
+ return format(message, data);
45
123
  }
46
124
  }
47
125
 
48
126
  let i18n;
49
127
 
128
+ /**
129
+ * @typedef {function(key: string|string[], data?: Object): string} TranslatorFn
130
+ */
131
+
132
+ /**
133
+ * Initializes or retrieves the singleton I18n instance and
134
+ * returns a translation function scoped to the given module.
135
+ *
136
+ * This function automatically derives the message namespace from the
137
+ * relative module path, so keys can be referenced without repeating the prefix.
138
+ *
139
+ * Example:
140
+ * ```js
141
+ * const i18n = require('./lib/i18n');
142
+ * const t = i18n(__filename);
143
+ * console.log(t('HELLO', { name: 'World' }));
144
+ * ```
145
+ *
146
+ * @param {string} modulePath - Absolute path to the module requesting translations
147
+ * (usually `__filename`).
148
+ * @returns {TranslatorFn} Translator function.
149
+ */
50
150
  module.exports = (modulePath) => {
51
151
  if (!i18n) {
52
152
  i18n = new I18n('en');
53
153
  }
54
154
 
55
- const moduleNamespace = path.relative(rootDirectory, modulePath)
155
+ const moduleNamespace = path
156
+ .relative(rootDirectory, modulePath)
56
157
  .replace(/(^(lib|src)+[/\\])|(\.js$)/ig, '')
57
- // Normalize OS specific path separators to forward slashes to be able to access translation
58
- // keys inside the translation JSON file.
59
- .split(path.sep).join('/');
158
+ .split(path.sep)
159
+ .join('/');
60
160
 
61
161
  return (key, data) => {
62
162
  const keyPath = [moduleNamespace];
@@ -69,14 +169,12 @@ module.exports = (modulePath) => {
69
169
  throw new Error(`'${key}' is not a valid message key`);
70
170
  }
71
171
 
72
- const message = i18n.get(keyPath, data);
73
-
74
- if (message === keyPath) {
75
- return key;
76
- }
77
-
78
- return message;
172
+ return i18n.get(keyPath, data);
79
173
  };
80
174
  };
81
175
 
176
+ /**
177
+ * Exposes the I18n class for testing or advanced usage.
178
+ * @type {typeof I18n}
179
+ */
82
180
  module.exports.I18n = I18n;
package/lib/variables.js CHANGED
@@ -2,6 +2,7 @@ const path = require('path');
2
2
 
3
3
  const ENV_KEY_DEVELOPMENT = 'development';
4
4
 
5
+ /** @type {"development" | "production"} */
5
6
  exports.ENV = process.env.NODE_ENV || ENV_KEY_DEVELOPMENT;
6
7
  exports.isDev = exports.ENV === ENV_KEY_DEVELOPMENT;
7
8
  exports.PUBLIC_FOLDER = 'public';
package/package.json CHANGED
@@ -1,29 +1,29 @@
1
1
  {
2
2
  "name": "@shopgate/webpack",
3
- "version": "7.29.1-beta.2",
3
+ "version": "7.30.0-alpha.3",
4
4
  "description": "The webpack configuration for Shopgate's Engage.",
5
5
  "main": "webpack.config.js",
6
6
  "license": "Apache-2.0",
7
7
  "dependencies": {
8
8
  "ajv": "^6.10.2",
9
9
  "babel-loader": "8.4.1",
10
- "chalk": "^2.4.2",
11
- "color": "^3.1.2",
12
- "compression-webpack-plugin": "^3.0.0",
13
- "css-loader": "1.0.1",
14
- "file-loader": "4.3.0",
15
- "html-webpack-plugin": "^3.2.0",
16
- "import-fresh": "3.0.0",
10
+ "chalk": "^4.1.2",
11
+ "color": "^4.2.3",
12
+ "compression-webpack-plugin": "^10.0.0",
13
+ "css-loader": "^6.11.0",
14
+ "css-minimizer-webpack-plugin": "^5.0.1",
15
+ "html-webpack-plugin": "^5.6.4",
16
+ "import-fresh": "^3.3.1",
17
17
  "intl": "1.2.5",
18
- "lodash": "^4.17.4",
19
- "messageformat": "^2.3.0",
18
+ "lodash": "^4.17.21",
19
+ "mini-css-extract-plugin": "^2.9.4",
20
20
  "progress-bar-webpack-plugin": "^2.1.0",
21
- "script-ext-html-webpack-plugin": "^2.1.5",
22
- "style-loader": "0.23.1",
23
- "terser-webpack-plugin": "^4.2.3",
24
- "webpack": "^4.47.0",
25
- "webpack-cli": "4.10.0",
26
- "workbox-webpack-plugin": "4.3.1"
21
+ "style-loader": "3.3.4",
22
+ "terser-webpack-plugin": "^5.3.14",
23
+ "webpack": "^5.102.1",
24
+ "webpack-bundle-analyzer": "^4.10.2",
25
+ "webpack-cli": "^5.1.4",
26
+ "workbox-webpack-plugin": "6.5.4"
27
27
  },
28
28
  "devDependencies": {
29
29
  "rxjs": "~5.5.12",
@@ -10,8 +10,9 @@
10
10
  <title>
11
11
  <%= htmlWebpackPlugin.options.title %>
12
12
  </title>
13
- <script src="<%= htmlWebpackPlugin.files.chunks.common.entry %>"></script>
14
- <% debugger; %>
13
+ <% const scripts = htmlWebpackPlugin.files.js; %>
14
+ <% const vendor = scripts.find(s => s.includes('vendor')); %>
15
+ <% if (vendor) { %><script src="<%= vendor %>"></script><% } %>
15
16
  <script>
16
17
  var SGConnect = {
17
18
  appId: "<%- htmlWebpackPlugin.options.appId %>"
@@ -25,7 +26,9 @@
25
26
  <body>
26
27
  <div id="root"></div>
27
28
  <div id="portals"></div>
28
- <script src="<%= htmlWebpackPlugin.files.chunks.app.entry %>"></script>
29
+ <% for (const js of scripts) { if (js !== vendor) { %>
30
+ <script src="<%= js %>"></script>
31
+ <% }} %>
29
32
  </body>
30
33
 
31
34
  </html>
package/webpack.config.js CHANGED
@@ -5,9 +5,11 @@ const chalk = require('chalk');
5
5
  const TerserPlugin = require('terser-webpack-plugin');
6
6
  const HTMLWebpackPlugin = require('html-webpack-plugin');
7
7
  const ProgressBarWebpackPlugin = require('progress-bar-webpack-plugin');
8
- const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
9
8
  const CompressionWebpackPlugin = require('compression-webpack-plugin');
10
9
  const { GenerateSW } = require('workbox-webpack-plugin');
10
+ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
11
+ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
12
+ const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
11
13
  const rxPaths = require('rxjs/_esm5/path-mapping');
12
14
  const ShopgateIndexerPlugin = require('./plugins/ShopgateIndexerPlugin');
13
15
  const ShopgateThemeConfigValidatorPlugin = require('./plugins/ShopgateThemeConfigValidatorPlugin');
@@ -19,6 +21,7 @@ const getThemeConfig = require('./lib/getThemeConfig');
19
21
  const getThemeLanguage = require('./lib/getThemeLanguage');
20
22
  const getDevConfig = require('./lib/getDevConfig');
21
23
  const i18n = require('./lib/i18n');
24
+ const { resolveForAliasPackage } = require('./lib/helpers');
22
25
  const getExtensionsNodeModulesPaths = require('./lib/getExtensionsNodeModulesPaths');
23
26
 
24
27
  const themePath = process.cwd();
@@ -26,44 +29,86 @@ const appConfig = getAppSettings(themePath);
26
29
  const themeConfig = getThemeConfig(themePath, appConfig);
27
30
  const isoLang = convertLanguageToISO(appConfig.language);
28
31
  const { sourceMap, ip, apiPort } = getDevConfig();
32
+ const themeLanguage = getThemeLanguage(themePath, appConfig.language);
29
33
  const t = i18n(__filename);
30
34
 
31
35
  const devtool = isDev ? sourceMap : (process.env.SOURCE_MAPS || false);
32
36
  const fileSuffix = devtool ? '.sm' : '';
37
+ const addBundleAnalyzer = !!process.env.BUNDLE_ANALYZER;
33
38
 
39
+ /**
40
+ * @type {import('webpack').Configuration}
41
+ */
34
42
  const config = {
35
43
  mode: ENV,
36
44
  entry: {
37
- app: [
38
- ...(!isDev ? [
39
- path.resolve(__dirname, 'lib', 'offline.js'),
40
- ] : []),
41
- path.resolve(__dirname, 'lib', 'polyfill.js'),
42
- path.resolve(themePath, 'index.jsx'),
43
- ],
44
- common: [
45
+ app: {
46
+ import: [
47
+ ...(!isDev ? [
48
+ path.resolve(__dirname, 'lib', 'offline.js'),
49
+ ] : []),
50
+ path.resolve(__dirname, 'lib', 'polyfill.js'),
51
+ path.resolve(themePath, 'index.jsx'),
52
+ ],
53
+ dependOn: 'vendor',
54
+ },
55
+ vendor: [
56
+ 'glamor',
45
57
  'intl',
46
58
  `intl/locale-data/jsonp/${isoLang}`,
47
59
  'react',
48
60
  'react-dom',
49
- 'glamor',
50
61
  'react-redux',
51
62
  'reselect',
52
63
  ],
53
64
  },
54
65
  output: {
55
- filename: !isDev ? `[name].[hash]${fileSuffix}.js` : `[name]${fileSuffix}.js`,
66
+ filename: !isDev ? `[name].[contenthash]${fileSuffix}.js` : `[name]${fileSuffix}.js`,
56
67
  chunkFilename: `[name].[chunkhash]${fileSuffix}.js`,
57
68
  path: path.resolve(themePath, PUBLIC_FOLDER),
58
69
  publicPath: isDev ? '/' : (process.env.publicPath || './'),
59
70
  },
60
71
  resolve: {
61
72
  extensions: ['.json', '.js', '.jsx', '.mjs'],
73
+ /**
74
+ * Aliases for module resolution. They guarantee that whenever one of the bundled modules
75
+ * uses in import to one of the packages, it will always resolve to the version of the core.
76
+ */
62
77
  alias: {
63
78
  ...rxPaths(),
64
- 'react-dom': '@hot-loader/react-dom',
79
+
80
+ // Packages from common module
81
+ react: resolveForAliasPackage('react'),
82
+ 'react-dom': resolveForAliasPackage('@hot-loader', '/react-dom'),
83
+ 'react-redux': resolveForAliasPackage('react-redux'),
84
+ reselect: resolveForAliasPackage('reselect'),
85
+ glamor: resolveForAliasPackage('glamor'),
86
+ intl: resolveForAliasPackage('intl'),
87
+ 'intl/locale-data/jsonp': resolveForAliasPackage('intl', '/locale-data/jsonp'),
88
+
89
+ // Additional packages that are sometimes used in devDependencies of extensions
90
+ 'react-helmet': resolveForAliasPackage('react-helmet'),
91
+ 'css-spring': resolveForAliasPackage('css-spring'),
92
+ 'react-transition-group': resolveForAliasPackage('react-transition-group'),
93
+ 'react-hot-loader': resolveForAliasPackage('react-hot-loader'),
94
+ '@virtuous': resolveForAliasPackage('@virtuous'),
95
+ lodash: resolveForAliasPackage('lodash'),
96
+ 'prop-types': resolveForAliasPackage('prop-types'),
97
+
98
+ // Internal Shopgate packages
99
+ '@shopgate/engage': resolveForAliasPackage('@shopgate/engage'),
100
+ '@shopgate/pwa-common': resolveForAliasPackage('@shopgate/pwa-common'),
101
+ '@shopgate/pwa-common-commerce': resolveForAliasPackage('@shopgate/pwa-common-commerce'),
102
+ '@shopgate/pwa-core': resolveForAliasPackage('@shopgate/pwa-core'),
103
+ '@shopgate/pwa-tracking': resolveForAliasPackage('@shopgate/pwa-tracking'),
104
+ '@shopgate/pwa-ui-ios': resolveForAliasPackage('@shopgate/pwa-ui-ios'),
105
+ '@shopgate/pwa-ui-material': resolveForAliasPackage('@shopgate/pwa-ui-material'),
106
+ '@shopgate/pwa-ui-shared': resolveForAliasPackage('@shopgate/pwa-ui-shared'),
107
+ '@shopgate/pwa-webcheckout-shopify': resolveForAliasPackage('@shopgate/pwa-webcheckout-shopify'),
108
+ '@shopgate/tracking-core': resolveForAliasPackage('@shopgate/tracking-core'),
65
109
  },
66
110
  modules: [
111
+ 'node_modules',
67
112
  path.resolve(themePath, 'widgets'),
68
113
  path.resolve(themePath, 'node_modules'),
69
114
  path.resolve(themePath, '..', '..', 'node_modules'),
@@ -73,23 +118,12 @@ const config = {
73
118
  },
74
119
  plugins: [
75
120
  new ShopgateThemeConfigValidatorPlugin(),
121
+
122
+ // Create mapping files inside the theme extensions folder the enable access to code that's
123
+ // provided by extensions via extension-config.json
76
124
  new ShopgateIndexerPlugin(),
77
- /**
78
- * Workaround to enable latest swiper version (11.2.1) with webpack.
79
- * The utils.mjs file in swiper/shared/utils.mjs is not compatible with webpack due to use of
80
- * optional chaining.
81
- *
82
- * Processing the module with babel-loader doesn't work, since transpilation of some array
83
- * operations break the module logic inside the browser.
84
- *
85
- * As a workaround we replace the file with a local patched version.
86
- * Alternative approaches e.g. via patch-package didn't work as expected due to issues in
87
- * release process.
88
- */
89
- new webpack.NormalModuleReplacementPlugin(
90
- /swiper[/\\]shared[/\\]utils\.mjs$/,
91
- path.resolve(__dirname, 'patches', 'swiper', 'shared', 'utils.mjs')
92
- ),
125
+
126
+ // Inject environment variables so that they are available within the bundled code
93
127
  new webpack.DefinePlugin({
94
128
  'process.env': {
95
129
  NODE_ENV: JSON.stringify(ENV),
@@ -98,10 +132,9 @@ const config = {
98
132
  THEME_CONFIG: JSON.stringify(themeConfig),
99
133
  THEME: JSON.stringify(process.env.theme),
100
134
  THEME_PATH: JSON.stringify(themePath),
101
- // @deprecated Replaced by LOCALE and LOCALE_FILE - kept for now for theme compatibility.
102
- LANG: JSON.stringify(isoLang),
103
135
  LOCALE: JSON.stringify(isoLang),
104
- LOCALE_FILE: JSON.stringify(getThemeLanguage(themePath, appConfig.language)),
136
+ LOCALE_FILE: JSON.stringify(themeLanguage),
137
+ LOCALE_FILE_LOWER_CASE: JSON.stringify(themeLanguage.toLowerCase()),
105
138
  IP: JSON.stringify(ip),
106
139
  PORT: JSON.stringify(apiPort),
107
140
  },
@@ -115,9 +148,8 @@ const config = {
115
148
  },
116
149
  },
117
150
  }),
118
- new webpack.optimize.ModuleConcatenationPlugin(),
119
- new webpack.HashedModuleIdsPlugin(),
120
- new webpack.NoEmitOnErrorsPlugin(),
151
+
152
+ // Plugin to minify the HTML output fo the default.ejs template
121
153
  new HTMLWebpackPlugin({
122
154
  title: appConfig.shopName || process.env.theme,
123
155
  filename: path.resolve(themePath, PUBLIC_FOLDER, 'index.html'),
@@ -136,11 +168,8 @@ const config = {
136
168
  minifyCSS: true,
137
169
  } : false,
138
170
  }),
139
- new ScriptExtHtmlWebpackPlugin({
140
- sync: ['app', 'common'],
141
- prefetch: /\.js$/,
142
- defaultAttribute: 'async',
143
- }),
171
+
172
+ // Progress bar that shows build progress in the console
144
173
  new ProgressBarWebpackPlugin({
145
174
  format: ` ${t('WEBPACK_PROGRESS', {
146
175
  bar: chalk.blue(':bar'),
@@ -150,9 +179,14 @@ const config = {
150
179
  })}`,
151
180
  clear: false,
152
181
  }),
182
+
183
+ // Bundle analyzer plugin to visualize size of webpack output files
184
+ ...(isDev && addBundleAnalyzer ? [
185
+ new BundleAnalyzerPlugin(),
186
+ ] : []),
153
187
  ...(!isDev ? [
154
188
  new CompressionWebpackPlugin({
155
- filename: '[path].gz[query]',
189
+ filename: '[path][base].gz[query]',
156
190
  algorithm: 'gzip',
157
191
  test: /\.js$|\.css$/,
158
192
  minRatio: 1,
@@ -162,23 +196,32 @@ const config = {
162
196
  clientsClaim: true,
163
197
  skipWaiting: true,
164
198
  }),
199
+ // Extract CSS into separate minified files on production builds
200
+ new MiniCssExtractPlugin({
201
+ filename: '[name].[contenthash].css',
202
+ chunkFilename: '[id].[contenthash].css',
203
+ }),
204
+ new CssMinimizerPlugin(),
165
205
  ] : []),
166
206
  ],
167
207
  module: {
168
208
  rules: [
169
209
  {
170
210
  test: /\.(png|jpe?g|gif|svg)$/i,
171
- use: [
172
- {
173
- loader: 'file-loader',
174
- },
175
- ],
211
+ type: 'asset/resource',
212
+ generator: {
213
+ filename: '[name].[contenthash][ext][query]',
214
+ },
176
215
  },
177
216
  {
178
217
  test: /\.css$/,
179
- use: [
218
+ // Bundle CSS on development, extract it into separate files on production
219
+ use: isDev ? [
180
220
  'style-loader',
181
221
  'css-loader',
222
+ ] : [
223
+ MiniCssExtractPlugin.loader,
224
+ 'css-loader',
182
225
  ],
183
226
  },
184
227
  {
@@ -187,7 +230,7 @@ const config = {
187
230
  },
188
231
  {
189
232
  test: /\.(js|jsx)$/,
190
- exclude: new RegExp(`node_modules\\b(?!${path.sep}@shopgate|${path.sep}react-leaflet|${path.sep}@react-leaflet)\\b.*`),
233
+ exclude: new RegExp(`node_modules\\b(?!\\${path.sep}@shopgate)\\b.*`),
191
234
  use: [
192
235
  {
193
236
  loader: 'babel-loader',
@@ -198,6 +241,13 @@ const config = {
198
241
  },
199
242
  ],
200
243
  },
244
+ {
245
+ test: /\.js$/,
246
+ include: /@babel\/runtime[\\/]+helpers[\\/]esm/,
247
+ resolve: {
248
+ fullySpecified: false,
249
+ },
250
+ },
201
251
  ],
202
252
  },
203
253
  devtool,
@@ -242,33 +292,18 @@ const config = {
242
292
  } : undefined,
243
293
  },
244
294
  optimization: {
295
+ emitOnErrors: false,
245
296
  usedExports: true,
246
297
  sideEffects: true,
247
- namedModules: true,
248
- namedChunks: true,
298
+ moduleIds: 'deterministic',
299
+ chunkIds: 'deterministic',
249
300
  nodeEnv: ENV,
250
301
  removeAvailableModules: true,
251
- splitChunks: {
252
- cacheGroups: {
253
- commons: {
254
- test: /node_modules/,
255
- name: 'common',
256
- chunks: 'all',
257
- minChunks: 2,
258
- },
259
- },
260
- },
261
302
  minimizer: [
262
303
  new TerserPlugin({
263
- parallel: true,
264
304
  extractComments: false,
265
305
  terserOptions: {
266
306
  ecma: 5,
267
- keep_fnames: false,
268
- mangle: true,
269
- safari10: false,
270
- toplevel: false,
271
- warnings: false,
272
307
  output: {
273
308
  comments: false,
274
309
  },
@@ -1,347 +0,0 @@
1
- /* eslint-disable import/extensions, require-jsdoc, no-void, no-param-reassign,
2
- prefer-destructuring, no-underscore-dangle, consistent-return, no-mixed-operators,
3
- eslint-comments/no-unlimited-disable, prefer-rest-params, max-len */
4
- /**
5
- * Replacement of a sub-module of the Swiper library.
6
- * Contains some refactored code inside the elementIsChildOfSlot function to remove optional
7
- * chaining which causes issues with webpack.
8
- */
9
- import { a as getWindow, g as getDocument } from 'swiper/shared/ssr-window.esm.mjs';
10
-
11
- function classesToTokens(classes) {
12
- if (classes === void 0) {
13
- classes = '';
14
- }
15
- return classes.trim().split(' ').filter(c => !!c.trim());
16
- }
17
-
18
- function deleteProps(obj) {
19
- const object = obj;
20
- Object.keys(object).forEach((key) => {
21
- try {
22
- object[key] = null;
23
- } catch (e) {
24
- // no getter for object
25
- }
26
- try {
27
- delete object[key];
28
- } catch (e) {
29
- // something got wrong
30
- }
31
- });
32
- }
33
- function nextTick(callback, delay) {
34
- if (delay === void 0) {
35
- delay = 0;
36
- }
37
- return setTimeout(callback, delay);
38
- }
39
- function now() {
40
- return Date.now();
41
- }
42
- function getComputedStyle(el) {
43
- const window = getWindow();
44
- let style;
45
- if (window.getComputedStyle) {
46
- style = window.getComputedStyle(el, null);
47
- }
48
- if (!style && el.currentStyle) {
49
- style = el.currentStyle;
50
- }
51
- if (!style) {
52
- style = el.style;
53
- }
54
- return style;
55
- }
56
- function getTranslate(el, axis) {
57
- if (axis === void 0) {
58
- axis = 'x';
59
- }
60
- const window = getWindow();
61
- let matrix;
62
- let curTransform;
63
- let transformMatrix;
64
- const curStyle = getComputedStyle(el);
65
- if (window.WebKitCSSMatrix) {
66
- curTransform = curStyle.transform || curStyle.webkitTransform;
67
- if (curTransform.split(',').length > 6) {
68
- curTransform = curTransform.split(', ').map(a => a.replace(',', '.')).join(', ');
69
- }
70
- // Some old versions of Webkit choke when 'none' is passed; pass
71
- // empty string instead in this case
72
- transformMatrix = new window.WebKitCSSMatrix(curTransform === 'none' ? '' : curTransform);
73
- } else {
74
- transformMatrix = curStyle.MozTransform || curStyle.OTransform || curStyle.MsTransform || curStyle.msTransform || curStyle.transform || curStyle.getPropertyValue('transform').replace('translate(', 'matrix(1, 0, 0, 1,');
75
- matrix = transformMatrix.toString().split(',');
76
- }
77
- if (axis === 'x') {
78
- // Latest Chrome and webkits Fix
79
- if (window.WebKitCSSMatrix) curTransform = transformMatrix.m41;
80
- // Crazy IE10 Matrix
81
- else if (matrix.length === 16) curTransform = parseFloat(matrix[12]);
82
- // Normal Browsers
83
- else curTransform = parseFloat(matrix[4]);
84
- }
85
- if (axis === 'y') {
86
- // Latest Chrome and webkits Fix
87
- if (window.WebKitCSSMatrix) curTransform = transformMatrix.m42;
88
- // Crazy IE10 Matrix
89
- else if (matrix.length === 16) curTransform = parseFloat(matrix[13]);
90
- // Normal Browsers
91
- else curTransform = parseFloat(matrix[5]);
92
- }
93
- return curTransform || 0;
94
- }
95
- function isObject(o) {
96
- return typeof o === 'object' && o !== null && o.constructor && Object.prototype.toString.call(o).slice(8, -1) === 'Object';
97
- }
98
- function isNode(node) {
99
- if (typeof window !== 'undefined' && typeof window.HTMLElement !== 'undefined') {
100
- return node instanceof HTMLElement;
101
- }
102
- return node && (node.nodeType === 1 || node.nodeType === 11);
103
- }
104
- function extend() {
105
- const to = Object(arguments.length <= 0 ? undefined : arguments[0]);
106
- const noExtend = ['__proto__', 'constructor', 'prototype'];
107
- for (let i = 1; i < arguments.length; i += 1) {
108
- const nextSource = i < 0 || arguments.length <= i ? undefined : arguments[i];
109
- if (nextSource !== undefined && nextSource !== null && !isNode(nextSource)) {
110
- const keysArray = Object.keys(Object(nextSource)).filter(key => noExtend.indexOf(key) < 0);
111
- for (let nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex += 1) {
112
- const nextKey = keysArray[nextIndex];
113
- const desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
114
- if (desc !== undefined && desc.enumerable) {
115
- if (isObject(to[nextKey]) && isObject(nextSource[nextKey])) {
116
- if (nextSource[nextKey].__swiper__) {
117
- to[nextKey] = nextSource[nextKey];
118
- } else {
119
- extend(to[nextKey], nextSource[nextKey]);
120
- }
121
- } else if (!isObject(to[nextKey]) && isObject(nextSource[nextKey])) {
122
- to[nextKey] = {};
123
- if (nextSource[nextKey].__swiper__) {
124
- to[nextKey] = nextSource[nextKey];
125
- } else {
126
- extend(to[nextKey], nextSource[nextKey]);
127
- }
128
- } else {
129
- to[nextKey] = nextSource[nextKey];
130
- }
131
- }
132
- }
133
- }
134
- }
135
- return to;
136
- }
137
- function setCSSProperty(el, varName, varValue) {
138
- el.style.setProperty(varName, varValue);
139
- }
140
- function animateCSSModeScroll(_ref) {
141
- const {
142
- swiper,
143
- targetPosition,
144
- side,
145
- } = _ref;
146
- const window = getWindow();
147
- const startPosition = -swiper.translate;
148
- let startTime = null;
149
- let time;
150
- const duration = swiper.params.speed;
151
- swiper.wrapperEl.style.scrollSnapType = 'none';
152
- window.cancelAnimationFrame(swiper.cssModeFrameID);
153
- const dir = targetPosition > startPosition ? 'next' : 'prev';
154
- const isOutOfBound = (current, target) => dir === 'next' && current >= target || dir === 'prev' && current <= target;
155
- const animate = () => {
156
- time = new Date().getTime();
157
- if (startTime === null) {
158
- startTime = time;
159
- }
160
- const progress = Math.max(Math.min((time - startTime) / duration, 1), 0);
161
- const easeProgress = 0.5 - Math.cos(progress * Math.PI) / 2;
162
- let currentPosition = startPosition + easeProgress * (targetPosition - startPosition);
163
- if (isOutOfBound(currentPosition, targetPosition)) {
164
- currentPosition = targetPosition;
165
- }
166
- swiper.wrapperEl.scrollTo({
167
- [side]: currentPosition,
168
- });
169
- if (isOutOfBound(currentPosition, targetPosition)) {
170
- swiper.wrapperEl.style.overflow = 'hidden';
171
- swiper.wrapperEl.style.scrollSnapType = '';
172
- setTimeout(() => {
173
- swiper.wrapperEl.style.overflow = '';
174
- swiper.wrapperEl.scrollTo({
175
- [side]: currentPosition,
176
- });
177
- });
178
- window.cancelAnimationFrame(swiper.cssModeFrameID);
179
- return;
180
- }
181
- swiper.cssModeFrameID = window.requestAnimationFrame(animate);
182
- };
183
- animate();
184
- }
185
- function getSlideTransformEl(slideEl) {
186
- return slideEl.querySelector('.swiper-slide-transform') || slideEl.shadowRoot && slideEl.shadowRoot.querySelector('.swiper-slide-transform') || slideEl;
187
- }
188
- function elementChildren(element, selector) {
189
- if (selector === void 0) {
190
- selector = '';
191
- }
192
- const window = getWindow();
193
- const children = [...element.children];
194
- if (window.HTMLSlotElement && element instanceof HTMLSlotElement) {
195
- children.push(...element.assignedElements());
196
- }
197
- if (!selector) {
198
- return children;
199
- }
200
- return children.filter(el => el.matches(selector));
201
- }
202
- function elementIsChildOfSlot(el, slot) {
203
- // Breadth-first search through all parent's children and assigned elements
204
- const elementsQueue = [slot];
205
- while (elementsQueue.length > 0) {
206
- const elementToCheck = elementsQueue.shift();
207
- if (el === elementToCheck) {
208
- return true;
209
- }
210
-
211
- // !!!! Rewrote this code to remove optional chaining syntax
212
- elementsQueue.push(
213
- ...elementToCheck.children,
214
- ...(elementToCheck.shadowRoot && elementToCheck.shadowRoot.children ? elementToCheck.shadowRoot.children : []),
215
- ...(elementToCheck.assignedElements ? elementToCheck.assignedElements() : [])
216
- );
217
- }
218
- }
219
- function elementIsChildOf(el, parent) {
220
- const window = getWindow();
221
- let isChild = parent.contains(el);
222
- if (!isChild && window.HTMLSlotElement && parent instanceof HTMLSlotElement) {
223
- const children = [...parent.assignedElements()];
224
- isChild = children.includes(el);
225
- if (!isChild) {
226
- isChild = elementIsChildOfSlot(el, parent);
227
- }
228
- }
229
- return isChild;
230
- }
231
- function showWarning(text) {
232
- try {
233
- console.warn(text);
234
- } catch (err) {
235
- // err
236
- }
237
- }
238
- function createElement(tag, classes) {
239
- if (classes === void 0) {
240
- classes = [];
241
- }
242
- const el = document.createElement(tag);
243
- el.classList.add(...(Array.isArray(classes) ? classes : classesToTokens(classes)));
244
- return el;
245
- }
246
- function elementOffset(el) {
247
- const window = getWindow();
248
- const document = getDocument();
249
- const box = el.getBoundingClientRect();
250
- const body = document.body;
251
- const clientTop = el.clientTop || body.clientTop || 0;
252
- const clientLeft = el.clientLeft || body.clientLeft || 0;
253
- const scrollTop = el === window ? window.scrollY : el.scrollTop;
254
- const scrollLeft = el === window ? window.scrollX : el.scrollLeft;
255
- return {
256
- top: box.top + scrollTop - clientTop,
257
- left: box.left + scrollLeft - clientLeft,
258
- };
259
- }
260
- function elementPrevAll(el, selector) {
261
- const prevEls = [];
262
- while (el.previousElementSibling) {
263
- const prev = el.previousElementSibling; // eslint-disable-line
264
- if (selector) {
265
- if (prev.matches(selector)) prevEls.push(prev);
266
- } else prevEls.push(prev);
267
- el = prev;
268
- }
269
- return prevEls;
270
- }
271
- function elementNextAll(el, selector) {
272
- const nextEls = [];
273
- while (el.nextElementSibling) {
274
- const next = el.nextElementSibling; // eslint-disable-line
275
- if (selector) {
276
- if (next.matches(selector)) nextEls.push(next);
277
- } else nextEls.push(next);
278
- el = next;
279
- }
280
- return nextEls;
281
- }
282
- function elementStyle(el, prop) {
283
- const window = getWindow();
284
- return window.getComputedStyle(el, null).getPropertyValue(prop);
285
- }
286
- function elementIndex(el) {
287
- let child = el;
288
- let i;
289
- if (child) {
290
- i = 0;
291
- // eslint-disable-next-line
292
- while ((child = child.previousSibling) !== null) {
293
- if (child.nodeType === 1) i += 1;
294
- }
295
- return i;
296
- }
297
- return undefined;
298
- }
299
- function elementParents(el, selector) {
300
- const parents = []; // eslint-disable-line
301
- let parent = el.parentElement; // eslint-disable-line
302
- while (parent) {
303
- if (selector) {
304
- if (parent.matches(selector)) parents.push(parent);
305
- } else {
306
- parents.push(parent);
307
- }
308
- parent = parent.parentElement;
309
- }
310
- return parents;
311
- }
312
- function elementTransitionEnd(el, callback) {
313
- function fireCallBack(e) {
314
- if (e.target !== el) return;
315
- callback.call(el, e);
316
- el.removeEventListener('transitionend', fireCallBack);
317
- }
318
- if (callback) {
319
- el.addEventListener('transitionend', fireCallBack);
320
- }
321
- }
322
- function elementOuterSize(el, size, includeMargins) {
323
- const window = getWindow();
324
- if (includeMargins) {
325
- return el[size === 'width' ? 'offsetWidth' : 'offsetHeight'] + parseFloat(window.getComputedStyle(el, null).getPropertyValue(size === 'width' ? 'margin-right' : 'margin-top')) + parseFloat(window.getComputedStyle(el, null).getPropertyValue(size === 'width' ? 'margin-left' : 'margin-bottom'));
326
- }
327
- return el.offsetWidth;
328
- }
329
- function makeElementsArray(el) {
330
- return (Array.isArray(el) ? el : [el]).filter(e => !!e);
331
- }
332
- function getRotateFix(swiper) {
333
- return (v) => {
334
- if (Math.abs(v) > 0 && swiper.browser && swiper.browser.need3dFix && Math.abs(v) % 90 === 0) {
335
- return v + 0.001;
336
- }
337
- return v;
338
- };
339
- }
340
-
341
- export {
342
- elementParents as a, elementOffset as b, createElement as c, now as d, elementChildren as e, elementOuterSize as f, getSlideTransformEl as g, elementIndex as h, classesToTokens as i, getTranslate as j, elementTransitionEnd as k, isObject as l, makeElementsArray as m, nextTick as n, getRotateFix as o, elementStyle as p, elementNextAll as q, elementPrevAll as r, setCSSProperty as s, animateCSSModeScroll as t, showWarning as u, elementIsChildOf as v, extend as w, deleteProps as x,
343
- };
344
-
345
- /* eslint-enable import/extensions, require-jsdoc, no-void, no-param-reassign,
346
- prefer-destructuring, no-underscore-dangle, consistent-return, no-mixed-operators,
347
- eslint-comments/no-unlimited-disable, prefer-rest-params, max-len */