Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backport-changelog/7.1/11359.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
https://github.com/WordPress/wordpress-develop/pull/11359
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this mean as 7.0 backport or 7.1 (I guess that depends on whether the change of the UMD builds happened on 7.0 or not)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the https://core.trac.wordpress.org/ticket/64958 discussion we settled that it's for 7.1. I moved the changelog entry to the right directory.


* https://github.com/WordPress/gutenberg/pull/76811
97 changes: 76 additions & 21 deletions bin/packages/build-vendors.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import path from 'path';
import { fileURLToPath } from 'url';
import { readFile, writeFile, mkdir } from 'fs/promises';
import esbuild from 'esbuild';

const __dirname = path.dirname( fileURLToPath( import.meta.url ) );
Expand All @@ -14,27 +15,75 @@ const VENDOR_SCRIPTS = [
name: 'react',
global: 'React',
handle: 'react',
dependencies: [ 'wp-polyfill' ],
},
{
name: 'react-dom',
global: 'ReactDOM',
handle: 'react-dom',
dependencies: [ 'react' ],
},
{
name: 'react/jsx-runtime',
global: 'ReactJSXRuntime',
handle: 'react-jsx-runtime',
dependencies: [ 'react' ],
},
];

/**
* Read the version from a package's package.json in node_modules.
*
* @param {string} packageName npm package name (e.g., 'react', 'react-dom').
* @return {Promise<string>} The package version string.
*/
async function getPackageVersion( packageName ) {
const packageJsonPath = path.join(
ROOT_DIR,
'node_modules',
packageName,
'package.json'
);
const packageJson = JSON.parse(
await readFile( packageJsonPath, 'utf-8' )
);
return packageJson.version;
}

/**
* Generate a .asset.php file for a vendor script.
*
* @param {Object} config Vendor script configuration.
* @param {string} config.handle WordPress script handle.
* @param {string} config.name Package name (e.g., 'react', 'react/jsx-runtime').
* @param {string[]} config.dependencies WordPress script dependencies.
*/
async function generateAssetFile( config ) {
const { handle, name, dependencies } = config;

// The npm package name is the first segment of the name (e.g., 'react/jsx-runtime' -> 'react').
const packageName = name.split( '/' )[ 0 ];
const version = await getPackageVersion( packageName );

const dependenciesString = dependencies
.map( ( dep ) => `'${ dep }'` )
.join( ', ' );
const assetContent = `<?php return array('dependencies' => array(${ dependenciesString }), 'version' => '${ version }');`;

const assetFilePath = path.join( VENDORS_DIR, `${ handle }.min.asset.php` );
await mkdir( path.dirname( assetFilePath ), { recursive: true } );
await writeFile( assetFilePath, assetContent );
}

/**
* Bundle a vendor script from node_modules into an IIFE script.
* This is used to build packages like React that don't ship UMD builds.
*
* @param {Object} config Vendor script configuration.
* @param {string} config.name Package name (e.g., 'react', 'react-dom', 'react/jsx-runtime').
* @param {string} config.global Global variable name (e.g., 'React', 'ReactDOM').
* @param {string} config.handle WordPress script handle (e.g., 'react', 'react-dom').
* @param {Object} config Vendor script configuration.
* @param {string} config.name Package name (e.g., 'react', 'react-dom', 'react/jsx-runtime').
* @param {string} config.global Global variable name (e.g., 'React', 'ReactDOM').
* @param {string} config.handle WordPress script handle (e.g., 'react', 'react-dom').
* @param {string[]} config.dependencies WordPress script dependencies.
* @return {Promise<void>} Promise that resolves when all builds are finished.
*/
async function bundleVendorScript( config ) {
Expand Down Expand Up @@ -64,23 +113,29 @@ async function bundleVendorScript( config ) {
},
};

// Build both minified and non-minified versions
await Promise.all(
[ false, true ].map( ( production ) => {
const outputFile = handle + ( production ? '.min.js' : '.js' );
return esbuild.build( {
entryPoints: [ name ],
outfile: path.join( VENDORS_DIR, outputFile ),
bundle: true,
format: 'iife',
globalName: global,
minify: production,
target: 'esnext', // Don't transpile, just bundle.
platform: 'browser',
plugins: [ reactExternalPlugin ],
} );
} )
);
const esbuildOptions = {
entryPoints: [ name ],
bundle: true,
format: 'iife',
globalName: global,
target: 'esnext',
platform: 'browser',
plugins: [ reactExternalPlugin ],
};

await Promise.all( [
esbuild.build( {
...esbuildOptions,
outfile: path.join( VENDORS_DIR, handle + '.js' ),
minify: false,
} ),
esbuild.build( {
...esbuildOptions,
outfile: path.join( VENDORS_DIR, handle + '.min.js' ),
minify: true,
} ),
generateAssetFile( config ),
] );
}

/**
Expand Down
52 changes: 27 additions & 25 deletions lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -367,32 +367,34 @@ function gutenberg_enqueue_stored_styles( $options = array() ) {
* @param WP_Scripts $scripts WP_Scripts instance.
*/
function gutenberg_register_vendor_scripts( $scripts ) {
$extension = SCRIPT_DEBUG ? '.js' : '.min.js';

gutenberg_override_script(
$scripts,
'react',
gutenberg_url( 'build/scripts/vendors/react' . $extension ),
// WordPress Core in `wp_register_development_scripts` sets `wp-react-refresh-entry` as a dependency to `react` when `SCRIPT_DEBUG` is true.
// We need to preserve that here.
SCRIPT_DEBUG ? array( 'wp-react-refresh-entry', 'wp-polyfill' ) : array( 'wp-polyfill' ),
'18'
);
gutenberg_override_script(
$scripts,
'react-dom',
gutenberg_url( 'build/scripts/vendors/react-dom' . $extension ),
array( 'react' ),
'18'
);
$extension = SCRIPT_DEBUG ? '.js' : '.min.js';
$vendors_dir = gutenberg_dir_path() . 'build/scripts/vendors/';

$vendor_handles = array( 'react', 'react-dom', 'react-jsx-runtime' );

foreach ( $vendor_handles as $handle ) {
$asset_file = $vendors_dir . $handle . '.min.asset.php';
$asset = file_exists( $asset_file ) ? require $asset_file : array();
$dependencies = $asset['dependencies'] ?? array();
$version = $asset['version'] ?? '0';

gutenberg_override_script(
$scripts,
$handle,
gutenberg_url( 'build/scripts/vendors/' . $handle . $extension ),
$dependencies,
$version
);
}

gutenberg_override_script(
$scripts,
'react-jsx-runtime',
gutenberg_url( 'build/scripts/vendors/react-jsx-runtime' . $extension ),
array( 'react' ),
'18'
);
// WordPress Core in `wp_register_development_scripts` sets `wp-react-refresh-entry`
// as a dependency to `react` when `SCRIPT_DEBUG` is true. Preserve that here.
if ( SCRIPT_DEBUG ) {
$react = $scripts->query( 'react', 'registered' );
if ( $react && ! in_array( 'wp-react-refresh-entry', $react->deps, true ) ) {
$react->deps[] = 'wp-react-refresh-entry';
}
}
}
add_action( 'wp_default_scripts', 'gutenberg_register_vendor_scripts' );

Expand Down
Loading