Make WordPress Core

Changeset 61543


Ignore:
Timestamp:
01/28/2026 10:27:19 AM (2 months ago)
Author:
youknowriad
Message:

Build/Test Tools: Fix React Refresh hot reloading for block plugins.

The react-refresh-entry.js script was bundling its own copy of
react-refresh/runtime instead of using the window.ReactRefreshRuntime
global set up by react-refresh-runtime.js. This created two separate
runtime instances: the entry script set up hooks on its bundled copy, while
plugins called performReactRefresh() on the window global — a different
instance with no hooks registered.

This splits the development webpack config into two configs so that
externals only applies to the entry script. The runtime config bundles
react-refresh/runtime and exposes it as window.ReactRefreshRuntime,
while the entry config uses that global as an external.

Props manzoorwanijk, wildworks.
See #64393.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/tools/webpack/development.js

    r61489 r61543  
    1515 * using `@wordpress/scripts` with the `--hot` flag.
    1616 *
     17 * Returns two separate configs:
     18 * 1. Runtime config - bundles react-refresh/runtime and exposes it as window.ReactRefreshRuntime
     19 * 2. Entry config - uses the window global as an external to ensure both scripts share the same runtime instance
     20 *
    1721 * @param {Object} env             Environment options.
    1822 * @param {string} env.buildTarget Build target directory.
    1923 * @param {boolean} env.watch      Whether to watch for changes.
    20  * @return {Object} Webpack configuration object.
     24 * @return {Object[]} Array of webpack configuration objects.
    2125 */
    2226module.exports = function( env = { buildTarget: 'src/', watch: false } ) {
    2327    const buildTarget = env.buildTarget || 'src/';
    2428
    25     const entry = {
    26         // React Refresh runtime - exposes ReactRefreshRuntime global.
    27         [ buildTarget + 'wp-includes/js/dist/development/react-refresh-runtime.js' ]: {
    28             import: 'react-refresh/runtime',
    29             library: {
    30                 name: 'ReactRefreshRuntime',
    31                 type: 'window',
    32             },
    33         },
    34         [ buildTarget + 'wp-includes/js/dist/development/react-refresh-runtime.min.js' ]: {
    35             import: 'react-refresh/runtime',
    36             library: {
    37                 name: 'ReactRefreshRuntime',
    38                 type: 'window',
    39             },
    40         },
    41         // React Refresh entry - injects runtime into global hook before React loads.
    42         [ buildTarget + 'wp-includes/js/dist/development/react-refresh-entry.js' ]:
    43             '@pmmmwh/react-refresh-webpack-plugin/client/ReactRefreshEntry.js',
    44         [ buildTarget + 'wp-includes/js/dist/development/react-refresh-entry.min.js' ]:
    45             '@pmmmwh/react-refresh-webpack-plugin/client/ReactRefreshEntry.js',
    46     };
    47 
    48     return {
     29    const baseConfig = {
    4930        target: 'browserslist',
    5031        // Must use development mode to preserve process.env.NODE_ENV checks
     
    5334        devtool: false,
    5435        cache: true,
    55         entry,
    5636        output: {
    5737            path: baseDir,
     
    7050        watch: env.watch,
    7151    };
     52
     53    // Config for react-refresh-runtime.js - bundles the runtime and exposes
     54    // it as window.ReactRefreshRuntime. No externals - this creates the global.
     55    const runtimeConfig = {
     56        ...baseConfig,
     57        name: 'runtime',
     58        entry: {
     59            [ buildTarget + 'wp-includes/js/dist/development/react-refresh-runtime.js' ]: {
     60                import: 'react-refresh/runtime',
     61                library: {
     62                    name: 'ReactRefreshRuntime',
     63                    type: 'window',
     64                },
     65            },
     66            [ buildTarget + 'wp-includes/js/dist/development/react-refresh-runtime.min.js' ]: {
     67                import: 'react-refresh/runtime',
     68                library: {
     69                    name: 'ReactRefreshRuntime',
     70                    type: 'window',
     71                },
     72            },
     73        },
     74    };
     75
     76    // Config for react-refresh-entry.js - uses window.ReactRefreshRuntime as an
     77    // external instead of bundling its own copy. This ensures the hooks set up
     78    // by the entry are on the same runtime instance that plugins use for
     79    // performReactRefresh().
     80    const entryConfig = {
     81        ...baseConfig,
     82        name: 'entry',
     83        entry: {
     84            [ buildTarget + 'wp-includes/js/dist/development/react-refresh-entry.js' ]:
     85                '@pmmmwh/react-refresh-webpack-plugin/client/ReactRefreshEntry.js',
     86            [ buildTarget + 'wp-includes/js/dist/development/react-refresh-entry.min.js' ]:
     87                '@pmmmwh/react-refresh-webpack-plugin/client/ReactRefreshEntry.js',
     88        },
     89        externals: {
     90            'react-refresh/runtime': 'ReactRefreshRuntime',
     91        },
     92    };
     93
     94    return [ runtimeConfig, entryConfig ];
    7295};
  • trunk/webpack.config.js

    r61487 r61543  
    1616    // Blocks, packages, script modules, and vendors are now sourced from
    1717    // the Gutenberg build (see tools/gutenberg/copy-gutenberg-build.js).
     18    // Note: developmentConfig returns an array of configs, so we spread it.
    1819    const config = [
    1920        mediaConfig( env ),
    20         developmentConfig( env ),
     21        ...developmentConfig( env ),
    2122    ];
    2223
Note: See TracChangeset for help on using the changeset viewer.