monorepo结构下启动react项目局部热更新
基础知识#
阅读之前你需要知道的知识包括
- 什么是monorepo
- monorepo的一些应用场景
- webpack的配置的编写
- 什么是pnpm
遇到的问题#
- 多子项目单独开发时,如何确定react版本?
- 如何保证主项目加载子项目作为组件时,二者公用同一个react实例?
- 开发时,如何处理互相存在依赖子系统?
- 外置化加载react&react-dom时,hot-reload失效
问题原因&分析#
- 为什么会引入问题[1]:这是因为使用用monorepo这个结构时,我们希望所有的子系统和主系统(系统的入口),使用同一个react版本,以便系统整体的核心依赖的控制,并且减少多版本兼容带来的额外的bug。
- 为什么会引入问题[2]:
- hooks的使用要求二者必须使用的是一个react实例
- 对于context,它的使用也依赖于同一react实例
- 为什么会引入问题[3]:当我们同时满足问题[1]和[2]时,react和react-dom将不能被打包到一个bandle包中,所以在webpack层面,我们需要使用external,排除react。与此同时,由于主流的用于处理react热更新的插件,react-refresh-webpack-plugin对于外置加载的react core有严格的顺序要求,所以导致了热更新的失效
如何解决问题?#
多子项目单独开发时,如何确定react版本,以及如何保证主项目加载子项目作为组件时,二者公用同一个react实例#
我们期待所有这个系统中,所有项目的react依赖都指向同一个react
core文件,这样在系统所有项目都构建完成后,react的版本就确定了下来,所以我们可以修改webpack.config.js
和项目的HTML模版来达到这个目标:
对于webpack.config.js
增加: 1
2
3
4
5{
//...
external: ['react','react-dom']
//...
}
1 | <script data-tn="react-bundle" type="text/javascript" src="{{reactBundlePath | safe}}"><script> |
这里的 reactBundlePath
和
reactDomBundlePath
是react单独编译后的结果,对应的是两个js文件。
至此react core就完成外置化,所有的子项目在单独开发时,都可以修改自己的HTML模版,以使用公共的react core,并且主系统和子系统的react实例始终一直,在
开发时,如何处理互相存在依赖子系统#
在webpack5之前,可以在webpack中增加: 1
2
3
4
5
6
7
8
9
10{
//...
resolve:{
//...
alias:{
'<module_name>': 'project/root/path/provide/modules'
}
}
//...
}import * as MouduleName from 'module_name'
的方式使用另一个子项目暴露的功能了。
外置化加载react&react-dom时,hot-reload失效#
由于react-refresh-webpack-plugin对于外置加载的react core有严格的顺序要求,所以我们需要修改项目打包的输出,由原来的单入口,改为多入口。并且在HTML模版中控制它们的加载顺序。
对于webpack.config.js
需要修改: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41{
//...
entry: isDevelopment
? {
whm: 'webpack-hot-middleware/client?quiet=true&reload=true&path=/<default>/<route>/__webpack_hmr&timeout=2000', //启用webpack-hot-middleware作为热更新服务时需要增加的
reactRefreshEntry: '@pmmmwh/react-refresh-webpack-plugin/client/ReactRefreshEntry.js', //让react产生局部更新
main: clientEntry, //项目本身的入口文件
}
: clientEntry,//项目本身的入口文件
//...
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',
},
}),
]:[])
}
对于HTML模版(这里的具体语法,需要根据具体使用的模版解析器语法来):
1 | {% if isDev%} |
至此就完成在monorepo下,react项目的局部热更新。
monorepo结构下启动react项目局部热更新