webpack自动刷新浏览器源码解析
作者:尼羲
在我们日常的前端开发过程中,在编辑器里只需要保存代码,浏览器就会自动刷新当前页面。这个过程被称为热更新。
其实实现这一功能需要两步:
- 监听代码的变化
- 自动刷新浏览器
下面看一下这两个步骤是如何实现的。
配置webpack热更新模式
- 初识化项目并导入依赖
npm i webpack webpack-cli -D npm i webpack-dev-server -D npm i html-webpack-plugin -D
然后,我们需要弄明白,webpack 从版本 webpack@4 之后,需要通过 webpack CLI 来启动服务,提供了启动开发服务的命令。
# 启动开发服务器 webpack serve --mode development --config webpack.config.js
// pkg.json { "scripts": { "dev": "webpack serve --mode development --config webpack.config.js", "build": "webpack build --mode production --config webpack.config.js" }, "devDependencies": { "webpack": "^5.45.1", "webpack-cli": "^4.7.2", "webpack-dev-server": "^3.11.2", "html-webpack-plugin": "^5.3.2", } }
在启动开发服务的时候,在 webpack 的配置文件中配置 devServe
属性,即可开启热更新模式。
// webpack.config.js const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist'), }, devServer: { hot: true, // 开启热更新 port: 8080, // 指定服务器端口号 }, plugins: [ new HtmlWebpackPlugin({ template: './index.html' }) ] }
源码解析
开启本地服务
首先通过webpack创建了一个compiler实例,然后通过创建自定义server实例,开启了一个本地服务。
// node_modules/webpack-dev-server/bin/webpack-dev-server.js const webpack = require('webpack'); const config = require('../../webpack.config'); const Server = require('../lib/Server') const compiler = webpack(config); const server = new Server(compiler); server.listen(8080, 'localhost', () => {})
这个自定义Server 不仅是创建了一个http服务,它还基于http服务创建了一个websocket服务,同时监听浏览器的接入,当浏览器成功接入时向它发送hash值,从而实现服务端和浏览器间的双向通信。
// node_modules/webpack-dev-server/lib/Server.js class Server { constructor() { this.setupApp(); this.createServer(); } //创建http应用 setupApp() { this.app = express(); } //创建http服务 createServer() { this.server = http.createServer(this.app); } //监听端口号 listen(port, host, callback) { this.server.listen(port, host, callback) this.createSocketServer(); } //基于http服务创建websocket服务,并注册监听事件connection createSocketServer() { const io = socketIO(this.server); io.on('connection', (socket) => { this.clientSocketList.push(socket); socket.emit('hash', this.currentHash); socket.emit('ok'); socket.on('disconnect', () => { let index = this.clientSocketList.indexOf(socket); this.clientSocketList.splice(index, 1) }) }) } } module.exports = Server;
监听编译完成
仅仅在建立websocket连接时,服务端向浏览器发送hash和拉取代码的通知还不够,我们还希望当代码改变时,浏览器也可以接到这样的通知。于是,在开启服务前,还需要对编译完成事件进行监听。
//监听编译完成,当编译完成后通过websocket向浏览器发送广播 setupHooks() { let { compiler } = this; compiler.hooks.done.tap('webpack-dev-server', (stats) => { this.currentHash = stats.hash; this.clientSocketList.forEach((socket) => { socket.emit('hash', this.currentHash); socket.emit('ok'); }) }) }
监听文件修改
要想在代码修改的时候,触发重新编译,那么就需要对代码的变动进行监听。这一步,源码是通过webpackDevMiddleware
库实现的。库中使用了compiler.watch对文件的修改进行了监听,并且通过memory-fs
实现了将编译的产物存放到内存中,这也是为什么我们在dist目录下看不到变化的内容,放到内存的好处就是为了更快的读写从而提高开发效率。
// node_modules/webpack-dev-middleware/index.js const MemoryFs = require('memory-fs') compiler.watch({}, () => {}) let fs = new MemoryFs(); this.fs = compiler.outputFileSystem = fs;
向浏览器中插入客户端代码
前面提到要想实现浏览器和本地服务的通信,那么就需要浏览器接入到本地开启的websocket服务,然而浏览器本身并不具备这样的能力,这就需要我们自己提供这样的客户端代码将它运行在浏览器。因此自定Server在开启http服务之前,就调用了updateCompiler()
方法,它修改了webpack配置中的entry,使得插入的两个文件的代码可以一同被打包到 main.js 中,运行在浏览器。
//node_modules/webpack-dev-server/lib/utils/updateCompiler.js const path = require('path'); function updateCompiler(compiler) { compiler.options.entry = { main: [ path.resolve(__dirname, '../../client/index.js'), path.resolve(__dirname, '../../../webpack/hot/dev-server.js'), config.entry, ] } } module.exports = updateCompiler
node_modules /webpack-dev-server/client/index.js
这段代码会放在浏览器作为客户端代码,它用来建立 websocket 连接,当服务端发送hash广播时就保存hash,当服务端发送ok广播时就调用reloadApp()。
let currentHash; let hotEmitter = new EventEmitter(); const socket = window.io('/'); socket.on('hash', (hash) => { currentHash = hash; }) socket.on('ok', () => { reloadApp(); }) function reloadApp() { hotEmitter.emit('webpackHotUpdate', currentHash) }
webpack/hot/dev-server.js
reloadApp()继续调用module.hot.check(),当然第一次加载页面时是不会被调用的。至于这里为啥会分成两个文件,个人理解是为了解藕,每个模块负责不同的分工。
let lastHash; hotEmitter.on('webpackHotUpdate', (currentHash) => { if (!lastHash) { lastHash = currentHash; return; } module.hot.check(); })
module.hot.check()是哪来的?答案是HotModuleReplacementPlugin
。
到此这篇关于webpack自动刷新浏览器源码解析的文章就介绍到这了,更多相关webpack自动刷新浏览器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!