From 9fbd0ac2620dc07dbb0666856ccf470307103ba1 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:50:24 -0500 Subject: [PATCH 1/4] Use Grunt for basic copy tasks. --- Gruntfile.js | 133 +++++++++++++++++++++++++++++- package.json | 2 +- tools/gutenberg/copy.js | 175 ++-------------------------------------- 3 files changed, 136 insertions(+), 174 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index b2d64ea9b7216..0436ef5dbd391 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -587,7 +587,123 @@ module.exports = function(grunt) { certificates: { src: 'vendor/composer/ca-bundle/res/cacert.pem', dest: SOURCE_DIR + 'wp-includes/certificates/ca-bundle.crt' - } + }, + + // Gutenberg: PHP infrastructure files (routes.php, pages.php, constants.php, pages/, routes/). + 'gutenberg-php': { + options: { + process: function( content ) { + /* + * Fix boot module asset file path for Core's different directory structure. + * FROM: __DIR__ . '/../../modules/boot/index.min.asset.php' + * TO: ABSPATH . WPINC . '/js/dist/script-modules/boot/index.min.asset.php' + */ + return content.replace( + /__DIR__\s*\.\s*['"]\/..\/\..\/modules\/boot\/index\.min\.asset\.php['"]/g, + "ABSPATH . WPINC . '/js/dist/script-modules/boot/index.min.asset.php'" + ); + } + }, + files: [ { + expand: true, + cwd: 'gutenberg/build', + src: [ + 'routes.php', + 'pages.php', + 'constants.php', + 'pages/**/*.php', + 'routes/**/*.php', + ], + dest: WORKING_DIR + 'wp-includes/build/', + } ], + }, + + // Gutenberg: script modules (gutenberg/build/modules/ -> wp-includes/js/dist/script-modules/). + 'gutenberg-modules': { + options: { + process: function( content, srcpath ) { + // Strip sourceMappingURL comments from JS files. + if ( srcpath.endsWith( '.js' ) ) { + return content.replace( /^\s*\/\/#\s*sourceMappingURL=.*$/gm, '' ).trimEnd(); + } + return content; + } + }, + files: [ { + expand: true, + cwd: 'gutenberg/build/modules', + src: [ '**/*', '!**/*.map' ], + dest: WORKING_DIR + 'wp-includes/js/dist/script-modules/', + } ], + }, + + // Gutenberg: styles (gutenberg/build/styles/ -> wp-includes/css/dist/). + 'gutenberg-styles': { + files: [ { + expand: true, + cwd: 'gutenberg/build/styles', + src: [ '**/*', '!**/*.map' ], + dest: WORKING_DIR + 'wp-includes/css/dist/', + } ], + }, + + // Gutenberg: theme JSON files (gutenberg/lib/ -> wp-includes/). + 'gutenberg-theme-json': { + options: { + process: function( content, srcpath ) { + // Replace the local schema URL with the canonical public URL for Core. + if ( path.basename( srcpath ) === 'theme.json' ) { + return content.replace( + '"$schema": "../schemas/json/theme.json"', + '"$schema": "https://schemas.wp.org/trunk/theme.json"' + ); + } + return content; + } + }, + files: [ + { + src: 'gutenberg/lib/theme.json', + dest: WORKING_DIR + 'wp-includes/theme.json', + }, + { + src: 'gutenberg/lib/theme-i18n.json', + dest: WORKING_DIR + 'wp-includes/theme-i18n.json', + }, + ], + }, + + // Gutenberg: icon manifest and SVG library (gutenberg/packages/icons/src/ -> wp-includes/icons/). + 'gutenberg-icons': { + options: { + process: function( content, srcpath ) { + /* + * Remove the 'gutenberg' text domain from _x() calls in manifest.php. + * FROM: _x( '...', 'icon label', 'gutenberg' ) + * TO: _x( '...', 'icon label' ) + */ + if ( path.basename( srcpath ) === 'manifest.php' ) { + return content.replace( + /_x\(\s*([^,]+),\s*([^,]+),\s*['"]gutenberg['"]\s*\)/g, + '_x( $1, $2 )' + ); + } + return content; + } + }, + files: [ + { + src: 'gutenberg/packages/icons/src/manifest.php', + dest: WORKING_DIR + 'wp-includes/icons/manifest.php', + }, + { + expand: true, + cwd: 'gutenberg/packages/icons/src/library', + src: '*.svg', + dest: WORKING_DIR + 'wp-includes/icons/library/', + }, + ], + }, }, sass: { colors: { @@ -1500,7 +1616,7 @@ module.exports = function(grunt) { } ); } ); - grunt.registerTask( 'gutenberg:copy', 'Copies Gutenberg build output to WordPress Core.', function() { + grunt.registerTask( 'gutenberg:copy', 'Copies Gutenberg JS packages and block assets to WordPress Core.', function() { const done = this.async(); const buildDir = grunt.option( 'dev' ) ? 'src' : 'build'; grunt.util.spawn( { @@ -1948,6 +2064,15 @@ module.exports = function(grunt) { } ); } ); + grunt.registerTask( 'build:gutenberg', [ + 'copy:gutenberg-php', + 'gutenberg:copy', + 'copy:gutenberg-modules', + 'copy:gutenberg-styles', + 'copy:gutenberg-theme-json', + 'copy:gutenberg-icons', + ] ); + grunt.registerTask( 'build', function() { if ( grunt.option( 'dev' ) ) { grunt.task.run( [ @@ -1955,7 +2080,7 @@ module.exports = function(grunt) { 'build:js', 'build:css', 'build:codemirror', - 'gutenberg:copy', + 'build:gutenberg', 'copy-vendor-scripts', 'build:certificates' ] ); @@ -1967,7 +2092,7 @@ module.exports = function(grunt) { 'build:js', 'build:css', 'build:codemirror', - 'gutenberg:copy', + 'build:gutenberg', 'copy-vendor-scripts', 'replace:source-maps', 'verify:build' diff --git a/package.json b/package.json index 30b0b84b1b480..fe34bc70bf120 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "wicg-inert": "3.1.3" }, "scripts": { - "postinstall": "npm run gutenberg:download && npm run gutenberg:copy -- --dev", + "postinstall": "npm run gutenberg:download && grunt build:gutenberg --dev", "build": "grunt build", "build:dev": "grunt build --dev", "dev": "grunt watch --dev", diff --git a/tools/gutenberg/copy.js b/tools/gutenberg/copy.js index d87f82b6f40ed..8421abc3da664 100644 --- a/tools/gutenberg/copy.js +++ b/tools/gutenberg/copy.js @@ -12,7 +12,6 @@ const fs = require( 'fs' ); const path = require( 'path' ); const json2php = require( 'json2php' ); -const glob = require( 'glob' ); // Paths. const rootDir = path.resolve( __dirname, '../..' ); @@ -839,48 +838,6 @@ function parsePHPArray( phpArrayContent ) { } } -/** - * Transform PHP file contents to work in Core. - * - * @param {string} content - File content. - * @return {string} Transformed content. - */ -function transformPHPContent( content ) { - let transformed = content; - - /* - * Fix boot module asset file path for Core's different directory structure. - * FROM: __DIR__ . '/../../modules/boot/index.min.asset.php' - * TO: ABSPATH . WPINC . '/js/dist/script-modules/boot/index.min.asset.php' - * This is needed because Core copies modules to a different location than the plugin structure. - */ - transformed = transformed.replace( - /__DIR__\s*\.\s*['"]\/\.\.\/\.\.\/modules\/boot\/index\.min\.asset\.php['"]/g, - "ABSPATH . WPINC . '/js/dist/script-modules/boot/index.min.asset.php'" - ); - - return transformed; -} - -/** - * Transform manifest.php to remove gutenberg text domain. - * - * @param {string} content - File content. - * @return {string} Transformed content. - */ -function transformManifestPHP( content ) { - /* - * Remove 'gutenberg' text domain from _x() calls. - * FROM: _x( '...', 'icon label', 'gutenberg' ) - * TO: _x( '...', 'icon label' ) - */ - const transformedContent = content.replace( - /_x\(\s*([^,]+),\s*([^,]+),\s*['"]gutenberg['"]\s*\)/g, - '_x( $1, $2 )' - ); - return transformedContent; -} - /** * Main execution function. */ @@ -893,42 +850,7 @@ async function main() { process.exit( 1 ); } - // 1. Copy PHP infrastructure. - console.log( '\nšŸ“¦ Copying PHP infrastructure...' ); - const phpConfig = COPY_CONFIG.phpInfrastructure; - const phpDest = path.join( wpIncludesDir, phpConfig.destination ); - - // Copy PHP files. - for ( const file of phpConfig.files ) { - const src = path.join( gutenbergBuildDir, file ); - const dest = path.join( phpDest, file ); - - if ( fs.existsSync( src ) ) { - fs.mkdirSync( path.dirname( dest ), { recursive: true } ); - let content = fs.readFileSync( src, 'utf8' ); - content = transformPHPContent( content ); - fs.writeFileSync( dest, content ); - console.log( ` āœ… ${ file }` ); - } else { - console.log( - ` āš ļø ${ file } not found (may not exist in this Gutenberg version)` - ); - } - } - - // Copy PHP directories. - for ( const dir of phpConfig.directories ) { - const src = path.join( gutenbergBuildDir, dir ); - const dest = path.join( phpDest, dir ); - - if ( fs.existsSync( src ) ) { - console.log( ` šŸ“ Copying ${ dir }/...` ); - copyDirectory( src, dest, transformPHPContent ); - console.log( ` āœ… ${ dir }/ copied` ); - } - } - - // 2. Copy JavaScript packages. + // 1. Copy JavaScript packages. console.log( '\nšŸ“¦ Copying JavaScript packages...' ); const scriptsConfig = COPY_CONFIG.scripts; const scriptsSrc = path.join( gutenbergBuildDir, scriptsConfig.source ); @@ -1048,112 +970,27 @@ async function main() { console.log( ' āœ… JavaScript packages copied' ); } - // 3. Copy script modules. - console.log( '\nšŸ“¦ Copying script modules...' ); - const modulesConfig = COPY_CONFIG.modules; - const modulesSrc = path.join( gutenbergBuildDir, modulesConfig.source ); - const modulesDest = path.join( wpIncludesDir, modulesConfig.destination ); - - if ( fs.existsSync( modulesSrc ) ) { - // Use the same source map removal transform. - copyDirectory( modulesSrc, modulesDest, removeSourceMaps ); - console.log( ' āœ… Script modules copied' ); - } - - // 4. Copy styles. - console.log( '\nšŸ“¦ Copying styles...' ); - const stylesConfig = COPY_CONFIG.styles; - const stylesSrc = path.join( gutenbergBuildDir, stylesConfig.source ); - const stylesDest = path.join( wpIncludesDir, stylesConfig.destination ); - - if ( fs.existsSync( stylesSrc ) ) { - copyDirectory( stylesSrc, stylesDest ); - console.log( ' āœ… Styles copied' ); - } - - // 5. Copy blocks (unified: scripts, styles, PHP, JSON). + // 2. Copy blocks (unified: scripts, styles, PHP, JSON). console.log( '\nšŸ“¦ Copying blocks...' ); - const blocksDest = path.join( - wpIncludesDir, - COPY_CONFIG.blocks.destination - ); copyBlockAssets( COPY_CONFIG.blocks ); - // 6. Copy theme JSON files (from Gutenberg lib directory). - console.log( '\nšŸ“¦ Copying theme JSON files...' ); - const themeJsonConfig = COPY_CONFIG.themeJson; - const gutenbergLibDir = path.join( gutenbergDir, 'lib' ); - - for ( const fileMap of themeJsonConfig.files ) { - const src = path.join( gutenbergLibDir, fileMap.from ); - const dest = path.join( wpIncludesDir, fileMap.to ); - - if ( fs.existsSync( src ) ) { - let content = fs.readFileSync( src, 'utf8' ); - - if ( themeJsonConfig.transform && fileMap.from === 'theme.json' ) { - // Transform schema URL for Core. - content = content.replace( - '"$schema": "../schemas/json/theme.json"', - '"$schema": "https://schemas.wp.org/trunk/theme.json"' - ); - } - - fs.writeFileSync( dest, content ); - console.log( ` āœ… ${ fileMap.to }` ); - } else { - console.log( ` āš ļø Not found: ${ fileMap.from }` ); - } - } - - // Copy remaining files to wp-includes. - console.log( '\nšŸ“¦ Copying remaining files to wp-includes...' ); - for ( const fileMap of COPY_CONFIG.wpIncludes ) { - const dest = path.join( wpIncludesDir, fileMap.destination ); - fs.mkdirSync( dest, { recursive: true } ); - for ( const src of fileMap.files ) { - const matches = glob.sync( path.join( gutenbergDir, src ) ); - if ( ! matches.length ) { - throw new Error( `No files found matching '${ src }'` ); - } - for ( const match of matches ) { - const destPath = path.join( dest, path.basename( match ) ); - // Apply transformation for manifest.php to remove gutenberg text domain. - if ( path.basename( match ) === 'manifest.php' ) { - let content = fs.readFileSync( match, 'utf8' ); - content = transformManifestPHP( content ); - fs.writeFileSync( destPath, content ); - } else { - fs.copyFileSync( match, destPath ); - } - } - } - } - - // 7. Generate script-modules-packages.php from individual asset files. + // 3. Generate script-modules-packages.php from individual asset files. console.log( '\nšŸ“¦ Generating script-modules-packages.php...' ); generateScriptModulesPackages(); - // 8. Generate script-loader-packages.php. + // 4. Generate script-loader-packages.php. console.log( '\nšŸ“¦ Generating script-loader-packages.php...' ); generateScriptLoaderPackages(); - // 9. Generate require-dynamic-blocks.php and require-static-blocks.php. + // 5. Generate require-dynamic-blocks.php and require-static-blocks.php. console.log( '\nšŸ“¦ Generating block registration files...' ); generateBlockRegistrationFiles(); - // 10. Generate blocks-json.php from block.json files. + // 6. Generate blocks-json.php from block.json files. console.log( '\nšŸ“¦ Generating blocks-json.php...' ); generateBlocksJson(); - // Summary. console.log( '\nāœ… Copy complete!' ); - console.log( '\nšŸ“Š Summary:' ); - console.log( ` PHP infrastructure: ${ phpDest }` ); - console.log( ` JavaScript: ${ scriptsDest }` ); - console.log( ` Script modules: ${ modulesDest }` ); - console.log( ` Styles: ${ stylesDest }` ); - console.log( ` Blocks: ${ blocksDest }` ); } // Run main function. From 8ff38f229187a043db540a04c2d3cf4d1c1edc93 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Fri, 6 Mar 2026 00:00:14 -0500 Subject: [PATCH 2/4] Move script module sourceMappingURL replacement. --- Gruntfile.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 0436ef5dbd391..b9510f42037c7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -620,15 +620,6 @@ module.exports = function(grunt) { // Gutenberg: script modules (gutenberg/build/modules/ -> wp-includes/js/dist/script-modules/). 'gutenberg-modules': { - options: { - process: function( content, srcpath ) { - // Strip sourceMappingURL comments from JS files. - if ( srcpath.endsWith( '.js' ) ) { - return content.replace( /^\s*\/\/#\s*sourceMappingURL=.*$/gm, '' ).trimEnd(); - } - return content; - } - }, files: [ { expand: true, cwd: 'gutenberg/build/modules', @@ -1452,6 +1443,12 @@ module.exports = function(grunt) { BUILD_DIR + 'wp-includes/js/dist/vendor/**/*.js' ], dest: BUILD_DIR + 'wp-includes/js/dist/vendor/' + }, + { + expand: true, + cwd: BUILD_DIR + 'wp-includes/js/dist/script-modules/', + src: [ '**/*.js' ], + dest: BUILD_DIR + 'wp-includes/js/dist/script-modules/', } ] } From ca2a100b0a6a7fe2d47a58e7ecdb4646cef17be6 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Fri, 6 Mar 2026 00:05:35 -0500 Subject: [PATCH 3/4] Fully rely on Grunt to remove sourceMappingURL. --- Gruntfile.js | 17 ++++++----------- tools/gutenberg/copy.js | 36 ++++-------------------------------- 2 files changed, 10 insertions(+), 43 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index b9510f42037c7..ff57446e37a80 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1429,20 +1429,15 @@ module.exports = function(grunt) { }, { expand: true, - flatten: true, - src: [ - BUILD_DIR + 'wp-includes/js/dist/block-editor.js', - BUILD_DIR + 'wp-includes/js/dist/commands.js', - ], - dest: BUILD_DIR + 'wp-includes/js/dist/' + cwd: BUILD_DIR + 'wp-includes/js/dist/', + src: [ '*.js' ], + dest: BUILD_DIR + 'wp-includes/js/dist/', }, { expand: true, - flatten: true, - src: [ - BUILD_DIR + 'wp-includes/js/dist/vendor/**/*.js' - ], - dest: BUILD_DIR + 'wp-includes/js/dist/vendor/' + cwd: BUILD_DIR + 'wp-includes/js/dist/vendor/', + src: [ '**/*.js' ], + dest: BUILD_DIR + 'wp-includes/js/dist/vendor/', }, { expand: true, diff --git a/tools/gutenberg/copy.js b/tools/gutenberg/copy.js index 8421abc3da664..18db83372d87f 100644 --- a/tools/gutenberg/copy.js +++ b/tools/gutenberg/copy.js @@ -856,15 +856,6 @@ async function main() { const scriptsSrc = path.join( gutenbergBuildDir, scriptsConfig.source ); const scriptsDest = path.join( wpIncludesDir, scriptsConfig.destination ); - /* - * Transform function to remove source map comments from all JS files. - * Only match actual source map comments at the start of a line (possibly - * with whitespace), not occurrences inside string literals. - */ - const removeSourceMaps = ( content ) => { - return content.replace( /^\s*\/\/# sourceMappingURL=.*$/gm, '' ).trimEnd(); - }; - if ( fs.existsSync( scriptsSrc ) ) { const entries = fs.readdirSync( scriptsSrc, { withFileTypes: true } ); @@ -899,12 +890,7 @@ async function main() { const srcFile = path.join( src, file ); const destFile = path.join( dest, file ); - let content = fs.readFileSync( - srcFile, - 'utf8' - ); - content = removeSourceMaps( content ); - fs.writeFileSync( destFile, content ); + fs.copyFileSync( srcFile, destFile ); copiedCount++; } } @@ -913,7 +899,7 @@ async function main() { ); } else { // Copy other special directories normally. - copyDirectory( src, dest, removeSourceMaps ); + copyDirectory( src, dest ); console.log( ` āœ… ${ entry.name }/ → ${ destName }/` ); @@ -941,18 +927,7 @@ async function main() { recursive: true, } ); - // Apply source map removal for .js files. - if ( file.endsWith( '.js' ) ) { - let content = fs.readFileSync( - srcFile, - 'utf8' - ); - content = removeSourceMaps( content ); - fs.writeFileSync( destPath, content ); - } else { - // Copy other files as-is (.min.asset.php). - fs.copyFileSync( srcFile, destPath ); - } + fs.copyFileSync( srcFile, destPath ); } } } @@ -960,10 +935,7 @@ async function main() { // Copy root-level JS files. const dest = path.join( scriptsDest, entry.name ); fs.mkdirSync( path.dirname( dest ), { recursive: true } ); - - let content = fs.readFileSync( src, 'utf8' ); - content = removeSourceMaps( content ); - fs.writeFileSync( dest, content ); + fs.copyFileSync( src, dest ); } } From 09886698e8c81e19d4f3e22990a81057c5bf3533 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Fri, 6 Mar 2026 00:09:16 -0500 Subject: [PATCH 4/4] Comment clean up. --- Gruntfile.js | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index ff57446e37a80..2d71eae5f1a59 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -588,16 +588,11 @@ module.exports = function(grunt) { src: 'vendor/composer/ca-bundle/res/cacert.pem', dest: SOURCE_DIR + 'wp-includes/certificates/ca-bundle.crt' }, - - // Gutenberg: PHP infrastructure files (routes.php, pages.php, constants.php, pages/, routes/). + // Gutenberg PHP infrastructure files (routes.php, pages.php, constants.php, pages/, routes/). 'gutenberg-php': { options: { process: function( content ) { - /* - * Fix boot module asset file path for Core's different directory structure. - * FROM: __DIR__ . '/../../modules/boot/index.min.asset.php' - * TO: ABSPATH . WPINC . '/js/dist/script-modules/boot/index.min.asset.php' - */ + // Fix boot module asset file path for Core's different directory structure. return content.replace( /__DIR__\s*\.\s*['"]\/..\/\..\/modules\/boot\/index\.min\.asset\.php['"]/g, "ABSPATH . WPINC . '/js/dist/script-modules/boot/index.min.asset.php'" @@ -617,8 +612,6 @@ module.exports = function(grunt) { dest: WORKING_DIR + 'wp-includes/build/', } ], }, - - // Gutenberg: script modules (gutenberg/build/modules/ -> wp-includes/js/dist/script-modules/). 'gutenberg-modules': { files: [ { expand: true, @@ -627,8 +620,6 @@ module.exports = function(grunt) { dest: WORKING_DIR + 'wp-includes/js/dist/script-modules/', } ], }, - - // Gutenberg: styles (gutenberg/build/styles/ -> wp-includes/css/dist/). 'gutenberg-styles': { files: [ { expand: true, @@ -637,8 +628,6 @@ module.exports = function(grunt) { dest: WORKING_DIR + 'wp-includes/css/dist/', } ], }, - - // Gutenberg: theme JSON files (gutenberg/lib/ -> wp-includes/). 'gutenberg-theme-json': { options: { process: function( content, srcpath ) { @@ -663,16 +652,10 @@ module.exports = function(grunt) { }, ], }, - - // Gutenberg: icon manifest and SVG library (gutenberg/packages/icons/src/ -> wp-includes/icons/). 'gutenberg-icons': { options: { process: function( content, srcpath ) { - /* - * Remove the 'gutenberg' text domain from _x() calls in manifest.php. - * FROM: _x( '...', 'icon label', 'gutenberg' ) - * TO: _x( '...', 'icon label' ) - */ + // Remove the 'gutenberg' text domain from _x() calls in manifest.php. if ( path.basename( srcpath ) === 'manifest.php' ) { return content.replace( /_x\(\s*([^,]+),\s*([^,]+),\s*['"]gutenberg['"]\s*\)/g,