首先要知道webpack-dev-server和webpack-dev-middleware这两个依赖库的主要区别。
webpack-dev-server主要负责项目的服务启动和一些前置工作(比如生成webpack编译主引擎(compile实例))。
webpack-dev-middleware主要提供的是本地文件操作方面相关的服务,比如文件的编译,输出(setFs())和监听(compile.watch());
当执行npm start的时候:
- webpack-dev-server就开始生成编译引擎(也就是compiler实例)
- 生成完实例以后,紧接着启动Server服务,而且可以看到,Server.js主要使用的是express框架
- 启动了本地服务以后,又紧接着启动了websocket服务
- Server.js还做了一件事情,就是修改了webpack.config.js的entry配置,主要实现的步骤是:
- 调用了updateCompiler方法,updateCompiler中又调用了addEntries方法,addEntries方法中有 2 段很关键的代码
- addEntries方法执行以后,会改变entry入口的配置
-
{ entry: { index: [ '../node_modules/webpack-dev-server/client/index.js', '../node_modules/webpack/hot/dev-server.js', './src/index.js' ], }, }
- 新增websocket文件的原因,是因为上面的webpack-dev-server启动的websocket服务目前服务端(webpack项目)代码已经有了,但是浏览器的代码还没有,总不能webpack通知浏览器要更新代码了,但是浏览器怎么接收消息并触发更新事件呢?所以肯定要把这部分代码给到浏览器。而第二个文件主要是用于检查更新逻辑的
- 调用了updateCompiler方法,updateCompiler中又调用了addEntries方法,addEntries方法中有 2 段很关键的代码
- Server.js调用了updateCompiler方法实现了入口文件配置的修改以后,又紧接着做了一件事,就是调用了setupHooks方法,这个方法主要是用来监听每次
webpack
编译完成的。 - 上面主要就是Server.js做的事情,下面看看文件的监听是怎么做的
- 文件的监听主要是在webpack-dev-middleware中做的
- 监听文件的变化主要是通过文件的生成时间是否有变动来实现的。
- 现在,文件的监听以及websocket通知已经做好了,下面看看浏览器收到通知以后具体做了哪些事情
- 刚才Server.js调用updateCompiler方法把websocket的客户端代码配置到了entry入口中,所以现在看看那段代码做了什么
- 刚才Server.js调用updateCompiler方法把websocket的客户端代码配置到了entry入口中,所以现在看看那段代码做了什么
- 现在,Server.js通过updateCompiler方法添加到entry中的第一个文件作用已经说清楚了,添加的第二个文件,也就是webpack/hot/dev-server的作用还没说
- 补充一点,
check
方法是是HotModuleReplacementPlugin
插件搞的,把代码注入到了bundle.js中 - check方法的源码在webpack/lib/HotModuleReplacement.runtime.js(简称HMR runtime)中
- check方法中会利用 到webpack/lib/web/JsonpMainTemplate.runtime(简称Jsonp runtime)中的两个方法hotDownloadManifest和hotDownloadUpdateChunk
- hotDownloadManifest的主要作用是发送{hash}.hot-update.json的ajax请求,获取更新清单以及获取下次热更新的Hash 标识
- hotDownloadUpdateChunk主要作用是通过得到上面的更新清单去发送{hash}.hot-update.js 请求下载相应的更新块,而且是通过JSONP的方式
- 通过上面一系列的文件监听、socket通信、浏览器收到通信并请求到新文件这些操作以后,就看是如何进行热更新的。打开hot-update.js文件以后,会发现一个webpackHotUpdate的方法。
- 源码中这个方法有一个callback,会执行hotAddUpdateChunk方法,
- hotAddUpdateChunk接收两个参数,一个是chunkId(对应hot-update.js响应体中的webpackHotUpdate函数中的“main”),一个是moreModules(对应hot-update.js响应体中的webpackHotUpdate函数中的那个对象);拿到需要更新的模块以后,hotAddUpdateChunk会用for in的形式遍历模块对象,并且赋值给全局全量hotUpdate。然后执行hotUpdateDownloaded方法
- 来看下hotUpdateDownloaded做了什么事,当执行hotUpdateDownloaded的时候,也就意味着热更新的前期工作已经全部做完了(webpack监听变更文件、Browser请求变更文件、处理变更文件)。所以hotUpdateDownloaded会把runtime状态置为“ready”,然后执行hotApply准备开始应用。
- 上面的所有的操作全部处于准备阶段,接下的hotApply才是正式应用module到页面中
- hotApply主要调用了hotApplyInternal这个方法。应用新模块主要分为了三个阶段
- 第一阶段,找出所有过期的依赖和模块
- 第二阶段,删除缓存中的依赖要替换的模块,
- 将runtime的状态置为“apply”并将新的模块添加到modules中
- 到此,所有的新模块已经全部在module中了,代码下次调用__webpack_require__的时候,就是最新模块