diff --git a/Gruntfile.js b/Gruntfile.js index b2d64ea9b7216..2d71eae5f1a59 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -587,7 +587,97 @@ 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. + 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-modules': { + files: [ { + expand: true, + cwd: 'gutenberg/build/modules', + src: [ '**/*', '!**/*.map' ], + dest: WORKING_DIR + 'wp-includes/js/dist/script-modules/', + } ], + }, + 'gutenberg-styles': { + files: [ { + expand: true, + cwd: 'gutenberg/build/styles', + src: [ '**/*', '!**/*.map' ], + dest: WORKING_DIR + 'wp-includes/css/dist/', + } ], + }, + '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-icons': { + options: { + process: function( content, srcpath ) { + // 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, + '_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: { @@ -1322,20 +1412,21 @@ 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, + cwd: BUILD_DIR + 'wp-includes/js/dist/script-modules/', + src: [ '**/*.js' ], + dest: BUILD_DIR + 'wp-includes/js/dist/script-modules/', } ] } @@ -1500,7 +1591,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 +2039,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 +2055,7 @@ module.exports = function(grunt) { 'build:js', 'build:css', 'build:codemirror', - 'gutenberg:copy', + 'build:gutenberg', 'copy-vendor-scripts', 'build:certificates' ] ); @@ -1967,7 +2067,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..18db83372d87f 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,56 +850,12 @@ 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 ); 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 } ); @@ -977,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++; } } @@ -991,7 +899,7 @@ async function main() { ); } else { // Copy other special directories normally. - copyDirectory( src, dest, removeSourceMaps ); + copyDirectory( src, dest ); console.log( ` āœ… ${ entry.name }/ → ${ destName }/` ); @@ -1019,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 ); } } } @@ -1038,122 +935,34 @@ 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 ); } } 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.