From 3498f0c8d21f13ce53dc477a73499e398324c7d9 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Thu, 26 Mar 2026 12:29:13 +0100 Subject: [PATCH 1/3] Copy React vendor scripts from Gutenberg, update version number --- Gruntfile.js | 13 +++---------- src/wp-includes/script-loader.php | 7 ++++--- tests/phpunit/tests/dependencies/scripts.php | 12 ++++++------ tools/gutenberg/copy.js | 20 ++++++++++++++++---- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 61f18481e23a8..ac8e85faeac21 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -51,8 +51,8 @@ module.exports = function(grunt) { */ '!wp-includes/js/dist', 'wp-includes/js/dist/vendor/*.js', - // Managed by the Gutenberg-related tasks. - '!wp-includes/js/dist/vendor/react-jsx-runtime*', + // Managed by the Gutenberg-related tasks (react, react-dom, react-jsx-runtime). + '!wp-includes/js/dist/vendor/react*', ], // Files sourced from the Gutenberg repository built asset that are ignored by version control. @@ -61,7 +61,7 @@ module.exports = function(grunt) { SOURCE_DIR + 'wp-includes/css/dist', SOURCE_DIR + 'wp-includes/js/dist/*.js', SOURCE_DIR + 'wp-includes/js/dist/script-modules', - SOURCE_DIR + 'wp-includes/js/dist/vendor/react-jsx-runtime*', + SOURCE_DIR + 'wp-includes/js/dist/vendor/react*', ], // Files sourced from the Gutenberg repository built asset that are managed through version control. @@ -455,13 +455,6 @@ module.exports = function(grunt) { [ WORKING_DIR + 'wp-includes/js/dist/vendor/regenerator-runtime.js' ]: [ './node_modules/regenerator-runtime/runtime.js' ], [ WORKING_DIR + 'wp-includes/js/dist/vendor/regenerator-runtime.min.js' ]: [ './node_modules/regenerator-runtime/runtime.js' ], }, - // React libraries: react, react-dom - { - [ WORKING_DIR + 'wp-includes/js/dist/vendor/react.js' ]: [ './node_modules/react/umd/react.development.js' ], - [ WORKING_DIR + 'wp-includes/js/dist/vendor/react.min.js' ]: [ './node_modules/react/umd/react.production.min.js' ], - [ WORKING_DIR + 'wp-includes/js/dist/vendor/react-dom.js' ]: [ './node_modules/react-dom/umd/react-dom.development.js' ], - [ WORKING_DIR + 'wp-includes/js/dist/vendor/react-dom.min.js' ]: [ './node_modules/react-dom/umd/react-dom.production.min.js' ], - }, // Polyfills { // @wordpress/babel-preset-default diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 01ef8a348e5bc..109472b2213e6 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -106,9 +106,10 @@ function wp_default_packages_vendor( $scripts ) { ); $vendor_scripts_versions = array( - 'react' => '18.3.1.1', // Final .1 due to switch to UMD build, can be removed in the next update. - 'react-dom' => '18.3.1.1', // Final .1 due to switch to UMD build, can be removed in the next update. - 'react-jsx-runtime' => '18.3.1', + // Add `-wp` suffix to React package versions to differentiate from the UMD builds. We build our own bundles now. + 'react' => '18.3.1-wp', + 'react-dom' => '18.3.1-wp', + 'react-jsx-runtime' => '18.3.1-wp', 'regenerator-runtime' => '0.14.1', 'moment' => '2.30.1', 'lodash' => '4.18.1', diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php index bbf301fe424cd..a805010caf3b1 100644 --- a/tests/phpunit/tests/dependencies/scripts.php +++ b/tests/phpunit/tests/dependencies/scripts.php @@ -4161,18 +4161,18 @@ public function test_vendor_script_versions_registered_manually( $script, $handl } /* - * Append '.1' to the version number for React and ReactDOM. + * Append '-wp' to the version number for React-related scripts. * - * This is due to a change in the build to use the UMD version of the + * This is due to a change in the build to stop using the UMD version of the * scripts, requiring a different version number in order to break the * caches of some CDNs. * - * This can be removed in the next update to the packages. + * This can be removed in the next update to the packages (to React 19). * - * See https://core.trac.wordpress.org/ticket/62422 + * See https://core.trac.wordpress.org/ticket/64958 */ - if ( in_array( $handle, array( 'react', 'react-dom' ), true ) ) { - $package_json[ $script ] .= '.1'; + if ( in_array( $handle, array( 'react', 'react-dom', 'react-jsx-runtime' ), true ) ) { + $package_json[ $script ] .= '-wp'; } $script_query = $wp_scripts->query( $handle, 'registered' ); diff --git a/tools/gutenberg/copy.js b/tools/gutenberg/copy.js index 3da78e4b14611..20a0dee905a92 100644 --- a/tools/gutenberg/copy.js +++ b/tools/gutenberg/copy.js @@ -183,19 +183,31 @@ function copyScripts( config ) { ) { /* * Copy special directories with rename (vendors/ → vendor/). - * Only copy react-jsx-runtime from vendors (react and react-dom come from Core's node_modules). + * Copy the React vendor scripts (react, react-dom, react-jsx-runtime) + * built by Gutenberg. */ const destName = config.directoryRenames[ entry.name ]; const dest = path.join( scriptsDest, destName ); if ( entry.name === 'vendors' ) { - // Only copy react-jsx-runtime files, skip react and react-dom. + /* + * Only copy these React scripts. The build directory also + * contains future versions (react-19, react-dom-19, etc.) + * that should not be copied. + */ + const copiedVendorScripts = [ + 'react', + 'react-dom', + 'react-jsx-runtime', + ]; const vendorFiles = fs.readdirSync( src ); let copiedCount = 0; fs.mkdirSync( dest, { recursive: true } ); for ( const file of vendorFiles ) { if ( - file.startsWith( 'react-jsx-runtime' ) && + copiedVendorScripts.some( ( prefix ) => + file.startsWith( prefix + '.' ) + ) && file.endsWith( '.js' ) ) { const srcFile = path.join( src, file ); @@ -206,7 +218,7 @@ function copyScripts( config ) { } } console.log( - ` ✅ ${ entry.name }/ → ${ destName }/ (react-jsx-runtime only, ${ copiedCount } files)` + ` ✅ ${ entry.name }/ → ${ destName }/ (${ copiedCount } files)` ); } } else { From 177e2a6eba3e8f75e95a1ba22a3cf7511b202f16 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Thu, 16 Jul 2026 10:20:15 +0200 Subject: [PATCH 2/3] Remove legacy react dependencies and related test code --- package-lock.json | 13 +++++++---- package.json | 3 --- tests/phpunit/tests/dependencies/scripts.php | 24 +++----------------- 3 files changed, 11 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9bb47a3281b6d..c057f2d9f667f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,9 +31,6 @@ "moment": "2.30.1", "objectFitPolyfill": "2.3.5", "polyfill-library": "4.8.0", - "react": "18.3.1", - "react-dom": "18.3.1", - "react-is": "18.3.1", "regenerator-runtime": "0.14.1", "underscore": "1.13.8", "whatwg-fetch": "3.6.20", @@ -22345,7 +22342,8 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "node_modules/js-yaml": { "version": "3.14.2", @@ -23425,6 +23423,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -27872,6 +27871,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -27883,6 +27883,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -27894,7 +27895,8 @@ "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true }, "node_modules/react-refresh": { "version": "0.14.0", @@ -28917,6 +28919,7 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0" } diff --git a/package.json b/package.json index 1fe69cace1d06..96305f8268fda 100644 --- a/package.json +++ b/package.json @@ -104,9 +104,6 @@ "moment": "2.30.1", "objectFitPolyfill": "2.3.5", "polyfill-library": "4.8.0", - "react": "18.3.1", - "react-dom": "18.3.1", - "react-is": "18.3.1", "regenerator-runtime": "0.14.1", "underscore": "1.13.8", "whatwg-fetch": "3.6.20", diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php index a805010caf3b1..b638533bec7d9 100644 --- a/tests/phpunit/tests/dependencies/scripts.php +++ b/tests/phpunit/tests/dependencies/scripts.php @@ -4160,21 +4160,6 @@ public function test_vendor_script_versions_registered_manually( $script, $handl $handle = $script; } - /* - * Append '-wp' to the version number for React-related scripts. - * - * This is due to a change in the build to stop using the UMD version of the - * scripts, requiring a different version number in order to break the - * caches of some CDNs. - * - * This can be removed in the next update to the packages (to React 19). - * - * See https://core.trac.wordpress.org/ticket/64958 - */ - if ( in_array( $handle, array( 'react', 'react-dom', 'react-jsx-runtime' ), true ) ) { - $package_json[ $script ] .= '-wp'; - } - $script_query = $wp_scripts->query( $handle, 'registered' ); $this->assertNotFalse( $script_query, "The script '{$handle}' should be registered." ); @@ -4210,9 +4195,6 @@ public function data_vendor_script_versions_registered_manually() { 'objectFitPolyfill' => array( 'objectFitPolyfill', 'wp-polyfill-object-fit' ), 'polyfill-library (dom rect)' => array( 'polyfill-library', 'wp-polyfill-dom-rect' ), 'polyfill-library (node contains)' => array( 'polyfill-library', 'wp-polyfill-node-contains' ), - 'react (jsx-runtime)' => array( 'react', 'react-jsx-runtime' ), - 'react (React)' => array( 'react' ), - 'react-dom' => array( 'react-dom' ), 'regenerator-runtime' => array( 'regenerator-runtime' ), 'underscore' => array( 'underscore' ), 'vanilla-js-hoverintent' => array( 'hoverintent', 'hoverintent-js' ), @@ -4254,14 +4236,14 @@ static function ( $dependency ) { ); // Exclude packages that are not registered in WordPress. - $exclude = array( 'react-is', 'json2php', 'espree' ); + $exclude = array( 'json2php', 'espree' ); $package_json_dependencies = array_diff( $package_json_dependencies, $exclude ); /* * Ensure the arrays are unique. * - * This is for the react package as it is included in the data provider - * as both `react` and `react-jsx-runtime`. + * Some packages are included in the data provider more than once, e.g. + * `polyfill-library` provides both the DOM rect and node contains scripts. */ $package_json_dependencies = array_unique( $package_json_dependencies ); $data_provider_dependencies = array_unique( $data_provider_dependencies ); From 8f711427d8742c3956fa523bcd098aa22f6c0183 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Thu, 16 Jul 2026 12:25:56 +0200 Subject: [PATCH 3/3] Move Gutenberg vendor copying to Gruntfile --- Gruntfile.js | 12 +++-- tools/gutenberg/copy.js | 103 +++++++++++----------------------------- 2 files changed, 37 insertions(+), 78 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index ac8e85faeac21..fe3f8c5cba2f4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -51,8 +51,6 @@ module.exports = function(grunt) { */ '!wp-includes/js/dist', 'wp-includes/js/dist/vendor/*.js', - // Managed by the Gutenberg-related tasks (react, react-dom, react-jsx-runtime). - '!wp-includes/js/dist/vendor/react*', ], // Files sourced from the Gutenberg repository built asset that are ignored by version control. @@ -61,7 +59,6 @@ module.exports = function(grunt) { SOURCE_DIR + 'wp-includes/css/dist', SOURCE_DIR + 'wp-includes/js/dist/*.js', SOURCE_DIR + 'wp-includes/js/dist/script-modules', - SOURCE_DIR + 'wp-includes/js/dist/vendor/react*', ], // Files sourced from the Gutenberg repository built asset that are managed through version control. @@ -455,6 +452,15 @@ module.exports = function(grunt) { [ WORKING_DIR + 'wp-includes/js/dist/vendor/regenerator-runtime.js' ]: [ './node_modules/regenerator-runtime/runtime.js' ], [ WORKING_DIR + 'wp-includes/js/dist/vendor/regenerator-runtime.min.js' ]: [ './node_modules/regenerator-runtime/runtime.js' ], }, + // React libraries: react, react-dom, react-jsx-runtime (sourced from the Gutenberg build). + { + [ WORKING_DIR + 'wp-includes/js/dist/vendor/react.js' ]: [ './gutenberg/build/scripts/vendors/react.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/react.min.js' ]: [ './gutenberg/build/scripts/vendors/react.min.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/react-dom.js' ]: [ './gutenberg/build/scripts/vendors/react-dom.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/react-dom.min.js' ]: [ './gutenberg/build/scripts/vendors/react-dom.min.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/react-jsx-runtime.js' ]: [ './gutenberg/build/scripts/vendors/react-jsx-runtime.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/react-jsx-runtime.min.js' ]: [ './gutenberg/build/scripts/vendors/react-jsx-runtime.min.js' ], + }, // Polyfills { // @wordpress/babel-preset-default diff --git a/tools/gutenberg/copy.js b/tools/gutenberg/copy.js index 20a0dee905a92..792a4f083c30d 100644 --- a/tools/gutenberg/copy.js +++ b/tools/gutenberg/copy.js @@ -34,10 +34,8 @@ const wpIncludesDir = path.join( rootDir, 'src', 'wp-includes' ); * * @typedef ScriptsConfig * @type {object} - * @property {string} source - Gutenberg-relative source directory (e.g. `'scripts'`). - * @property {string} destination - Subpath under `wp-includes/` where packages land (e.g. `'js/dist'`). - * @property {boolean} copyDirectories - Whether to copy whole directories (with optional renames) as-is. - * @property {Record} directoryRenames - Map of source directory name → destination directory name. + * @property {string} source - Gutenberg-relative source directory (e.g. `'scripts'`). + * @property {string} destination - Subpath under `wp-includes/` where packages land (e.g. `'js/dist'`). */ /** @@ -69,11 +67,6 @@ const COPY_CONFIG = { scripts: { source: 'scripts', destination: 'js/dist', - copyDirectories: true, - // Rename vendors/ to vendor/ when copying. - directoryRenames: { - vendors: 'vendor', - }, }, /* @@ -175,75 +168,35 @@ function copyScripts( config ) { const src = path.join( scriptsSrc, entry.name ); if ( entry.isDirectory() ) { - // Check if this should be copied as a directory (like vendors/). - if ( - config.copyDirectories && - config.directoryRenames && - config.directoryRenames[ entry.name ] - ) { - /* - * Copy special directories with rename (vendors/ → vendor/). - * Copy the React vendor scripts (react, react-dom, react-jsx-runtime) - * built by Gutenberg. - */ - const destName = config.directoryRenames[ entry.name ]; - const dest = path.join( scriptsDest, destName ); - - if ( entry.name === 'vendors' ) { - /* - * Only copy these React scripts. The build directory also - * contains future versions (react-19, react-dom-19, etc.) - * that should not be copied. - */ - const copiedVendorScripts = [ - 'react', - 'react-dom', - 'react-jsx-runtime', - ]; - const vendorFiles = fs.readdirSync( src ); - let copiedCount = 0; - fs.mkdirSync( dest, { recursive: true } ); - for ( const file of vendorFiles ) { - if ( - copiedVendorScripts.some( ( prefix ) => - file.startsWith( prefix + '.' ) - ) && - file.endsWith( '.js' ) - ) { - const srcFile = path.join( src, file ); - const destFile = path.join( dest, file ); - - fs.copyFileSync( srcFile, destFile ); - copiedCount++; - } - } - console.log( - ` ✅ ${ entry.name }/ → ${ destName }/ (${ copiedCount } files)` + /* + * The vendors/ directory (react, react-dom, react-jsx-runtime) is + * copied by the copy:vendor-js Grunt task, not here. + */ + if ( entry.name === 'vendors' ) { + continue; + } + + /* + * Flatten package structure: package-name/index.js → package-name.js. + * This matches Core's expected file structure. + */ + const packageFiles = fs.readdirSync( src ); + + for ( const file of packageFiles ) { + if ( /^index\.(js|min\.js)$/.test( file ) ) { + const srcFile = path.join( src, file ); + // Replace 'index.' with 'package-name.'. + const destFile = file.replace( + /^index\./, + `${ entry.name }.` ); - } - } else { - /* - * Flatten package structure: package-name/index.js → package-name.js. - * This matches Core's expected file structure. - */ - const packageFiles = fs.readdirSync( src ); - - for ( const file of packageFiles ) { - if ( /^index\.(js|min\.js)$/.test( file ) ) { - const srcFile = path.join( src, file ); - // Replace 'index.' with 'package-name.'. - const destFile = file.replace( - /^index\./, - `${ entry.name }.` - ); - const destPath = path.join( scriptsDest, destFile ); + const destPath = path.join( scriptsDest, destFile ); - fs.mkdirSync( path.dirname( destPath ), { - recursive: true, - } ); + fs.mkdirSync( path.dirname( destPath ), { + recursive: true, + } ); - fs.copyFileSync( srcFile, destPath ); - } + fs.copyFileSync( srcFile, destPath ); } } } else if ( entry.isFile() && entry.name.endsWith( '.js' ) ) {