webpack自動刷新瀏覽器源碼解析
在我們?nèi)粘5那岸碎_發(fā)過程中,在編輯器里只需要保存代碼,瀏覽器就會自動刷新當前頁面。這個過程被稱為熱更新。
其實實現(xiàn)這一功能需要兩步:
- 監(jiān)聽代碼的變化
- 自動刷新瀏覽器
下面看一下這兩個步驟是如何實現(xiàn)的。
配置webpack熱更新模式
- 初識化項目并導入依賴
npm i webpack webpack-cli -D npm i webpack-dev-server -D npm i html-webpack-plugin -D
然后,我們需要弄明白,webpack 從版本 webpack@4 之后,需要通過 webpack CLI 來啟動服務(wù),提供了啟動開發(fā)服務(wù)的命令。
# 啟動開發(fā)服務(wù)器 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",
}
}在啟動開發(fā)服務(wù)的時候,在 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, // 指定服務(wù)器端口號
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html'
})
]
}源碼解析
開啟本地服務(wù)
首先通過webpack創(chuàng)建了一個compiler實例,然后通過創(chuàng)建自定義server實例,開啟了一個本地服務(wù)。
// 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 不僅是創(chuàng)建了一個http服務(wù),它還基于http服務(wù)創(chuàng)建了一個websocket服務(wù),同時監(jiān)聽瀏覽器的接入,當瀏覽器成功接入時向它發(fā)送hash值,從而實現(xiàn)服務(wù)端和瀏覽器間的雙向通信。
// node_modules/webpack-dev-server/lib/Server.js
class Server {
constructor() {
this.setupApp();
this.createServer();
}
//創(chuàng)建http應(yīng)用
setupApp() {
this.app = express();
}
//創(chuàng)建http服務(wù)
createServer() {
this.server = http.createServer(this.app);
}
//監(jiān)聽端口號
listen(port, host, callback) {
this.server.listen(port, host, callback)
this.createSocketServer();
}
//基于http服務(wù)創(chuàng)建websocket服務(wù),并注冊監(jiān)聽事件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;監(jiān)聽編譯完成
僅僅在建立websocket連接時,服務(wù)端向瀏覽器發(fā)送hash和拉取代碼的通知還不夠,我們還希望當代碼改變時,瀏覽器也可以接到這樣的通知。于是,在開啟服務(wù)前,還需要對編譯完成事件進行監(jiān)聽。
//監(jiān)聽編譯完成,當編譯完成后通過websocket向瀏覽器發(fā)送廣播
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');
})
})
}監(jiān)聽文件修改
要想在代碼修改的時候,觸發(fā)重新編譯,那么就需要對代碼的變動進行監(jiān)聽。這一步,源碼是通過??webpackDevMiddleware???庫實現(xiàn)的。庫中使用了compiler.watch對文件的修改進行了監(jiān)聽,并且通過??memory-fs??實現(xiàn)了將編譯的產(chǎn)物存放到內(nèi)存中,這也是為什么我們在dist目錄下看不到變化的內(nèi)容,放到內(nèi)存的好處就是為了更快的讀寫從而提高開發(fā)效率。
// node_modules/webpack-dev-middleware/index.js
const MemoryFs = require('memory-fs')
compiler.watch({}, () => {})
let fs = new MemoryFs();
this.fs = compiler.outputFileSystem = fs;向瀏覽器中插入客戶端代碼
前面提到要想實現(xiàn)瀏覽器和本地服務(wù)的通信,那么就需要瀏覽器接入到本地開啟的websocket服務(wù),然而瀏覽器本身并不具備這樣的能力,這就需要我們自己提供這樣的客戶端代碼將它運行在瀏覽器。因此自定Server在開啟http服務(wù)之前,就調(diào)用了??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 = updateCompilernode_modules /webpack-dev-server/client/index.js
這段代碼會放在瀏覽器作為客戶端代碼,它用來建立 websocket 連接,當服務(wù)端發(fā)送hash廣播時就保存hash,當服務(wù)端發(fā)送ok廣播時就調(diào)用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()繼續(xù)調(diào)用module.hot.check(),當然第一次加載頁面時是不會被調(diào)用的。至于這里為啥會分成兩個文件,個人理解是為了解藕,每個模塊負責不同的分工。
let lastHash;
hotEmitter.on('webpackHotUpdate', (currentHash) => {
if (!lastHash) {
lastHash = currentHash;
return;
}
module.hot.check();
})module.hot.check()是哪來的?答案是??HotModuleReplacementPlugin??。
到此這篇關(guān)于webpack自動刷新瀏覽器源碼解析的文章就介紹到這了,更多相關(guān)webpack自動刷新瀏覽器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序MUI導航欄透明漸變功能示例(通過改變opacity實現(xiàn))
這篇文章主要介紹了微信小程序MUI導航欄透明漸變功能,結(jié)合實例形式分析了通過改變opacity實現(xiàn)透明度漸變功能相關(guān)操作技巧,需要的朋友可以參考下2019-01-01
前端使用JavaScript結(jié)合CSS實現(xiàn)3D旋轉(zhuǎn)跟隨鼠標變化
這篇文章主要介紹了前端使用JavaScript結(jié)合CSS實現(xiàn)3D旋轉(zhuǎn)跟隨鼠標變化,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2023-01-01

