webpack自動(dòng)刷新瀏覽器源碼解析
在我們?nèi)粘5那岸碎_發(fā)過程中,在編輯器里只需要保存代碼,瀏覽器就會(huì)自動(dòng)刷新當(dāng)前頁面。這個(gè)過程被稱為熱更新。
其實(shí)實(shí)現(xiàn)這一功能需要兩步:
- 監(jiān)聽代碼的變化
- 自動(dòng)刷新瀏覽器
下面看一下這兩個(gè)步驟是如何實(shí)現(xiàn)的。
配置webpack熱更新模式
- 初識(shí)化項(xiàng)目并導(dǎo)入依賴
npm i webpack webpack-cli -D npm i webpack-dev-server -D npm i html-webpack-plugin -D
然后,我們需要弄明白,webpack 從版本 webpack@4 之后,需要通過 webpack CLI 來啟動(dòng)服務(wù),提供了啟動(dòng)開發(fā)服務(wù)的命令。
# 啟動(dòng)開發(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", } }
在啟動(dòng)開發(fā)服務(wù)的時(shí)候,在 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ù)器端口號(hào) }, plugins: [ new HtmlWebpackPlugin({ template: './index.html' }) ] }
源碼解析
開啟本地服務(wù)
首先通過webpack創(chuàng)建了一個(gè)compiler實(shí)例,然后通過創(chuàng)建自定義server實(shí)例,開啟了一個(gè)本地服務(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', () => {})
這個(gè)自定義Server 不僅是創(chuàng)建了一個(gè)http服務(wù),它還基于http服務(wù)創(chuàng)建了一個(gè)websocket服務(wù),同時(shí)監(jiān)聽瀏覽器的接入,當(dāng)瀏覽器成功接入時(shí)向它發(fā)送hash值,從而實(shí)現(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)聽端口號(hào) listen(port, host, callback) { this.server.listen(port, host, callback) this.createSocketServer(); } //基于http服務(wù)創(chuàng)建websocket服務(wù),并注冊(cè)監(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連接時(shí),服務(wù)端向?yàn)g覽器發(fā)送hash和拉取代碼的通知還不夠,我們還希望當(dāng)代碼改變時(shí),瀏覽器也可以接到這樣的通知。于是,在開啟服務(wù)前,還需要對(duì)編譯完成事件進(jìn)行監(jiān)聽。
//監(jiān)聽編譯完成,當(dāng)編譯完成后通過websocket向?yàn)g覽器發(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)聽文件修改
要想在代碼修改的時(shí)候,觸發(fā)重新編譯,那么就需要對(duì)代碼的變動(dòng)進(jìn)行監(jiān)聽。這一步,源碼是通過??webpackDevMiddleware?
??庫實(shí)現(xiàn)的。庫中使用了compiler.watch對(duì)文件的修改進(jìn)行了監(jiān)聽,并且通過??memory-fs?
?實(shí)現(xiàn)了將編譯的產(chǎn)物存放到內(nèi)存中,這也是為什么我們?cè)赿ist目錄下看不到變化的內(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;
向?yàn)g覽器中插入客戶端代碼
前面提到要想實(shí)現(xiàn)瀏覽器和本地服務(wù)的通信,那么就需要瀏覽器接入到本地開啟的websocket服務(wù),然而瀏覽器本身并不具備這樣的能力,這就需要我們自己提供這樣的客戶端代碼將它運(yùn)行在瀏覽器。因此自定Server在開啟http服務(wù)之前,就調(diào)用了??updateCompiler()?
?方法,它修改了webpack配置中的entry,使得插入的兩個(gè)文件的代碼可以一同被打包到 main.js 中,運(yùn)行在瀏覽器。
//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
這段代碼會(huì)放在瀏覽器作為客戶端代碼,它用來建立 websocket 連接,當(dāng)服務(wù)端發(fā)送hash廣播時(shí)就保存hash,當(dāng)服務(wù)端發(fā)送ok廣播時(shí)就調(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(),當(dāng)然第一次加載頁面時(shí)是不會(huì)被調(diào)用的。至于這里為啥會(huì)分成兩個(gè)文件,個(gè)人理解是為了解藕,每個(gè)模塊負(fù)責(zé)不同的分工。
let lastHash; hotEmitter.on('webpackHotUpdate', (currentHash) => { if (!lastHash) { lastHash = currentHash; return; } module.hot.check(); })
module.hot.check()是哪來的?答案是??HotModuleReplacementPlugin?
?。
到此這篇關(guān)于webpack自動(dòng)刷新瀏覽器源碼解析的文章就介紹到這了,更多相關(guān)webpack自動(dòng)刷新瀏覽器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
uni-app實(shí)現(xiàn)獲取驗(yàn)證碼倒計(jì)時(shí)功能
這篇文章主要為大家詳細(xì)介紹了uni-app實(shí)現(xiàn)獲取驗(yàn)證碼倒計(jì)時(shí)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11原生js實(shí)現(xiàn)秒表計(jì)時(shí)器功能
這篇文章主要為大家詳細(xì)介紹了原生js實(shí)現(xiàn)秒表計(jì)時(shí)器功能,可以開始、暫停、清除,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02JS實(shí)現(xiàn)上下左右對(duì)稱的九九乘法表
九九乘法表使用很多種語言都可以實(shí)現(xiàn),本文給大家介紹js使用for、while循環(huán)來完成四種對(duì)稱的九九乘法表,對(duì)九九乘法表需要的朋友參考下2016-02-02Chrome調(diào)試折騰記之JS斷點(diǎn)調(diào)試技巧
這篇文章主要介紹了Chrome調(diào)試折騰記之JS斷點(diǎn)調(diào)試技巧,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09JavaScript中訪問節(jié)點(diǎn)對(duì)象的方法有哪些如何使用
js訪問節(jié)點(diǎn)對(duì)象的方法有很多,比如getElementById在本文將舉例為大家介紹下2013-09-09利用javascript的面向?qū)ο蟮奶匦詫?shí)現(xiàn)限制試用期
Javascript是一種面向?qū)ο蟮哪_本語言,其也具有面向?qū)ο蟮娜筇匦裕墙裉煳覀儾辉敿?xì)的講解javascript的面向?qū)ο筇匦?,今天我們簡單的了解一下javascript的面向?qū)ο筇匦?,然后學(xué)習(xí)一下怎樣實(shí)現(xiàn)試用期的限制!2011-08-08