From 0d7235a49ca08e431aae4f2f2bb63f2bfbf21fa1 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 4 Jan 2024 17:13:52 +0100 Subject: [PATCH 01/21] Snapshot webpack.config --- .../scripts/test/__snapshots__/index.js.snap | 334 ++++++++++++++++++ packages/scripts/test/index.js | 5 + 2 files changed, 339 insertions(+) create mode 100644 packages/scripts/test/__snapshots__/index.js.snap create mode 100644 packages/scripts/test/index.js diff --git a/packages/scripts/test/__snapshots__/index.js.snap b/packages/scripts/test/__snapshots__/index.js.snap new file mode 100644 index 00000000000000..1b4a4f150b0f31 --- /dev/null +++ b/packages/scripts/test/__snapshots__/index.js.snap @@ -0,0 +1,334 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should leave webpack.config.js untouched 1`] = ` +{ + "devServer": { + "allowedHosts": "auto", + "devMiddleware": { + "writeToDisk": true, + }, + "host": "localhost", + "port": 8887, + "proxy": { + "/build": { + "pathRewrite": { + "^/build": "", + }, + }, + }, + }, + "devtool": "source-map", + "entry": [Function], + "mode": "development", + "module": { + "rules": [ + { + "enforce": "pre", + "exclude": [ + /node_modules/, + ], + "test": /\\\\\\.\\(j\\|t\\)sx\\?\\$/, + "use": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/source-map-loader/dist/cjs.js", + }, + { + "exclude": /node_modules/, + "test": /\\\\\\.\\(j\\|t\\)sx\\?\\$/, + "use": [ + { + "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/babel-loader/lib/index.js", + "options": { + "cacheDirectory": true, + }, + }, + ], + }, + { + "test": /\\\\\\.css\\$/, + "use": [ + { + "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/mini-css-extract-plugin/dist/loader.js", + }, + { + "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/css-loader/dist/cjs.js", + "options": { + "importLoaders": 1, + "modules": { + "auto": true, + }, + "sourceMap": true, + }, + }, + { + "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/postcss-loader/dist/cjs.js", + "options": { + "postcssOptions": { + "ident": "postcss", + "plugins": [ + { + "browsers": undefined, + "info": [Function], + "options": { + "grid": true, + }, + "postcssPlugin": "autoprefixer", + "prepare": [Function], + }, + ], + "sourceMap": true, + }, + }, + }, + ], + }, + { + "test": /\\\\\\.pcss\\$/, + "use": [ + { + "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/mini-css-extract-plugin/dist/loader.js", + }, + { + "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/css-loader/dist/cjs.js", + "options": { + "importLoaders": 1, + "modules": { + "auto": true, + }, + "sourceMap": true, + }, + }, + { + "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/postcss-loader/dist/cjs.js", + "options": { + "postcssOptions": { + "ident": "postcss", + "plugins": [ + { + "browsers": undefined, + "info": [Function], + "options": { + "grid": true, + }, + "postcssPlugin": "autoprefixer", + "prepare": [Function], + }, + ], + "sourceMap": true, + }, + }, + }, + ], + }, + { + "test": /\\\\\\.\\(sc\\|sa\\)ss\\$/, + "use": [ + { + "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/mini-css-extract-plugin/dist/loader.js", + }, + { + "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/css-loader/dist/cjs.js", + "options": { + "importLoaders": 1, + "modules": { + "auto": true, + }, + "sourceMap": true, + }, + }, + { + "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/postcss-loader/dist/cjs.js", + "options": { + "postcssOptions": { + "ident": "postcss", + "plugins": [ + { + "browsers": undefined, + "info": [Function], + "options": { + "grid": true, + }, + "postcssPlugin": "autoprefixer", + "prepare": [Function], + }, + ], + "sourceMap": true, + }, + }, + }, + { + "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/sass-loader/dist/cjs.js", + "options": { + "sourceMap": true, + }, + }, + ], + }, + { + "issuer": /\\\\\\.\\(j\\|t\\)sx\\?\\$/, + "test": /\\\\\\.svg\\$/, + "type": "javascript/auto", + "use": [ + "@svgr/webpack", + "url-loader", + ], + }, + { + "issuer": /\\\\\\.\\(pc\\|sc\\|sa\\|c\\)ss\\$/, + "test": /\\\\\\.svg\\$/, + "type": "asset/inline", + }, + { + "generator": { + "filename": "images/[name].[hash:8][ext]", + }, + "test": /\\\\\\.\\(bmp\\|png\\|jpe\\?g\\|gif\\|webp\\)\\$/i, + "type": "asset/resource", + }, + { + "generator": { + "filename": "fonts/[name].[hash:8][ext]", + }, + "test": /\\\\\\.\\(woff\\|woff2\\|eot\\|ttf\\|otf\\)\\$/i, + "type": "asset/resource", + }, + ], + }, + "optimization": { + "concatenateModules": false, + "minimizer": [ + TerserPlugin { + "options": { + "exclude": undefined, + "extractComments": false, + "include": undefined, + "minimizer": { + "implementation": [Function], + "options": { + "compress": { + "passes": 2, + }, + "mangle": { + "reserved": [ + "__", + "_n", + "_nx", + "_x", + ], + }, + "output": { + "comments": /translators:/i, + }, + }, + }, + "parallel": true, + "test": /\\\\\\.\\[cm\\]\\?js\\(\\\\\\?\\.\\*\\)\\?\\$/i, + }, + }, + ], + "splitChunks": { + "cacheGroups": { + "default": false, + "style": { + "chunks": "all", + "enforce": true, + "name": [Function], + "test": /\\[\\\\\\\\/\\]style\\(\\\\\\.module\\)\\?\\\\\\.\\(pc\\|sc\\|sa\\|c\\)ss\\$/, + "type": "css/mini-extract", + }, + }, + }, + }, + "output": { + "filename": "[name].js", + "path": "/Users/jonsurrell/a8c/gutenberg/trunk/build", + }, + "plugins": [ + DefinePlugin { + "definitions": { + "SCRIPT_DEBUG": true, + }, + }, + CleanWebpackPlugin { + "apply": [Function], + "cleanAfterEveryBuildPatterns": [ + "!fonts/**", + "!images/**", + ], + "cleanOnceBeforeBuildPatterns": [ + "**/*", + ], + "cleanStaleWebpackAssets": false, + "currentAssets": [], + "dangerouslyAllowCleanPatternsOutsideProject": false, + "dry": false, + "handleDone": [Function], + "handleInitial": [Function], + "initialClean": false, + "outputPath": "", + "protectWebpackAssets": true, + "removeFiles": [Function], + "verbose": false, + }, + RenderPathsPlugin {}, + CopyPlugin { + "options": {}, + "patterns": [ + { + "context": "src", + "from": "**/block.json", + "noErrorOnMissing": true, + "transform": [Function], + }, + { + "context": "src", + "filter": [Function], + "from": "**/*.php", + "noErrorOnMissing": true, + }, + ], + }, + MiniCssExtractPlugin { + "_sortedModulesCache": WeakMap {}, + "options": { + "chunkFilename": "[name].css", + "experimentalUseImportModule": undefined, + "filename": "[name].css", + "ignoreOrder": false, + "runtime": true, + }, + "runtimeOptions": { + "attributes": undefined, + "insert": undefined, + "linkType": "text/css", + }, + }, + DependencyExtractionWebpackPlugin { + "externalizedDeps": Set {}, + "options": { + "combineAssets": false, + "combinedOutputFile": null, + "externalizedReport": false, + "injectPolyfill": false, + "outputFilename": null, + "outputFormat": "php", + "useDefaults": true, + }, + "useModules": false, + }, + ], + "resolve": { + "alias": { + "lodash-es": "lodash", + }, + "extensions": [ + ".jsx", + ".ts", + ".tsx", + "...", + ], + }, + "stats": { + "children": false, + }, + "target": "browserslist", +} +`; diff --git a/packages/scripts/test/index.js b/packages/scripts/test/index.js new file mode 100644 index 00000000000000..78c2db9360a30b --- /dev/null +++ b/packages/scripts/test/index.js @@ -0,0 +1,5 @@ +test( 'should leave webpack.config.js untouched', () => { + expect( + require( '@wordpress/scripts/config/webpack.config' ) + ).toMatchSnapshot(); +} ); From e09598e324015367ad39e653b816e27e783e1518 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Wed, 27 Dec 2023 16:38:32 +0100 Subject: [PATCH 02/21] Implement *module block.json entrypoint build --- packages/scripts/CHANGELOG.md | 4 + packages/scripts/config/webpack.config.js | 167 +++++++++++---- packages/scripts/utils/block-json.js | 41 ++++ packages/scripts/utils/config.js | 248 ++++++++++++---------- packages/scripts/utils/index.js | 6 + schemas/json/block.json | 42 ++++ 6 files changed, 354 insertions(+), 154 deletions(-) create mode 100644 packages/scripts/utils/block-json.js diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index f6fb412377f7ef..cd7f36a7ae7612 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New Features + +- Support block.json `editorModule`, `module`, and `viewModule` fields. + ### Breaking Changes - Drop support for Node.js versions < 18. diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 1e060d0e142c91..e6044de56feeb7 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -4,7 +4,7 @@ const { BundleAnalyzerPlugin } = require( 'webpack-bundle-analyzer' ); const { CleanWebpackPlugin } = require( 'clean-webpack-plugin' ); const CopyWebpackPlugin = require( 'copy-webpack-plugin' ); -const { DefinePlugin } = require( 'webpack' ); +const webpack = require( 'webpack' ); const browserslist = require( 'browserslist' ); const MiniCSSExtractPlugin = require( 'mini-css-extract-plugin' ); const { basename, dirname, resolve } = require( 'path' ); @@ -110,14 +110,17 @@ const cssLoaders = [ }, ]; -const config = { +/** @type {webpack.Configuration} */ +const baseConfig = { mode, target, - entry: getWebpackEntryPoints, output: { filename: '[name].js', path: resolve( process.cwd(), 'build' ), }, + experiments: { + outputModule: true, + }, resolve: { alias: { 'lodash-es': 'lodash', @@ -245,8 +248,55 @@ const config = { }, ], }, + stats: { + children: false, + }, +}; + +// WP_DEVTOOL global variable controls how source maps are generated. +// See: https://webpack.js.org/configuration/devtool/#devtool. +if ( process.env.WP_DEVTOOL ) { + baseConfig.devtool = process.env.WP_DEVTOOL; +} + +if ( ! isProduction ) { + // Set default sourcemap mode if it wasn't set by WP_DEVTOOL. + baseConfig.devtool = baseConfig.devtool || 'source-map'; + baseConfig.devServer = { + devMiddleware: { + writeToDisk: true, + }, + allowedHosts: 'auto', + host: 'localhost', + port: 8887, + proxy: { + '/build': { + pathRewrite: { + '^/build': '', + }, + }, + }, + }; +} + +// Add source-map-loader if devtool is set, whether in dev mode or not. +if ( baseConfig.devtool ) { + baseConfig.module.rules.unshift( { + test: /\.(j|t)sx?$/, + exclude: [ /node_modules/ ], + use: require.resolve( 'source-map-loader' ), + enforce: 'pre', + } ); +} + +/** @type {webpack.Configuration} */ +const scriptConfig = { + ...baseConfig, + + entry: getWebpackEntryPoints( 'script' ), + plugins: [ - new DefinePlugin( { + new webpack.DefinePlugin( { // Inject the `SCRIPT_DEBUG` global, used for development features flagging. SCRIPT_DEBUG: ! isProduction, } ), @@ -255,7 +305,7 @@ const config = { // fonts and images. It is a known limitations: // https://github.com/johnagan/clean-webpack-plugin/issues/159 new CleanWebpackPlugin( { - cleanAfterEveryBuildPatterns: [ '!fonts/**', '!images/**' ], + cleanOnceBeforeBuildPatterns: [ '!fonts/**', '!images/**' ], // Prevent it from deleting webpack assets during builds that have // multiple configurations returned in the webpack config. cleanStaleWebpackAssets: false, @@ -324,45 +374,80 @@ const config = { ! process.env.WP_NO_EXTERNALS && new DependencyExtractionWebpackPlugin(), ].filter( Boolean ), - stats: { - children: false, - }, }; -// WP_DEVTOOL global variable controls how source maps are generated. -// See: https://webpack.js.org/configuration/devtool/#devtool. -if ( process.env.WP_DEVTOOL ) { - config.devtool = process.env.WP_DEVTOOL; -} +/** @type {webpack.Configuration} */ +const moduleConfig = { + ...baseConfig, -if ( ! isProduction ) { - // Set default sourcemap mode if it wasn't set by WP_DEVTOOL. - config.devtool = config.devtool || 'source-map'; - config.devServer = { - devMiddleware: { - writeToDisk: true, - }, - allowedHosts: 'auto', - host: 'localhost', - port: 8887, - proxy: { - '/build': { - pathRewrite: { - '^/build': '', - }, - }, + entry: getWebpackEntryPoints( 'module' ), + + output: { + ...baseConfig.output, + module: true, + chunkFormat: 'module', + library: { + ...baseConfig.output.library, + type: 'module', }, - }; -} + }, -// Add source-map-loader if devtool is set, whether in dev mode or not. -if ( config.devtool ) { - config.module.rules.unshift( { - test: /\.(j|t)sx?$/, - exclude: [ /node_modules/ ], - use: require.resolve( 'source-map-loader' ), - enforce: 'pre', - } ); -} + plugins: [ + new webpack.DefinePlugin( { + // Inject the `SCRIPT_DEBUG` global, used for development features flagging. + SCRIPT_DEBUG: ! isProduction, + } ), + new RenderPathsPlugin(), + new CopyWebpackPlugin( { + patterns: [ + { + from: '**/block.json', + context: getWordPressSrcDirectory(), + noErrorOnMissing: true, + transform( content, absoluteFrom ) { + const convertExtension = ( path ) => { + return path.replace( /\.(j|t)sx?$/, '.js' ); + }; + + if ( basename( absoluteFrom ) === 'block.json' ) { + const blockJson = JSON.parse( content.toString() ); + [ 'viewModule', 'module', 'editorModule' ].forEach( + ( key ) => { + if ( Array.isArray( blockJson[ key ] ) ) { + blockJson[ key ] = + blockJson[ key ].map( + convertExtension + ); + } else if ( + typeof blockJson[ key ] === 'string' + ) { + blockJson[ key ] = convertExtension( + blockJson[ key ] + ); + } + } + ); + + return JSON.stringify( blockJson, null, 2 ); + } + + return content; + }, + }, + ], + } ), + // 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(), + // MiniCSSExtractPlugin to extract the CSS thats gets imported into JavaScript. + new MiniCSSExtractPlugin( { filename: '[name].css' } ), + // React Fast Refresh. + hasReactFastRefresh && new ReactRefreshWebpackPlugin(), + // WP_NO_EXTERNALS global variable controls whether scripts' assets get + // generated, and the default externals set. + ! process.env.WP_NO_EXTERNALS && + new DependencyExtractionWebpackPlugin(), + ].filter( Boolean ), +}; -module.exports = config; +module.exports = [ scriptConfig, moduleConfig ]; diff --git a/packages/scripts/utils/block-json.js b/packages/scripts/utils/block-json.js new file mode 100644 index 00000000000000..4d5ca2917c011b --- /dev/null +++ b/packages/scripts/utils/block-json.js @@ -0,0 +1,41 @@ +const scriptFields = new Set( [ 'viewScript', 'script', 'editorScript' ] ); +const moduleFields = new Set( [ 'viewModule', 'module', 'editorModule' ] ); + +/** + * @param {{}} blockJson + * @return {null|Record} Fields + */ +function getBlockJsonModuleFields( blockJson ) { + let result = null; + for ( const field of moduleFields ) { + if ( Object.hasOwn( blockJson, field ) ) { + if ( ! result ) { + result = {}; + } + result[ field ] = blockJson[ field ]; + } + } + return result; +} + +/** + * @param {{}} blockJson + * @return {null|Record} Fields + */ +function getBlockJsonScriptFields( blockJson ) { + let result = null; + for ( const field of scriptFields ) { + if ( Object.hasOwn( blockJson, field ) ) { + if ( ! result ) { + result = {}; + } + result[ field ] = blockJson[ field ]; + } + } + return result; +} + +module.exports = { + getBlockJsonModuleFields, + getBlockJsonScriptFields, +}; diff --git a/packages/scripts/utils/config.js b/packages/scripts/utils/config.js index e4e42255f95dd3..93820a5dca5260 100644 --- a/packages/scripts/utils/config.js +++ b/packages/scripts/utils/config.js @@ -17,6 +17,10 @@ const { } = require( './cli' ); const { fromConfigRoot, fromProjectRoot, hasProjectFile } = require( './file' ); const { hasPackageProp } = require( './package' ); +const { + getBlockJsonModuleFields, + getBlockJsonScriptFields, +} = require( './block-json' ); const { log } = console; // See https://babeljs.io/docs/en/config-files#configuration-file-types. @@ -186,104 +190,52 @@ function getWordPressSrcDirectory() { * * @see https://webpack.js.org/concepts/entry-points/ * - * @return {Object} The list of entry points. + * @param {'script' | 'module'} buildType */ -function getWebpackEntryPoints() { - // 1. Handles the legacy format for entry points when explicitly provided with the `process.env.WP_ENTRY`. - if ( process.env.WP_ENTRY ) { - return JSON.parse( process.env.WP_ENTRY ); - } +function getWebpackEntryPoints( buildType ) { + /** + * @return {Object} The list of entry points. + */ + return () => { + // 1. Handles the legacy format for entry points when explicitly provided with the `process.env.WP_ENTRY`. + if ( process.env.WP_ENTRY ) { + return buildType === 'script' + ? JSON.parse( process.env.WP_ENTRY ) + : {}; + } - // Continue only if the source directory exists. - if ( ! hasProjectFile( getWordPressSrcDirectory() ) ) { - log( - chalk.yellow( - `Source directory "${ getWordPressSrcDirectory() }" was not found. Please confirm there is a "src" directory in the root or the value passed to --webpack-src-dir is correct.` - ) - ); - return {}; - } + // Continue only if the source directory exists. + if ( ! hasProjectFile( getWordPressSrcDirectory() ) ) { + log( + chalk.yellow( + `Source directory "${ getWordPressSrcDirectory() }" was not found. Please confirm there is a "src" directory in the root or the value passed to --webpack-src-dir is correct.` + ) + ); + return {}; + } - // 2. Checks whether any block metadata files can be detected in the defined source directory. - // It scans all discovered files looking for JavaScript assets and converts them to entry points. - const blockMetadataFiles = glob( '**/block.json', { - absolute: true, - cwd: fromProjectRoot( getWordPressSrcDirectory() ), - } ); + // 2. Checks whether any block metadata files can be detected in the defined source directory. + // It scans all discovered files looking for JavaScript assets and converts them to entry points. + const blockMetadataFiles = glob( '**/block.json', { + absolute: true, + cwd: fromProjectRoot( getWordPressSrcDirectory() ), + } ); + + if ( blockMetadataFiles.length > 0 ) { + const srcDirectory = fromProjectRoot( + getWordPressSrcDirectory() + sep + ); + + const entryPoints = {}; - if ( blockMetadataFiles.length > 0 ) { - const srcDirectory = fromProjectRoot( - getWordPressSrcDirectory() + sep - ); - const entryPoints = blockMetadataFiles.reduce( - ( accumulator, blockMetadataFile ) => { + for ( const blockMetadataFile of blockMetadataFiles ) { + const fileContents = readFileSync( blockMetadataFile ); + let parsedBlockJson; // wrapping in try/catch in case the file is malformed // this happens especially when new block.json files are added // at which point they are completely empty and therefore not valid JSON try { - const { editorScript, script, viewScript } = JSON.parse( - readFileSync( blockMetadataFile ) - ); - [ editorScript, script, viewScript ] - .flat() - .filter( - ( value ) => value && value.startsWith( 'file:' ) - ) - .forEach( ( value ) => { - // Removes the `file:` prefix. - const filepath = join( - dirname( blockMetadataFile ), - value.replace( 'file:', '' ) - ); - - // Takes the path without the file extension, and relative to the defined source directory. - if ( ! filepath.startsWith( srcDirectory ) ) { - log( - chalk.yellow( - `Skipping "${ value.replace( - 'file:', - '' - ) }" listed in "${ blockMetadataFile.replace( - fromProjectRoot( sep ), - '' - ) }". File is located outside of the "${ getWordPressSrcDirectory() }" directory.` - ) - ); - return; - } - const entryName = filepath - .replace( extname( filepath ), '' ) - .replace( srcDirectory, '' ) - .replace( /\\/g, '/' ); - - // Detects the proper file extension used in the defined source directory. - const [ entryFilepath ] = glob( - `${ entryName }.[jt]s?(x)`, - { - absolute: true, - cwd: fromProjectRoot( - getWordPressSrcDirectory() - ), - } - ); - - if ( ! entryFilepath ) { - log( - chalk.yellow( - `Skipping "${ value.replace( - 'file:', - '' - ) }" listed in "${ blockMetadataFile.replace( - fromProjectRoot( sep ), - '' - ) }". File does not exist in the "${ getWordPressSrcDirectory() }" directory.` - ) - ); - return; - } - accumulator[ entryName ] = entryFilepath; - } ); - return accumulator; + parsedBlockJson = JSON.parse( fileContents ); } catch ( error ) { chalk.yellow( `Skipping "${ blockMetadataFile.replace( @@ -291,35 +243,105 @@ function getWebpackEntryPoints() { '' ) }" due to malformed JSON.` ); - return accumulator; } - }, - {} - ); - if ( Object.keys( entryPoints ).length > 0 ) { - return entryPoints; + const fields = + buildType === 'script' + ? getBlockJsonScriptFields( parsedBlockJson ) + : getBlockJsonModuleFields( parsedBlockJson ); + + if ( ! fields ) { + continue; + } + + for ( const value of Object.values( fields ).flat() ) { + if ( ! value.startsWith( 'file:' ) ) { + continue; + } + + // Removes the `file:` prefix. + const filepath = join( + dirname( blockMetadataFile ), + value.replace( 'file:', '' ) + ); + + // Takes the path without the file extension, and relative to the defined source directory. + if ( ! filepath.startsWith( srcDirectory ) ) { + log( + chalk.yellow( + `Skipping "${ value.replace( + 'file:', + '' + ) }" listed in "${ blockMetadataFile.replace( + fromProjectRoot( sep ), + '' + ) }". File is located outside of the "${ getWordPressSrcDirectory() }" directory.` + ) + ); + return; + } + const entryName = filepath + .replace( extname( filepath ), '' ) + .replace( srcDirectory, '' ) + .replace( /\\/g, '/' ); + + // Detects the proper file extension used in the defined source directory. + const [ entryFilepath ] = glob( + `${ entryName }.?(m)[jt]s?(x)`, + { + absolute: true, + cwd: fromProjectRoot( getWordPressSrcDirectory() ), + } + ); + + if ( ! entryFilepath ) { + log( + chalk.yellow( + `Skipping "${ value.replace( + 'file:', + '' + ) }" listed in "${ blockMetadataFile.replace( + fromProjectRoot( sep ), + '' + ) }". File does not exist in the "${ getWordPressSrcDirectory() }" directory.` + ) + ); + return; + } + entryPoints[ entryName ] = entryFilepath; + } + } + + if ( Object.keys( entryPoints ).length > 0 ) { + return entryPoints; + } } - } - // 3. Checks whether a standard file name can be detected in the defined source directory, - // and converts the discovered file to entry point. - const [ entryFile ] = glob( 'index.[jt]s?(x)', { - absolute: true, - cwd: fromProjectRoot( getWordPressSrcDirectory() ), - } ); + // Don't do any further processing if this is a module build. + // This only respects *module block.json fields. + if ( buildType === 'module' ) { + return {}; + } - if ( ! entryFile ) { - log( - chalk.yellow( - `No entry file discovered in the "${ getWordPressSrcDirectory() }" directory.` - ) - ); - return {}; - } + // 3. Checks whether a standard file name can be detected in the defined source directory, + // and converts the discovered file to entry point. + const [ entryFile ] = glob( 'index.[jt]s?(x)', { + absolute: true, + cwd: fromProjectRoot( getWordPressSrcDirectory() ), + } ); + + if ( ! entryFile ) { + log( + chalk.yellow( + `No entry file discovered in the "${ getWordPressSrcDirectory() }" directory.` + ) + ); + return {}; + } - return { - index: entryFile, + return { + index: entryFile, + }; }; } diff --git a/packages/scripts/utils/index.js b/packages/scripts/utils/index.js index ae93160381df44..148895ecbc4edf 100644 --- a/packages/scripts/utils/index.js +++ b/packages/scripts/utils/index.js @@ -25,6 +25,10 @@ const { } = require( './config' ); const { fromProjectRoot, fromConfigRoot, hasProjectFile } = require( './file' ); const { getPackageProp, hasPackageProp } = require( './package' ); +const { + getBlockJsonModuleFields, + getBlockJsonScriptFields, +} = require( './block-json' ); module.exports = { fromProjectRoot, @@ -40,6 +44,8 @@ module.exports = { getWordPressSrcDirectory, getWebpackEntryPoints, getRenderPropPaths, + getBlockJsonModuleFields, + getBlockJsonScriptFields, hasArgInCLI, hasBabelConfig, hasCssnanoConfig, diff --git a/schemas/json/block.json b/schemas/json/block.json index fd69ea1badb339..de0612bc891a43 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -752,6 +752,48 @@ } ] }, + "editorModule": { + "description": "Block type editor module definition. It will only be enqueued in the context of the editor.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "module": { + "description": "Block type frontend and editor module definition. It will be enqueued both in the editor and when viewing the content on the front of the site.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "viewModule": { + "description": "Block type frontend module definition. It will be enqueued only when viewing the content on the front of the site.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, "editorStyle": { "description": "Block type editor style definition. It will only be enqueued in the context of the editor.", "oneOf": [ From 1689a3ae1babb3349dbf178caa42ad98d0f6b4c8 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 4 Jan 2024 17:39:59 +0100 Subject: [PATCH 03/21] Add --experimental-modules flag to start + build --- packages/scripts/config/webpack.config.js | 120 ++++++++++++---------- packages/scripts/scripts/build.js | 4 + packages/scripts/scripts/start.js | 4 + packages/scripts/utils/config.js | 4 +- 4 files changed, 77 insertions(+), 55 deletions(-) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index e6044de56feeb7..37b6956530f62a 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -30,6 +30,7 @@ const { getWordPressSrcDirectory, getWebpackEntryPoints, getRenderPropPaths, + getAsBooleanFromENV, } = require( '../utils' ); const isProduction = process.env.NODE_ENV === 'production'; @@ -39,6 +40,9 @@ if ( ! browserslist.findConfig( '.' ) ) { target += ':' + fromConfigRoot( '.browserslistrc' ); } const hasReactFastRefresh = hasArgInCLI( '--hot' ) && ! isProduction; +const hasExperimentalModulesFlag = getAsBooleanFromENV( + 'WP_EXPERIMENTAL_MODULES' +); /** * The plugin recomputes the render paths once on each compilation. It is necessary to avoid repeating processing @@ -376,43 +380,49 @@ const scriptConfig = { ].filter( Boolean ), }; -/** @type {webpack.Configuration} */ -const moduleConfig = { - ...baseConfig, +if ( hasExperimentalModulesFlag ) { + /** @type {webpack.Configuration} */ + const moduleConfig = { + ...baseConfig, - entry: getWebpackEntryPoints( 'module' ), + entry: getWebpackEntryPoints( 'module' ), - output: { - ...baseConfig.output, - module: true, - chunkFormat: 'module', - library: { - ...baseConfig.output.library, - type: 'module', + output: { + ...baseConfig.output, + module: true, + chunkFormat: 'module', + library: { + ...baseConfig.output.library, + type: 'module', + }, }, - }, - plugins: [ - new webpack.DefinePlugin( { - // Inject the `SCRIPT_DEBUG` global, used for development features flagging. - SCRIPT_DEBUG: ! isProduction, - } ), - new RenderPathsPlugin(), - new CopyWebpackPlugin( { - patterns: [ - { - from: '**/block.json', - context: getWordPressSrcDirectory(), - noErrorOnMissing: true, - transform( content, absoluteFrom ) { - const convertExtension = ( path ) => { - return path.replace( /\.(j|t)sx?$/, '.js' ); - }; + plugins: [ + new webpack.DefinePlugin( { + // Inject the `SCRIPT_DEBUG` global, used for development features flagging. + SCRIPT_DEBUG: ! isProduction, + } ), + new RenderPathsPlugin(), + new CopyWebpackPlugin( { + patterns: [ + { + from: '**/block.json', + context: getWordPressSrcDirectory(), + noErrorOnMissing: true, + transform( content, absoluteFrom ) { + const convertExtension = ( path ) => { + return path.replace( /\.(j|t)sx?$/, '.js' ); + }; - if ( basename( absoluteFrom ) === 'block.json' ) { - const blockJson = JSON.parse( content.toString() ); - [ 'viewModule', 'module', 'editorModule' ].forEach( - ( key ) => { + if ( basename( absoluteFrom ) === 'block.json' ) { + const blockJson = JSON.parse( + content.toString() + ); + [ + 'viewModule', + 'module', + 'editorModule', + ].forEach( ( key ) => { if ( Array.isArray( blockJson[ key ] ) ) { blockJson[ key ] = blockJson[ key ].map( @@ -425,29 +435,31 @@ const moduleConfig = { blockJson[ key ] ); } - } - ); + } ); - return JSON.stringify( blockJson, null, 2 ); - } + return JSON.stringify( blockJson, null, 2 ); + } - return content; + return content; + }, }, - }, - ], - } ), - // 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(), - // MiniCSSExtractPlugin to extract the CSS thats gets imported into JavaScript. - new MiniCSSExtractPlugin( { filename: '[name].css' } ), - // React Fast Refresh. - hasReactFastRefresh && new ReactRefreshWebpackPlugin(), - // WP_NO_EXTERNALS global variable controls whether scripts' assets get - // generated, and the default externals set. - ! process.env.WP_NO_EXTERNALS && - new DependencyExtractionWebpackPlugin(), - ].filter( Boolean ), -}; + ], + } ), + // 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(), + // MiniCSSExtractPlugin to extract the CSS thats gets imported into JavaScript. + new MiniCSSExtractPlugin( { filename: '[name].css' } ), + // React Fast Refresh. + hasReactFastRefresh && new ReactRefreshWebpackPlugin(), + // WP_NO_EXTERNALS global variable controls whether scripts' assets get + // generated, and the default externals set. + ! process.env.WP_NO_EXTERNALS && + new DependencyExtractionWebpackPlugin(), + ].filter( Boolean ), + }; -module.exports = [ scriptConfig, moduleConfig ]; + module.exports = [ scriptConfig, moduleConfig ]; +} else { + module.exports = scriptConfig; +} diff --git a/packages/scripts/scripts/build.js b/packages/scripts/scripts/build.js index 714038fd80ee4e..0eef2afb451bfc 100644 --- a/packages/scripts/scripts/build.js +++ b/packages/scripts/scripts/build.js @@ -12,6 +12,10 @@ const EXIT_ERROR_CODE = 1; process.env.NODE_ENV = process.env.NODE_ENV || 'production'; +if ( hasArgInCLI( '--experimental-modules' ) ) { + process.env.WP_EXPERIMENTAL_MODULES = true; +} + if ( hasArgInCLI( '--webpack-no-externals' ) ) { process.env.WP_NO_EXTERNALS = true; } diff --git a/packages/scripts/scripts/start.js b/packages/scripts/scripts/start.js index cf29709f3eff15..6296192ef302b1 100644 --- a/packages/scripts/scripts/start.js +++ b/packages/scripts/scripts/start.js @@ -10,6 +10,10 @@ const { sync: resolveBin } = require( 'resolve-bin' ); const { getArgFromCLI, getWebpackArgs, hasArgInCLI } = require( '../utils' ); const EXIT_ERROR_CODE = 1; +if ( hasArgInCLI( '--experimental-modules' ) ) { + process.env.WP_EXPERIMENTAL_MODULES = true; +} + if ( hasArgInCLI( '--webpack-no-externals' ) ) { process.env.WP_NO_EXTERNALS = true; } diff --git a/packages/scripts/utils/config.js b/packages/scripts/utils/config.js index 93820a5dca5260..987967b9105d04 100644 --- a/packages/scripts/utils/config.js +++ b/packages/scripts/utils/config.js @@ -112,7 +112,9 @@ const hasPostCSSConfig = () => */ const getWebpackArgs = () => { // Gets all args from CLI without those prefixed with `--webpack`. - let webpackArgs = getArgsFromCLI( [ '--webpack' ] ); + let webpackArgs = getArgsFromCLI( [ '--webpack' ] ).filter( + ( arg ) => arg !== '--experimental-modules' + ); const hasWebpackOutputOption = hasArgInCLI( '-o' ) || hasArgInCLI( '--output' ); From b51983464c1314974c025c7ced9f8c8d8ac28d12 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 4 Jan 2024 21:44:27 +0100 Subject: [PATCH 04/21] Only support viewModule --- packages/scripts/CHANGELOG.md | 2 +- packages/scripts/config/webpack.config.js | 6 +----- packages/scripts/utils/block-json.js | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index cd7f36a7ae7612..8a002ed334165f 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -4,7 +4,7 @@ ### New Features -- Support block.json `editorModule`, `module`, and `viewModule` fields. +- Support `viewModule` field in block.json for `build` and `start` scripts ([#57461](https://github.com/WordPress/gutenberg/pull/57461)). ### Breaking Changes diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 37b6956530f62a..7351014fcc20b9 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -418,11 +418,7 @@ if ( hasExperimentalModulesFlag ) { const blockJson = JSON.parse( content.toString() ); - [ - 'viewModule', - 'module', - 'editorModule', - ].forEach( ( key ) => { + [ 'viewModule' ].forEach( ( key ) => { if ( Array.isArray( blockJson[ key ] ) ) { blockJson[ key ] = blockJson[ key ].map( diff --git a/packages/scripts/utils/block-json.js b/packages/scripts/utils/block-json.js index 4d5ca2917c011b..ff987cb32be32e 100644 --- a/packages/scripts/utils/block-json.js +++ b/packages/scripts/utils/block-json.js @@ -1,5 +1,5 @@ +const moduleFields = new Set( [ 'viewModule' ] ); const scriptFields = new Set( [ 'viewScript', 'script', 'editorScript' ] ); -const moduleFields = new Set( [ 'viewModule', 'module', 'editorModule' ] ); /** * @param {{}} blockJson From 6fb69545603564ced7e0bd1936b23bd87c247102 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 4 Jan 2024 21:44:41 +0100 Subject: [PATCH 05/21] Add notes about modules and --experimental-modules --- packages/scripts/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/scripts/README.md b/packages/scripts/README.md index 42a9c563ea77f7..a38e1006224f95 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -108,6 +108,11 @@ This script automatically use the optimized config but sometimes you may want to - `--webpack-src-dir` – Allows customization of the source code directory. Default is `src`. - `--output-path` – Allows customization of the output directory. Default is `build`. +Experimental support to support the block.json `viewModule` field is available via the +`--experimental-modules` option. With this option enabled, script and module fields will all be +compiled. The `viewModule` field is analogous to the `viewScript` field, but will compile a module +and should be registered in WordPress using the Modules API. + #### Advanced information This script uses [webpack](https://webpack.js.org/) behind the scenes. It’ll look for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it’ll use the default config provided by `@wordpress/scripts` packages. Learn more in the [Advanced Usage](#advanced-usage) section. @@ -391,6 +396,11 @@ This script automatically use the optimized config but sometimes you may want to - `--webpack-src-dir` – Allows customization of the source code directory. Default is `src`. - `--output-path` – Allows customization of the output directory. Default is `build`. +Experimental support to support the block.json `viewModule` field is available via the +`--experimental-modules` option. With this option enabled, script and module fields will all be +compiled. The `viewModule` field is analogous to the `viewScript` field, but will compile a module +and should be registered in WordPress using the Modules API. + #### Advanced information This script uses [webpack](https://webpack.js.org/) behind the scenes. It’ll look for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it’ll use the default config provided by `@wordpress/scripts` packages. Learn more in the [Advanced Usage](#advanced-usage) section. From 1b73c4a355a5b396dbc2753d4ba964b7b2969b53 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 4 Jan 2024 21:59:25 +0100 Subject: [PATCH 06/21] Reuse block-json utils in webpack config --- packages/scripts/config/webpack.config.js | 59 +++++++++++++---------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 7351014fcc20b9..44f7570715fc57 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -31,6 +31,8 @@ const { getWebpackEntryPoints, getRenderPropPaths, getAsBooleanFromENV, + getBlockJsonModuleFields, + getBlockJsonScriptFields, } = require( '../utils' ); const isProduction = process.env.NODE_ENV === 'production'; @@ -328,22 +330,22 @@ const scriptConfig = { if ( basename( absoluteFrom ) === 'block.json' ) { const blockJson = JSON.parse( content.toString() ); - [ 'viewScript', 'script', 'editorScript' ].forEach( - ( key ) => { - if ( Array.isArray( blockJson[ key ] ) ) { + + const fields = + getBlockJsonScriptFields( blockJson ); + if ( fields ) { + for ( const [ key, value ] of Object.entries( + fields + ) ) { + if ( Array.isArray( value ) ) { + blockJson[ key ] = + value.map( convertExtension ); + } else if ( typeof value === 'string' ) { blockJson[ key ] = - blockJson[ key ].map( - convertExtension - ); - } else if ( - typeof blockJson[ key ] === 'string' - ) { - blockJson[ key ] = convertExtension( - blockJson[ key ] - ); + convertExtension( value ); } } - ); + } return JSON.stringify( blockJson, null, 2 ); } @@ -418,20 +420,25 @@ if ( hasExperimentalModulesFlag ) { const blockJson = JSON.parse( content.toString() ); - [ 'viewModule' ].forEach( ( key ) => { - if ( Array.isArray( blockJson[ key ] ) ) { - blockJson[ key ] = - blockJson[ key ].map( - convertExtension - ); - } else if ( - typeof blockJson[ key ] === 'string' - ) { - blockJson[ key ] = convertExtension( - blockJson[ key ] - ); + + const fields = + getBlockJsonModuleFields( blockJson ); + if ( fields ) { + for ( const [ + key, + value, + ] of Object.entries( fields ) ) { + if ( Array.isArray( value ) ) { + blockJson[ key ] = + value.map( convertExtension ); + } else if ( + typeof value === 'string' + ) { + blockJson[ key ] = + convertExtension( value ); + } } - } ); + } return JSON.stringify( blockJson, null, 2 ); } From 1bf2b9b054f33c7a75d09503339eb21ac316e243 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 4 Jan 2024 22:03:05 +0100 Subject: [PATCH 07/21] Revert schema changes The field should be handled before updating the schema --- schemas/json/block.json | 42 ----------------------------------------- 1 file changed, 42 deletions(-) diff --git a/schemas/json/block.json b/schemas/json/block.json index de0612bc891a43..fd69ea1badb339 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -752,48 +752,6 @@ } ] }, - "editorModule": { - "description": "Block type editor module definition. It will only be enqueued in the context of the editor.", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "module": { - "description": "Block type frontend and editor module definition. It will be enqueued both in the editor and when viewing the content on the front of the site.", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "viewModule": { - "description": "Block type frontend module definition. It will be enqueued only when viewing the content on the front of the site.", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, "editorStyle": { "description": "Block type editor style definition. It will only be enqueued in the context of the editor.", "oneOf": [ From cb0e279bd47ccfcaf0b44b893c570d13d864a119 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 4 Jan 2024 22:04:21 +0100 Subject: [PATCH 08/21] Revert "Update webpack config snapshot" This reverts commit 35a6fa48a61f70a0ff9a2f517e93253616df248d. --- packages/scripts/test/__snapshots__/index.js.snap | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/scripts/test/__snapshots__/index.js.snap b/packages/scripts/test/__snapshots__/index.js.snap index 1b4a4f150b0f31..2802f73d95e4a2 100644 --- a/packages/scripts/test/__snapshots__/index.js.snap +++ b/packages/scripts/test/__snapshots__/index.js.snap @@ -303,6 +303,10 @@ exports[`should leave webpack.config.js untouched 1`] = ` }, DependencyExtractionWebpackPlugin { "externalizedDeps": Set {}, + "externalsPlugin": ExternalsPlugin { + "externals": [Function], + "type": "window", + }, "options": { "combineAssets": false, "combinedOutputFile": null, @@ -312,7 +316,6 @@ exports[`should leave webpack.config.js untouched 1`] = ` "outputFormat": "php", "useDefaults": true, }, - "useModules": false, }, ], "resolve": { From 6427b50269f814d3a1f66a0e8d1725f01e3fd41e Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 4 Jan 2024 22:24:08 +0100 Subject: [PATCH 09/21] Support mjs(x) and mts(x) extensions --- packages/scripts/config/webpack.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 44f7570715fc57..26b691d33f99b1 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -174,7 +174,7 @@ const baseConfig = { module: { rules: [ { - test: /\.(j|t)sx?$/, + test: /\.m?(j|t)sx?$/, exclude: /node_modules/, use: [ { @@ -325,7 +325,7 @@ const scriptConfig = { noErrorOnMissing: true, transform( content, absoluteFrom ) { const convertExtension = ( path ) => { - return path.replace( /\.(j|t)sx?$/, '.js' ); + return path.replace( /\.m?(j|t)sx?$/, '.js' ); }; if ( basename( absoluteFrom ) === 'block.json' ) { From 05f873bdb1ad5f3a72e7013552c198b960146818 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 4 Jan 2024 22:19:11 +0100 Subject: [PATCH 10/21] Only run CopyWebpackPlugin on one build --- packages/scripts/config/webpack.config.js | 75 ++++++----------------- packages/scripts/utils/block-json.js | 4 +- 2 files changed, 21 insertions(+), 58 deletions(-) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 26b691d33f99b1..b70b81a89a305b 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -331,21 +331,27 @@ const scriptConfig = { if ( basename( absoluteFrom ) === 'block.json' ) { const blockJson = JSON.parse( content.toString() ); - const fields = - getBlockJsonScriptFields( blockJson ); - if ( fields ) { - for ( const [ key, value ] of Object.entries( - fields - ) ) { - if ( Array.isArray( value ) ) { - blockJson[ key ] = - value.map( convertExtension ); - } else if ( typeof value === 'string' ) { - blockJson[ key ] = - convertExtension( value ); + [ + getBlockJsonScriptFields( blockJson ), + getBlockJsonModuleFields( blockJson ), + ].forEach( ( fields ) => { + if ( fields ) { + for ( const [ + key, + value, + ] of Object.entries( fields ) ) { + if ( Array.isArray( value ) ) { + blockJson[ key ] = + value.map( convertExtension ); + } else if ( + typeof value === 'string' + ) { + blockJson[ key ] = + convertExtension( value ); + } } } - } + } ); return JSON.stringify( blockJson, null, 2 ); } @@ -405,49 +411,6 @@ if ( hasExperimentalModulesFlag ) { SCRIPT_DEBUG: ! isProduction, } ), new RenderPathsPlugin(), - new CopyWebpackPlugin( { - patterns: [ - { - from: '**/block.json', - context: getWordPressSrcDirectory(), - noErrorOnMissing: true, - transform( content, absoluteFrom ) { - const convertExtension = ( path ) => { - return path.replace( /\.(j|t)sx?$/, '.js' ); - }; - - if ( basename( absoluteFrom ) === 'block.json' ) { - const blockJson = JSON.parse( - content.toString() - ); - - const fields = - getBlockJsonModuleFields( blockJson ); - if ( fields ) { - for ( const [ - key, - value, - ] of Object.entries( fields ) ) { - if ( Array.isArray( value ) ) { - blockJson[ key ] = - value.map( convertExtension ); - } else if ( - typeof value === 'string' - ) { - blockJson[ key ] = - convertExtension( value ); - } - } - } - - return JSON.stringify( blockJson, null, 2 ); - } - - return content; - }, - }, - ], - } ), // 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(), diff --git a/packages/scripts/utils/block-json.js b/packages/scripts/utils/block-json.js index ff987cb32be32e..892cc63c889e50 100644 --- a/packages/scripts/utils/block-json.js +++ b/packages/scripts/utils/block-json.js @@ -2,7 +2,7 @@ const moduleFields = new Set( [ 'viewModule' ] ); const scriptFields = new Set( [ 'viewScript', 'script', 'editorScript' ] ); /** - * @param {{}} blockJson + * @param {Object} blockJson * @return {null|Record} Fields */ function getBlockJsonModuleFields( blockJson ) { @@ -19,7 +19,7 @@ function getBlockJsonModuleFields( blockJson ) { } /** - * @param {{}} blockJson + * @param {Object} blockJson * @return {null|Record} Fields */ function getBlockJsonScriptFields( blockJson ) { From b02b435ba34f6a25be630ff53dadeb138bf3502d Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Fri, 5 Jan 2024 13:10:05 +0100 Subject: [PATCH 11/21] Remove CleanWebpackPlugin clean options --- packages/scripts/config/webpack.config.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index b70b81a89a305b..5848bd57f45158 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -306,14 +306,7 @@ const scriptConfig = { // Inject the `SCRIPT_DEBUG` global, used for development features flagging. SCRIPT_DEBUG: ! isProduction, } ), - // During rebuilds, all webpack assets that are not used anymore will be - // removed automatically. There is an exception added in watch mode for - // fonts and images. It is a known limitations: - // https://github.com/johnagan/clean-webpack-plugin/issues/159 new CleanWebpackPlugin( { - cleanOnceBeforeBuildPatterns: [ '!fonts/**', '!images/**' ], - // Prevent it from deleting webpack assets during builds that have - // multiple configurations returned in the webpack config. cleanStaleWebpackAssets: false, } ), new RenderPathsPlugin(), From 7dcc17339bb5299278a672feafcd9dc0c601bcda Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Fri, 5 Jan 2024 14:32:30 +0100 Subject: [PATCH 12/21] Restore cleaning on script build --- packages/scripts/config/webpack.config.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 5848bd57f45158..1e09937138101d 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -306,9 +306,17 @@ const scriptConfig = { // Inject the `SCRIPT_DEBUG` global, used for development features flagging. SCRIPT_DEBUG: ! isProduction, } ), - new CleanWebpackPlugin( { - cleanStaleWebpackAssets: false, - } ), + + // If we run a modules build, the 2 compilations can "clean" each other's output + // Prevent the cleaning from happening + ! hasExperimentalModulesFlag && + new CleanWebpackPlugin( { + cleanAfterEveryBuildPatterns: [ '!fonts/**', '!images/**' ], + // Prevent it from deleting webpack assets during builds that have + // multiple configurations returned in the webpack config. + cleanStaleWebpackAssets: false, + } ), + new RenderPathsPlugin(), new CopyWebpackPlugin( { patterns: [ From 15cc7a1ecefa5eff15227420c08d9b5b1b1710c7 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Fri, 5 Jan 2024 13:46:52 +0100 Subject: [PATCH 13/21] Update webpack config snapshot --- packages/scripts/test/__snapshots__/index.js.snap | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/scripts/test/__snapshots__/index.js.snap b/packages/scripts/test/__snapshots__/index.js.snap index 2802f73d95e4a2..f752d860ed1256 100644 --- a/packages/scripts/test/__snapshots__/index.js.snap +++ b/packages/scripts/test/__snapshots__/index.js.snap @@ -19,6 +19,9 @@ exports[`should leave webpack.config.js untouched 1`] = ` }, "devtool": "source-map", "entry": [Function], + "experiments": { + "outputModule": true, + }, "mode": "development", "module": { "rules": [ @@ -32,7 +35,7 @@ exports[`should leave webpack.config.js untouched 1`] = ` }, { "exclude": /node_modules/, - "test": /\\\\\\.\\(j\\|t\\)sx\\?\\$/, + "test": /\\\\\\.m\\?\\(j\\|t\\)sx\\?\\$/, "use": [ { "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/babel-loader/lib/index.js", @@ -303,10 +306,6 @@ exports[`should leave webpack.config.js untouched 1`] = ` }, DependencyExtractionWebpackPlugin { "externalizedDeps": Set {}, - "externalsPlugin": ExternalsPlugin { - "externals": [Function], - "type": "window", - }, "options": { "combineAssets": false, "combinedOutputFile": null, @@ -316,6 +315,7 @@ exports[`should leave webpack.config.js untouched 1`] = ` "outputFormat": "php", "useDefaults": true, }, + "useModules": false, }, ], "resolve": { From 85f250888fdda33a007d046d378440ed32fae05c Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Fri, 5 Jan 2024 13:47:20 +0100 Subject: [PATCH 14/21] Revert webpack config snapshot --- .../scripts/test/__snapshots__/index.js.snap | 337 ------------------ packages/scripts/test/index.js | 5 - 2 files changed, 342 deletions(-) delete mode 100644 packages/scripts/test/__snapshots__/index.js.snap delete mode 100644 packages/scripts/test/index.js diff --git a/packages/scripts/test/__snapshots__/index.js.snap b/packages/scripts/test/__snapshots__/index.js.snap deleted file mode 100644 index f752d860ed1256..00000000000000 --- a/packages/scripts/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,337 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should leave webpack.config.js untouched 1`] = ` -{ - "devServer": { - "allowedHosts": "auto", - "devMiddleware": { - "writeToDisk": true, - }, - "host": "localhost", - "port": 8887, - "proxy": { - "/build": { - "pathRewrite": { - "^/build": "", - }, - }, - }, - }, - "devtool": "source-map", - "entry": [Function], - "experiments": { - "outputModule": true, - }, - "mode": "development", - "module": { - "rules": [ - { - "enforce": "pre", - "exclude": [ - /node_modules/, - ], - "test": /\\\\\\.\\(j\\|t\\)sx\\?\\$/, - "use": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/source-map-loader/dist/cjs.js", - }, - { - "exclude": /node_modules/, - "test": /\\\\\\.m\\?\\(j\\|t\\)sx\\?\\$/, - "use": [ - { - "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/babel-loader/lib/index.js", - "options": { - "cacheDirectory": true, - }, - }, - ], - }, - { - "test": /\\\\\\.css\\$/, - "use": [ - { - "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/mini-css-extract-plugin/dist/loader.js", - }, - { - "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/css-loader/dist/cjs.js", - "options": { - "importLoaders": 1, - "modules": { - "auto": true, - }, - "sourceMap": true, - }, - }, - { - "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/postcss-loader/dist/cjs.js", - "options": { - "postcssOptions": { - "ident": "postcss", - "plugins": [ - { - "browsers": undefined, - "info": [Function], - "options": { - "grid": true, - }, - "postcssPlugin": "autoprefixer", - "prepare": [Function], - }, - ], - "sourceMap": true, - }, - }, - }, - ], - }, - { - "test": /\\\\\\.pcss\\$/, - "use": [ - { - "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/mini-css-extract-plugin/dist/loader.js", - }, - { - "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/css-loader/dist/cjs.js", - "options": { - "importLoaders": 1, - "modules": { - "auto": true, - }, - "sourceMap": true, - }, - }, - { - "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/postcss-loader/dist/cjs.js", - "options": { - "postcssOptions": { - "ident": "postcss", - "plugins": [ - { - "browsers": undefined, - "info": [Function], - "options": { - "grid": true, - }, - "postcssPlugin": "autoprefixer", - "prepare": [Function], - }, - ], - "sourceMap": true, - }, - }, - }, - ], - }, - { - "test": /\\\\\\.\\(sc\\|sa\\)ss\\$/, - "use": [ - { - "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/mini-css-extract-plugin/dist/loader.js", - }, - { - "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/css-loader/dist/cjs.js", - "options": { - "importLoaders": 1, - "modules": { - "auto": true, - }, - "sourceMap": true, - }, - }, - { - "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/postcss-loader/dist/cjs.js", - "options": { - "postcssOptions": { - "ident": "postcss", - "plugins": [ - { - "browsers": undefined, - "info": [Function], - "options": { - "grid": true, - }, - "postcssPlugin": "autoprefixer", - "prepare": [Function], - }, - ], - "sourceMap": true, - }, - }, - }, - { - "loader": "/Users/jonsurrell/a8c/gutenberg/trunk/node_modules/sass-loader/dist/cjs.js", - "options": { - "sourceMap": true, - }, - }, - ], - }, - { - "issuer": /\\\\\\.\\(j\\|t\\)sx\\?\\$/, - "test": /\\\\\\.svg\\$/, - "type": "javascript/auto", - "use": [ - "@svgr/webpack", - "url-loader", - ], - }, - { - "issuer": /\\\\\\.\\(pc\\|sc\\|sa\\|c\\)ss\\$/, - "test": /\\\\\\.svg\\$/, - "type": "asset/inline", - }, - { - "generator": { - "filename": "images/[name].[hash:8][ext]", - }, - "test": /\\\\\\.\\(bmp\\|png\\|jpe\\?g\\|gif\\|webp\\)\\$/i, - "type": "asset/resource", - }, - { - "generator": { - "filename": "fonts/[name].[hash:8][ext]", - }, - "test": /\\\\\\.\\(woff\\|woff2\\|eot\\|ttf\\|otf\\)\\$/i, - "type": "asset/resource", - }, - ], - }, - "optimization": { - "concatenateModules": false, - "minimizer": [ - TerserPlugin { - "options": { - "exclude": undefined, - "extractComments": false, - "include": undefined, - "minimizer": { - "implementation": [Function], - "options": { - "compress": { - "passes": 2, - }, - "mangle": { - "reserved": [ - "__", - "_n", - "_nx", - "_x", - ], - }, - "output": { - "comments": /translators:/i, - }, - }, - }, - "parallel": true, - "test": /\\\\\\.\\[cm\\]\\?js\\(\\\\\\?\\.\\*\\)\\?\\$/i, - }, - }, - ], - "splitChunks": { - "cacheGroups": { - "default": false, - "style": { - "chunks": "all", - "enforce": true, - "name": [Function], - "test": /\\[\\\\\\\\/\\]style\\(\\\\\\.module\\)\\?\\\\\\.\\(pc\\|sc\\|sa\\|c\\)ss\\$/, - "type": "css/mini-extract", - }, - }, - }, - }, - "output": { - "filename": "[name].js", - "path": "/Users/jonsurrell/a8c/gutenberg/trunk/build", - }, - "plugins": [ - DefinePlugin { - "definitions": { - "SCRIPT_DEBUG": true, - }, - }, - CleanWebpackPlugin { - "apply": [Function], - "cleanAfterEveryBuildPatterns": [ - "!fonts/**", - "!images/**", - ], - "cleanOnceBeforeBuildPatterns": [ - "**/*", - ], - "cleanStaleWebpackAssets": false, - "currentAssets": [], - "dangerouslyAllowCleanPatternsOutsideProject": false, - "dry": false, - "handleDone": [Function], - "handleInitial": [Function], - "initialClean": false, - "outputPath": "", - "protectWebpackAssets": true, - "removeFiles": [Function], - "verbose": false, - }, - RenderPathsPlugin {}, - CopyPlugin { - "options": {}, - "patterns": [ - { - "context": "src", - "from": "**/block.json", - "noErrorOnMissing": true, - "transform": [Function], - }, - { - "context": "src", - "filter": [Function], - "from": "**/*.php", - "noErrorOnMissing": true, - }, - ], - }, - MiniCssExtractPlugin { - "_sortedModulesCache": WeakMap {}, - "options": { - "chunkFilename": "[name].css", - "experimentalUseImportModule": undefined, - "filename": "[name].css", - "ignoreOrder": false, - "runtime": true, - }, - "runtimeOptions": { - "attributes": undefined, - "insert": undefined, - "linkType": "text/css", - }, - }, - DependencyExtractionWebpackPlugin { - "externalizedDeps": Set {}, - "options": { - "combineAssets": false, - "combinedOutputFile": null, - "externalizedReport": false, - "injectPolyfill": false, - "outputFilename": null, - "outputFormat": "php", - "useDefaults": true, - }, - "useModules": false, - }, - ], - "resolve": { - "alias": { - "lodash-es": "lodash", - }, - "extensions": [ - ".jsx", - ".ts", - ".tsx", - "...", - ], - }, - "stats": { - "children": false, - }, - "target": "browserslist", -} -`; diff --git a/packages/scripts/test/index.js b/packages/scripts/test/index.js deleted file mode 100644 index 78c2db9360a30b..00000000000000 --- a/packages/scripts/test/index.js +++ /dev/null @@ -1,5 +0,0 @@ -test( 'should leave webpack.config.js untouched', () => { - expect( - require( '@wordpress/scripts/config/webpack.config' ) - ).toMatchSnapshot(); -} ); From 040c796ab4fccdb0d214192f7e54c62203f0001d Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Fri, 5 Jan 2024 14:47:34 +0100 Subject: [PATCH 15/21] Mention experimantal in changelog --- packages/scripts/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 8a002ed334165f..a917cd119c17a1 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -4,7 +4,7 @@ ### New Features -- Support `viewModule` field in block.json for `build` and `start` scripts ([#57461](https://github.com/WordPress/gutenberg/pull/57461)). +- Add experimental support for `viewModule` field in block.json for `build` and `start` scripts ([#57461](https://github.com/WordPress/gutenberg/pull/57461)). ### Breaking Changes From 62b6ce3cef366814ec9c841ab4b201c9159d869d Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 8 Jan 2024 17:29:06 +0100 Subject: [PATCH 16/21] Remove RenderPathsPlugin from module config --- packages/scripts/config/webpack.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 1e09937138101d..5d88de09967763 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -411,7 +411,6 @@ if ( hasExperimentalModulesFlag ) { // Inject the `SCRIPT_DEBUG` global, used for development features flagging. SCRIPT_DEBUG: ! isProduction, } ), - new RenderPathsPlugin(), // 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(), From a5d62a0c833f2be1f163d7e02492623ab75cc19c Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 8 Jan 2024 18:41:37 +0100 Subject: [PATCH 17/21] Use arg prefix filtering for experimental-modules flag --- packages/scripts/utils/config.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/scripts/utils/config.js b/packages/scripts/utils/config.js index 987967b9105d04..8b1bbb1ca50590 100644 --- a/packages/scripts/utils/config.js +++ b/packages/scripts/utils/config.js @@ -112,9 +112,10 @@ const hasPostCSSConfig = () => */ const getWebpackArgs = () => { // Gets all args from CLI without those prefixed with `--webpack`. - let webpackArgs = getArgsFromCLI( [ '--webpack' ] ).filter( - ( arg ) => arg !== '--experimental-modules' - ); + let webpackArgs = getArgsFromCLI( [ + '--experimental-modules', + '--webpack', + ] ); const hasWebpackOutputOption = hasArgInCLI( '-o' ) || hasArgInCLI( '--output' ); From afe375d535fe12718c59ffd8932eb6f4c9371301 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 8 Jan 2024 19:07:39 +0100 Subject: [PATCH 18/21] One dev server --- packages/scripts/config/webpack.config.js | 33 ++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 5d88de09967763..95a2dee09ae25c 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -268,21 +268,6 @@ if ( process.env.WP_DEVTOOL ) { if ( ! isProduction ) { // Set default sourcemap mode if it wasn't set by WP_DEVTOOL. baseConfig.devtool = baseConfig.devtool || 'source-map'; - baseConfig.devServer = { - devMiddleware: { - writeToDisk: true, - }, - allowedHosts: 'auto', - host: 'localhost', - port: 8887, - proxy: { - '/build': { - pathRewrite: { - '^/build': '', - }, - }, - }, - }; } // Add source-map-loader if devtool is set, whether in dev mode or not. @@ -301,6 +286,24 @@ const scriptConfig = { entry: getWebpackEntryPoints( 'script' ), + devServer: isProduction + ? undefined + : { + devMiddleware: { + writeToDisk: true, + }, + allowedHosts: 'auto', + host: 'localhost', + port: 8887, + proxy: { + '/build': { + pathRewrite: { + '^/build': '', + }, + }, + }, + }, + plugins: [ new webpack.DefinePlugin( { // Inject the `SCRIPT_DEBUG` global, used for development features flagging. From d59256a5f570b7061140e1e8344fe178db9692bd Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 8 Jan 2024 19:07:51 +0100 Subject: [PATCH 19/21] No react refresh for modules --- packages/scripts/config/webpack.config.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 95a2dee09ae25c..e0fa8a2b55538e 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -383,8 +383,6 @@ const scriptConfig = { process.env.WP_BUNDLE_ANALYZER && new BundleAnalyzerPlugin(), // MiniCSSExtractPlugin to extract the CSS thats gets imported into JavaScript. new MiniCSSExtractPlugin( { filename: '[name].css' } ), - // React Fast Refresh. - hasReactFastRefresh && new ReactRefreshWebpackPlugin(), // WP_NO_EXTERNALS global variable controls whether scripts' assets get // generated, and the default externals set. ! process.env.WP_NO_EXTERNALS && From a48053e7df18ebf99e4199c23397b58150714625 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 9 Jan 2024 16:18:52 +0100 Subject: [PATCH 20/21] Move outputModule experiment to modulesConfig --- packages/scripts/config/webpack.config.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index e0fa8a2b55538e..3919558c2f05ca 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -124,9 +124,6 @@ const baseConfig = { filename: '[name].js', path: resolve( process.cwd(), 'build' ), }, - experiments: { - outputModule: true, - }, resolve: { alias: { 'lodash-es': 'lodash', @@ -397,6 +394,11 @@ if ( hasExperimentalModulesFlag ) { entry: getWebpackEntryPoints( 'module' ), + experiments: { + ...baseConfig.experiments, + outputModule: true, + }, + output: { ...baseConfig.output, module: true, From cffac91e70b224cdf01a1ce7f2468500976df796 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 9 Jan 2024 16:22:42 +0100 Subject: [PATCH 21/21] Fix README --- packages/scripts/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/scripts/README.md b/packages/scripts/README.md index a38e1006224f95..e4a2be8d9a3fae 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -108,7 +108,7 @@ This script automatically use the optimized config but sometimes you may want to - `--webpack-src-dir` – Allows customization of the source code directory. Default is `src`. - `--output-path` – Allows customization of the output directory. Default is `build`. -Experimental support to support the block.json `viewModule` field is available via the +Experimental support for the block.json `viewModule` field is available via the `--experimental-modules` option. With this option enabled, script and module fields will all be compiled. The `viewModule` field is analogous to the `viewScript` field, but will compile a module and should be registered in WordPress using the Modules API. @@ -396,7 +396,7 @@ This script automatically use the optimized config but sometimes you may want to - `--webpack-src-dir` – Allows customization of the source code directory. Default is `src`. - `--output-path` – Allows customization of the output directory. Default is `build`. -Experimental support to support the block.json `viewModule` field is available via the +Experimental support for the block.json `viewModule` field is available via the `--experimental-modules` option. With this option enabled, script and module fields will all be compiled. The `viewModule` field is analogous to the `viewScript` field, but will compile a module and should be registered in WordPress using the Modules API. @@ -733,8 +733,8 @@ module.exports = { If you follow this approach, please, be aware that: -- You should keep using the `wp-scripts` commands (`start` and `build`). Do not use `webpack` directly. -- Future versions of this package may change what webpack and Babel plugins we bundle, default configs, etc. Should those changes be necessary, they will be registered in the [package’s CHANGELOG](https://github.com/WordPress/gutenberg/blob/HEAD/packages/scripts/CHANGELOG.md), so make sure to read it before upgrading. +- You should keep using the `wp-scripts` commands (`start` and `build`). Do not use `webpack` directly. +- Future versions of this package may change what webpack and Babel plugins we bundle, default configs, etc. Should those changes be necessary, they will be registered in the [package’s CHANGELOG](https://github.com/WordPress/gutenberg/blob/HEAD/packages/scripts/CHANGELOG.md), so make sure to read it before upgrading. ## Contributing to this package