Make WordPress Core

Changeset 61543


Ignore:
Timestamp:
01/28/2026 10:27:19 AM (9 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.