基于webpack的热重载live reload和热更新HMR — 当文件被修改后如何让浏览器更新代码
在前端应用框架中不管是react还是vue,官方都提供了相应的脚手架方便开发者快速入手,当我们在开发时修改某个js或者css文件时,webpack会自动编译我们的文件,我们刷新浏览器就可以看到编译后的文件。为此我们会想,如果我们修改保存之后,文件被编译、浏览器自动刷新、或者浏览器局部刷新(不刷新整个浏览器),这样的话多好。当然,基于webpack打包工具的相关库已经实现了。下面对此部分流程做简单的分析
- 热重载live reload: 就是当修改文件之后,webpack自动编译,然后浏览器自动刷新->等价于页面window.location.reload()
- 热更新HMR: 热重载live reload并不能够保存应用的状态(states),当刷新页面后,应用之前状态丢失。举个列子:页面中点击按钮出现弹窗,当浏览器刷新后,弹窗也随即消失,要恢复到之前状态,还需再次点击按钮。而webapck热更新HMR则不会刷新浏览器,而是运行时对模块进行热替换,保证了应用状态不会丢失,提升了开发效率
相关版本选择:
- webpack 版本git checkout v2.7.0 版本
- webpack-dev-middleware 版本git checkout v1.12.2 版本
- webpack-dev-server 版本git checkout v2.9.7 版本
说明: 这里选择webpack的版本为V2,是因为之前debug webpack的打包流程时恰为v2的版本,那可能会问webpack-dev-server版本为什么是这个呢? 这里解释下,通过package.json中的字段peerDependencies可以选定版本,为此我选择了对应的最新版本 v2.9.7。同样webpack-dev-middleware版本选择也是一样的,主要看依赖关系。 附上webpack-dev-server库的package.json文件描述
"name": "webpack-dev-server", "version": "2.9.7", "peerDependencies": { "webpack": "^2.2.0 || ^3.0.0" // 这里说明需要的版本号 }
进入主题 demo是 webpack-dev-server 目录下面的examples/api/simple列子,只粘贴出关键代码,建议clone代码比对一下
server.js 入口文件
'use strict'; const Webpack = require('webpack'); const WebpackDevServer = require('../../../lib/Server'); const webpackConfig = require('./webpack.config'); const compiler = Webpack(webpackConfig); const devServerOptions = Object.assign({}, webpackConfig.devServer, { stats: { colors: true } }); const server = new WebpackDevServer(compiler, devServerOptions); server.listen(8080, '127.0.0.1', () => { console.log('Starting server on http://localhost:8080'); });
const webpackConfig = require('./webpack.config'); 文件如下
'use strict'; var path = require("path"); // our setup function adds behind-the-scenes bits to the config that all of our // examples need const { setup } = require('../../util'); module.exports = setup({ context: __dirname, entry: [ './app.js', '../../../client/index.js?http://localhost:8080/', 'webpack/hot/dev-server' ], devServer: { // 这里配置hot值决定当开发时文件被修改并保存后 更新模式为热更新HMR hot: true } });
入口entry 包含'../../../client/index.js?http://localhost:8080/' 以及 'webpack/hot/dev-server' 作用分别是:前者是WebpackDevServer的客户端浏览器代码,通过sockjs-client来链接Server端进行通信,比如开发时代码修改后保存,WebpackDevServer会通过 webpack-dev-middleware 拿到webpack编译后的结果,通过websockets 发送消息类型给客户端浏览器。 后者是webpack热更新HMR的客户端浏览器代码,打包时会insert进去,作用是当浏览器收到websockets发过来消息后,如果webpackConfig配置了webpack.HotModuleReplacementPlugin插件,就会走热更新HMR模式
../../../client/index.js 文件如下
'use strict'; const socket = require('./socket'); let urlParts; let hotReload = true; // __resourceQuery 也就是../../../client/index.js后面的参数 http://localhost:8080/ 通过webpack 打包时候替换 if (typeof __resourceQuery === 'string' && __resourceQuery) { // If this bundle is inlined, use the resource query to get the correct url. urlParts = url.parse(__resourceQuery.substr(1)); } else { // ... } let hot = false; let currentHash = ''; const onSocketMsg = { hot: function msgHot() { hot = true; }, hash: function msgHash(hash) { currentHash = hash; }, ok: function msgOk() { reloadApp(); } }; // 建立websockets 链接 socket(socketUrl, onSocketMsg); function reloadApp() { if (isUnloading || !hotReload) { return; } // 如果webpackConfig 中配置devServer.hot 为true,就走热更新HMR的模式,结论可以通过webpack-dev-server 的lib/Server.js 文件逻辑得出 if (hot) { const hotEmitter = require('webpack/hot/emitter'); hotEmitter.emit('webpackHotUpdate', currentHash); } else { // 否则走热重载live reload 直接刷新浏览器 applyReload(rootWindow, intervalId); } function applyReload(rootWindow, intervalId) { clearInterval(intervalId); log.info('[WDS] App updated. Reloading...'); rootWindow.location.reload(); } }
const socket = require('./socket'); 文件如下
'use strict'; const SockJS = require('sockjs-client'); let sock = null; function socket(url, handlers) { sock = new SockJS(url); sock.onclose = function onclose() { // 此处是重连的逻辑 省略... }; sock.onmessage = function onmessage(e) { // 当收到Server端的websockets 消息后执行对应的消息类型逻辑 // This assumes that all data sent via the websocket is JSON. const msg = JSON.parse(e.data); if (handlers[msg.type]) { handlers[msg.type](msg.data); } }; } module.exports = socket;