@nitro/webpack 10.0.3 → 11.0.0-beta.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.
package/lib/utils.js CHANGED
@@ -16,15 +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
- return mod;
27
- }
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
+ }
28
31
 
29
32
  module.exports = {
30
33
  getEnrichedConfig,
@@ -0,0 +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
+ };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@nitro/webpack",
3
- "version": "10.0.3",
3
+ "version": "11.0.0-beta.1",
4
4
  "description": "nitro webpack",
5
5
  "license": "MIT",
6
6
  "author": "The Nitro Team",
7
7
  "engines": {
8
- "node": ">=20.18.1 <21",
9
- "npm": ">=10.8.2 <11"
8
+ "node": ">=22.11.0 <25",
9
+ "npm": ">=10.9.0 <12"
10
10
  },
11
11
  "scripts": {
12
12
  "lint": "eslint .",
@@ -15,52 +15,54 @@
15
15
  },
16
16
  "files": [
17
17
  "lib",
18
- "plugins",
19
18
  "webpack-config"
20
19
  ],
21
20
  "keywords": [
22
21
  "frontend",
23
22
  "nitro"
24
23
  ],
24
+ "peerDependencies": {
25
+ "@nitro/app": ">=11.0.0-beta.1",
26
+ "webpack": "^5"
27
+ },
25
28
  "dependencies": {
26
- "@babel/core": "7.28.5",
27
- "@babel/preset-env": "7.28.5",
29
+ "@babel/core": "7.29.0",
30
+ "@babel/preset-env": "7.29.0",
28
31
  "@babel/preset-react": "7.28.5",
29
- "@babel/plugin-proposal-decorators": "7.28.0",
30
- "@babel/plugin-transform-class-properties": "7.27.1",
31
- "autoprefixer": "10.4.22",
32
+ "@babel/plugin-proposal-decorators": "7.29.0",
33
+ "@babel/plugin-transform-class-properties": "7.28.6",
34
+ "autoprefixer": "10.4.24",
35
+ "babel-loader": "10.0.0",
32
36
  "case-sensitive-paths-webpack-plugin": "2.4.0",
33
- "css-loader": "5.2.7",
37
+ "css-loader": "7.1.3",
34
38
  "cssnano": "7.1.2",
35
- "file-loader": "6.2.0",
39
+ "image-minimizer-webpack-plugin": "4.1.4",
40
+ "fork-ts-checker-webpack-plugin": "9.1.0",
36
41
  "handlebars-loader": "1.7.3",
37
- "imagemin": "7.0.1",
38
- "img-loader": "4.0.0",
39
- "js-config-webpack-plugin": "2.0.3",
40
- "mini-css-extract-plugin": "1.6.2",
42
+ "imagemin": "9.0.1",
43
+ "mini-css-extract-plugin": "2.10.0",
41
44
  "postcss": "8.5.6",
42
- "postcss-loader": "4.3.0",
45
+ "postcss-loader": "8.2.0",
43
46
  "resolve-url-loader": "5.0.0",
44
- "sass": "1.94.2",
45
- "sass-loader": "10.5.2",
46
- "svgo": "3.3.2",
47
- "ts-config-webpack-plugin": "2.0.3",
47
+ "sass": "1.97.3",
48
+ "sass-loader": "16.0.7",
49
+ "thread-loader": "4.0.4",
50
+ "ts-loader": "9.5.4",
48
51
  "typescript": "5.9.3",
49
- "url-loader": "4.1.1",
50
- "webpack": "4.47.0",
51
52
  "webpackbar": "7.0.0",
52
- "webpack-bundle-analyzer": "4.10.2"
53
+ "webpack-bundle-analyzer": "5.2.0"
53
54
  },
54
55
  "optionalDependencies": {
55
- "imagemin-mozjpeg": "9.0.0",
56
+ "imagemin-mozjpeg": "10.0.0",
56
57
  "imagemin-optipng": "8.0.0",
57
- "imagemin-pngquant": "9.0.2",
58
- "imagemin-svgo": "9.0.0"
58
+ "imagemin-pngquant": "10.0.0",
59
+ "imagemin-svgo": "11.0.1"
59
60
  },
