@nitro/webpack 11.0.0-beta.2 → 11.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/lib/utils.js CHANGED
@@ -16,18 +16,18 @@ function getEnrichedConfig(rule, config) {
16
16
  }
17
17
 
18
18
  // load optional package
19
- function getOptionalPackage(x) {
20
- let mod;
21
- try {
22
- mod = require(x);
23
- } catch (error) {
24
- mod = null;
25
- }
26
- if (!mod) {
27
- return null;
28
- }
29
- return mod.default ? mod.default : mod;
30
- }
19
+ function getOptionalPackage(x) {
20
+ let mod;
21
+ try {
22
+ mod = require(x);
23
+ } catch (error) {
24
+ mod = null;
25
+ }
26
+ if (!mod) {
27
+ return null;
28
+ }
29
+ return mod.default ? mod.default : mod;
30
+ }
31
31
 
32
32
  module.exports = {
33
33
  getEnrichedConfig,
@@ -1,124 +1,124 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const typescript = require('typescript');
6
- const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
7
- const utils = require('./utils');
8
-
9
- function resolveBabelConfigFile(context, ruleOptions) {
10
- if (ruleOptions && ruleOptions.babelConfigFile) {
11
- return path.resolve(context, ruleOptions.babelConfigFile);
12
- }
13
-
14
- const candidates = ['babel.config.js', '.babelrc', '.babelrc.js', '.babelrc.json'];
15
- for (const candidate of candidates) {
16
- const candidatePath = path.resolve(context, candidate);
17
- if (fs.existsSync(candidatePath)) {
18
- return candidatePath;
19
- }
20
- }
21
-
22
- return null;
23
- }
24
-
25
- function resolveTsConfigFile(context, ruleOptions) {
26
- if (ruleOptions && ruleOptions.tsConfigFile) {
27
- return path.resolve(context, ruleOptions.tsConfigFile);
28
- }
29
-
30
- return typescript.findConfigFile(context, fs.existsSync);
31
- }
32
-
33
- function getRuleOptions(ruleOptions) {
34
- return ruleOptions && typeof ruleOptions === 'object' ? ruleOptions : null;
35
- }
36
-
37
- function ensureResolveExtensions(webpackConfig, extensions, { prepend }) {
38
- if (!webpackConfig.resolve) {
39
- webpackConfig.resolve = {};
40
- }
41
- if (!Array.isArray(webpackConfig.resolve.extensions)) {
42
- webpackConfig.resolve.extensions = ['.js', '.json'];
43
- }
44
- const target = webpackConfig.resolve.extensions;
45
- const missing = extensions.filter((ext) => !target.includes(ext));
46
- if (missing.length === 0) {
47
- return;
48
- }
49
- if (prepend) {
50
- target.unshift(...missing);
51
- } else {
52
- target.push(...missing);
53
- }
54
- }
55
-
56
- function getBabelLoaderOptions(context, ruleOptions) {
57
- const babelConfigFile = resolveBabelConfigFile(context, ruleOptions);
58
- const babelLoaderOptions = {
59
- cacheDirectory: true,
60
- compact: true,
61
- };
62
- if (babelConfigFile) {
63
- babelLoaderOptions.configFile = babelConfigFile;
64
- }
65
- return babelLoaderOptions;
66
- }
67
-
68
- function addJSConfig(webpackConfig, context, ruleOptions) {
69
- const babelLoaderOptions = getBabelLoaderOptions(context, ruleOptions);
70
- const scriptRuleJs = {
71
- test: /\.[cm]?jsx?$/,
72
- exclude: [/[/\\\\]node_modules[/\\\\]/],
73
- use: [
74
- {
75
- loader: require.resolve('babel-loader'),
76
- options: babelLoaderOptions,
77
- },
78
- ],
79
- };
80
- webpackConfig.module.rules.push(utils.getEnrichedConfig(scriptRuleJs, getRuleOptions(ruleOptions)));
81
- ensureResolveExtensions(webpackConfig, ['.js', '.jsx', '.mjs', '.cjs'], { prepend: true });
82
- }
83
-
84
- function addTsConfig(webpackConfig, context, ruleOptions, { isProduction }) {
85
- const babelLoaderOptions = getBabelLoaderOptions(context, ruleOptions);
86
- const scriptRuleTs = {
87
- test: /\.[cm]?tsx?$/,
88
- exclude: [/[/\\\\]node_modules[/\\\\]/],
89
- use: [
90
- {
91
- loader: require.resolve('babel-loader'),
92
- options: babelLoaderOptions,
93
- },
94
- {
95
- loader: require.resolve('ts-loader'),
96
- options: {
97
- transpileOnly: true,
98
- },
99
- },
100
- ],
101
- };
102
- webpackConfig.module.rules.push(utils.getEnrichedConfig(scriptRuleTs, getRuleOptions(ruleOptions)));
103
- ensureResolveExtensions(webpackConfig, ['.ts', '.tsx', '.mts', '.cts'], { prepend: true });
104
-
105
- const tsConfigFile = resolveTsConfigFile(context, ruleOptions);
106
- if (tsConfigFile) {
107
- const forkCheckerOptions = {
108
- async: !isProduction,
109
- typescript: {
110
- diagnosticOptions: {
111
- semantic: true,
112
- syntactic: true,
113
- },
114
- configFile: tsConfigFile,
115
- },
116
- };
117
- webpackConfig.plugins.push(new ForkTsCheckerWebpackPlugin(forkCheckerOptions));
118
- }
119
- }
120
-
121
- module.exports = {
122
- addJSConfig,
123
- addTsConfig,
124
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const typescript = require('typescript');
6
+ const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
7
+ const utils = require('./utils');
8
+
9
+ function resolveBabelConfigFile(context, ruleOptions) {
10
+ if (ruleOptions && ruleOptions.babelConfigFile) {
11
+ return path.resolve(context, ruleOptions.babelConfigFile);
12
+ }
13
+
14
+ const candidates = ['babel.config.js', '.babelrc', '.babelrc.js', '.babelrc.json'];
15
+ for (const candidate of candidates) {
16
+ const candidatePath = path.resolve(context, candidate);
17
+ if (fs.existsSync(candidatePath)) {
18
+ return candidatePath;
19
+ }
20
+ }
21
+
22
+ return null;
23
+ }
24
+
25
+ function resolveTsConfigFile(context, ruleOptions) {
26
+ if (ruleOptions && ruleOptions.tsConfigFile) {
27
+ return path.resolve(context, ruleOptions.tsConfigFile);
28
+ }
29
+
30
+ return typescript.findConfigFile(context, fs.existsSync);
31
+ }
32
+
33
+ function getRuleOptions(ruleOptions) {
34
+ return ruleOptions && typeof ruleOptions === 'object' ? ruleOptions : null;
35
+ }
36
+
37
+ function ensureResolveExtensions(webpackConfig, extensions, { prepend }) {
38
+ if (!webpackConfig.resolve) {
39
+ webpackConfig.resolve = {};
40
+ }
41
+ if (!Array.isArray(webpackConfig.resolve.extensions)) {
42
+ webpackConfig.resolve.extensions = ['.js', '.json'];
43
+ }
44
+ const target = webpackConfig.resolve.extensions;
45
+ const missing = extensions.filter((ext) => !target.includes(ext));
46
+ if (missing.length === 0) {
47
+ return;
48
+ }
49
+ if (prepend) {
50
+ target.unshift(...missing);
51
+ } else {
52
+ target.push(...missing);
53
+ }
54
+ }
55
+
56
+ function getBabelLoaderOptions(context, ruleOptions) {
57
+ const babelConfigFile = resolveBabelConfigFile(context, ruleOptions);
58
+ const babelLoaderOptions = {
59
+ cacheDirectory: true,
60
+ compact: true,
61
+ };
62
+ if (babelConfigFile) {
63
+ babelLoaderOptions.configFile = babelConfigFile;
64
+ }
65
+ return babelLoaderOptions;
66
+ }
67
+
68
+ function addJSConfig(webpackConfig, context, ruleOptions) {
69
+ const babelLoaderOptions = getBabelLoaderOptions(context, ruleOptions);
70
+ const scriptRuleJs = {
71
+ test: /\.[cm]?jsx?$/,
72
+ exclude: [/[/\\\\]node_modules[/\\\\]/],
73
+ use: [
74
+ {
75
+ loader: require.resolve('babel-loader'),
76
+ options: babelLoaderOptions,
77
+ },
78
+ ],
79
+ };
80
+ webpackConfig.module.rules.push(utils.getEnrichedConfig(scriptRuleJs, getRuleOptions(ruleOptions)));
81
+ ensureResolveExtensions(webpackConfig, ['.js', '.jsx', '.mjs', '.cjs'], { prepend: true });
82
+ }
83
+
84
+ function addTsConfig(webpackConfig, context, ruleOptions, { isProduction }) {
85
+ const babelLoaderOptions = getBabelLoaderOptions(context, ruleOptions);
86
+ const scriptRuleTs = {
87
+ test: /\.[cm]?tsx?$/,
88
+ exclude: [/[/\\\\]node_modules[/\\\\]/],
89
+ use: [
90
+ {
91
+ loader: require.resolve('babel-loader'),
92
+ options: babelLoaderOptions,
93
+ },
94
+ {
95
+ loader: require.resolve('ts-loader'),
96
+ options: {
97
+ transpileOnly: true,
98
+ },
99
+ },
100
+ ],
101
+ };
102
+ webpackConfig.module.rules.push(utils.getEnrichedConfig(scriptRuleTs, getRuleOptions(ruleOptions)));
103
+ ensureResolveExtensions(webpackConfig, ['.ts', '.tsx', '.mts', '.cts'], { prepend: true });
104
+
105
+ const tsConfigFile = resolveTsConfigFile(context, ruleOptions);
106
+ if (tsConfigFile) {
107
+ const forkCheckerOptions = {
108
+ async: !isProduction,
109
+ typescript: {
110
+ diagnosticOptions: {
111
+ semantic: true,
112
+ syntactic: true,
113
+ },
114
+ configFile: tsConfigFile,
115
+ },
116
+ };
117
+ webpackConfig.plugins.push(new ForkTsCheckerWebpackPlugin(forkCheckerOptions));
118
+ }
119
+ }
120
+
121
+ module.exports = {
122
+ addJSConfig,
123
+ addTsConfig,
124
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitro/webpack",
3
- "version": "11.0.0-beta.2",
3
+ "version": "11.0.0",
4
4
  "description": "nitro webpack",
5
5
  "license": "MIT",
6
6
  "author": "The Nitro Team",
@@ -22,7 +22,7 @@
22
22
  "nitro"
23
23
  ],
24
24
  "peerDependencies": {
25
- "@nitro/app": ">=11.0.0-beta.2",
25
+ "@nitro/app": ">=11.0.0",
26
26
  "webpack": "^5"
27
27
  },
28
28
  "dependencies": {
@@ -34,7 +34,7 @@
34
34
  "autoprefixer": "10.4.24",
35
35
  "babel-loader": "10.0.0",
36
36
  "case-sensitive-paths-webpack-plugin": "2.4.0",
37
- "css-loader": "7.1.3",
37
+ "css-loader": "7.1.4",
38
38
  "cssnano": "7.1.2",
39
39
  "image-minimizer-webpack-plugin": "4.1.4",
40
40
  "fork-ts-checker-webpack-plugin": "9.1.0",
@@ -42,7 +42,7 @@
42
42
  "imagemin": "9.0.1",
43
43
  "mini-css-extract-plugin": "2.10.0",
44
44
  "postcss": "8.5.6",
45
- "postcss-loader": "8.2.0",
45
+ "postcss-loader": "8.2.1",
46
46
  "resolve-url-loader": "5.0.0",
47
47
  "sass": "1.97.3",
48
48
  "sass-loader": "16.0.7",
package/readme.md CHANGED
@@ -157,7 +157,7 @@ It makes sense to use a dynamic value e.g. an environment variable, as shown in
157
157
 
158
158
  By default, all js imports from 'node_modules' are extracted to a 'vendors.js' to use in your view files.
159
159
 
160
- Dynamically imported js files will be extracted to `public/js/dynamic/`.
160
+ Dynamically imported js files will be extracted to `public/js/chunk/`.
161
161
  You may use them in a promise chain.
162
162
 
163
163
  ```js
@@ -1,311 +1,312 @@
1
- const path = require('path');
2
- const fs = require('fs');
3
- const webpack = require('webpack');
4
- const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
5
- const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
6
- const MiniCssExtractPlugin = require('mini-css-extract-plugin');
7
- const TerserPlugin = require('terser-webpack-plugin');
8
- const WebpackBar = require('webpackbar');
9
- const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
10
- const utils = require('../lib/utils');
11
- const webpackRules = require('../lib/webpack-rules');
12
-
13
- const appDirectory = fs.realpathSync(process.cwd());
14
-
15
- const bannerData = {
16
- date: new Date().toISOString().slice(0, 19),
17
- pkg: require(`${appDirectory}/package.json`),
18
- };
19
-
20
- const banner = `${bannerData.pkg.name}
21
- @version v${bannerData.pkg.version}
22
- @date ${bannerData.date}`;
23
-
24
- module.exports = (options = { rules: {}, features: {} }) => {
25
- const imageMinimizerPlugins = [];
26
- const imageminMozjpeg = utils.getOptionalPackage('imagemin-mozjpeg');
27
- const imageminOptipng = utils.getOptionalPackage('imagemin-optipng');
28
- const imageminPngquant = utils.getOptionalPackage('imagemin-pngquant');
29
- const imageminSvgo = utils.getOptionalPackage('imagemin-svgo');
30
-
31
- if (imageminMozjpeg) {
32
- imageMinimizerPlugins.push(
33
- [
34
- 'mozjpeg',
35
- {
36
- quality: 75,
37
- progressive: true,
38
- },
39
- ]
40
- );
41
- }
42
- if (imageminOptipng) {
43
- imageMinimizerPlugins.push(
44
- [
45
- 'optipng',
46
- {
47
- optimizationLevel: 7,
48
- },
49
- ]
50
- );
51
- }
52
- if (imageminPngquant) {
53
- imageMinimizerPlugins.push('pngquant');
54
- }
55
- if (imageminSvgo) {
56
- imageMinimizerPlugins.push(
57
- [
58
- 'svgo',
59
- {
60
- plugins: [
61
- {
62
- name: 'preset-default',
63
- params: {
64
- overrides: {
65
- removeViewBox: false,
66
- },
67
- },
68
- },
69
- ],
70
- },
71
- ]
72
- );
73
- }
74
-
75
- const minimizerPlugins = [new TerserPlugin({ extractComments: false })];
76
- if (!(options.features.imageMinimizer === false || imageMinimizerPlugins.length === 0)) {
77
- minimizerPlugins.push(
78
- new ImageMinimizerPlugin({
79
- minimizer: {
80
- implementation: ImageMinimizerPlugin.imageminMinify,
81
- options: {
82
- plugins: imageMinimizerPlugins,
83
- },
84
- },
85
- })
86
- );
87
- }
88
-
89
- const webpackConfig = {
90
- mode: 'production',
91
- devtool: 'hidden-source-map',
92
- context: appDirectory,
93
- entry: {
94
- ui: './src/ui',
95
- proto: './src/proto',
96
- },
97
- output: {
98
- path: path.resolve(appDirectory, 'public', 'assets'),
99
- filename: 'js/[name].min.js',
100
- chunkFilename: 'js/chunks/[name]-[contenthash:7].min.js',
101
- publicPath: '/assets/',
102
- },
103
- resolve: {
104
- symlinks: false,
105
- },
106
- module: {
107
- rules: [],
108
- },
109
- plugins: [new CaseSensitivePathsPlugin({ debug: false }), new WebpackBar()],
110
- optimization: {
111
- moduleIds: 'deterministic',
112
- chunkIds: 'deterministic',
113
- runtimeChunk: false,
114
- splitChunks: {
115
- chunks: 'all',
116
- minSize: 3000,
117
- cacheGroups: {
118
- default: false,
119
- defaultVendors: false,
120
- vendors: {
121
- test: /[\\/]node_modules[\\/].*\.(js|jsx|mjs|ts|tsx)$/,
122
- chunks: (chunk) => chunk.canBeInitial() && chunk.name && chunk.name !== 'proto',
123
- name: 'vendors',
124
- enforce: true,
125
- },
126
- },
127
- },
128
- minimizer: minimizerPlugins,
129
- },
130
- stats: {
131
- preset: 'errors-warnings',
132
- assets: true,
133
- assetsSort: 'size',
134
- children: false,
135
- chunks: false,
136
- modules: false,
137
- colors: true,
138
- entrypoints: true,
139
- errors: true,
140
- errorDetails: true,
141
- hash: false,
142
- builtAt: false,
143
- timings: true,
144
- performance: true,
145
- warnings: true,
146
- },
147
- performance: {
148
- hints: 'warning',
149
- maxEntrypointSize: 760 * 1024,
150
- maxAssetSize: 380 * 1024,
151
- },
152
- };
153
- const theme = options.features.theme ? options.features.theme : false;
154
- if (theme) {
155
- webpackConfig.entry.ui = `./src/ui.${theme}`;
156
- webpackConfig.output.path = path.resolve(webpackConfig.output.path, theme);
157
- webpackConfig.output.publicPath = `${webpackConfig.output.publicPath}${theme}/`;
158
- }
159
-
160
- // scripts (js/ts via babel)
161
- if (options.rules.script) {
162
- const scriptOptions = typeof options.rules.script === 'object' ? options.rules.script : null;
163
- webpackRules.addJSConfig(webpackConfig, appDirectory, scriptOptions);
164
- if (scriptOptions && scriptOptions.typescript) {
165
- webpackRules.addTsConfig(webpackConfig, appDirectory, scriptOptions, { isProduction: true });
166
- }
167
- }
168
-
169
- // css & scss
170
- if (options.rules.style) {
171
- const scssMiniCSSExtractOptions = {
172
- ...(options.rules.style.publicPath && { publicPath: options.rules.style.publicPath })
173
- };
174
- const scssLoaderOptions = {
175
- sourceMap: true,
176
- ...(options.rules.style.sassOptions && { sassOptions: options.rules.style.sassOptions }),
177
- };
178
- webpackConfig.module.rules.push({
179
- test: /\.s?css$/,
180
- use: [
181
- {
182
- loader: MiniCssExtractPlugin.loader,
183
- options: scssMiniCSSExtractOptions,
184
- },
185
- {
186
- loader: require.resolve('css-loader'),
187
- options: {
188
- importLoaders: 2,
189
- sourceMap: true,
190
- },
191
- },
192
- {
193
- loader: require.resolve('postcss-loader'),
194
- options: {
195
- sourceMap: true,
196
- postcssOptions: () => {
197
- return {
198
- plugins: [
199
- require('autoprefixer')({
200
- // @see autoprefixer options: https://github.com/postcss/autoprefixer#options
201
- // flexbox: 'no-2009' will add prefixes only for final and IE versions of specification.
202
- flexbox: 'no-2009',
203
- // grid: 'autoplace': enable autoprefixer grid translations and include autoplacement support.
204
- // not enabled - use `/* autoprefixer grid: autoplace */` in your css file
205
- }),
206
- require('cssnano'),
207
- ],
208
- };
209
- },
210
- },
211
- },
212
- {
213
- loader: require.resolve('resolve-url-loader'),
214
- options: {
215
- sourceMap: true,
216
- },
217
- },
218
- {
219
- loader: require.resolve('sass-loader'),
220
- options: scssLoaderOptions,
221
- },
222
- ],
223
- });
224
-
225
- webpackConfig.plugins.push(
226
- new MiniCssExtractPlugin({
227
- filename: 'css/[name].min.css',
228
- chunkFilename: 'css/chunks/[name]-[contenthash:7].min.css',
229
- }),
230
- );
231
- }
232
-
233
- // handlebars precompiled templates
234
- if (options.rules.hbs) {
235
- const hbsRule = {
236
- test: /\.hbs$/,
237
- use: {
238
- loader: require.resolve('handlebars-loader'),
239
- // options: {
240
- // helperDirs: [
241
- // path.resolve(__dirname, '../../app/templating/hbs/helpers'),
242
- // ],
243
- // knownHelpers: [],
244
- // runtime: '',
245
- // partialDirs: ''
246
- // },
247
- },
248
- };
249
- webpackConfig.module.rules.push(utils.getEnrichedConfig(hbsRule, options.rules.hbs));
250
- }
251
-
252
- // woff fonts (for example, in CSS files)
253
- if (options.rules.woff) {
254
- const woffRule = {
255
- test: /.(woff(2)?)(\?[a-z0-9]+)?$/,
256
- type: 'asset/resource',
257
- generator: {
258
- filename: 'media/fonts/[name]-[contenthash:7].[ext]',
259
- },
260
- };
261
- webpackConfig.module.rules.push(utils.getEnrichedConfig(woffRule, options.rules.woff));
262
- }
263
-
264
- // different font types (legacy - eg. used in older library css)
265
- if (options.rules.font) {
266
- const fontRule = {
267
- test: /\.(eot|svg|ttf|woff|woff2)([?#]+[A-Za-z0-9-_]*)*$/,
268
- type: 'asset',
269
- parser: {
270
- dataUrlCondition: {
271
- maxSize: 2 * 1028,
272
- },
273
- },
274
- generator: {
275
- filename: 'media/font/[name]-[contenthash:7].[ext]',
276
- },
277
- };
278
- webpackConfig.module.rules.push(utils.getEnrichedConfig(fontRule, options.rules.font));
279
- }
280
-
281
- // images
282
- if (options.rules.image) {
283
- const imageRule = {
284
- test: /\.(png|jpg|gif|svg)$/,
285
- type: 'asset',
286
- parser: {
287
- dataUrlCondition: {
288
- maxSize: 1028,
289
- },
290
- },
291
- generator: {
292
- filename: 'media/[ext]/[name]-[contenthash:7].[ext]',
293
- },
294
- };
295
-
296
- webpackConfig.module.rules.push(utils.getEnrichedConfig(imageRule, options.rules.image));
297
- }
298
-
299
- // feature banner (enabled by default)
300
- if (!options.features.banner === false) {
301
- webpackConfig.plugins.push(new webpack.BannerPlugin({ banner, entryOnly: true }));
302
- }
303
-
304
- // feature bundle analyzer
305
- if (options.features.bundleAnalyzer) {
306
- webpackConfig.plugins.push(new BundleAnalyzerPlugin());
307
- }
308
-
309
- return webpackConfig;
310
- };
311
- module.exports.appDirectory = appDirectory;
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const webpack = require('webpack');
4
+ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
5
+ const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
6
+ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
7
+ const TerserPlugin = require('terser-webpack-plugin');
8
+ const WebpackBar = require('webpackbar');
9
+ const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
10
+ const utils = require('../lib/utils');
11
+ const webpackRules = require('../lib/webpack-rules');
12
+
13
+ const appDirectory = fs.realpathSync(process.cwd());
14
+
15
+ const bannerData = {
16
+ date: new Date().toISOString().slice(0, 19),
17
+ pkg: require(`${appDirectory}/package.json`),
18
+ };
19
+
20
+ const banner = `${bannerData.pkg.name}
21
+ @version v${bannerData.pkg.version}
22
+ @date ${bannerData.date}`;
23
+
24
+ module.exports = (options = { rules: {}, features: {} }) => {
25
+ const imageMinimizerPlugins = [];
26
+ const imageminMozjpeg = utils.getOptionalPackage('imagemin-mozjpeg');
27
+ const imageminOptipng = utils.getOptionalPackage('imagemin-optipng');
28
+ const imageminPngquant = utils.getOptionalPackage('imagemin-pngquant');
29
+ const imageminSvgo = utils.getOptionalPackage('imagemin-svgo');
30
+
31
+ if (imageminMozjpeg) {
32
+ imageMinimizerPlugins.push(
33
+ [
34
+ 'mozjpeg',
35
+ {
36
+ quality: 75,
37
+ progressive: true,
38
+ },
39
+ ]
40
+ );
41
+ }
42
+ if (imageminOptipng) {
43
+ imageMinimizerPlugins.push(
44
+ [
45
+ 'optipng',
46
+ {
47
+ optimizationLevel: 7,
48
+ },
49
+ ]
50
+ );
51
+ }
52
+ if (imageminPngquant) {
53
+ imageMinimizerPlugins.push('pngquant');
54
+ }
55
+ if (imageminSvgo) {
56
+ // svgo configuration: https://github.com/svg/svgo#configuration
57
+ imageMinimizerPlugins.push(
58
+ [
59
+ 'svgo',
60
+ {
61
+ plugins: [
62
+ {
63
+ name: 'preset-default',
64
+ params: {
65
+ overrides: {
66
+ removeViewBox: false,
67
+ },
68
+ },
69
+ },
70
+ ],
71
+ },
72
+ ]
73
+ );
74
+ }
75
+
76
+ const minimizerPlugins = [new TerserPlugin({ extractComments: false })];
77
+ if (!(options.features.imageMinimizer === false || imageMinimizerPlugins.length === 0)) {
78
+ minimizerPlugins.push(
79
+ new ImageMinimizerPlugin({
80
+ minimizer: {
81
+ implementation: ImageMinimizerPlugin.imageminMinify,
82
+ options: {
83
+ plugins: imageMinimizerPlugins,
84
+ },
85
+ },
86
+ })
87
+ );
88
+ }
89
+
90
+ const webpackConfig = {
91
+ mode: 'production',
92
+ devtool: 'hidden-source-map',
93
+ context: appDirectory,
94
+ entry: {
95
+ ui: './src/ui',
96
+ proto: './src/proto',
97
+ },
98
+ output: {
99
+ path: path.resolve(appDirectory, 'public', 'assets'),
100
+ filename: 'js/[name].min.js',
101
+ chunkFilename: 'js/chunks/[name]-[contenthash:7].min.js',
102
+ publicPath: '/assets/',
103
+ },
104
+ resolve: {
105
+ symlinks: false,
106
+ },
107
+ module: {
108
+ rules: [],
109
+ },
110
+ plugins: [new CaseSensitivePathsPlugin({ debug: false }), new WebpackBar()],
111
+ optimization: {
112
+ moduleIds: 'deterministic',
113
+ chunkIds: 'deterministic',
114
+ runtimeChunk: false,
115
+ splitChunks: {
116
+ chunks: 'all',
117
+ minSize: 3000,
118
+ cacheGroups: {
119
+ default: false,
120
+ defaultVendors: false,
121
+ vendors: {
122
+ test: /[\\/]node_modules[\\/].*\.(js|jsx|mjs|ts|tsx)$/,
123
+ chunks: (chunk) => chunk.canBeInitial() && chunk.name && chunk.name !== 'proto',
124
+ name: 'vendors',
125
+ enforce: true,
126
+ },
127
+ },
128
+ },
129
+ minimizer: minimizerPlugins,
130
+ },
131
+ stats: {
132
+ preset: 'errors-warnings',
133
+ assets: true,
134
+ assetsSort: 'size',
135
+ children: false,
136
+ chunks: false,
137
+ modules: false,
138
+ colors: true,
139
+ entrypoints: true,
140
+ errors: true,
141
+ errorDetails: true,
142
+ hash: false,
143
+ builtAt: false,
144
+ timings: true,
145
+ performance: true,
146
+ warnings: true,
147
+ },
148
+ performance: {
149
+ hints: 'warning',
150
+ maxEntrypointSize: 760 * 1024,
151
+ maxAssetSize: 380 * 1024,
152
+ },
153
+ };
154
+ const theme = options.features.theme ? options.features.theme : false;
155
+ if (theme) {
156
+ webpackConfig.entry.ui = `./src/ui.${theme}`;
157
+ webpackConfig.output.path = path.resolve(webpackConfig.output.path, theme);
158
+ webpackConfig.output.publicPath = `${webpackConfig.output.publicPath}${theme}/`;
159
+ }
160
+
161
+ // scripts (js/ts via babel)
162
+ if (options.rules.script) {
163
+ const scriptOptions = typeof options.rules.script === 'object' ? options.rules.script : null;
164
+ webpackRules.addJSConfig(webpackConfig, appDirectory, scriptOptions);
165
+ if (scriptOptions && scriptOptions.typescript) {
166
+ webpackRules.addTsConfig(webpackConfig, appDirectory, scriptOptions, { isProduction: true });
167
+ }
168
+ }
169
+
170
+ // css & scss
171
+ if (options.rules.style) {
172
+ const scssMiniCSSExtractOptions = {
173
+ ...(options.rules.style.publicPath && { publicPath: options.rules.style.publicPath })
174
+ };
175
+ const scssLoaderOptions = {
176
+ sourceMap: true,
177
+ ...(options.rules.style.sassOptions && { sassOptions: options.rules.style.sassOptions }),
178
+ };
179
+ webpackConfig.module.rules.push({
180
+ test: /\.s?css$/,
181
+ use: [
182
+ {
183
+ loader: MiniCssExtractPlugin.loader,
184
+ options: scssMiniCSSExtractOptions,
185
+ },
186
+ {
187
+ loader: require.resolve('css-loader'),
188
+ options: {
189
+ importLoaders: 2,
190
+ sourceMap: true,
191
+ },
192
+ },
193
+ {
194
+ loader: require.resolve('postcss-loader'),
195
+ options: {
196
+ sourceMap: true,
197
+ postcssOptions: () => {
198
+ return {
199
+ plugins: [
200
+ require('autoprefixer')({
201
+ // @see autoprefixer options: https://github.com/postcss/autoprefixer#options
202
+ // flexbox: 'no-2009' will add prefixes only for final and IE versions of specification.
203
+ flexbox: 'no-2009',
204
+ // grid: 'autoplace': enable autoprefixer grid translations and include autoplacement support.
205
+ // not enabled - use `/* autoprefixer grid: autoplace */` in your css file
206
+ }),
207
+ require('cssnano'),
208
+ ],
209
+ };
210
+ },
211
+ },
212
+ },
213
+ {
214
+ loader: require.resolve('resolve-url-loader'),
215
+ options: {
216
+ sourceMap: true,
217
+ },
218
+ },
219
+ {
220
+ loader: require.resolve('sass-loader'),
221
+ options: scssLoaderOptions,
222
+ },
223
+ ],
224
+ });
225
+
226
+ webpackConfig.plugins.push(
227
+ new MiniCssExtractPlugin({
228
+ filename: 'css/[name].min.css',
229
+ chunkFilename: 'css/chunks/[name]-[contenthash:7].min.css',
230
+ }),
231
+ );
232
+ }
233
+
234
+ // handlebars precompiled templates
235
+ if (options.rules.hbs) {
236
+ const hbsRule = {
237
+ test: /\.hbs$/,
238
+ use: {
239
+ loader: require.resolve('handlebars-loader'),
240
+ // options: {
241
+ // helperDirs: [
242
+ // path.resolve(__dirname, '../../app/templating/hbs/helpers'),
243
+ // ],
244
+ // knownHelpers: [],
245
+ // runtime: '',
246
+ // partialDirs: ''
247
+ // },
248
+ },
249
+ };
250
+ webpackConfig.module.rules.push(utils.getEnrichedConfig(hbsRule, options.rules.hbs));
251
+ }
252
+
253
+ // woff fonts (for example, in CSS files)
254
+ if (options.rules.woff) {
255
+ const woffRule = {
256
+ test: /.(woff(2)?)(\?[a-z0-9]+)?$/,
257
+ type: 'asset/resource',
258
+ generator: {
259
+ filename: 'media/fonts/[name]-[contenthash:7].[ext]',
260
+ },
261
+ };
262
+ webpackConfig.module.rules.push(utils.getEnrichedConfig(woffRule, options.rules.woff));
263
+ }
264
+
265
+ // different font types (legacy - eg. used in older library css)
266
+ if (options.rules.font) {
267
+ const fontRule = {
268
+ test: /\.(eot|svg|ttf|woff|woff2)([?#]+[A-Za-z0-9-_]*)*$/,
269
+ type: 'asset',
270
+ parser: {
271
+ dataUrlCondition: {
272
+ maxSize: 2 * 1028,
273
+ },
274
+ },
275
+ generator: {
276
+ filename: 'media/font/[name]-[contenthash:7].[ext]',
277
+ },
278
+ };
279
+ webpackConfig.module.rules.push(utils.getEnrichedConfig(fontRule, options.rules.font));
280
+ }
281
+
282
+ // images
283
+ if (options.rules.image) {
284
+ const imageRule = {
285
+ test: /\.(png|jpg|gif|svg)$/,
286
+ type: 'asset',
287
+ parser: {
288
+ dataUrlCondition: {
289
+ maxSize: 1028,
290
+ },
291
+ },
292
+ generator: {
293
+ filename: 'media/[ext]/[name]-[contenthash:7].[ext]',
294
+ },
295
+ };
296
+
297
+ webpackConfig.module.rules.push(utils.getEnrichedConfig(imageRule, options.rules.image));
298
+ }
299
+
300
+ // feature banner (enabled by default)
301
+ if (!options.features.banner === false) {
302
+ webpackConfig.plugins.push(new webpack.BannerPlugin({ banner, entryOnly: true }));
303
+ }
304
+
305
+ // feature bundle analyzer
306
+ if (options.features.bundleAnalyzer) {
307
+ webpackConfig.plugins.push(new BundleAnalyzerPlugin());
308
+ }
309
+
310
+ return webpackConfig;
311
+ };
312
+ module.exports.appDirectory = appDirectory;