Skip to content

Commit fd7c81b

Browse files
youknowriadclaude
andcommitted
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. This created two separate runtime instances: one in the entry script where hooks were set up, and one as the window global that plugins use for performReactRefresh(). Since they were different instances, the refresh never triggered. This fix splits the webpack development config into two separate configs: - Runtime config: bundles react-refresh and exposes it as window.ReactRefreshRuntime (no externals) - Entry config: uses window.ReactRefreshRuntime as an external, ensuring hooks are set up on the same runtime instance Follow-up to [60055]. Props youknowriad. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent cfa06b1 commit fd7c81b

2 files changed

Lines changed: 51 additions & 27 deletions

File tree

tools/webpack/development.js

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,45 +14,25 @@ const { baseDir } = require( './shared' );
1414
* These scripts enable hot module replacement for plugins
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
*/
2226
module.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
5132
// in the source files. These scripts are only used during development.
5233
mode: 'development',
5334
devtool: false,
5435
cache: true,
55-
entry,
5636
output: {
5737
path: baseDir,
5838
filename: '[name]',
@@ -69,4 +49,47 @@ module.exports = function( env = { buildTarget: 'src/', watch: false } ) {
6949
},
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
};

webpack.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ module.exports = function (
1515
// Only building Core-specific media files and development scripts.
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

2324
return config;

0 commit comments

Comments
 (0)