What you need to know before reading:
external to exclude react. At the same time, the mainstream plugin for handling react hot reload, react-refresh-webpack-plugin, imposes a strict ordering requirement on an externally loaded react core, which breaks hot reload.We want the react dependency of every project in this system to point at the same react core file, so once all projects in the system are built, the react version is fixed. We can achieve this by modifying webpack.config.js and the project's HTML template:
For webpack.config.js, add:
javascript{ //... external: ['react','react-dom'] //... }
For the HTML template (the exact syntax here depends on the template parser you use):
HTML5<script data-tn="react-bundle" type="text/javascript" src="REACT_BUNDLE_URL"></script> <script data-tn="react-dom-bundle" type="text/javascript" src="REACT_DOM_BUNDLE_URL"></script>
Here reactBundlePath and reactDomBundlePath are the results of compiling react separately; they correspond to two js files.
At this point react core is externalized. Every sub-project, when developed on its own, can modify its own HTML template to use the shared react core, and the main system and sub-systems always use the same react instance.
Before webpack 5, you could add this to webpack:
javascript{ //... resolve:{ //... alias:{ '<module_name>': 'project/root/path/provide/modules' } } //... }
Then you could easily use the functionality exposed by another sub-project via import * as MouduleName from 'module_name'.
Because react-refresh-webpack-plugin imposes a strict ordering requirement on an externally loaded react core, we need to change the project's build output from a single entry to multiple entries, and control their load order in the HTML template.
For webpack.config.js, change:
javascript{ //... entry: isDevelopment ? { whm: 'webpack-hot-middleware/client?quiet=true&reload=true&path=/<default>/<route>/__webpack_hmr&timeout=2000', //required when using webpack-hot-middleware as the hot reload service reactRefreshEntry: '@pmmmwh/react-refresh-webpack-plugin/client/ReactRefreshEntry.js', //makes react perform partial updates main: clientEntry, //the project's own entry file } : clientEntry,//the project's own entry file //... module: { rules: [ { oneOf: [ { test: /\.(js|ts|tsx)$/, exclude: /node_modules/, loader: 'babel-loader', options: { plugins: ['lodash', isDev && require.resolve('react-refresh/babel')].filter( Boolean ), presets: [['@babel/env']], }, }, ], }, ], }, //... plugins: defultPlugins.concat(isDevelopment ? [ new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin(), new ReactRefreshWebpackPlugin({ overlay: { sockIntegration: 'whm', }, }), ]:[]) }
For the HTML template (the exact syntax here depends on the template parser you use):
HTML5<!-- Development entry points --> <script type="text/javascript" src="BUNDLE_BASE_URL/client.bundle.js"></script> <script type="text/javascript" src="BUNDLE_BASE_URL/vendors.client.bundle.js"></script> <script type="text/javascript" src="BUNDLE_BASE_URL/whm.client.bundle.js"></script> <script type="text/javascript" src="BUNDLE_BASE_URL/reactRefreshEntry.client.bundle.js"></script> <script type="text/javascript" src="BUNDLE_BASE_URL/main.client.bundle.js"></script> <!-- Production: render one script tag per client bundle manifest entry. --> <script type="text/javascript" src="BUNDLE_BASE_URL/CLIENT_BUNDLE_FILE"></script>
That completes local hot reload for a React project inside a monorepo.