webpack自動刷新瀏覽器源碼解析
在我們?nèi)粘5那岸碎_發(fā)過程中,在編輯器里只需要保存代碼,瀏覽器就會自動刷新當(dāng)前頁面。這個過程被稱為熱更新。
其實實現(xiàn)這一功能需要兩步:
- 監(jiān)聽代碼的變化
- 自動刷新瀏覽器
下面看一下這兩個步驟是如何實現(xiàn)的。
配置webpack熱更新模式
- 初識化項目并導(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 來啟動服務(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)聽瀏覽器的接入,當(dāng)瀏覽器成功接入時向它發(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和拉取代碼的通知還不夠,我們還希望當(dāng)代碼改變時,瀏覽器也可以接到這樣的通知。于是,在開啟服務(wù)前,還需要對編譯完成事件進行監(jiān)聽。
//監(jiān)聽編譯完成,當(dāng)編譯完成后通過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 = updateCompiler
node_modules /webpack-dev-server/client/index.js
這段代碼會放在瀏覽器作為客戶端代碼,它用來建立 websocket 連接,當(dāng)服務(wù)端發(fā)送hash廣播時就保存hash,當(dāng)服務(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(),當(dāng)然第一次加載頁面時是不會被調(diào)用的。至于這里為啥會分成兩個文件,個人理解是為了解藕,每個模塊負責(zé)不同的分工。
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)文章
Chrome調(diào)試折騰記之JS斷點調(diào)試技巧
這篇文章主要介紹了Chrome調(diào)試折騰記之JS斷點調(diào)試技巧,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09JavaScript中訪問節(jié)點對象的方法有哪些如何使用
js訪問節(jié)點對象的方法有很多,比如getElementById在本文將舉例為大家介紹下2013-09-09利用javascript的面向?qū)ο蟮奶匦詫崿F(xiàn)限制試用期
Javascript是一種面向?qū)ο蟮哪_本語言,其也具有面向?qū)ο蟮娜筇匦?,但是今天我們不詳細的講解javascript的面向?qū)ο筇匦?,今天我們簡單的了解一下javascript的面向?qū)ο筇匦?,然后學(xué)習(xí)一下怎樣實現(xiàn)試用期的限制!2011-08-08