From ca09082f12e735cb5752ca563e2c469925006495 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Thu, 8 Jul 2021 16:18:53 +0200 Subject: [PATCH 1/2] Build: Split config into two parts - blocks and packages --- tools/webpack/blocks.js | 172 +++++++++++++++++++++++ tools/webpack/packages.js | 284 ++++---------------------------------- tools/webpack/shared.js | 109 +++++++++++++++ webpack.config.js | 3 +- 4 files changed, 309 insertions(+), 259 deletions(-) create mode 100644 tools/webpack/blocks.js create mode 100644 tools/webpack/shared.js diff --git a/tools/webpack/blocks.js b/tools/webpack/blocks.js new file mode 100644 index 00000000000000..d96f334dcb5a88 --- /dev/null +++ b/tools/webpack/blocks.js @@ -0,0 +1,172 @@ +/** + * External dependencies + */ +const CopyWebpackPlugin = require( 'copy-webpack-plugin' ); +const { escapeRegExp } = require( 'lodash' ); +const { join, sep } = require( 'path' ); +const fastGlob = require( 'fast-glob' ); + +/** + * Internal dependencies + */ +const { + devtool, + mode, + moduleConfig, + optimization, + plugins, + stylesTransform, + watchOptions, +} = require( './shared' ); + +/* + * Matches a block's name in paths in the form + * build-module//view.js + */ +const blockNameRegex = new RegExp( /(?<=build-module\/).*(?=(\/view))/g ); + +const createEntrypoints = () => { + /* + * Returns an array of paths to view.js files within the `@wordpress/block-library` package. + * These paths can be matched by the regex `blockNameRegex` in order to extract + * the block's name. + * + * Returns an empty array if no files were found. + */ + const blockViewScriptPaths = fastGlob.sync( + './packages/block-library/build-module/**/view.js' + ); + + /* + * Go through the paths found above, in order to define webpack entry points for + * each block's view.js file. + */ + return blockViewScriptPaths.reduce( ( entries, scriptPath ) => { + const [ blockName ] = scriptPath.match( blockNameRegex ); + + return { + ...entries, + [ 'blocks/' + blockName ]: scriptPath, + }; + }, {} ); +}; + +module.exports = { + name: 'blocks', + optimization, + mode, + entry: createEntrypoints(), + output: { + devtoolNamespace: 'wp', + filename: './build/block-library/[name]/view.min.js', + path: join( __dirname, '..', '..' ), + }, + module: moduleConfig, + plugins: [ + ...plugins, + new CopyWebpackPlugin( { + patterns: [].concat( + [ + 'style', + 'style-rtl', + 'editor', + 'editor-rtl', + 'theme', + 'theme-rtl', + ].map( ( filename ) => ( { + from: `./packages/block-library/build-style/*/${ filename }.css`, + to( { absoluteFilename } ) { + const [ , dirname ] = absoluteFilename.match( + new RegExp( + `([\\w-]+)${ escapeRegExp( + sep + ) }${ filename }\\.css$` + ) + ); + + return join( + 'build/block-library/blocks', + dirname, + filename + '.css' + ); + }, + transform: stylesTransform, + } ) ), + Object.entries( { + './packages/block-library/src/': + 'build/block-library/blocks/', + './packages/edit-widgets/src/blocks/': + 'build/edit-widgets/blocks/', + './packages/widgets/src/blocks/': 'build/widgets/blocks/', + } ).flatMap( ( [ from, to ] ) => [ + { + from: `${ from }/**/index.php`, + to( { absoluteFilename } ) { + const [ , dirname ] = absoluteFilename.match( + new RegExp( + `([\\w-]+)${ escapeRegExp( + sep + ) }index\\.php$` + ) + ); + + return join( to, `${ dirname }.php` ); + }, + transform: ( content ) => { + content = content.toString(); + + // Within content, search for any function definitions. For + // each, replace every other reference to it in the file. + return ( + content + .match( /^function [^\(]+/gm ) + .reduce( ( result, functionName ) => { + // Trim leading "function " prefix from match. + functionName = functionName.slice( 9 ); + + // Prepend the Gutenberg prefix, substituting any + // other core prefix (e.g. "wp_"). + return result.replace( + new RegExp( functionName, 'g' ), + ( match ) => + 'gutenberg_' + + match.replace( /^wp_/, '' ) + ); + }, content ) + // The core blocks override procedure takes place in + // the init action default priority to ensure that core + // blocks would have been registered already. Since the + // blocks implementations occur at the default priority + // and due to WordPress hooks behavior not considering + // mutations to the same priority during another's + // callback, the Gutenberg build blocks are modified + // to occur at a later priority. + .replace( + /(add_action\(\s*'init',\s*'gutenberg_register_block_[^']+'(?!,))/, + '$1, 20' + ) + ); + }, + noErrorOnMissing: true, + }, + { + from: `${ from }/*/block.json`, + to( { absoluteFilename } ) { + const [ , dirname ] = absoluteFilename.match( + new RegExp( + `([\\w-]+)${ escapeRegExp( + sep + ) }block\\.json$` + ) + ); + + return join( to, dirname, 'block.json' ); + }, + }, + ] ) + ), + } ), + ].filter( Boolean ), + watchOptions, + devtool, +}; diff --git a/tools/webpack/packages.js b/tools/webpack/packages.js index 7cf0a8d8b0512a..3647c8c429a870 100644 --- a/tools/webpack/packages.js +++ b/tools/webpack/packages.js @@ -1,22 +1,14 @@ /** * External dependencies */ -const { BundleAnalyzerPlugin } = require( 'webpack-bundle-analyzer' ); -const { DefinePlugin } = require( 'webpack' ); const CopyWebpackPlugin = require( 'copy-webpack-plugin' ); -const TerserPlugin = require( 'terser-webpack-plugin' ); -const postcss = require( 'postcss' ); -const { escapeRegExp, compact } = require( 'lodash' ); -const { join, sep } = require( 'path' ); -const fastGlob = require( 'fast-glob' ); +const { join } = require( 'path' ); /** * WordPress dependencies */ const CustomTemplatedPathPlugin = require( '@wordpress/custom-templated-path-webpack-plugin' ); const LibraryExportDefaultPlugin = require( '@wordpress/library-export-default-webpack-plugin' ); -const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' ); -const ReadableJsAssetsWebpackPlugin = require( '@wordpress/readable-js-assets-webpack-plugin' ); const { camelCaseDash, } = require( '@wordpress/dependency-extraction-webpack-plugin/lib/util' ); @@ -25,11 +17,15 @@ const { * Internal dependencies */ const { dependencies } = require( '../../package' ); - const { - NODE_ENV: mode = 'development', - WP_DEVTOOL: devtool = mode === 'production' ? false : 'source-map', -} = process.env; + devtool, + mode, + moduleConfig, + optimization, + plugins, + stylesTransform, + watchOptions, +} = require( './shared' ); const WORDPRESS_NAMESPACE = '@wordpress/'; const BUNDLED_PACKAGES = [ '@wordpress/icons', '@wordpress/interface' ]; @@ -43,149 +39,26 @@ const gutenbergPackages = Object.keys( dependencies ) ) .map( ( packageName ) => packageName.replace( WORDPRESS_NAMESPACE, '' ) ); -const stylesTransform = ( content ) => { - if ( mode === 'production' ) { - return postcss( [ - require( 'cssnano' )( { - preset: [ - 'default', - { - discardComments: { - removeAll: true, - }, - }, - ], - } ), - ] ) - .process( content, { - from: 'src/app.css', - to: 'dest/app.css', - } ) - .then( ( result ) => result.css ); - } - return content; -}; - -/* - * Matches a block's name in paths in the form - * build-module//view.js - */ -const blockNameRegex = new RegExp( /(?<=build-module\/).*(?=(\/view))/g ); - -const createEntrypoints = () => { - /* - * Returns an array of paths to view.js files within the `@wordpress/block-library` package. - * These paths can be matched by the regex `blockNameRegex` in order to extract - * the block's name. - * - * Returns an empty array if no files were found. - */ - const blockViewScriptPaths = fastGlob.sync( - './packages/block-library/build-module/**/view.js' - ); - - /* - * Go through the paths found above, in order to define webpack entry points for - * each block's view.js file. - */ - const blockViewScriptEntries = blockViewScriptPaths.reduce( - ( entries, scriptPath ) => { - const [ blockName ] = scriptPath.match( blockNameRegex ); - - return { - ...entries, - [ 'blocks/' + blockName ]: scriptPath, - }; - }, - {} - ); - - const packageEntries = gutenbergPackages.reduce( ( memo, packageName ) => { +module.exports = { + name: 'packages', + optimization, + mode, + entry: gutenbergPackages.reduce( ( memo, packageName ) => { return { ...memo, [ packageName ]: `./packages/${ packageName }`, }; - }, {} ); - - return { ...packageEntries, ...blockViewScriptEntries }; -}; - -module.exports = { - optimization: { - // Only concatenate modules in production, when not analyzing bundles. - concatenateModules: - mode === 'production' && ! process.env.WP_BUNDLE_ANALYZER, - minimizer: [ - new TerserPlugin( { - cache: true, - parallel: true, - sourceMap: mode !== 'production', - terserOptions: { - output: { - comments: /translators:/i, - }, - compress: { - passes: 2, - }, - mangle: { - reserved: [ '__', '_n', '_nx', '_x' ], - }, - }, - extractComments: false, - } ), - ], - }, - mode, - entry: createEntrypoints(), + }, {} ), output: { devtoolNamespace: 'wp', - filename: ( pathData ) => { - const { chunk } = pathData; - const { entryModule } = chunk; - const { rawRequest, rootModule } = entryModule; - - // When processing ESM files, the requested path - // is defined in `entryModule.rootModule.rawRequest`, instead of - // being present in `entryModule.rawRequest`. - // In the context of frontend view files, they would be processed - // as ESM if they use `import` or `export` within it. - const request = rootModule?.rawRequest || rawRequest; - - if ( request.includes( '/view.js' ) ) { - return `./build/block-library/[name]/view.min.js`; - } - - return `./build/[name]/index.min.js`; - }, + filename: './build/[name]/index.min.js', path: join( __dirname, '..', '..' ), library: [ 'wp', '[camelName]' ], libraryTarget: 'window', }, - module: { - rules: compact( [ - mode !== 'production' && { - test: /\.js$/, - use: require.resolve( 'source-map-loader' ), - enforce: 'pre', - }, - ] ), - }, + module: moduleConfig, plugins: [ - // The WP_BUNDLE_ANALYZER global variable enables a utility that represents bundle - // content as a convenient interactive zoomable treemap. - process.env.WP_BUNDLE_ANALYZER && new BundleAnalyzerPlugin(), - new DefinePlugin( { - // Inject the `GUTENBERG_PHASE` global, used for feature flagging. - 'process.env.GUTENBERG_PHASE': JSON.stringify( - parseInt( - process.env.npm_package_config_GUTENBERG_PHASE, - 10 - ) || 1 - ), - 'process.env.FORCE_REDUCED_MOTION': JSON.stringify( - process.env.FORCE_REDUCED_MOTION - ), - } ), + ...plugins, new CustomTemplatedPathPlugin( { camelName( path, data ) { return camelCaseDash( data.chunk.name ); @@ -202,120 +75,15 @@ module.exports = { 'warning', ] ), new CopyWebpackPlugin( { - patterns: [].concat( - gutenbergPackages.map( ( packageName ) => ( { - from: `./packages/${ packageName }/build-style/*.css`, - to: `./build/${ packageName }/`, - flatten: true, - transform: stylesTransform, - noErrorOnMissing: true, - } ) ), - [ - 'style', - 'style-rtl', - 'editor', - 'editor-rtl', - 'theme', - 'theme-rtl', - ].map( ( filename ) => ( { - from: `./packages/block-library/build-style/*/${ filename }.css`, - to( { absoluteFilename } ) { - const [ , dirname ] = absoluteFilename.match( - new RegExp( - `([\\w-]+)${ escapeRegExp( - sep - ) }${ filename }\\.css$` - ) - ); - - return join( - 'build/block-library/blocks', - dirname, - filename + '.css' - ); - }, - transform: stylesTransform, - } ) ), - Object.entries( { - './packages/block-library/src/': - 'build/block-library/blocks/', - './packages/edit-widgets/src/blocks/': - 'build/edit-widgets/blocks/', - './packages/widgets/src/blocks/': 'build/widgets/blocks/', - } ).flatMap( ( [ from, to ] ) => [ - { - from: `${ from }/**/index.php`, - to( { absoluteFilename } ) { - const [ , dirname ] = absoluteFilename.match( - new RegExp( - `([\\w-]+)${ escapeRegExp( - sep - ) }index\\.php$` - ) - ); - - return join( to, `${ dirname }.php` ); - }, - transform: ( content ) => { - content = content.toString(); - - // Within content, search for any function definitions. For - // each, replace every other reference to it in the file. - return ( - content - .match( /^function [^\(]+/gm ) - .reduce( ( result, functionName ) => { - // Trim leading "function " prefix from match. - functionName = functionName.slice( 9 ); - - // Prepend the Gutenberg prefix, substituting any - // other core prefix (e.g. "wp_"). - return result.replace( - new RegExp( functionName, 'g' ), - ( match ) => - 'gutenberg_' + - match.replace( /^wp_/, '' ) - ); - }, content ) - // The core blocks override procedure takes place in - // the init action default priority to ensure that core - // blocks would have been registered already. Since the - // blocks implementations occur at the default priority - // and due to WordPress hooks behavior not considering - // mutations to the same priority during another's - // callback, the Gutenberg build blocks are modified - // to occur at a later priority. - .replace( - /(add_action\(\s*'init',\s*'gutenberg_register_block_[^']+'(?!,))/, - '$1, 20' - ) - ); - }, - noErrorOnMissing: true, - }, - { - from: `${ from }/*/block.json`, - to( { absoluteFilename } ) { - const [ , dirname ] = absoluteFilename.match( - new RegExp( - `([\\w-]+)${ escapeRegExp( - sep - ) }block\\.json$` - ) - ); - - return join( to, dirname, 'block.json' ); - }, - }, - ] ) - ), + patterns: gutenbergPackages.map( ( packageName ) => ( { + from: `./packages/${ packageName }/build-style/*.css`, + to: `./build/${ packageName }/`, + flatten: true, + transform: stylesTransform, + noErrorOnMissing: true, + } ) ), } ), - new DependencyExtractionWebpackPlugin( { injectPolyfill: true } ), - mode === 'production' && new ReadableJsAssetsWebpackPlugin(), ].filter( Boolean ), - watchOptions: { - ignored: [ '**/node_modules', '**/packages/*/src' ], - aggregateTimeout: 500, - }, + watchOptions, devtool, }; diff --git a/tools/webpack/shared.js b/tools/webpack/shared.js new file mode 100644 index 00000000000000..861573e8414cf6 --- /dev/null +++ b/tools/webpack/shared.js @@ -0,0 +1,109 @@ +/** + * External dependencies + */ +const { BundleAnalyzerPlugin } = require( 'webpack-bundle-analyzer' ); +const { DefinePlugin } = require( 'webpack' ); +const TerserPlugin = require( 'terser-webpack-plugin' ); +const { compact } = require( 'lodash' ); +const postcss = require( 'postcss' ); + +/** + * WordPress dependencies + */ +const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' ); +const ReadableJsAssetsWebpackPlugin = require( '@wordpress/readable-js-assets-webpack-plugin' ); + +const { + NODE_ENV: mode = 'development', + WP_DEVTOOL: devtool = mode === 'production' ? false : 'source-map', +} = process.env; + +const stylesTransform = ( content ) => { + if ( mode === 'production' ) { + return postcss( [ + require( 'cssnano' )( { + preset: [ + 'default', + { + discardComments: { + removeAll: true, + }, + }, + ], + } ), + ] ) + .process( content, { + from: 'src/app.css', + to: 'dest/app.css', + } ) + .then( ( result ) => result.css ); + } + return content; +}; + +const optimization = { + // Only concatenate modules in production, when not analyzing bundles. + concatenateModules: + mode === 'production' && ! process.env.WP_BUNDLE_ANALYZER, + minimizer: [ + new TerserPlugin( { + cache: true, + parallel: true, + sourceMap: mode !== 'production', + terserOptions: { + output: { + comments: /translators:/i, + }, + compress: { + passes: 2, + }, + mangle: { + reserved: [ '__', '_n', '_nx', '_x' ], + }, + }, + extractComments: false, + } ), + ], +}; + +const moduleConfig = { + rules: compact( [ + mode !== 'production' && { + test: /\.js$/, + use: require.resolve( 'source-map-loader' ), + enforce: 'pre', + }, + ] ), +}; + +const plugins = [ + // The WP_BUNDLE_ANALYZER global variable enables a utility that represents bundle + // content as a convenient interactive zoomable treemap. + process.env.WP_BUNDLE_ANALYZER && new BundleAnalyzerPlugin(), + new DefinePlugin( { + // Inject the `GUTENBERG_PHASE` global, used for feature flagging. + 'process.env.GUTENBERG_PHASE': JSON.stringify( + parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ) || 1 + ), + 'process.env.FORCE_REDUCED_MOTION': JSON.stringify( + process.env.FORCE_REDUCED_MOTION + ), + } ), + new DependencyExtractionWebpackPlugin( { injectPolyfill: true } ), + mode === 'production' && new ReadableJsAssetsWebpackPlugin(), +]; + +const watchOptions = { + ignored: [ '**/node_modules', '**/packages/*/src' ], + aggregateTimeout: 500, +}; + +module.exports = { + devtool, + mode, + moduleConfig, + optimization, + plugins, + stylesTransform, + watchOptions, +}; diff --git a/webpack.config.js b/webpack.config.js index 44d14153a938e4..ee4f6ed51104b6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,7 @@ /** * Internal dependencies */ +const blocksConfig = require( './tools/webpack/blocks' ); const packagesConfig = require( './tools/webpack/packages' ); -module.exports = packagesConfig; +module.exports = [ blocksConfig, packagesConfig ]; From a674d333ec261ae2dacd5fcdc3d04f7705a39a1e Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Tue, 13 Jul 2021 17:30:14 +0200 Subject: [PATCH 2/2] Consolidate some config options under `baseConfig` export --- tools/webpack/blocks.js | 16 +---- tools/webpack/packages.js | 16 +---- tools/webpack/shared.js | 120 +++++++++++++++++++------------------- 3 files changed, 63 insertions(+), 89 deletions(-) diff --git a/tools/webpack/blocks.js b/tools/webpack/blocks.js index d96f334dcb5a88..98e1a7074fda9d 100644 --- a/tools/webpack/blocks.js +++ b/tools/webpack/blocks.js @@ -9,15 +9,7 @@ const fastGlob = require( 'fast-glob' ); /** * Internal dependencies */ -const { - devtool, - mode, - moduleConfig, - optimization, - plugins, - stylesTransform, - watchOptions, -} = require( './shared' ); +const { baseConfig, plugins, stylesTransform } = require( './shared' ); /* * Matches a block's name in paths in the form @@ -52,16 +44,14 @@ const createEntrypoints = () => { }; module.exports = { + ...baseConfig, name: 'blocks', - optimization, - mode, entry: createEntrypoints(), output: { devtoolNamespace: 'wp', filename: './build/block-library/[name]/view.min.js', path: join( __dirname, '..', '..' ), }, - module: moduleConfig, plugins: [ ...plugins, new CopyWebpackPlugin( { @@ -167,6 +157,4 @@ module.exports = { ), } ), ].filter( Boolean ), - watchOptions, - devtool, }; diff --git a/tools/webpack/packages.js b/tools/webpack/packages.js index 3647c8c429a870..c20a23c3388b82 100644 --- a/tools/webpack/packages.js +++ b/tools/webpack/packages.js @@ -17,15 +17,7 @@ const { * Internal dependencies */ const { dependencies } = require( '../../package' ); -const { - devtool, - mode, - moduleConfig, - optimization, - plugins, - stylesTransform, - watchOptions, -} = require( './shared' ); +const { baseConfig, plugins, stylesTransform } = require( './shared' ); const WORDPRESS_NAMESPACE = '@wordpress/'; const BUNDLED_PACKAGES = [ '@wordpress/icons', '@wordpress/interface' ]; @@ -40,9 +32,8 @@ const gutenbergPackages = Object.keys( dependencies ) .map( ( packageName ) => packageName.replace( WORDPRESS_NAMESPACE, '' ) ); module.exports = { + ...baseConfig, name: 'packages', - optimization, - mode, entry: gutenbergPackages.reduce( ( memo, packageName ) => { return { ...memo, @@ -56,7 +47,6 @@ module.exports = { library: [ 'wp', '[camelName]' ], libraryTarget: 'window', }, - module: moduleConfig, plugins: [ ...plugins, new CustomTemplatedPathPlugin( { @@ -84,6 +74,4 @@ module.exports = { } ) ), } ), ].filter( Boolean ), - watchOptions, - devtool, }; diff --git a/tools/webpack/shared.js b/tools/webpack/shared.js index 861573e8414cf6..f2f1cdf2aaefc4 100644 --- a/tools/webpack/shared.js +++ b/tools/webpack/shared.js @@ -18,62 +18,46 @@ const { WP_DEVTOOL: devtool = mode === 'production' ? false : 'source-map', } = process.env; -const stylesTransform = ( content ) => { - if ( mode === 'production' ) { - return postcss( [ - require( 'cssnano' )( { - preset: [ - 'default', - { - discardComments: { - removeAll: true, - }, +const baseConfig = { + optimization: { + // Only concatenate modules in production, when not analyzing bundles. + concatenateModules: + mode === 'production' && ! process.env.WP_BUNDLE_ANALYZER, + minimizer: [ + new TerserPlugin( { + cache: true, + parallel: true, + sourceMap: mode !== 'production', + terserOptions: { + output: { + comments: /translators:/i, + }, + compress: { + passes: 2, + }, + mangle: { + reserved: [ '__', '_n', '_nx', '_x' ], }, - ], - } ), - ] ) - .process( content, { - from: 'src/app.css', - to: 'dest/app.css', - } ) - .then( ( result ) => result.css ); - } - return content; -}; - -const optimization = { - // Only concatenate modules in production, when not analyzing bundles. - concatenateModules: - mode === 'production' && ! process.env.WP_BUNDLE_ANALYZER, - minimizer: [ - new TerserPlugin( { - cache: true, - parallel: true, - sourceMap: mode !== 'production', - terserOptions: { - output: { - comments: /translators:/i, - }, - compress: { - passes: 2, - }, - mangle: { - reserved: [ '__', '_n', '_nx', '_x' ], }, + extractComments: false, + } ), + ], + }, + mode, + module: { + rules: compact( [ + mode !== 'production' && { + test: /\.js$/, + use: require.resolve( 'source-map-loader' ), + enforce: 'pre', }, - extractComments: false, - } ), - ], -}; - -const moduleConfig = { - rules: compact( [ - mode !== 'production' && { - test: /\.js$/, - use: require.resolve( 'source-map-loader' ), - enforce: 'pre', - }, - ] ), + ] ), + }, + watchOptions: { + ignored: [ '**/node_modules', '**/packages/*/src' ], + aggregateTimeout: 500, + }, + devtool, }; const plugins = [ @@ -93,17 +77,31 @@ const plugins = [ mode === 'production' && new ReadableJsAssetsWebpackPlugin(), ]; -const watchOptions = { - ignored: [ '**/node_modules', '**/packages/*/src' ], - aggregateTimeout: 500, +const stylesTransform = ( content ) => { + if ( mode === 'production' ) { + return postcss( [ + require( 'cssnano' )( { + preset: [ + 'default', + { + discardComments: { + removeAll: true, + }, + }, + ], + } ), + ] ) + .process( content, { + from: 'src/app.css', + to: 'dest/app.css', + } ) + .then( ( result ) => result.css ); + } + return content; }; module.exports = { - devtool, - mode, - moduleConfig, - optimization, + baseConfig, plugins, stylesTransform, - watchOptions, };