Skip to content

Commit 5b0105a

Browse files
youknowriaddmsnell
authored 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 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. git-svn-id: https://develop.svn.wordpress.org/trunk@61543 602fd350-edb4-49c9-b593-d223f7449a82 (cherry picked from commit 8be3411)
1 parent ac72265 commit 5b0105a

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)