Webpack 热更新与自动刷新浏览器原理深度解析

作者:测试手账时间:2026-10-26 10:03:34浏览:514

本文深入解析 Webpack 实现浏览器自动刷新与热更新的底层机制。通过梳理 webpack-dev-server 源码,详细阐述如何配置开启热更新、建立 WebSocket 双向通信、监听文件变更以及内存存储编译产物等核心步骤,帮助开发者理解开发环境高效构建背后的技术原理。

在前端日常开发中,开发者只需在编辑器中保存代码,浏览器即可自动刷新当前页面,这一便捷功能被称为热更新。

实现该功能主要依赖两个核心步骤:

  • 监听代码的变化
  • 自动刷新浏览器

接下来将详细解析这两个步骤的具体实现方式。

配置webpack热更新模式

  • 首先初始化项目并安装相关依赖。
npm i webpack webpack-cli -D
npm i webpack-dev-server -D
npm i html-webpack-plugin -D

需要注意的是,自 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 服务,还基于此建立了 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 自动刷新浏览器源码解析的介绍已完成,更多相关内容可搜索本站历史文章或继续浏览后续文章,感谢支持。

作者最新文章