参考:https://juejin.cn/post/6844904008432222215
参考:https://github.com/advanced-frontend/daily-interview-question/issues/118
唉,写的我快吐血了。。。唉妈的。。。呜呜呜。。。。明天在写吧张京。。。
webpack-dev-server只负责启动服务和前置准备工作,所有文件相关的操作都抽离到webpack-dev-middleware库了,主要是本地文件的编译和输出以及监听,职责的划分更清晰了。
首先,介绍webpack-dev-server:
webpack-dev-server 主要包含了三个部分:
1.webpack: 负责编译代码
2.webpack-dev-middleware: 主要负责构建内存文件系统,把webpack的 OutputFileSystem 替换成 InMemoryFileSystem。同时作为Express的中间件拦截请求,从内存文件系统中把结果拿出来。
3.express:负责搭建请求路由服务。
工作流程:
- 启动dev-server,webpack开始构建,在编译期间会向 entry 文件注入热更新代码;
- Client 首次打开后,Server 和 Client 基于webSocket建立通讯渠道;
- 修改文件,Server 端监听文件发送变动[^1],webpack开始编译,直到编译完成会会绑定一个监听事件setupHooks [^2]的方法去触发Done事件;【当监听到一次webpack编译结束,就会调用_sendStats方法通过websocket给浏览器发送通知,ok和hash事件,这样浏览器就可以拿到最新的hash值了,做检查更新逻辑。】
[^1] 监听文件的变动主要是webpack-dev-middleware来完成的,主要是通过
1、调用了compiler.watch方法这个方法主要做了 2 件事:
(1) 首先对本地文件代码进行编译打包,也就是webpack的一系列编译流程。
(2) 其次编译结束后,开启对本地文件的监听,当文件发生变化,重新编译,编译完成之后继续监听。为什么代码的改动保存会自动编译,重新打包?这一系列的重新检测编译就归功于compiler.watch这个方法了。监听本地文件的变化主要是通过文件的生成时间是否有变化,这里就不细讲了。
2、执行setFs方法,这个方法主要目的就是将编译后的文件打包到内存。
这就是为什么在开发的过程中,你会发现dist目录没有打包后的代码,因为都在内存中。原因就在于访问内存中的代码比访问文件系统中的文件更快,而且也减少了代码写入文件的开销,这一切都归功于memory-fs。
// node_modules/webpack-dev-middleware/index.js
compiler.watch(options.watchOptions, (err) => {
if (err) { /*错误处理*/ }
});
// 通过“memory-fs”库将打包后的文件写入内存
setFs(context, compiler);
[^2] 当监听到一次webpack编译结束,_sendStats方法就通过websoket给浏览器发送通知,检查下是否需要热更新。
_sendStats方法中的ok和hash【socket方法建立了websocket和服务端的连接,并注册了 2 个监听事件】。
1、hash事件,更新最新一次打包后的hash值。
2、ok事件,进行热更新检查。
// node_modules/webpack-dev-server/lib/Server.js
// 绑定监听事件
setupHooks() {
const {done} = compiler.hooks;
// 监听webpack的done钩子,tapable提供的监听方法
done.tap('webpack-dev-server', (stats) => {
this._sendStats(this.sockets, this.getStats(stats));
this._stats = stats;
});
};
// 通过websoket给客户端发消息
_sendStats() {
this.sockWrite(sockets, 'hash', stats.hash);
this.sockWrite(sockets, 'ok');
}
- Server通过websocket 发送消息告知 Client;
- Client根据Server的消息(hash值和state状态),通过ajax请求获取 Server 的manifest描述文件;
- Client对比当前 modules tree ,再次发请求到 Server 端获取新的JS模块;
- Client获取到新的JS模块后,会更新 modules tree并替换掉现有的模块;
- 最后调用 module.hot.accept() 完成热更新;