60
61
  "devDependencies": {
61
62
  "@merkle-open/eslint-config": "4.0.1",
62
63
  "eslint": "8.57.1",
63
- "eslint-plugin-import": "2.32.0"
64
+ "eslint-plugin-import": "2.32.0",
65
+ "webpack": "5.105.2"
64
66
  },
65
67
  "publishConfig": {
66
68
  "access": "public"
package/readme.md CHANGED
@@ -4,16 +4,15 @@
4
4
 
5
5
  # Nitro Webpack
6
6
 
7
- Configurable and easy to use webpack 4 config for nitro projects.
7
+ Configurable and easy to use webpack 5 config for nitro projects.
8
8
 
9
9
  ## Usage
10
10
 
11
11
  ```
12
12
  const options = {
13
13
  rules: {
14
- js: true,
15
- ts: false,
16
- scss: true,
14
+ script: true,
15
+ style: true,
17
16
  hbs: true,
18
17
  woff: true,
19
18
  font: false,
@@ -22,8 +21,8 @@ const options = {
22
21
  features: {
23
22
  banner: true,
24
23
  bundleAnalyzer: false,
24
+ imageMinimizer: true,
25
25
  theme: false,
26
- dynamicAlias: false,
27
26
  },
28
27
  };
29
28
  const webpackConfig = require('@nitro/webpack/webpack-config/webpack.config.dev')(options);
@@ -37,27 +36,20 @@ module.exports = webpackConfig;
37
36
 
38
37
  No loader rule is enabled by default. Activate following prepared rules you need in `options.rules`
39
38
 
40
- #### `options.rules.js`
39
+ #### `options.rules.script`
41
40
 
42
41
  - Type: boolean || object
43
42
  - default: false
44
- - file types: js, jsx, mjs
43
+ - file types: js, jsx, mjs, cjs (and optional ts, tsx, mts, cts)
45
44
 
46
45
  Config:
47
46
 
48
- - `true` or `{}` activates JavaScript support
47
+ - `true` or `{}` activates JavaScript support (via Babel)
48
+ - `{ typescript: true }` adds TypeScript support
49
+ - `{ babelConfigFile: 'path/to/babel.config.js' }` will be used as Babel config (default: auto-detect)
50
+ - `{ tsConfigFile: 'path/to/tsconfig.json' }` is used for type-checking with ForkTsCheckerWebpackPlugin (default: auto-detect)
49
51
 
50
- #### `options.rules.ts`
51
-
52
- - Type: boolean
53
- - default: false
54
- - file types: ts, tsx
55
-
56
- Config:
57
-
58
- - `true` will activate TypeScript support
59
-
60
- #### `options.rules.scss`
52
+ #### `options.rules.style`
61
53
 
62
54
  - Type: boolean || object
63
55
  - default: false
@@ -65,10 +57,9 @@ Config:
65
57
 
66
58
  Config:
67
59
 
68
- - `true` or `{}` will activate scss support
60
+ - `true` or `{}` will activate css & scss support
69
61
  - `{ publicPath: '../' }` provide a separate public path for stylesheets. By default, webpack uses the value from 'output.publicPath'. (only relevant for production build)
70
- - `{ implementation: require('node-sass') }` gives the possibility to use 'node-sass' as sass implementation. (you have to add 'node-sass' as a dev-dependency in your project)
71
- - `{ sassOptions: { ... } }` gives the possibility to add options for the ['dart-sass'](https://sass-lang.com/documentation/js-api/interfaces/options/) or ['node-sass'](https://github.com/sass/node-sass/#options) implementation (e.g. ignore some deprecations for dart-sass with `silenceDeprecations: [...]`)
62
+ - `{ sassOptions: { ... } }` gives the possibility to add options for the ['dart-sass'](https://sass-lang.com/documentation/js-api/interfaces/options/)
72
63
 
73
64
  #### `options.rules.hbs`
74
65
 
@@ -141,6 +132,13 @@ Enable some additional features
141
132
 
142
133
  `true` will add the bundleAnalyser plugin and opens a browser window with the stats
143
134
 
135
+ #### `options.features.imageMinimizer`
136
+
137
+ - Type: boolean
138
+ - default: true
139
+
140
+ `false` will disable image minifaction functionality
141
+
144
142
  #### `options.features.theme`
145
143
 
146
144
  - Type: string || false
@@ -153,21 +151,6 @@ A string will activate theming support:
153
151
 
154
152
  It makes sense to use a dynamic value e.g. an environment variable, as shown in the example configuration.
155
153
 
156
- #### `options.features.dynamicAlias`
157
-
158
- - Type: object || false
159
- - default: false
160
-
161
- A proper configured dynamicAlias feature will activate the DynamicAliasResolverPlugin
162
- which can change import paths in source files dynamically on compile time as desired.
163
-
164
- Properties:
165
-
166
- - `options.features.dynamicAlias.search` (string || RegExp)
167
- search term to be replaced (e.g. '/theme/light')
168
- - `options.features.dynamicAlias.replace` (string)
169
- string as replacement (e.g. `/theme/${theme}`)
170
-
171
154
  ## Extending Configuration
172
155
 
173
156
  ### Code Splitting
@@ -1,26 +1,19 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
- const crypto = require('crypto');
4
3
  const webpack = require('webpack');
5
4
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
6
5
  const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
7
- const JsConfigWebpackPlugin = require('js-config-webpack-plugin');
8
6
  const MiniCssExtractPlugin = require('mini-css-extract-plugin');
9
- const TsConfigWebpackPlugin = require('ts-config-webpack-plugin');
10
- const DynamicAliasResolverPlugin = require('../plugins/dynamicAliasResolver');
11
7
  const utils = require('../lib/utils');
8
+ const webpackRules = require('../lib/webpack-rules');
12
9
 
13
10
  const hotMiddlewareScript = 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=true';
14
11
  const appDirectory = fs.realpathSync(process.cwd());
15
12
 
16
- // hack: OpenSSL 3 does not support md4 anymore, but legacy webpack 4 hardcoded it: https://github.com/webpack/webpack/issues/13572
17
- const crypto_orig_createHash = crypto.createHash;
18
- crypto.createHash = algorithm => crypto_orig_createHash(algorithm === 'md4' ? 'sha256' : algorithm);
19
-
20
13
  module.exports = (options = { rules: {}, features: {} }) => {
21
14
  const webpackConfig = {
22
15
  mode: 'development',
23
- devtool: 'eval-source-map',
16
+ devtool: 'source-map',
24
17
  context: appDirectory,
25
18
  entry: {
26
19
  ui: ['./src/ui', hotMiddlewareScript],
@@ -29,7 +22,7 @@ module.exports = (options = { rules: {}, features: {} }) => {
29
22
  output: {
30
23
  path: path.resolve(appDirectory, 'public', 'assets'),
31
24
  filename: 'js/[name].js',
32
- chunkFilename: 'js/[name]-[hash:7].js',
25
+ chunkFilename: 'js/[name]-[contenthash:7].js',
33
26
  publicPath: '/assets/',
34
27
  pathinfo: false,
35
28
  },
@@ -52,47 +45,43 @@ module.exports = (options = { rules: {}, features: {} }) => {
52
45
  ],
53
46
  },
54
47
  optimization: {
55
- noEmitOnErrors: true,
48
+ emitOnErrors: false,
49
+ moduleIds: 'deterministic',
50
+ chunkIds: 'deterministic',
51
+ runtimeChunk: 'single',
56
52
  splitChunks: {
57
- name: true,
58
- automaticNameDelimiter: '/',
53
+ chunks: 'all',
54
+ minSize: 3000,
59
55
  cacheGroups: {
60
- // allow dynamic imports for node_modules also
61
- dynamic: {
62
- minSize: 3000,
63
- chunks: 'async',
64
- priority: 0,
65
- },
66
- // extract js node_modules to vendors file
56
+ default: false,
57
+ defaultVendors: false,
67
58
  vendors: {
68
59
  test: /[\\/]node_modules[\\/].*\.(js|jsx|mjs|ts|tsx)$/,
69
- // use fix filename for usage in view
70
- filename: 'js/vendors.js',
71
- // Exclude proto dependencies going into vendors
72
- chunks: (chunk) => chunk.name !== 'proto',
73
- priority: -10,
60
+ chunks: (chunk) => chunk.canBeInitial() && chunk.name && chunk.name !== 'proto',
61
+ name: 'vendors',
74
62
  enforce: true,
75
63
  },
76
64
  },
77
65
  },
78
66
  },
79
67
  stats: {
80
- all: undefined,
68
+ preset: 'errors-warnings',
81
69
  assets: false,
82
70
  children: false,
83
71
  chunks: false,
84
72
  modules: false,
85
73
  colors: true,
86
- depth: false,
87
74
  entrypoints: false,
88
75
  errors: true,
89
76
  errorDetails: false,
90
77
  hash: false,
78
+ builtAt: false,
79
+ timings: false,
91
80
  performance: true,
92
81
  warnings: true,
93
82
  },
94
83
  infrastructureLogging: {
95
- level: 'warn'
84
+ level: 'warn',
96
85
  }
97
86
  };
98
87
  const theme = options.features.theme ? options.features.theme : false;
@@ -103,21 +92,20 @@ module.exports = (options = { rules: {}, features: {} }) => {
103
92
  webpackConfig.output.publicPath = `${webpackConfig.output.publicPath}${theme}/`;
104
93
  }
105
94
 
106
- // js
107
- if (options.rules.js) {
108
- webpackConfig.plugins.push(new JsConfigWebpackPlugin({ babelConfigFile: './babel.config.js' }));
109
- }
110
-
111
- // typescript
112
- if (options.rules.ts) {
113
- webpackConfig.plugins.push(new TsConfigWebpackPlugin());
95
+ // scripts (js/ts via babel)
96
+ if (options.rules.script) {
97
+ const scriptOptions = typeof options.rules.script === 'object' ? options.rules.script : null;
98
+ webpackRules.addJSConfig(webpackConfig, appDirectory, scriptOptions);
99
+ if (scriptOptions && scriptOptions.typescript) {
100
+ webpackRules.addTsConfig(webpackConfig, appDirectory, scriptOptions, { isProduction: false });
101
+ }
114
102
  }
115
103
 
116
104
  // css & scss
117
- if (options.rules.scss) {
105
+ if (options.rules.style) {
118
106
  const scssLoaderOptions = {
119
- ...(options.rules.scss.implementation && { implementation: options.rules.scss.implementation }),
120
- ...(options.rules.scss.sassOptions && { sassOptions: options.rules.scss.sassOptions }),
107
+ sourceMap: true,
108
+ ...(options.rules.style.sassOptions && { sassOptions: options.rules.style.sassOptions }),
121
109
  };
122
110
  webpackConfig.module.rules.push({
123
111
  test: /\.s?css$/,
@@ -129,11 +117,13 @@ module.exports = (options = { rules: {}, features: {} }) => {
129
117
  loader: require.resolve('css-loader'),
130
118
  options: {
131
119
  importLoaders: 2,
120
+ sourceMap: true,
132
121
  },
133
122
  },
134
123
  {
135
124
  loader: require.resolve('postcss-loader'),
136
125
  options: {
126
+ sourceMap: true,
137
127
  postcssOptions: () => {
138
128
  return {
139
129
  plugins: [
@@ -151,6 +141,9 @@ module.exports = (options = { rules: {}, features: {} }) => {
151
141
  },
152
142
  {
153
143
  loader: require.resolve('resolve-url-loader'),
144
+ options: {
145
+ sourceMap: true,
146
+ },
154
147
  },
155
148
  {
156
149
  loader: require.resolve('sass-loader'),
@@ -163,12 +156,6 @@ module.exports = (options = { rules: {}, features: {} }) => {
163
156
  new MiniCssExtractPlugin({
164
157
  filename: 'css/[name].css',
165
158
  }),
166
- // we need SourceMapDevToolPlugin to make sourcemaps work
167
- // with MiniCSSExtractPlugin hmr mode
168
- // related: https://github.com/webpack-contrib/mini-css-extract-plugin/issues/29
169
- new webpack.SourceMapDevToolPlugin({
170
- filename: '[file].map',
171
- })
172
159
  );
173
160
  }
174
161
 
@@ -195,11 +182,9 @@ module.exports = (options = { rules: {}, features: {} }) => {
195
182
  if (options.rules.woff) {
196
183
  const woffRule = {
197
184
  test: /.(woff(2)?)(\?[a-z0-9]+)?$/,
198
- use: {
199
- loader: require.resolve('file-loader'),
200
- options: {
201
- name: 'media/fonts/[name]-[hash:7].[ext]',
202
- },
185
+ type: 'asset/resource',
186
+ generator: {
187
+ filename: 'media/fonts/[name]-[contenthash:7].[ext]',
203
188
  },
204
189
  };
205
190
  webpackConfig.module.rules.push(utils.getEnrichedConfig(woffRule, options.rules.woff));
@@ -209,13 +194,15 @@ module.exports = (options = { rules: {}, features: {} }) => {
209
194
  if (options.rules.font) {
210
195
  const fontRule = {
211
196
  test: /\.(eot|svg|ttf|woff|woff2)([?#]+[A-Za-z0-9-_]*)*$/,
212
- use: {
213
- loader: require.resolve('url-loader'),
214
- options: {
215
- limit: 2 * 1028,
216
- name: 'media/font/[name]-[hash:7].[ext]',
197
+ type: 'asset',
198
+ parser: {
199
+ dataUrlCondition: {
200
+ maxSize: 2 * 1028,
217
201
  },
218
202
  },
203
+ generator: {
204
+ filename: 'media/font/[name]-[contenthash:7].[ext]',
205
+ },
219
206
  };
220
207
  webpackConfig.module.rules.push(utils.getEnrichedConfig(fontRule, options.rules.font));
221
208
  }
@@ -224,8 +211,14 @@ module.exports = (options = { rules: {}, features: {} }) => {
224
211
  if (options.rules.image) {
225
212
  const imageRule = {
226
213
  test: /\.(png|jpg|gif|svg)$/,
227
- use: {
228
- loader: require.resolve('file-loader'),
214
+ type: 'asset',
215
+ parser: {
216
+ dataUrlCondition: {
217
+ maxSize: 3 * 1028,
218
+ },
219
+ },
220
+ generator: {
221
+ filename: 'media/[ext]/[name]-[contenthash:7].[ext]',
229
222
  },
230
223
  };
231
224
  webpackConfig.module.rules.push(utils.getEnrichedConfig(imageRule, options.rules.image));
@@ -236,15 +229,6 @@ module.exports = (options = { rules: {}, features: {} }) => {
236
229
  webpackConfig.plugins.push(new BundleAnalyzerPlugin());
237
230
  }
238
231
 
239
- // feature dynamic alias
240
- if (
241
- options.features.dynamicAlias &&
242
- options.features.dynamicAlias.search &&
243
- options.features.dynamicAlias.replace
244
- ) {
245
- webpackConfig.resolve.plugins = [new DynamicAliasResolverPlugin(options.features.dynamicAlias)];
246
- }
247
-
248
232
  return webpackConfig;
249
233
  };
250
234
  module.exports.hotMiddlewareScript = hotMiddlewareScript;
@@ -1,318 +1,311 @@
1
- const path = require('path');
2
- const fs = require('fs');
3
- const crypto = require('crypto');
4
- const webpack = require('webpack');
5
- const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
6
- const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
7
- const JsConfigWebpackPlugin = require('js-config-webpack-plugin');
8
- const MiniCssExtractPlugin = require('mini-css-extract-plugin');
9
- const TsConfigWebpackPlugin = require('ts-config-webpack-plugin');
10
- const WebpackBar = require('webpackbar');
11
- const DynamicAliasResolverPlugin = require('../plugins/dynamicAliasResolver');
12
- const utils = require('../lib/utils');
13
-
14
- // hack: OpenSSL 3 does not support md4 anymore, but legacy webpack 4 hardcoded it: https://github.com/webpack/webpack/issues/13572
15
- const crypto_orig_createHash = crypto.createHash;
16
- crypto.createHash = algorithm => crypto_orig_createHash(algorithm === 'md4' ? 'sha256' : algorithm);
17
-
18
- const appDirectory = fs.realpathSync(process.cwd());
19
-
20
- const bannerData = {
21
- date: new Date().toISOString().slice(0, 19),
22
- pkg: require(`${appDirectory}/package.json`),
23
- };
24
-
25
- const banner = `${bannerData.pkg.name}
26
- @version v${bannerData.pkg.version}
27
- @date ${bannerData.date}`;
28
-
29
- module.exports = (options = { rules: {}, features: {} }) => {
30
- const webpackConfig = {
31
- mode: 'production',
32
- devtool: 'hidden-source-map',
33
- context: appDirectory,
34
- entry: {
35
- ui: './src/ui',
36
- proto: './src/proto',
37
- },
38
- output: {
39
- path: path.resolve(appDirectory, 'public', 'assets'),
40
- filename: 'js/[name].min.js',
41
- chunkFilename: 'js/[name]-[contenthash:7].min.js',
42
- publicPath: '/assets/',
43
- },
44
- resolve: {
45
- symlinks: false,
46
- },
47
- module: {
48
- rules: [],
49
- },
50
- plugins: [new CaseSensitivePathsPlugin({ debug: false }), new WebpackBar()],
51
- optimization: {
52
- splitChunks: {
53
- name: true,
54
- automaticNameDelimiter: '/',
55
- cacheGroups: {
56
- // allow dynamic imports for node_modules also
57
- dynamic: {
58
- minSize: 3000,
59
- chunks: 'async',
60
- priority: 0,
61
- },
62
- // extract js node_modules to vendors file
63
- vendors: {
64
- test: /[\\/]node_modules[\\/].*\.(js|jsx|mjs|ts|tsx)$/,
65
- // use fix filename for usage in view
66
- filename: 'js/vendors.min.js',
67
- // Exclude proto dependencies going into vendors
68
- chunks: (chunk) => chunk.name !== 'proto',
69
- priority: -10,
70
- enforce: true,
71
- },
72
- },
73
- },
74
- },
75
- stats: {
76
- all: undefined,
77
- assets: true,
78
- children: false,
79
- chunks: false,
80
- modules: false,
81
- colors: true,
82
- depth: false,
83
- entrypoints: false,
84
- errors: true,
85
- errorDetails: true,
86
- hash: false,
87
- performance: true,
88
- warnings: false,
89
- },
90
- };
91
- const theme = options.features.theme ? options.features.theme : false;
92
- if (theme) {
93
- webpackConfig.entry.ui = `./src/ui.${theme}`;
94
- webpackConfig.output.path = path.resolve(webpackConfig.output.path, theme);
95
- webpackConfig.output.publicPath = `${webpackConfig.output.publicPath}${theme}/`;
96
- }
97
-
98
- // js
99
- if (options.rules.js) {
100
- webpackConfig.plugins.push(new JsConfigWebpackPlugin({ babelConfigFile: './babel.config.js' }));
101
- }
102
-
103
- // typescript
104
- if (options.rules.ts) {
105
- webpackConfig.plugins.push(new TsConfigWebpackPlugin());
106
- }
107
-
108
- // css & scss
109
- if (options.rules.scss) {
110
- const scssMiniCSSExtractOptions = {
111
- ...(options.rules.scss.publicPath && { publicPath: options.rules.scss.publicPath })
112
- };
113
- const scssLoaderOptions = {
114
- ...(options.rules.scss.implementation && { implementation: options.rules.scss.implementation }),
115
- ...(options.rules.scss.sassOptions && { sassOptions: options.rules.scss.sassOptions }),
116
- };
117
- webpackConfig.module.rules.push({
118
- test: /\.s?css$/,
119
- use: [
120
- {
121
- loader: MiniCssExtractPlugin.loader,
122
- options: scssMiniCSSExtractOptions,
123
- },
124
- {
125
- loader: require.resolve('css-loader'),
126
- options: {
127
- importLoaders: 2,
128
- },
129
- },
130
- {
131
- loader: require.resolve('postcss-loader'),
132
- options: {
133
- postcssOptions: () => {
134
- return {
135
- plugins: [
136
- require('autoprefixer')({
137
- // @see autoprefixer options: https://github.com/postcss/autoprefixer#options
138
- // flexbox: 'no-2009' will add prefixes only for final and IE versions of specification.
139
- flexbox: 'no-2009',
140
- // grid: 'autoplace': enable autoprefixer grid translations and include autoplacement support.
141
- // not enabled - use `/* autoprefixer grid: autoplace */` in your css file
142
- }),
143
- require('cssnano'),
144
- ],
145
- };
146
- },
147
- },
148
- },
149
- {
150
- loader: require.resolve('resolve-url-loader'),
151
- },
152
- {
153
- loader: require.resolve('sass-loader'),
154
- options: scssLoaderOptions,
155
- },
156
- ],
157
- });
158
-
159
- webpackConfig.plugins.push(
160
- new MiniCssExtractPlugin({
161
- filename: 'css/[name].min.css',
162
- chunkFilename: 'css/[name]-[contenthash:7].min.css',
163
- }),
164
- );
165
- }
166
-
167
- // handlebars precompiled templates
168
- if (options.rules.hbs) {
169
- const hbsRule = {
170
- test: /\.hbs$/,
171
- use: {
172
- loader: require.resolve('handlebars-loader'),
173
- // options: {
174
- // helperDirs: [
175
- // path.resolve(__dirname, '../../app/templating/hbs/helpers'),
176
- // ],
177
- // knownHelpers: [],
178
- // runtime: '',
179
- // partialDirs: ''
180
- // },
181
- },
182
- };
183
- webpackConfig.module.rules.push(utils.getEnrichedConfig(hbsRule, options.rules.hbs));
184
- }
185
-
186
- // woff fonts (for example, in CSS files)
187
- if (options.rules.woff) {
188
- const woffRule = {
189
- test: /.(woff(2)?)(\?[a-z0-9]+)?$/,
190
- use: {
191
- loader: require.resolve('file-loader'),
192
- options: {
193
- name: 'media/fonts/[name]-[hash:7].[ext]',
194
- },
195
- },
196
- };
197
- webpackConfig.module.rules.push(utils.getEnrichedConfig(woffRule, options.rules.woff));
198
- }
199
-
200
- // different font types (legacy - eg. used in older library css)
201
- if (options.rules.font) {
202
- const fontRule = {
203
- test: /\.(eot|svg|ttf|woff|woff2)([?#]+[A-Za-z0-9-_]*)*$/,
204
- use: {
205
- loader: require.resolve('url-loader'),
206
- options: {
207
- limit: 2 * 1028,
208
- name: 'media/font/[name]-[hash:7].[ext]',
209
- },
210
- },
211
- };
212
- webpackConfig.module.rules.push(utils.getEnrichedConfig(fontRule, options.rules.font));
213
- }
214
-
215
- // images
216
- if (options.rules.image) {
217
-
218
- const imageminMozjpeg = utils.getOptionalPackage('imagemin-mozjpeg');
219
- const imageminOptipng = utils.getOptionalPackage('imagemin-optipng');
220
- const imageminPngquant = utils.getOptionalPackage('imagemin-pngquant');
221
- const imageminSvgo = utils.getOptionalPackage('imagemin-svgo');
222
-
223
- // console.log('imagemin-mozjpeg: ', imageminMozjpeg ? 'installed' : 'NOT');
224
- // console.log('imagemin-optipng: ', imageminOptipng ? 'installed' : 'NOT');
225
- // console.log('imagemin-pngquant: ', imageminPngquant ? 'installed' : 'NOT');
226
- // console.log('imagemin-svgo: ', imageminSvgo ? 'installed' : 'NOT');
227
-
228
- // image loader & minification
229
- const imageMinificationRule = {
230
- test: /\.(png|jpg|svg)$/,
231
- // Specify enforce: 'pre' to apply the loader before url-loader
232
- enforce: 'pre',
233
- use: {
234
- loader: require.resolve('img-loader'),
235
- options: {
236
- plugins: [],
237
- },
238
- },
239
- };
240
-
241
- if (imageminMozjpeg) {
242
- imageMinificationRule.use.options.plugins.push(
243
- imageminMozjpeg({
244
- quality: 75,
245
- progressive: true,
246
- })
247
- );
248
- }
249
- if (imageminOptipng) {
250
- imageMinificationRule.use.options.plugins.push(
251
- imageminOptipng({
252
- optimizationLevel: 7,
253
- })
254
- );
255
- }
256
- if (imageminPngquant) {
257
- imageMinificationRule.use.options.plugins.push(
258
- imageminPngquant({
259
- // floyd: 0.5,
260
- // speed: 2
261
- })
262
- );
263
- }
264
- if (imageminSvgo) {
265
- imageMinificationRule.use.options.plugins.push(
266
- imageminSvgo({
267
- plugins: [
268
- {
269
- name: 'preset-default',
270
- params: {
271
- overrides: {
272
- removeViewBox: false,
273
- },
274
- },
275
- },
276
- ],
277
- })
278
- );
279
- }
280
-
281
- // url loader for images (for example, in CSS files)
282
- // inlines assets below a limit
283
- const imageRule = {
284
- test: /\.(png|jpg|gif|svg)$/,
285
- use: {
286
- loader: require.resolve('url-loader'),
287
- options: {
288
- limit: 3 * 1028,
289
- name: 'media/[ext]/[name]-[hash:7].[ext]',
290
- },
291
- },
292
- };
293
-
294
- webpackConfig.module.rules.push(imageMinificationRule, utils.getEnrichedConfig(imageRule, options.rules.image));
295
- }
296
-
297
- // feature banner (enabled by default)
298
- if (!options.features.banner === false) {
299
- webpackConfig.plugins.push(new webpack.BannerPlugin({ banner }));
300
- }
301
-
302
- // feature bundle analyzer
303
- if (options.features.bundleAnalyzer) {
304
- webpackConfig.plugins.push(new BundleAnalyzerPlugin());
305
- }
306
-
307
- // feature dynamic alias
308
- if (
309
- options.features.dynamicAlias &&
310
- options.features.dynamicAlias.search &&
311
- options.features.dynamicAlias.replace
312
- ) {
313
- webpackConfig.resolve.plugins = [new DynamicAliasResolverPlugin(options.features.dynamicAlias)];
314
- }
315
-
316
- return webpackConfig;
317
- };
318
- 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
+ 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,31 +0,0 @@
1
- // developed for @nitro/webpack with infos & outline from:
2
- // https://stackoverflow.com/questions/54320025/tap-into-webpack-resolve-loader-to-add-fallback
3
-
4
- class DynamicAliasResolver {
5
- constructor({ search, replace /*, fallback*/ }) {
6
- this.search = search;
7
- this.replace = replace;
8
- // this.fallback = fallback;
9
- this.filterFunction =
10
- this.search instanceof RegExp ? (value) => search.test(value) : (value) => value.indexOf(search) !== -1;
11
- }
12
- apply(resolver) {
13
- // current & next hook
14
- const source = 'resolve';
15
- const target = resolver.ensureHook('parsedResolve');
16
-
17
- resolver.getHook(source).tapAsync('DynamicAliasResolver', (request, resolveContext, cb) => {
18
- // change request if we find search term
19
- if (this.filterFunction(request.request)) {
20
- // possible improvement: check for existing file and use fallback
21
- request.request = request.request.replace(this.search, this.replace);
22
- // continue to the next hook
23
- return resolver.doResolve(target, request, null, resolveContext, cb);
24
- }
25
- // continue
26
- return cb();
27
- });
28
- }
29
- }
30
-
31
- module.exports = DynamicAliasResolver;