@caweb/webpack 1.6.8 → 1.7.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/args.js CHANGED
@@ -15,11 +15,13 @@ let argv0 = process.argv0.replace(/.*node[.a-z\s]*/, '');
15
15
 
16
16
  // flags can be passed via argv0
17
17
  // we also add args from NODE_OPTIONS
18
+ // we also add args from CAWEB_NODE_OPTIONS
18
19
  let { values: flags } = parseArgs( {
19
20
  args: [
20
21
  ...argv,
21
22
  ...argv0.split(' '),
22
- ...(process.env.NODE_OPTIONS ? process.env.NODE_OPTIONS.trim().replace(/(^'|'$)/g, '').split(' ') : []).filter( Boolean )
23
+ ...(process.env.NODE_OPTIONS ? process.env.NODE_OPTIONS.trim().replace(/(^'|'$)/g, '').split(' ') : []).filter( Boolean ),
24
+ ...(process.env.CAWEB_NODE_OPTIONS ? process.env.CAWEB_NODE_OPTIONS.trim().replace(/(^'|'$)/g, '').split(' ') : []).filter( Boolean )
23
25
  ].filter( Boolean ),
24
26
  strict: false,
25
27
  allowPositionals: true,
package/lib/utils.js ADDED
@@ -0,0 +1,173 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { validate } from 'schema-utils';
5
+ import path, { join, sep, dirname } from 'path';
6
+ import { existsSync, realpathSync, readFileSync } from 'fs';
7
+ import { readPackageUp } from 'read-package-up';
8
+ import FastGlob from 'fast-glob';
9
+
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ const { sync: glob } = FastGlob;
15
+
16
+ const { warn } = console;
17
+
18
+ const { packageJson, path: pkgPath } = await readPackageUp({cwd: realpathSync( process.cwd() ) });
19
+
20
+ const moduleFields = new Set( [ 'viewScriptModule', 'viewModule' ] );
21
+
22
+ const scriptFields = new Set( [ 'viewScript', 'script', 'editorScript' ] );
23
+
24
+ const hasProjectFile = ( fileName ) =>
25
+ existsSync( fromProjectRoot( fileName ) )
26
+
27
+ const fromProjectRoot = ( fileName ) =>
28
+ path.join( path.dirname( getPackagePath() ), fileName )
29
+
30
+ const getPackagePath = () => pkgPath
31
+
32
+ function getProjectSourcePath() {
33
+ return process.env.WP_SOURCE_PATH || 'src';
34
+ }
35
+
36
+ function getBlockJsonScriptFields( blockJson ) {
37
+ let result = null;
38
+ for ( const field of scriptFields ) {
39
+ if ( Object.hasOwn( blockJson, field ) ) {
40
+ if ( ! result ) {
41
+ result = {};
42
+ }
43
+ result[ field ] = blockJson[ field ];
44
+ }
45
+ }
46
+ return result;
47
+ }
48
+
49
+ function getBlockJsonModuleFields( blockJson ) {
50
+ let result = null;
51
+ for ( const field of moduleFields ) {
52
+ if ( Object.hasOwn( blockJson, field ) ) {
53
+ if ( ! result ) {
54
+ result = {};
55
+ }
56
+ result[ field ] = blockJson[ field ];
57
+ }
58
+ }
59
+ return result;
60
+ }
61
+
62
+ function getPhpFilePaths( context, props ) {
63
+ // Continue only if the source directory exists.
64
+ if ( ! hasProjectFile( context ) ) {
65
+ return [];
66
+ }
67
+
68
+ // Checks whether any block metadata files can be detected in the defined source directory.
69
+ const blockMetadataFiles = glob( '**/block.json', {
70
+ absolute: true,
71
+ cwd: fromProjectRoot( context ),
72
+ } );
73
+
74
+ const srcDirectory = fromProjectRoot( context + sep );
75
+
76
+ return blockMetadataFiles.flatMap( ( blockMetadataFile ) => {
77
+ const paths = [];
78
+ let parsedBlockJson;
79
+ try {
80
+ parsedBlockJson = JSON.parse( readFileSync( blockMetadataFile ) );
81
+ } catch ( error ) {
82
+ warn(
83
+ `Not scanning "${ blockMetadataFile.replace(
84
+ fromProjectRoot( sep ),
85
+ ''
86
+ ) }" due to detect render files due to malformed JSON.`
87
+ );
88
+ return paths;
89
+ }
90
+
91
+ for ( const prop of props ) {
92
+ if (
93
+ typeof parsedBlockJson?.[ prop ] !== 'string' ||
94
+ ! parsedBlockJson[ prop ]?.startsWith( 'file:' )
95
+ ) {
96
+ continue;
97
+ }
98
+
99
+ // Removes the `file:` prefix.
100
+ const filepath = join(
101
+ dirname( blockMetadataFile ),
102
+ parsedBlockJson[ prop ].replace( 'file:', '' )
103
+ );
104
+
105
+ // Takes the path without the file extension, and relative to the defined source directory.
106
+ if ( ! filepath.startsWith( srcDirectory ) ) {
107
+ warn(
108
+ `Skipping "${ parsedBlockJson[ prop ].replace(
109
+ 'file:',
110
+ ''
111
+ ) }" listed in "${ blockMetadataFile.replace(
112
+ fromProjectRoot( sep ),
113
+ ''
114
+ ) }". File is located outside of the "${ context }" directory.`
115
+ );
116
+ continue;
117
+ }
118
+ paths.push( filepath.replace( /\\/g, '/' ) );
119
+ }
120
+ return paths;
121
+ } );
122
+ }
123
+
124
+ const phpFilePathsPluginSchema = {
125
+ type: 'object',
126
+ properties: {
127
+ context: {
128
+ type: 'string',
129
+ },
130
+ props: {
131
+ type: 'array',
132
+ items: {
133
+ type: 'string',
134
+ },
135
+ },
136
+ },
137
+ };
138
+
139
+ /**
140
+ * The plugin recomputes PHP file paths once on each compilation. It is necessary to avoid repeating processing
141
+ * when filtering every discovered PHP file in the source folder. This is the most performant way to ensure that
142
+ * changes in `block.json` files are picked up in watch mode.
143
+ */
144
+ class PhpFilePathsPlugin {
145
+ /**
146
+ * PHP file paths from `render` and `variations` props found in `block.json` files.
147
+ *
148
+ * @type {string[]}
149
+ */
150
+ static paths;
151
+
152
+ constructor( options = {} ) {
153
+ validate( phpFilePathsPluginSchema, options, {
154
+ name: 'PHP File Paths Plugin',
155
+ baseDataPath: 'options',
156
+ } );
157
+
158
+ this.options = options;
159
+ }
160
+
161
+ apply( compiler ) {
162
+ const pluginName = this.constructor.name;
163
+
164
+ compiler.hooks.thisCompilation.tap( pluginName, () => {
165
+ this.constructor.paths = getPhpFilePaths(
166
+ this.options.context,
167
+ this.options.props
168
+ );
169
+ } );
170
+ }
171
+ }
172
+
173
+ export { PhpFilePathsPlugin, getBlockJsonScriptFields, getBlockJsonModuleFields, fromProjectRoot, getProjectSourcePath };
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Modified WordPress Scripts Webpack Configuration
3
+ *
4
+ * @package CAWebPublishing
5
+ * @link https://webpack.js.org/configuration/
6
+ */
7
+
8
+ /**
9
+ * External Dependencies
10
+ */
11
+ import baseConfig from '@wordpress/scripts/config/webpack.config.js';
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import * as prettier from 'prettier';
15
+
16
+ /**
17
+ * Internal dependencies
18
+ */
19
+
20
+ let output = [];
21
+
22
+ // Update some of the default WordPress webpack rules.
23
+ baseConfig.module.rules.forEach((rule, i) => {
24
+ const r = new RegExp(rule.test).toString();
25
+
26
+ switch(r){
27
+ // WordPress adds a hash to asset file names we remove that hash.
28
+ case new RegExp(/\.(bmp|png|jpe?g|gif|webp)$/i).toString():
29
+ rule.generator.filename = 'images/[name][ext]';
30
+ break;
31
+ case new RegExp(/\.(woff|woff2|eot|ttf|otf)$/i).toString():
32
+ rule.generator.filename = 'fonts/[name][ext]';
33
+ break;
34
+ case new RegExp(/\.svg$/).toString():
35
+ // we don't want SVG to be asset/inline otherwise the resource may not be available.
36
+ // the asset should be an asset/resource we move them to the fonts folder.
37
+ if( 'asset/inline' === rule.type ){
38
+ rule.type = 'asset/resource';
39
+ rule.generator = { filename: 'fonts/[name][ext]' };
40
+
41
+ delete rule.issuer;
42
+ }
43
+ break;
44
+ // silence deprecation warnings from sass
45
+ case new RegExp(/\.(sc|sa)ss$/).toString():
46
+ // The postcss-loader is the 3rd loader
47
+ rule.use[2].options.postcssOptions.plugins = 'postcssPlugins'
48
+ // The sass-loader is last
49
+ rule.use[rule.use.length-1].options.sassOptions = {
50
+ silenceDeprecations: ['global-builtin', 'import', 'color-functions', 'if-function']
51
+ };
52
+ break;
53
+ case new RegExp(/\.m?(j|t)sx?$/).toString():
54
+ // @since @wordpress/scripts@30.20.0 babel-loader is used for js and ts files
55
+ // Added the Transform class properties syntax plugin to the babel-loader.
56
+ // @see https://babeljs.io/docs/en/babel-plugin-proposal-transform-class-properties
57
+ rule.use[0].options.plugins.push('@babel/plugin-transform-class-properties');
58
+ delete rule.use[0].options.cacheDirectory;
59
+ delete rule.use[0].options.configFile;
60
+ delete rule.use[0].options.babelrc;
61
+
62
+ // this one only applies if hasReactFastRefresh and we are serving
63
+ rule.use[0].options.plugins.push('react-refresh/babel');
64
+
65
+ // we add thread-loader before the babel-loader
66
+ // Spawns multiple processes and split work between them. This makes faster build.
67
+ // @see https://webpack.js.org/loaders/thread-loader/
68
+ rule.use = [{
69
+ loader: 'thread-loader',
70
+ options: {
71
+ workers: -1,
72
+ },
73
+ }].concat(rule.use);
74
+
75
+ break;
76
+ }
77
+ });
78
+
79
+ // Update some of the default WordPress plugins.
80
+ // when importing the base configuration, the plugins are already instantiated so we have to loop through them and modify the options of the instantiated plugins.
81
+ baseConfig.plugins = baseConfig.plugins.map((plugin, i) => {
82
+ const pluginName = plugin.constructor.name;
83
+
84
+ switch(pluginName){
85
+ case 'MiniCssExtractPlugin':
86
+ // we only need the filename
87
+ delete plugin.options.ignoreOrder;
88
+ delete plugin.options.runtime;
89
+ delete plugin.options.experimental;
90
+ delete plugin.options.experimentalUseImportModule;
91
+
92
+ break;
93
+ case 'DefinePlugin':
94
+ Object.entries(plugin.definitions).forEach( ([d, n]) => {
95
+ // these are booleans but come in as strings, so we set them again so they are boolean
96
+ plugin.definitions[d] = ('true' === n)
97
+ })
98
+ break;
99
+ case 'RtlCssPlugin':
100
+ // we disable the RTL CSS generation we don't need them
101
+ plugin = false;
102
+ break;
103
+
104
+ }
105
+
106
+ return plugin;
107
+ }).filter( Boolean );
108
+
109
+ /**
110
+ * we remove the WordPress devServer declaration since we can only have 1 when exporting multiple configurations
111
+ *
112
+ * @see https://github.com/webpack/webpack-cli/issues/2408#issuecomment-793052542
113
+ */
114
+ delete baseConfig.devServer;
115
+
116
+ // Serialize all the data from webpack config and saves it to the output array
117
+ // this is the last chance to make any changes and some changes have to be made here otherwise prettier will fail to format the code correctly.
118
+ const serializeData = ( data ) => {
119
+ Object.entries(data).forEach(([k, v]) => {
120
+ // if the key is not a number, set it as the property key
121
+ // if the key has a dash it has to be wrapped in quotes
122
+ let key = Number.isNaN(Number(k)) ? (k.includes('-') || k.includes('.') ? `'${k}':` : `${k}:`) : '';
123
+
124
+ // if the key is blank then the value is being used in an array.
125
+ let isInArray = ! key.length;
126
+
127
+ switch( typeof v ){
128
+ case 'string':
129
+ v = v.match(/(browserslist:)?.*\/node_modules\/(@wordpress\/)?([\w-]+)\/.*/g) ?
130
+ v.replace(/(browserslist:)?.*\/node_modules\/(@wordpress\/)?([\w-]+)\/.*/g, '$1$2$3') : v;
131
+
132
+ if(v.startsWith('browserslist:')){
133
+ v = "browserslist.findConfig( '.' ) ? browserslist.findConfig( '.' ) : 'browserslist:extends @wordpress/browserslist-config'";
134
+ } else if ( 'path' === k ){
135
+ // since we want it to be dynamic based on where the user is running the webpack command
136
+ v = "resolve( process.cwd(), 'build' )";
137
+ // this is the filename for the MiniCssExtractPlugin which needs to be modified to match the mode
138
+ } else if ( 'filename' === k && v.endsWith('.css') ){
139
+ v = 'isProduction ? \'[name].min.css\' : \'[name].css\''
140
+ } else if( 'mini-css-extract-plugin' === v ){
141
+ v = 'MiniCssExtractPlugin.loader';
142
+ // all of these values can be wrapped in a require.resolve since they are all packages that need to be resolved
143
+ } else if(
144
+ '@wordpress/babel-preset-default' === v ||
145
+ 'react-refresh/babel' === v ||
146
+ [ 'loader', 'use', 'target' ].includes(k)
147
+ ){
148
+ v = `require.resolve( '${v}' )`;
149
+
150
+ } else if ('postcssPlugins' === v ){
151
+ // we don't have to do anything, we just dont need it wrapped in quotes sinces its a constant.
152
+ }else{
153
+ // all other string values should be wrapped in quotes
154
+ v = `'${v}'`;
155
+
156
+ }
157
+ case 'boolean':
158
+ case 'number':
159
+
160
+ // push to output
161
+ output.push(`${ key } ${v}`);
162
+ break;
163
+ case 'object':
164
+ // if the value is a regex
165
+ if( v instanceof RegExp ){
166
+ output.push(`${key}${v}`)
167
+ // if the value is a boolean
168
+ }else if ( v instanceof Boolean ){
169
+ output.push(`${key}${v}`)
170
+
171
+ // if the value is an array
172
+ }else if( v instanceof Array ){
173
+ output.push(`${key}[`)
174
+ serializeData(v);
175
+ output.push(`]`)
176
+
177
+ // if the constructor name is not Object or Array, assume it a class
178
+ // these are usually plugins used in the configurations
179
+ } else if ( ! ['Object', 'Array'].includes( v.constructor.name ) ){
180
+ // the DefinePlugin is part of the webpack package not a standalone plugin
181
+ // so if the plugin is DefinePlugin we prefix the name with webpack.
182
+ let pluginName = 'DefinePlugin' !== v.constructor.name ? v.constructor.name :`webpack.${v.constructor.name}`;
183
+
184
+ let config = {};
185
+
186
+ // each plugin has a different configuration
187
+ switch( pluginName ){
188
+ case 'webpack.DefinePlugin':
189
+ config = v.definitions;
190
+ break;
191
+ case 'TerserPlugin':
192
+ // the terserplugin does not need the minimizer, test option, but is missing the terserOptions
193
+ delete v.options.minimizer;
194
+ delete v.options.test;
195
+
196
+ // add the terserOptions
197
+ v.options.terserOptions = {
198
+ output: {
199
+ comments: /translators:/i,
200
+ },
201
+ compress: {
202
+ passes: 2,
203
+ },
204
+ mangle: {
205
+ reserved: [ '__', '_n', '_nx', '_x' ],
206
+ },
207
+ }
208
+ case 'PhpFilePathsPlugin':
209
+ case 'MiniCssExtractPlugin':
210
+ config = v.options;
211
+ break;
212
+ case 'ReactRefreshPlugin': // this is the ReactRefreshWebpackPlugin
213
+ pluginName = 'ReactRefreshWebpackPlugin';
214
+ break;
215
+ case 'CopyPlugin': // this is the CopyWebpackPlugin
216
+ pluginName = 'CopyWebpackPlugin'
217
+ config = v;
218
+
219
+ }
220
+
221
+ // if the plugin has configurations
222
+ if( Object.keys(config).length ){
223
+
224
+ output.push(`new ${pluginName}({`)
225
+ serializeData( config );
226
+ output.push('})')
227
+ }else{
228
+
229
+ // DependencyExtractionWebpackPlugin
230
+ if( 'DependencyExtractionWebpackPlugin' === pluginName ){
231
+ output.push( `externals && new ${pluginName}()` );
232
+ }else {
233
+ output.push(`new ${pluginName}()`)
234
+ }
235
+ }
236
+
237
+
238
+ }else{
239
+ output.push(`${key} {`)
240
+ serializeData(v);
241
+ output.push(`}`)
242
+ }
243
+ break;
244
+ case 'function':
245
+ // these functions can just be executed
246
+ if( 'entry' === k ){
247
+ output.push(`${key}${JSON.stringify(v('script'))}`);
248
+ }else if( ['name', 'transform'].includes(k) ) {
249
+ output.push(v.toString());
250
+ }else if( ['implementation', 'filter'].includes(k) ){
251
+ output.push( `${key}${v.toString()}` )
252
+ }
253
+ break;
254
+ }
255
+ })
256
+ }
257
+
258
+ // header information for the output file
259
+ let header = `/**\n * This file is autogenerated and should not be modified.
260
+ * Last modified: ${ new Date().toString()}\n */`;
261
+
262
+ // Imports that have to be included
263
+ let imports = Object.entries({
264
+ 'CopyWebpackPlugin': 'copy-webpack-plugin',
265
+ 'webpack': 'webpack',
266
+ 'browserslist': 'browserslist',
267
+ 'MiniCssExtractPlugin': 'mini-css-extract-plugin',
268
+ '{basename, dirname, relative, resolve, sep}': 'path',
269
+ 'TerserPlugin': 'terser-webpack-plugin',
270
+ '{ realpathSync }': 'fs',
271
+ 'DependencyExtractionWebpackPlugin': '@wordpress/dependency-extraction-webpack-plugin',
272
+ 'postcssPlugins': '@wordpress/postcss-plugins-preset',
273
+ '{ createRequire }': 'module',
274
+ '{ getArgVal }': './lib/args.js',
275
+ '{PhpFilePathsPlugin, getBlockJsonScriptFields, getBlockJsonModuleFields, fromProjectRoot, getProjectSourcePath}': './lib/utils.js',
276
+ }).map(([p, r]) => `import ${p} from '${r}';`).filter(Boolean).join('\n');
277
+
278
+ let constants = Object.entries({
279
+ 'isProduction': "getArgVal('mode', 'development') === 'production'",
280
+ 'hasReactFastRefresh': '! isProduction',
281
+ 'require': 'createRequire(import.meta.url)',
282
+ 'externals': 'getArgVal("externals", true)',
283
+ }).map(([p, r]) => `const ${p} = ${r};`).filter(Boolean).join('\n');
284
+
285
+ // start data serialization
286
+ serializeData( baseConfig );
287
+
288
+ // if an object was created we replace the leading comma that is created when joining the output
289
+ let formattedCode = await prettier.format(
290
+ `${header}
291
+ ${imports}\n
292
+ ${constants}\n
293
+ export default {${output.join(',').replace(/([{[(]),/g, '$1')}}`,
294
+ {
295
+ filepath: '.prettierrc.js'
296
+ }
297
+ )
298
+
299
+ // we write this modified webpack config to the root
300
+ fs.writeFileSync( path.resolve('.', 'webpack.wp.config.js'), formattedCode );
301
+
302
+ // we create one utility file that will contain only the necessary @wordpress/scripts/utils functions/classes used
303
+ import { getPhpFilePaths, hasProjectFile, fromProjectRoot, getBlockJsonScriptFields, getBlockJsonModuleFields, getProjectSourcePath } from '@wordpress/scripts/utils/index.js';
304
+ import { getPackagePath } from '@wordpress/scripts/utils/package.js';
305
+
306
+ // we read the phpFilePathsPlugin file and replace the require statements into import statements since we want to write it as an ES module
307
+ let phpFilePathsPlugin = fs.readFileSync(
308
+ path.resolve('.', 'node_modules', '@wordpress', 'scripts', 'plugins', 'php-file-paths-plugin', 'index.js')
309
+ )
310
+ .toString()
311
+ .replace("const { validate } = require( 'schema-utils' );",
312
+ [
313
+ "import { validate } from 'schema-utils';",
314
+ "import path, { join, sep, dirname } from 'path';",
315
+ "import { existsSync, realpathSync, readFileSync } from 'fs';",
316
+ "import { readPackageUp } from 'read-package-up';",
317
+ "import FastGlob from 'fast-glob';\n",
318
+ ].join('\n')
319
+ )
320
+ .replace("const { getPhpFilePaths } = require( '../../utils' );", [
321
+ 'const { sync: glob } = FastGlob;',
322
+ 'const { warn } = console;',
323
+ 'const { packageJson, path: pkgPath } = await readPackageUp({cwd: realpathSync( process.cwd() ) });',
324
+ "const moduleFields = new Set( [ 'viewScriptModule', 'viewModule' ] );",
325
+ "const scriptFields = new Set( [ 'viewScript', 'script', 'editorScript' ] );",
326
+ `const hasProjectFile = ${hasProjectFile.toString()}`,
327
+ `const fromProjectRoot = ${fromProjectRoot.toString()}`,
328
+ `const getPackagePath = ${getPackagePath.toString()}`,
329
+ getProjectSourcePath.toString(),
330
+ getBlockJsonScriptFields.toString(),
331
+ getBlockJsonModuleFields.toString(),
332
+ getPhpFilePaths.toString(),
333
+ ].join('\n\n'))
334
+ .replace("module.exports = PhpFilePathsPlugin", "export { PhpFilePathsPlugin, getBlockJsonScriptFields, getBlockJsonModuleFields, fromProjectRoot, getProjectSourcePath }")
335
+
336
+ // we write this modified plugin to the lib/utils.js folder
337
+ fs.writeFileSync( path.resolve('.', 'lib', 'utils.js' ), phpFilePathsPlugin );
338
+
339
+ export default baseConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caweb/webpack",
3
- "version": "1.6.8",
3
+ "version": "1.7.0",
4
4
  "description": "CAWebPublishing Webpack Configuration",
5
5
  "main": "webpack.config.js",
6
6
  "files": [
@@ -8,13 +8,15 @@
8
8
  "lib",
9
9
  "tests",
10
10
  "webpack.config.js",
11
+ "webpack.wp.config.js",
11
12
  "tsconfig.json"
12
13
  ],
13
14
  "type": "module",
14
15
  "scripts": {
15
16
  "webpack": "webpack",
17
+ "gen:wp": "node lib/webpack.wp.configurator.js",
16
18
  "test:config": "webpack configtest",
17
- "test:serve": "set NODE_OPTIONS='--template ./patterns/default.html --search-template ./patterns/search.html' && webpack serve --config ./webpack.config.js ./tests/webpack.tests.js --merge",
19
+ "test:serve": "cross-env CAWEB_NODE_OPTIONS='--template ./patterns/default.html --search-template ./patterns/search.html' webpack serve --config ./webpack.config.js ./tests/webpack.tests.js --merge",
18
20
  "test": "echo \\\"Error: run tests from root\\\" && exit 0"
19
21
  },
20
22
  "repository": {
@@ -36,15 +38,27 @@
36
38
  },
37
39
  "homepage": "https://github.com/CAWebPublishing/webpack#readme",
38
40
  "dependencies": {
39
- "@babel/plugin-proposal-class-properties": "^7.18.6",
40
- "@wordpress/scripts": "^31.4.0",
41
+ "@babel/plugin-transform-class-properties": "^7.28.6",
42
+ "@wordpress/babel-preset-default": "^8.40.0",
43
+ "@wordpress/dependency-extraction-webpack-plugin": "^6.40.0",
44
+ "@wordpress/postcss-plugins-preset": "^5.40.0",
45
+ "babel-loader": "^10.0.0",
46
+ "copy-webpack-plugin": "^13.0.1",
47
+ "css-loader": "^7.1.4",
41
48
  "css-minimizer-webpack-plugin": "^7.0.4",
42
49
  "deepmerge": "^4.3.1",
50
+ "fast-glob": "^3.3.3",
43
51
  "handlebars": "^4.7.8",
44
52
  "handlebars-loader": "^1.7.3",
45
53
  "html-format": "^1.1.7",
46
54
  "html-webpack-plugin": "^5.6.6",
47
- "html-webpack-skip-assets-plugin": "^1.0.4",
55
+ "mini-css-extract-plugin": "^2.10.0",
56
+ "postcss-loader": "^8.2.1",
57
+ "react-refresh": "^0.18.0",
58
+ "read-package-up": "^12.0.0",
59
+ "sass": "^1.97.3",
60
+ "sass-loader": "^16.0.7",
61
+ "source-map-loader": "^5.0.0",
48
62
  "thread-loader": "^4.0.4",
49
63
  "ts-loader": "^9.5.4",
50
64
  "webpack": "^5.105.2",
@@ -52,5 +66,14 @@
52
66
  "webpack-dev-server": "^5.2.3",
53
67
  "webpack-merge": "^6.0.1",
54
68
  "webpack-remove-empty-scripts": "^1.1.1"
69
+ },
70
+ "devDependencies": {
71
+ "@wordpress/scripts": "^31.5.0",
72
+ "cross-env": "^10.1.0",
73
+ "prettier": "^3.8.1"
74
+ },
75
+ "overrides": {
76
+ "minimatch": "^10.2.4",
77
+ "webpack-dev-server": "^5.2.3"
55
78
  }
56
79
  }
package/webpack.config.js CHANGED
@@ -9,24 +9,16 @@
9
9
  /**
10
10
  * External Dependencies
11
11
  */
12
- // import baseConfig from '@wordpress/scripts/config/webpack.config.js';
13
- import baseConfig from './lib/webpack.wp.config.js';
12
+ import baseConfig from './webpack.wp.config.js';
13
+
14
14
  import fs from 'fs';
15
15
  import path from 'path';
16
16
  import { fileURLToPath } from 'url';
17
17
 
18
18
  // webpack plugins
19
19
  import { merge } from 'webpack-merge';
20
- // import MiniCSSExtractPlugin from 'mini-css-extract-plugin';
21
20
  import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
22
21
  import RemoveEmptyScriptsPlugin from 'webpack-remove-empty-scripts';
23
- import {HtmlWebpackSkipAssetsPlugin} from 'html-webpack-skip-assets-plugin';
24
- // import RtlCssPlugin from 'rtlcss-webpack-plugin';
25
- // import {HtmlWebpackLinkTypePlugin} from 'html-webpack-link-type-plugin';
26
-
27
- // import JSHintPlugin from '@caweb/jshint-webpack-plugin';
28
- // import CSSAuditPlugin from '@caweb/css-audit-webpack-plugin';
29
- // import A11yPlugin from '@caweb/a11y-webpack-plugin';
30
22
 
31
23
  /**
32
24
  * Internal dependencies
@@ -49,7 +41,7 @@ let caweb = fs.existsSync( path.join(appPath, 'caweb.json') ) ?
49
41
  JSON.parse(fs.readFileSync(path.join(appPath, 'caweb.json')))
50
42
  : {};
51
43
 
52
- let mode = getArgVal('--mode') ? getArgVal('--mode') : baseConfig.mode;
44
+ let mode = getArgVal('mode', 'development');
53
45
  let isProduction = mode === 'production';
54
46
  let devServer = false;
55
47
 
@@ -102,7 +94,8 @@ let webpackConfig = {
102
94
  filename: isProduction ? '[name].min.js' : '[name].js',
103
95
  chunkFilename: isProduction ? '[name].min.js?v=[chunkhash]' : '[name].js?v=[chunkhash]',
104
96
  pathinfo: false,
105
- clean: isProduction
97
+ clean: isProduction,
98
+ path: path.resolve( process.cwd(), 'build' )
106
99
  },
107
100
 
108
101
  /**
@@ -164,7 +157,6 @@ let webpackConfig = {
164
157
  * Devtool Configuration
165
158
  * WordPress by default uses 'source-map' for devtool which affects build and rebuild speed.
166
159
  * For development we switch to 'eval' which is much faster.
167
- * For production we turn off devtool completely.
168
160
  * @see https://webpack.js.org/configuration/devtool/#devtool
169
161
  */
170
162
  devtool: isProduction ? 'source-map' : 'eval',
@@ -221,14 +213,14 @@ let webpackConfig = {
221
213
  new RemoveEmptyScriptsPlugin(),
222
214
 
223
215
  // certain files can be skipped when serving
224
- new HtmlWebpackSkipAssetsPlugin({
225
- skipAssets: [
226
- /.*-rtl.css/, // we skip the Right-to-Left Styles
227
- /css-audit.*/, // we skip the CSSAudit Files
228
- /a11y.*/, // we skip the A11y Files
229
- /jshint.*/, // we skip the JSHint Files
230
- ]
231
- }),
216
+ // new HtmlWebpackSkipAssetsPlugin({
217
+ // skipAssets: [
218
+ // /.*-rtl.css/, // we skip the Right-to-Left Styles
219
+ // /css-audit.*/, // we skip the CSSAudit Files
220
+ // /a11y.*/, // we skip the A11y Files
221
+ // /jshint.*/, // we skip the JSHint Files
222
+ // ]
223
+ // }),
232
224
  ],
233
225
 
234
226
  /**
@@ -0,0 +1,295 @@
1
+ /**
2
+ * This file is autogenerated and should not be modified.
3
+ * Last modified: Thu Feb 26 2026 08:36:43 GMT-0800 (Pacific Standard Time)
4
+ */
5
+ import CopyWebpackPlugin from "copy-webpack-plugin";
6
+ import webpack from "webpack";
7
+ import browserslist from "browserslist";
8
+ import MiniCssExtractPlugin from "mini-css-extract-plugin";
9
+ import { basename, dirname, relative, resolve, sep } from "path";
10
+ import TerserPlugin from "terser-webpack-plugin";
11
+ import { realpathSync } from "fs";
12
+ import DependencyExtractionWebpackPlugin from "@wordpress/dependency-extraction-webpack-plugin";
13
+ import postcssPlugins from "@wordpress/postcss-plugins-preset";
14
+ import { createRequire } from "module";
15
+ import { getArgVal } from "./lib/args.js";
16
+ import {
17
+ PhpFilePathsPlugin,
18
+ getBlockJsonScriptFields,
19
+ getBlockJsonModuleFields,
20
+ fromProjectRoot,
21
+ getProjectSourcePath,
22
+ } from "./lib/utils.js";
23
+
24
+ const isProduction = getArgVal("mode", "development") === "production";
25
+ const hasReactFastRefresh = !isProduction;
26
+ const require = createRequire(import.meta.url);
27
+ const externals = getArgVal("externals", true);
28
+
29
+ export default {
30
+ mode: "development",
31
+ target: browserslist.findConfig(".")
32
+ ? browserslist.findConfig(".")
33
+ : "browserslist:extends @wordpress/browserslist-config",
34
+ output: {
35
+ filename: "[name].js",
36
+ chunkFilename: "[name].js?ver=[chunkhash]",
37
+ path: resolve(process.cwd(), "build"),
38
+ clean: { keep: /^(fonts|images)\// },
39
+ },
40
+ resolve: {
41
+ alias: { "lodash-es": "lodash" },
42
+ extensions: [".jsx", ".ts", ".tsx", "..."],
43
+ },
44
+ optimization: {
45
+ concatenateModules: false,
46
+ runtimeChunk: false,
47
+ splitChunks: {
48
+ cacheGroups: {
49
+ style: {
50
+ type: "css/mini-extract",
51
+ test: /[\\/]style(\.module)?\.(pc|sc|sa|c)ss$/,
52
+ chunks: "all",
53
+ enforce: true,
54
+ name(_, chunks, cacheGroupKey) {
55
+ const chunkName = chunks[0].name;
56
+ return `${dirname(
57
+ chunkName,
58
+ )}/${cacheGroupKey}-${basename(chunkName)}`;
59
+ },
60
+ },
61
+ default: false,
62
+ },
63
+ },
64
+ minimizer: [
65
+ new TerserPlugin({
66
+ extractComments: false,
67
+ parallel: true,
68
+ terserOptions: {
69
+ output: { comments: /translators:/i },
70
+ compress: { passes: 2 },
71
+ mangle: { reserved: ["__", "_n", "_nx", "_x"] },
72
+ },
73
+ }),
74
+ ],
75
+ },
76
+ module: {
77
+ rules: [
78
+ {
79
+ test: /\.(j|t)sx?$/,
80
+ exclude: [/node_modules/],
81
+ use: require.resolve("source-map-loader"),
82
+ enforce: "pre",
83
+ },
84
+ {
85
+ test: /\.m?(j|t)sx?$/,
86
+ exclude: /node_modules/,
87
+ use: [
88
+ {
89
+ loader: require.resolve("thread-loader"),
90
+ options: { workers: -1 },
91
+ },
92
+ {
93
+ loader: require.resolve("babel-loader"),
94
+ options: {
95
+ presets: [require.resolve("@wordpress/babel-preset-default")],
96
+ plugins: [
97
+ "@babel/plugin-transform-class-properties",
98
+ require.resolve("react-refresh/babel"),
99
+ ],
100
+ },
101
+ },
102
+ ],
103
+ },
104
+ {
105
+ test: /\.css$/,
106
+ use: [
107
+ { loader: MiniCssExtractPlugin.loader },
108
+ {
109
+ loader: require.resolve("css-loader"),
110
+ options: {
111
+ importLoaders: 1,
112
+ sourceMap: true,
113
+ modules: { auto: true },
114
+ },
115
+ },
116
+ {
117
+ loader: require.resolve("postcss-loader"),
118
+ options: {
119
+ postcssOptions: {
120
+ ident: "postcss",
121
+ sourceMap: true,
122
+ plugins: postcssPlugins,
123
+ },
124
+ },
125
+ },
126
+ ],
127
+ },
128
+ {
129
+ test: /\.pcss$/,
130
+ use: [
131
+ { loader: MiniCssExtractPlugin.loader },
132
+ {
133
+ loader: require.resolve("css-loader"),
134
+ options: {
135
+ importLoaders: 1,
136
+ sourceMap: true,
137
+ modules: { auto: true },
138
+ },
139
+ },
140
+ {
141
+ loader: require.resolve("postcss-loader"),
142
+ options: {
143
+ postcssOptions: {
144
+ ident: "postcss",
145
+ sourceMap: true,
146
+ plugins: postcssPlugins,
147
+ },
148
+ },
149
+ },
150
+ ],
151
+ },
152
+ {
153
+ test: /\.(sc|sa)ss$/,
154
+ use: [
155
+ { loader: MiniCssExtractPlugin.loader },
156
+ {
157
+ loader: require.resolve("css-loader"),
158
+ options: {
159
+ importLoaders: 1,
160
+ sourceMap: true,
161
+ modules: { auto: true },
162
+ },
163
+ },
164
+ {
165
+ loader: require.resolve("postcss-loader"),
166
+ options: {
167
+ postcssOptions: {
168
+ ident: "postcss",
169
+ sourceMap: true,
170
+ plugins: postcssPlugins,
171
+ },
172
+ },
173
+ },
174
+ {
175
+ loader: require.resolve("sass-loader"),
176
+ options: {
177
+ sourceMap: true,
178
+ sassOptions: {
179
+ silenceDeprecations: [
180
+ "global-builtin",
181
+ "import",
182
+ "color-functions",
183
+ "if-function",
184
+ ],
185
+ },
186
+ },
187
+ },
188
+ ],
189
+ },
190
+ {
191
+ test: /\.svg$/,
192
+ issuer: /\.(j|t)sx?$/,
193
+ use: ["@svgr/webpack", "url-loader"],
194
+ type: "javascript/auto",
195
+ },
196
+ {
197
+ test: /\.svg$/,
198
+ type: "asset/resource",
199
+ generator: { filename: "fonts/[name][ext]" },
200
+ },
201
+ {
202
+ test: /\.(bmp|png|jpe?g|gif|webp)$/i,
203
+ type: "asset/resource",
204
+ generator: { filename: "images/[name][ext]" },
205
+ },
206
+ {
207
+ test: /\.(woff|woff2|eot|ttf|otf)$/i,
208
+ type: "asset/resource",
209
+ generator: { filename: "fonts/[name][ext]" },
210
+ },
211
+ ],
212
+ },
213
+ stats: { children: false },
214
+ devtool: "source-map",
215
+ entry: {},
216
+ plugins: [
217
+ new webpack.DefinePlugin({
218
+ "globalThis.SCRIPT_DEBUG": true,
219
+ SCRIPT_DEBUG: true,
220
+ }),
221
+ new PhpFilePathsPlugin({ context: "src", props: ["render", "variations"] }),
222
+ new CopyWebpackPlugin({
223
+ patterns: [
224
+ {
225
+ from: "**/block.json",
226
+ context: "src",
227
+ noErrorOnMissing: true,
228
+ transform(content, absoluteFrom) {
229
+ const convertExtension = (path) => {
230
+ return path.replace(/\.m?(j|t)sx?$/, ".js");
231
+ };
232
+
233
+ if (basename(absoluteFrom) === "block.json") {
234
+ const blockJson = JSON.parse(content.toString());
235
+
236
+ [
237
+ getBlockJsonScriptFields(blockJson),
238
+ getBlockJsonModuleFields(blockJson),
239
+ ].forEach((fields) => {
240
+ if (fields) {
241
+ for (const [key, value] of Object.entries(fields)) {
242
+ if (Array.isArray(value)) {
243
+ blockJson[key] = value.map(convertExtension);
244
+ } else if (typeof value === "string") {
245
+ blockJson[key] = convertExtension(value);
246
+ }
247
+ }
248
+ }
249
+ });
250
+
251
+ if (hasReactFastRefresh) {
252
+ // Prepends the file reference to the shared runtime chunk to every script type defined for the block.
253
+ const runtimePath = relative(
254
+ dirname(absoluteFrom),
255
+ fromProjectRoot(getProjectSourcePath() + sep + "runtime.js"),
256
+ );
257
+ const fields = getBlockJsonScriptFields(blockJson);
258
+ for (const [fieldName] of Object.entries(fields)) {
259
+ blockJson[fieldName] = [
260
+ `file:${runtimePath}`,
261
+ ...(Array.isArray(blockJson[fieldName])
262
+ ? blockJson[fieldName]
263
+ : [blockJson[fieldName]]),
264
+ ];
265
+ }
266
+ }
267
+
268
+ return JSON.stringify(blockJson, null, 2);
269
+ }
270
+
271
+ return content;
272
+ },
273
+ },
274
+ {
275
+ from: "**/*.php",
276
+ context: "src",
277
+ noErrorOnMissing: true,
278
+ filter: (filepath) => {
279
+ return (
280
+ process.env.WP_COPY_PHP_FILES_TO_DIST ||
281
+ PhpFilePathsPlugin.paths.includes(
282
+ realpathSync(filepath).replace(/\\/g, "/"),
283
+ )
284
+ );
285
+ },
286
+ },
287
+ ],
288
+ options: {},
289
+ }),
290
+ new MiniCssExtractPlugin({
291
+ filename: isProduction ? "[name].min.css" : "[name].css",
292
+ }),
293
+ externals && new DependencyExtractionWebpackPlugin(),
294
+ ],
295
+ };
@@ -1,99 +0,0 @@
1
- /**
2
- * Modified WordPress Scripts Webpack Configuration
3
- *
4
- * @package CAWebPublishing
5
- * @link https://webpack.js.org/configuration/
6
- */
7
-
8
- /**
9
- * External Dependencies
10
- */
11
- import baseConfig from '@wordpress/scripts/config/webpack.config.js';
12
- import { getArgVal } from './args.js';
13
-
14
- /**
15
- * Internal dependencies
16
- */
17
-
18
- // Wordpress ignores the webpack --mode flag
19
- // if the flag is passed we use that mode
20
- // otherwise use whatever Wordpress is using
21
- let mode = getArgVal('mode') ? getArgVal('mode') : baseConfig.mode;
22
- let isProduction = mode === 'production';
23
-
24
- // Update some of the default WordPress webpack rules.
25
- baseConfig.module.rules.forEach((rule, i) => {
26
- const r = new RegExp(rule.test).toString();
27
-
28
- switch(r){
29
- // WordPress adds a hash to asset file names we remove that hash.
30
- case new RegExp(/\.(bmp|png|jpe?g|gif|webp)$/i).toString():
31
- rule.generator.filename = 'images/[name][ext]';
32
- break;
33
- case new RegExp(/\.(woff|woff2|eot|ttf|otf)$/i).toString():
34
- rule.generator.filename = 'fonts/[name][ext]';
35
- break;
36
- case new RegExp(/\.svg$/).toString():
37
- // we don't want SVG to be asset/inline otherwise the resource may not be available.
38
- // the asset should be an asset/resource we move them to the fonts folder.
39
- if( 'asset/inline' === rule.type ){
40
- rule.type = 'asset/resource';
41
- rule.generator = { filename: 'fonts/[name][ext]' };
42
-
43
- delete rule.issuer;
44
- }
45
- break;
46
- // silence deprecation warnings from sass
47
- case new RegExp(/\.(sc|sa)ss$/).toString():
48
- rule.use[rule.use.length-1].options.sassOptions = {
49
- silenceDeprecations: ['global-builtin', 'import', 'color-functions', 'if-function']
50
- };
51
- break;
52
- case new RegExp(/\.m?(j|t)sx?$/).toString():
53
- // @since @wordpress/scripts@30.20.0 babel-loader is used for js and ts files
54
-
55
- // Added the Transform class properties syntax plugin to the babel-loader.
56
- // @see https://babeljs.io/docs/en/babel-plugin-proposal-class-properties
57
- rule.use[0].options.plugins.push('@babel/plugin-proposal-class-properties');
58
-
59
- // we add thread-loader before the babel-loader
60
- // Spawns multiple processes and split work between them. This makes faster build.
61
- // @see https://webpack.js.org/loaders/thread-loader/
62
- rule.use = [{
63
- loader: 'thread-loader',
64
- options: {
65
- workers: -1,
66
- },
67
- }].concat(rule.use);
68
-
69
- break;
70
- }
71
- });
72
-
73
- // Update some of the default WordPress plugins.
74
- baseConfig.plugins = baseConfig.plugins.map((plugin, i) => {
75
- const pluginName = plugin.constructor.name;
76
-
77
- switch(pluginName){
78
- case 'MiniCssExtractPlugin':
79
- // we change the default naming of the CSS files
80
- plugin.options.filename = isProduction ? '[name].min.css' : '[name].css';
81
- break;
82
- case 'RtlCssPlugin':
83
- // we disable the RTL CSS generation
84
- plugin = false;
85
- break;
86
-
87
- }
88
-
89
- return plugin;
90
- }).filter( Boolean );
91
-
92
- /**
93
- * we remove the WordPress devServer declaration since we can only have 1 when exporting multiple configurations
94
- *
95
- * @see https://github.com/webpack/webpack-cli/issues/2408#issuecomment-793052542
96
- */
97
- delete baseConfig.devServer;
98
-
99
- export default baseConfig;