# 正文
上一节我们完成了生产环境的配置,从本节开始一起来做一些“装修”的工作,让应用骨架的开发体验更好,同时也是解决我们之前留下的坑。
良好的开发体验可以大大提高我们的开发效率,目前有一个明显的缺陷,开发环境中需要手动刷新页面来看效果。
而我们要做的就是 - 搞定他!
使用webpack --watch来动态监听前端文件的改变并实时打包,输出新bundle.js文件,这样文件多了之后打包速度会很慢,此外这样的打包方式不能做到hot replace,即每次webpack编译之后需要手动刷新浏览器。
所以本节我们就是用热更新机制来解决这个问题。
# 热更新
对于热更,相信大家都有所了解,一种是热重载,一种是模块热替换。
我们这里要实现的是模块热替换。
# webpack-dev-server
因为该模块已经内置了热更新机制,只需要进行一些简单的配置就能将这个特性继承到我们的项目中。
所以开发环境中实现热更新推荐使用webpack-dev-server。
ps:webpack-dev-server后面都使用wds简称来代替
该库的作用主要是用来提供静态资源文件的更新和访问。其内部会启动一个基于express的http服务器。 该http服务和浏览器之间使用websocket进行实时通讯。当原始文件作出改动后,wds会实时编译,但是最后的构建文件并没有输出到硬盘,而是全部载入内存,省去了每次对磁盘的 io,所以性能上也较以往的方式有很大的提升。
启动webpack-dev-server服务有2种方式:
- 通过
cmd line - 通过
Node.js API
为了可以更加灵活控制,我们采用API方式来启动。
另外还需要结合react-hot-loader库来实现 react 开发环境下的热更新。
# 具体实施
# 端口约定
我们约定webpack-dev-server服务端口为9002,应用骨架的node server启动端口为9001,保持不变。
ps:这些端口可以根据自己的喜好来调整。
# 安装模块
npm i webpack-dev-server -D
react-hot-loader 可以结合 wds 实现热更新过程中保存 react 组件的状态不丢失
npm i react-hot-loader -D
# react-hot-loader 配置
.babelrc` 内增加 `react-hot-loader/babel
// .babelrc
{
"plugins": ["react-hot-loader/babel"]
}
- 根组件使用热导出
// src/client/app/layout.js
import { hot } from 'react-hot-loader/root';
class Index extends React.Component{
//...
}
export default hot(Index);
- 安装
@hot-loader/react-dom
它替换了同一版本的react-dom包,使用了额外的补丁来支持热重新加载。
直接使用react-dom,控制台会出现下面的警告

webpack配置入口entry调整
//webpack.dev.config
module.exports = {
entry: {
main:['react-hot-loader/patch',resolvePath('../src/client/app/index.js'), //入口文件
}
# wds 配置
通过 api 方式启动 wds 服务时,会忽略webpack.dev.config内的配置,需要将配置作为参数传递给WebpackDevServer。
创建一个 wds 配置文件
// webpack/scripts/webpack-dev-server.config.js
const path = require('path');
module.exports = function (port,publicPath) {
return {
quiet: true,//不显示构建日志
contentBase: path.resolve(__dirname, '../../dist/static'),
publicPath: publicPath,//必须和 webpack.dev.config 配置一致
hot: true,
progress:true,
open: false,
compress: true,
watchOptions: {
ignored: /node_modules/,
//当第一个文件更改,会在重新构建前增加延迟。
//这个选项允许 webpack 将这段时间内进行的任何其他更改都聚合到一次重新构建里。以毫秒为单位:
aggregateTimeout: 500,
//指定毫秒为单位进行轮询
poll: 500
}
}
}
# 相关 api 调用
在 webpack/scripts下创建wds-start.js文件,用于启动 wds 服务。
- 获得
webpack compiler
wds 需要和 webpack 相结合才能工作
//webpack dev 环境配置
const clientConfig = require('../webpack.dev.config');
// 获得webpack compiler
function getWebPackCompiler() {
return webpack(clientConfig);
}
- 创建
wds服务
这里的服务指的是 wds 内部会启动一个 http server,用来支持 socket 和 资源访问。
//wds 配置
const getWdsConfig = require('./webpack-dev-server.config');
//创建 wds 服务
function createWdsServer() {
let compiler = getWebPackCompiler();
compiler.hooks.done.tap('done', function (data) {
console.log('\n wds server compile done'); //编译完成时的提示
});
return new WebpackDevServer(compiler, getWdsConfig(clientConfig.output.publicPath));
}
- 启动
wds服务
createWdsServer会返回一个 http server 实例,这里启动的端口设置为9002。
// 启动 WebpackDevServer
function runWdsServer() {
let devServer = createWdsServer();
let port=9002;//wds 服务端口
devServer.listen(port,'localhost',err => {
if (err) {
return console.log(err);
}
console.log(chalk.cyan('Starting the development node server...\n'));
console.log('🚀 started');
});
}
runWdsServer();
# 关键 - 配置 publicPath
wds 和 webpack.dev.config 的publicPath必须一致,否则热更新会无效,会默认请求到node server,导致静态资源访问无效。
下面对webpack.dev.config.js进行调整,端口指向的是wds的端口9002,因为所有的静态资源现在是由wds提供的。
// webpack.dev.config.js
output: {
//...
publicPath: 'http://localhost:9002/'
}
# 配置 npm scripts 命令
"wds:watch": "BABEL_ENV=development node ./webpack/scripts/wds-start.js",
npm run wds:watch

从上图可以看出编译已经通过,而且支持实时编译,但是热更新还看不出来。
# 调整开发环境启动命令
// webpack/start.js
//移除 webpack -watch 监听
- spawn('npm', ['run', 'fe:watch'], { stdio: 'inherit' });
//增加 wds 监听
+ spawn('npm', ['run', 'wds:watch'], { stdio: 'inherit' });
# 客户端渲染入口代码配置
// src/client/app/index.js
//只有在开发环境才启用热更新
if (process.env.NODE_ENV==='development' && module.hot) {
module.hot.accept();
}
# 更改 node server 的 js 脚本地址
因为我们采用的是双服务模式
wds服务提供静态资源服务node server提供 ssr 能力
所以我们需要将静态资源的地址指向 wds 服务的9002端口
// src/server/common/assets.js
- let devHost = '//localhost:9001';
+ let devHost = '//localhost:9002';
//...
# 测试 - 启动热更新
执行 npm run dev
看下图,socket已成功建立链接,后面只要更新了文件就会自动更新页面,且页面不会刷新,组件的状态也不会丢失。

# 小结
本地开发服务启动后,会启动三个进程,其中包含两个服务,也就是我们最开始说的双服务,wds服务和node server,在这两个服务的配合下完成了开发环境的热更新开发体验。
wds服务进程,提供静态资源访问和热更新功能- 服务端代码监听进程
node http server提供react ssr能力
node server 会从wds 9002端口服务上加载静态资源。
好了,本小节到此结束,快来试试热更新吧。
本节代码已上传