Skip to content

Commit 268f1ee

Browse files
jsnajdryouknowriadramonjdandrewserong
authored andcommitted
Build: detect version and generate asset.php for vendor scripts (WordPress#76811)
* Build: detect version and generate asset.php for vendor scripts * Add backport changelog entry * Move changelog entry from 7.0 to 7.1 Co-authored-by: jsnajdr <jsnajdr@git.wordpress.org> Co-authored-by: youknowriad <youknowriad@git.wordpress.org> Co-authored-by: ramonjd <ramonopoly@git.wordpress.org> Co-authored-by: andrewserong <andrewserong@git.wordpress.org>
1 parent 2ed86e2 commit 268f1ee

File tree

3 files changed

+106
-46
lines changed

3 files changed

+106
-46
lines changed

backport-changelog/7.1/11359.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
https://github.com/WordPress/wordpress-develop/pull/11359
2+
3+
* https://github.com/WordPress/gutenberg/pull/76811

bin/packages/build-vendors.mjs

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import path from 'path';
44
import { fileURLToPath } from 'url';
5+
import { readFile, writeFile, mkdir } from 'fs/promises';
56
import esbuild from 'esbuild';
67

78
const __dirname = path.dirname( fileURLToPath( import.meta.url ) );
@@ -14,27 +15,75 @@ const VENDOR_SCRIPTS = [
1415
name: 'react',
1516
global: 'React',
1617
handle: 'react',
18+
dependencies: [ 'wp-polyfill' ],
1719
},
1820
{
1921
name: 'react-dom',
2022
global: 'ReactDOM',
2123
handle: 'react-dom',
24+
dependencies: [ 'react' ],
2225
},
2326
{
2427
name: 'react/jsx-runtime',
2528
global: 'ReactJSXRuntime',
2629
handle: 'react-jsx-runtime',
30+
dependencies: [ 'react' ],
2731
},
2832
];
2933

34+
/**
35+
* Read the version from a package's package.json in node_modules.
36+
*
37+
* @param {string} packageName npm package name (e.g., 'react', 'react-dom').
38+
* @return {Promise<string>} The package version string.
39+
*/
40+
async function getPackageVersion( packageName ) {
41+
const packageJsonPath = path.join(
42+
ROOT_DIR,
43+
'node_modules',
44+
packageName,
45+
'package.json'
46+
);
47+
const packageJson = JSON.parse(
48+
await readFile( packageJsonPath, 'utf-8' )
49+
);
50+
return packageJson.version;
51+
}
52+
53+
/**
54+
* Generate a .asset.php file for a vendor script.
55+
*
56+
* @param {Object} config Vendor script configuration.
57+
* @param {string} config.handle WordPress script handle.
58+
* @param {string} config.name Package name (e.g., 'react', 'react/jsx-runtime').
59+
* @param {string[]} config.dependencies WordPress script dependencies.
60+
*/
61+
async function generateAssetFile( config ) {
62+
const { handle, name, dependencies } = config;
63+
64+
// The npm package name is the first segment of the name (e.g., 'react/jsx-runtime' -> 'react').
65+
const packageName = name.split( '/' )[ 0 ];
66+
const version = await getPackageVersion( packageName );
67+
68+
const dependenciesString = dependencies
69+
.map( ( dep ) => `'${ dep }'` )
70+
.join( ', ' );
71+
const assetContent = `<?php return array('dependencies' => array(${ dependenciesString }), 'version' => '${ version }');`;
72+
73+
const assetFilePath = path.join( VENDORS_DIR, `${ handle }.min.asset.php` );
74+
await mkdir( path.dirname( assetFilePath ), { recursive: true } );
75+
await writeFile( assetFilePath, assetContent );
76+
}
77+
3078
/**
3179
* Bundle a vendor script from node_modules into an IIFE script.
3280
* This is used to build packages like React that don't ship UMD builds.
3381
*
34-
* @param {Object} config Vendor script configuration.
35-
* @param {string} config.name Package name (e.g., 'react', 'react-dom', 'react/jsx-runtime').
36-
* @param {string} config.global Global variable name (e.g., 'React', 'ReactDOM').
37-
* @param {string} config.handle WordPress script handle (e.g., 'react', 'react-dom').
82+
* @param {Object} config Vendor script configuration.
83+
* @param {string} config.name Package name (e.g., 'react', 'react-dom', 'react/jsx-runtime').
84+
* @param {string} config.global Global variable name (e.g., 'React', 'ReactDOM').
85+
* @param {string} config.handle WordPress script handle (e.g., 'react', 'react-dom').
86+
* @param {string[]} config.dependencies WordPress script dependencies.
3887
* @return {Promise<void>} Promise that resolves when all builds are finished.
3988
*/
4089
async function bundleVendorScript( config ) {
@@ -64,23 +113,29 @@ async function bundleVendorScript( config ) {
64113
},
65114
};
66115

