From 240f3036426dac349158aecfa529d0909031de04 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 9 Jan 2024 16:53:10 +0100 Subject: [PATCH] WP Scripts: Build block.json viewModule (#57461) Add handling of viewModule field in block.json when using wp-scripts build or start. As the Modules API matures, our tooling should support it. We add an option to clearly mark this an an experimental feature: `wp-scripts build --experimental-modules` or `wp-scripts start --experimental-modules`. To support modules with webpack, it was necessary to run a multi-compilation. An array of webpack configurations is used instead of a single webpack configuration. This is because module compilation is an option at the compilation level and cannot be set for specific entrypoints. If the `--experimental-modules` option is found, we use an environment variable to change the webpack.config export to return an array `[ scriptWebpackConfig, moduleWebpackConfig ]`. Without the experimental option, the `webpack.config` export is the same. Consumers should be able to continue extending this config without noticing any differences. Part of #57492. --- packages/scripts/CHANGELOG.md | 4 + packages/scripts/README.md | 14 +- packages/scripts/config/webpack.config.js | 196 +++++++++++------ packages/scripts/scripts/build.js | 4 + packages/scripts/scripts/start.js | 4 + packages/scripts/utils/block-json.js | 41 ++++ packages/scripts/utils/config.js | 253 ++++++++++++---------- packages/scripts/utils/index.js | 6 + 8 files changed, 341 insertions(+), 181 deletions(-) create mode 100644 packages/scripts/utils/block-json.js diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index f6fb412377f7e..a917cd119c17a 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New Features + +- Add experimental support for `viewModule` field in block.json for `build` and `start` scripts ([#57461](https://github.com/WordPress/gutenberg/pull/57461)). + ### Breaking Changes - Drop support for Node.js versions < 18. diff --git a/packages/scripts/README.md b/packages/scripts/README.md index 42a9c563ea77f..e4a2be8d9a3fa 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 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. + #### 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 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. + #### 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. @@ -723,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 diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 1e060d0e142c9..3919558c2f05c 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' ); @@ -30,6 +30,9 @@ const { getWordPressSrcDirectory, getWebpackEntryPoints, getRenderPropPaths, + getAsBooleanFromENV, + getBlockJsonModuleFields, + getBlockJsonScriptFields, } = require( '../utils' ); const isProduction = process.env.NODE_ENV === 'production'; @@ -39,6 +42,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 @@ -110,10 +116,10 @@ const cssLoaders = [ }, ]; -const config = { +/** @type {webpack.Configuration} */ +const baseConfig = { mode, target, - entry: getWebpackEntryPoints, output: { filename: '[name].js', path: resolve( process.cwd(), 'build' ), @@ -165,7 +171,7 @@ const config = { module: { rules: [ { - test: /\.(j|t)sx?$/, + test: /\.m?(j|t)sx?$/, exclude: /node_modules/, use: [ { @@ -245,21 +251,72 @@ 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'; +} + +// 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' ), + + devServer: isProduction + ? undefined + : { + devMiddleware: { + writeToDisk: true, + }, + allowedHosts: 'auto', + host: 'localhost', + port: 8887, + proxy: { + '/build': { + pathRewrite: { + '^/build': '', + }, + }, + }, + }, + plugins: [ - new DefinePlugin( { + new webpack.DefinePlugin( { // 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( { - cleanAfterEveryBuildPatterns: [ '!fonts/**', '!images/**' ], - // Prevent it from deleting webpack assets during builds that have - // multiple configurations returned in the webpack config. - 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: [ @@ -269,27 +326,33 @@ const config = { 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' ) { const blockJson = JSON.parse( content.toString() ); - [ 'viewScript', 'script', 'editorScript' ].forEach( - ( key ) => { - if ( Array.isArray( blockJson[ key ] ) ) { - blockJson[ key ] = - blockJson[ key ].map( - convertExtension - ); - } else if ( - typeof blockJson[ key ] === 'string' - ) { - blockJson[ key ] = convertExtension( - blockJson[ key ] - ); + + [ + 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 ); } @@ -317,52 +380,55 @@ const config = { 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 ), - 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; -} +if ( hasExperimentalModulesFlag ) { + /** @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, + entry: getWebpackEntryPoints( 'module' ), + + experiments: { + ...baseConfig.experiments, + outputModule: true, }, - allowedHosts: 'auto', - host: 'localhost', - port: 8887, - proxy: { - '/build': { - pathRewrite: { - '^/build': '', - }, + + 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, + } ), + // 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 ), }; -} -// 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', - } ); + module.exports = [ scriptConfig, moduleConfig ]; +} else { + module.exports = scriptConfig; } - -module.exports = config; diff --git a/packages/scripts/scripts/build.js b/packages/scripts/scripts/build.js index 714038fd80ee4..0eef2afb451bf 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 cf29709f3eff1..6296192ef302b 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/block-json.js b/packages/scripts/utils/block-json.js new file mode 100644 index 0000000000000..892cc63c889e5 --- /dev/null +++ b/packages/scripts/utils/block-json.js @@ -0,0 +1,41 @@ +const moduleFields = new Set( [ 'viewModule' ] ); +const scriptFields = new Set( [ 'viewScript', 'script', 'editorScript' ] ); + +/** + * @param {Object} 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 {Object} 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 e4e42255f95dd..8b1bbb1ca5059 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. @@ -108,7 +112,10 @@ const hasPostCSSConfig = () => */ const getWebpackArgs = () => { // Gets all args from CLI without those prefixed with `--webpack`. - let webpackArgs = getArgsFromCLI( [ '--webpack' ] ); + let webpackArgs = getArgsFromCLI( [ + '--experimental-modules', + '--webpack', + ] ); const hasWebpackOutputOption = hasArgInCLI( '-o' ) || hasArgInCLI( '--output' ); @@ -186,104 +193,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 +246,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 ae93160381df4..148895ecbc4ed 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,