67-
// Build both minified and non-minified versions
68-
await Promise.all(
69-
[ false, true ].map( ( production ) => {
70-
const outputFile = handle + ( production ? '.min.js' : '.js' );
71-
return esbuild.build( {
72-
entryPoints: [ name ],
73-
outfile: path.join( VENDORS_DIR, outputFile ),
74-
bundle: true,
75-
format: 'iife',
76-
globalName: global,
77-
minify: production,
78-
target: 'esnext', // Don't transpile, just bundle.
79-
platform: 'browser',
80-
plugins: [ reactExternalPlugin ],
81-
} );
82-
} )
83-
);
116+
const esbuildOptions = {
117+
entryPoints: [ name ],
118+
bundle: true,
119+
format: 'iife',
120+
globalName: global,
121+
target: 'esnext',
122+
platform: 'browser',
123+
plugins: [ reactExternalPlugin ],
124+
};
125+
126+
await Promise.all( [
127+
esbuild.build( {
128+
...esbuildOptions,
129+
outfile: path.join( VENDORS_DIR, handle + '.js' ),
130+
minify: false,
131+
} ),
132+
esbuild.build( {
133+
...esbuildOptions,
134+
outfile: path.join( VENDORS_DIR, handle + '.min.js' ),
135+
minify: true,
136+
} ),
137+
generateAssetFile( config ),
138+
] );
84139
}
85140

86141
/**

lib/client-assets.php

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -367,32 +367,34 @@ function gutenberg_enqueue_stored_styles( $options = array() ) {
367367
* @param WP_Scripts $scripts WP_Scripts instance.
368368
*/
369369
function gutenberg_register_vendor_scripts( $scripts ) {
370-
$extension = SCRIPT_DEBUG ? '.js' : '.min.js';
371-
372-
gutenberg_override_script(
373-
$scripts,
374-
'react',
375-
gutenberg_url( 'build/scripts/vendors/react' . $extension ),
376-
// WordPress Core in `wp_register_development_scripts` sets `wp-react-refresh-entry` as a dependency to `react` when `SCRIPT_DEBUG` is true.
377-
// We need to preserve that here.
378-
SCRIPT_DEBUG ? array( 'wp-react-refresh-entry', 'wp-polyfill' ) : array( 'wp-polyfill' ),
379-
'18'
380-
);
381-
gutenberg_override_script(
382-
$scripts,
383-
'react-dom',
384-
gutenberg_url( 'build/scripts/vendors/react-dom' . $extension ),
385-
array( 'react' ),
386-
'18'
387-
);
370+
$extension = SCRIPT_DEBUG ? '.js' : '.min.js';
371+
$vendors_dir = gutenberg_dir_path() . 'build/scripts/vendors/';
372+
373+
$vendor_handles = array( 'react', 'react-dom', 'react-jsx-runtime' );
374+
375+
foreach ( $vendor_handles as $handle ) {
376+
$asset_file = $vendors_dir . $handle . '.min.asset.php';
377+
$asset = file_exists( $asset_file ) ? require $asset_file : array();
378+
$dependencies = $asset['dependencies'] ?? array();
379+
$version = $asset['version'] ?? '0';
380+
381+
gutenberg_override_script(
382+
$scripts,
383+
$handle,
384+
gutenberg_url( 'build/scripts/vendors/' . $handle . $extension ),
385+
$dependencies,
386+
$version
387+
);
388+
}
388389

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

0 commit comments

Comments
 (0)