徹底搞懂并實(shí)現(xiàn) webpack 熱更新原理
點(diǎn)擊上方 前端瓶子君,關(guān)注公眾號(hào)
回復(fù)算法,加入前端編程面試算法每日一題群
目錄
-
HMR是什么
-
使用場景 -
配置使用HMR
-
配置webpack -
解析webpack打包后的文件內(nèi)容 -
配置HMR -
HMR原理
-
debug服務(wù)端源碼
-
服務(wù)端簡易實(shí)現(xiàn) -
服務(wù)端調(diào)試階段 -
debug客戶端源碼
-
客戶端簡易實(shí)現(xiàn) -
客戶端調(diào)試階段 -
問題
-
總結(jié)
-
招聘
HMR是什么
HMR即Hot Module Replacement是指當(dāng)你對代碼修改并保存后,webpack將會(huì)對代碼進(jìn)行重新打包,并將改動(dòng)的模塊發(fā)送到瀏覽器端,瀏覽器用新的模塊替換掉舊的模塊,去實(shí)現(xiàn)局部更新頁面而非整體刷新頁面。接下來將從使用到實(shí)現(xiàn)一版簡易功能帶領(lǐng)大家深入淺出HMR。
文章首發(fā)于@careteen/webpack-hmr,轉(zhuǎn)載請注明來源即可。
使用場景
如上圖所示,一個(gè)注冊頁面包含用戶名、密碼、郵箱三個(gè)必填輸入框,以及一個(gè)提交按鈕,當(dāng)你在調(diào)試郵箱模塊改動(dòng)了代碼時(shí),沒做任何處理情況下是會(huì)刷新整個(gè)頁面,頻繁的改動(dòng)代碼會(huì)浪費(fèi)你大量時(shí)間去重新填寫內(nèi)容。預(yù)期是保留用戶名、密碼的輸入內(nèi)容,而只替換郵箱這一模塊。這一訴求就需要借助webpack-dev-server的熱模塊更新功能。
相對于live reload整體刷新頁面的方案,HMR的優(yōu)點(diǎn)在于可以保存應(yīng)用的狀態(tài),提高開發(fā)效率。
配置使用HMR
配置webpack
首先借助webpack搭建項(xiàng)目
-
初識(shí)化項(xiàng)目并導(dǎo)入依賴
mkdir webpack-hmr && cd webpack-hmr
npm i -y
npm i -S webpack webpack-cli webpack-dev-server html-webpack-plugin
-
配置文件 webpack.config.js
const path = require('path')
const webpack = require('webpack')
const htmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development', // 開發(fā)模式不壓縮代碼,方便調(diào)試
entry: './src/index.js', // 入口文件
output: {
path: path.join(__dirname, 'dist'),
filename: 'main.js'
},
devServer: {
contentBase: path.join(__dirname, 'dist')
},
plugins: [
new htmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
})
]
}
-
新建 src/index.html模板文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Webpack Hot Module Replacement</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
-
新建 src/index.js入口文件編寫簡單邏輯
var root = document.getElementById('root')
function render () {
root.innerHTML = require('./content.js')
}
render()
-
新建依賴文件 src/content.js導(dǎo)出字符供index渲染頁面
var ret = 'Hello Webpack Hot Module Replacement'
module.exports = ret
// export default ret
-
配置 package.json
"scripts": {
"dev": "webpack-dev-server",
"build": "webpack"
}
-
然后 npm run dev即可啟動(dòng)項(xiàng)目 -
通過 npm run build打包生成靜態(tài)資源到dist目錄
接下來先分析下dist目錄中的文件
解析webpack打包后的文件內(nèi)容
-
webpack自己實(shí)現(xiàn)的一套commonjs規(guī)范講解 -
區(qū)分commonjs和esmodule
dist目錄結(jié)構(gòu)
.
├── index.html
└── main.js
其中index.html內(nèi)容如下
<!-- ... -->
<div id="root"></div>
<script type="text/javascript" src="main.js"></script></body>
<!-- ... -->
使用html-webpack-plugin插件將入口文件及其依賴通過script標(biāo)簽引入
先對main.js內(nèi)容去掉注釋和無關(guān)內(nèi)容進(jìn)行分析
(function (modules) { // webpackBootstrap
// ...
})
({
"./src/content.js":
(function (module, exports) {
eval("var ret = 'Hello Webpack Hot Module Replacement'\n\nmodule.exports = ret\n// export default ret\n\n");
}),
"./src/index.js": (function (module, exports, __webpack_require__) {
eval("var root = document.getElementById('root')\nfunction render () {\n root.innerHTML = __webpack_require__(/*! ./content.js */ \"./src/content.js\")\n}\nrender()\n\n\n");
})
});
可見webpack打包后會(huì)產(chǎn)出一個(gè)自執(zhí)行函數(shù),其參數(shù)為一個(gè)對象
"./src/content.js": (function (module, exports) {
eval("...")
}
鍵為入口文件或依賴文件相對于根目錄的相對路徑,值則是一個(gè)函數(shù),其中使用eval執(zhí)行文件的內(nèi)容字符。
-
再進(jìn)入自執(zhí)行函數(shù)體內(nèi),可見webpack自己實(shí)現(xiàn)了一套 commonjs規(guī)范
(function (modules) {
// 模塊緩存
var installedModules = {};
function __webpack_require__(moduleId) {
// 判斷是否有緩存
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 沒有緩存則創(chuàng)建一個(gè)模塊對象并將其放入緩存
var module = installedModules[moduleId] = {
i: moduleId,
l: false, // 是否已加載
exports: {}
};
// 執(zhí)行模塊函數(shù)
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 將狀態(tài)置為已加載
module.l = true;
// 返回模塊對象
return module.exports;
}
// ...
// 加載入口文件
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
如果對上面
commonjs規(guī)范感興趣可以前往我的另一篇文章手摸手帶你實(shí)現(xiàn)commonjs規(guī)范
給出上面代碼主要是先對webpack的產(chǎn)出文件混個(gè)眼熟,不要懼怕。其實(shí)任何一個(gè)不管多復(fù)雜的事物都是由更小更簡單的東西組成,剖開它認(rèn)識(shí)它愛上它。
配置HMR
接下來配置并感受一下熱更新帶來的便捷開發(fā)
webpack.config.js配置
// ...
devServer: {
hot: true
}
// ...
./src/index.js配置
// ...
if (module.hot) {
module.hot.accept(['./content.js'], () => {
render()
})
}
當(dāng)更改./content.js的內(nèi)容并保存時(shí),可以看到頁面沒有刷新,但是內(nèi)容已經(jīng)被替換了。
這對提高開發(fā)效率意義重大。接下來將一層層剖開它,認(rèn)識(shí)它的實(shí)現(xiàn)原理。
HMR原理
如上圖所示,右側(cè)Server端使用webpack-dev-server去啟動(dòng)本地服務(wù),內(nèi)部實(shí)現(xiàn)主要使用了webpack、express、websocket。
-
使用
express啟動(dòng)本地服務(wù),當(dāng)瀏覽器訪問資源時(shí)對此做響應(yīng)。 -
服務(wù)端和客戶端使用
websocket實(shí)現(xiàn)長連接 -
webpack監(jiān)聽源文件的變化,即當(dāng)開發(fā)者保存文件時(shí)觸發(fā)webpack的重新編譯。 -
每次編譯都會(huì)生成 hash值、已改動(dòng)模塊的json文件、已改動(dòng)模塊代碼的js文件 -
編譯完成后通過 socket向客戶端推送當(dāng)前編譯的hash戳 -
客戶端的
websocket監(jiān)聽到有文件改動(dòng)推送過來的hash戳,會(huì)和上一次對比 -
一致則走緩存 -
不一致則通過 ajax和jsonp向服務(wù)端獲取最新資源 -
使用
內(nèi)存文件系統(tǒng)去替換有修改的內(nèi)容實(shí)現(xiàn)局部刷新
上圖先只看個(gè)大概,下面將從服務(wù)端和客戶端兩個(gè)方面進(jìn)行詳細(xì)分析
debug服務(wù)端源碼
現(xiàn)在也只需要關(guān)注上圖的右側(cè)服務(wù)端部分,左側(cè)可以暫時(shí)忽略。下面步驟主要是debug服務(wù)端源碼分析其詳細(xì)思路,也給出了代碼所處的具體位置,感興趣的可以先行定位到下面的代碼處設(shè)置斷點(diǎn),然后觀察數(shù)據(jù)的變化情況。也可以先跳過閱讀此步驟。
-
啟動(dòng)
webpack-dev-server服務(wù)器,源代碼地址@webpack-dev-server/webpack-dev-server.js#L173 -
創(chuàng)建webpack實(shí)例,源代碼地址@webpack-dev-server/webpack-dev-server.js#L89
-
創(chuàng)建Server服務(wù)器,源代碼地址@webpack-dev-server/webpack-dev-server.js#L107
-
添加webpack的done事件回調(diào),源代碼地址@webpack-dev-server/Server.js#L122
-
編譯完成向客戶端發(fā)送消息,源代碼地址@webpack-dev-server/Server.js#L184 -
創(chuàng)建express應(yīng)用app,源代碼地址@webpack-dev-server/Server.js#L123
-
設(shè)置文件系統(tǒng)為內(nèi)存文件系統(tǒng),源代碼地址@webpack-dev-middleware/fs.js#L115
-
添加webpack-dev-middleware中間件,源代碼地址@webpack-dev-server/Server.js#L125
-
中間件負(fù)責(zé)返回生成的文件,源代碼地址@webpack-dev-middleware/middleware.js#L20 -
啟動(dòng)webpack編譯,源代碼地址@webpack-dev-middleware/index.js#L51
-
創(chuàng)建http服務(wù)器并啟動(dòng)服務(wù),源代碼地址@webpack-dev-server/Server.js#L135
-
使用sockjs在瀏覽器端和服務(wù)端之間建立一個(gè) websocket 長連接,源代碼地址@webpack-dev-server/Server.js#L745
-
創(chuàng)建socket服務(wù)器,源代碼地址@webpack-dev-server/SockJSServer.js#L34
服務(wù)端簡易實(shí)現(xiàn)
上面是我通過debug得出dev-server運(yùn)行流程比較核心的幾個(gè)點(diǎn),下面將其抽象整合到一個(gè)文件中。
啟動(dòng)webpack-dev-server服務(wù)器
先導(dǎo)入所有依賴
const path = require('path') // 解析文件路徑
const express = require('express') // 啟動(dòng)本地服務(wù)
const mime = require('mime') // 獲取文件類型 實(shí)現(xiàn)一個(gè)靜態(tài)服務(wù)器
const webpack = require('webpack') // 讀取配置文件進(jìn)行打包
const MemoryFileSystem = require('memory-fs') // 使用內(nèi)存文件系統(tǒng)更快,文件生成在內(nèi)存中而非真實(shí)文件
const config = require('./webpack.config') // 獲取webpack配置文件
創(chuàng)建webpack實(shí)例
const compiler = webpack(config)
compiler代表整個(gè)webpack編譯任務(wù),全局只有一個(gè)
創(chuàng)建Server服務(wù)器
class Server {
constructor(compiler) {
this.compiler = compiler
}
listen(port) {
this.server.listen(port, () => {
console.log(`服務(wù)器已經(jīng)在${port}端口上啟動(dòng)了`)
})
}
}
let server = new Server(compiler)
server.listen(8000)
在后面是通過express來當(dāng)啟動(dòng)服務(wù)的
添加webpack的done事件回調(diào)
constructor(compiler) {
let sockets = []
let lasthash
compiler.hooks.done.tap('webpack-dev-server', (stats) => {
lasthash = stats.hash
// 每當(dāng)新一個(gè)編譯完成后都會(huì)向客戶端發(fā)送消息
sockets.forEach(socket => {
socket.emit('hash', stats.hash) // 先向客戶端發(fā)送最新的hash值
socket.emit('ok') // 再向客戶端發(fā)送一個(gè)ok
})
})
}
webpack編譯后提供提供了一系列鉤子函數(shù),以供插件能訪問到它的各個(gè)生命周期節(jié)點(diǎn),并對其打包內(nèi)容做修改。compiler.hooks.done則是插件能修改其內(nèi)容的最后一個(gè)節(jié)點(diǎn)。
編譯完成通過socket向客戶端發(fā)送消息,推送每次編譯產(chǎn)生的hash。另外如果是熱更新的話,還會(huì)產(chǎn)出二個(gè)補(bǔ)丁文件,里面描述了從上一次結(jié)果到這一次結(jié)果都有哪些chunk和模塊發(fā)生了變化。
使用let sockets = []數(shù)組去存放當(dāng)打開了多個(gè)Tab時(shí)每個(gè)Tab的socket實(shí)例。
創(chuàng)建express應(yīng)用app
let app = new express()
設(shè)置文件系統(tǒng)為內(nèi)存文件系統(tǒng)
let fs = new MemoryFileSystem()
使用MemoryFileSystem將compiler的產(chǎn)出文件打包到內(nèi)存中。
添加webpack-dev-middleware中間件
function middleware(req, res, next) {
if (req.url === '/favicon.ico') {
return res.sendStatus(404)
}
// /index.html dist/index.html
let filename = path.join(config.output.path, req.url.slice(1))
let stat = fs.statSync(filename)
if (stat.isFile()) { // 判斷是否存在這個(gè)文件,如果在的話直接把這個(gè)讀出來發(fā)給瀏覽器
let content = fs.readFileSync(filename)
let contentType = mime.getType(filename)
res.setHeader('Content-Type', contentType)
res.statusCode = res.statusCode || 200
res.send(content)
} else {
return res.sendStatus(404)
}
}
app.use(middleware)
使用expres啟動(dòng)了本地開發(fā)服務(wù)后,使用中間件去為其構(gòu)造一個(gè)靜態(tài)服務(wù)器,并使用了內(nèi)存文件系統(tǒng),使讀取文件后存放到內(nèi)存中,提高讀寫效率,最終返回生成的文件。
啟動(dòng)webpack編譯
compiler.watch({}, err => {
console.log('又一次編譯任務(wù)成功完成了')
})
以監(jiān)控的模式啟動(dòng)一次webpack編譯,當(dāng)編譯成功之后執(zhí)行回調(diào)
創(chuàng)建http服務(wù)器并啟動(dòng)服務(wù)
constructor(compiler) {
// ...
this.server = require('http').createServer(app)
// ...
}
listen(port) {
this.server.listen(port, () => {
console.log(`服務(wù)器已經(jīng)在${port}端口上啟動(dòng)了`)
})
}
使用sockjs在瀏覽器端和服務(wù)端之間建立一個(gè) websocket 長連接
constructor(compiler) {
// ...
this.server = require('http').createServer(app)
let io = require('socket.io')(this.server)
io.on('connection', (socket) => {
sockets.push(socket)
socket.emit('hash', lastHash)
socket.emit('ok')
})
}
啟動(dòng)一個(gè) websocket服務(wù)器,然后等待連接來到,連接到來之后存進(jìn)sockets池
當(dāng)有文件改動(dòng),webpack重新編譯時(shí),向客戶端推送hash和ok兩個(gè)事件
服務(wù)端調(diào)試階段
感興趣的可以根據(jù)上面debug服務(wù)端源碼所帶的源碼位置,并在瀏覽器的調(diào)試模式下設(shè)置斷點(diǎn)查看每個(gè)階段的值。
node dev-server.js
使用我們自己編譯的dev-server.js啟動(dòng)服務(wù),可看到頁面可以正常展示,但還沒有實(shí)現(xiàn)熱更新。
下面將調(diào)式客戶端的源代碼分析其實(shí)現(xiàn)流程。
debug客戶端源碼
現(xiàn)在也只需要關(guān)注上圖的左側(cè)客戶端部分,右側(cè)可以暫時(shí)忽略。下面步驟主要是debug客戶端源碼分析其詳細(xì)思路,也給出了代碼所處的具體位置,感興趣的可以先行定位到下面的代碼處設(shè)置斷點(diǎn),然后觀察數(shù)據(jù)的變化情況。也可以先跳過閱讀此步驟。
debug客戶端源碼分析其詳細(xì)思路
-
webpack-dev-server/client端會(huì)監(jiān)聽到此hash消息,源代碼地址@webpack-dev-server/index.js#L54 -
客戶端收到ok的消息后會(huì)執(zhí)行reloadApp方法進(jìn)行更新,源代碼地址index.js#L101 -
在reloadApp中會(huì)進(jìn)行判斷,是否支持熱更新,如果支持的話發(fā)射webpackHotUpdate事件,如果不支持則直接刷新瀏覽器,源代碼地址reloadApp.js#L7 -
在webpack/hot/dev-server.js會(huì)監(jiān)聽webpackHotUpdate事件,源代碼地址dev-server.js#L55 -
在check方法里會(huì)調(diào)用module.hot.check方法,源代碼地址dev-server.js#L13 -
HotModuleReplacement.runtime請求Manifest,源代碼地址HotModuleReplacement.runtime.js#L180 -
它通過調(diào)用 JsonpMainTemplate.runtime的hotDownloadManifest方法,源代碼地址JsonpMainTemplate.runtime.js#L23 -
調(diào)用JsonpMainTemplate.runtime的hotDownloadUpdateChunk方法通過JSONP請求獲取到最新的模塊代碼,源代碼地址JsonpMainTemplate.runtime.js#L14 -
補(bǔ)丁JS取回來后會(huì)調(diào)用JsonpMainTemplate.runtime.js的webpackHotUpdate方法,源代碼地址JsonpMainTemplate.runtime.js#L8 -
然后會(huì)調(diào)用HotModuleReplacement.runtime.js的hotAddUpdateChunk方法動(dòng)態(tài)更新模塊代碼,源代碼地址HotModuleReplacement.runtime.js#L222 -
然后調(diào)用hotApply方法進(jìn)行熱更新,源代碼地址HotModuleReplacement.runtime.js#L257、HotModuleReplacement.runtime.js#L278
客戶端簡易實(shí)現(xiàn)
上面是我通過debug得出dev-server運(yùn)行流程比較核心的幾個(gè)點(diǎn),下面將其抽象整合成一個(gè)文件。
webpack-dev-server/client端會(huì)監(jiān)聽到此hash消息
在開發(fā)客戶端功能之前,需要在src/index.html中引入socket.io
<script src="/socket.io/socket.io.js"></script>
下面連接socket并接受消息
let socket = io('/')
socket.on('connect', onConnected)
const onConnected = () => {
console.log('客戶端連接成功')
}
let hotCurrentHash // lastHash 上一次 hash值
let currentHash // 這一次的hash值
socket.on('hash', (hash) => {
currentHash = hash
})
將服務(wù)端webpack每次編譯所產(chǎn)生hash進(jìn)行緩存
客戶端收到ok的消息后會(huì)執(zhí)行reloadApp方法進(jìn)行更新
socket.on('ok', () => {
reloadApp(true)
})
reloadApp中判斷是否支持熱更新
// 當(dāng)收到ok事件后,會(huì)重新刷新app
function reloadApp(hot) {
if (hot) { // 如果hot為true 走熱更新的邏輯
hotEmitter.emit('webpackHotUpdate')
} else { // 如果不支持熱更新,則直接重新加載
window.location.reload()
}
}
在reloadApp中會(huì)進(jìn)行判斷,是否支持熱更新,如果支持的話發(fā)射webpackHotUpdate事件,如果不支持則直接刷新瀏覽器。
在webpack/hot/dev-server.js會(huì)監(jiān)聽webpackHotUpdate事件
首先需要一個(gè)發(fā)布訂閱去綁定事件并在合適的時(shí)機(jī)觸發(fā)。
class Emitter {
constructor() {
this.listeners = {}
}
on(type, listener) {
this.listeners[type] = listener
}
emit(type) {
this.listeners[type] && this.listeners[type]()
}
}
let hotEmitter = new Emitter()
hotEmitter.on('webpackHotUpdate', () => {
if (!hotCurrentHash || hotCurrentHash == currentHash) {
return hotCurrentHash = currentHash
}
hotCheck()
})
會(huì)判斷是否為第一次進(jìn)入頁面和代碼是否有更新。
上面的發(fā)布訂閱較為簡單,且只支持先發(fā)布后訂閱功能。對于一些較為復(fù)雜的場景可能需要先訂閱后發(fā)布,此時(shí)可以移步@careteen/event-emitter。其實(shí)現(xiàn)原理也挺簡單,需要維護(hù)一個(gè)離線事件棧存放還沒發(fā)布就訂閱的事件,等到訂閱時(shí)可以取出所有事件執(zhí)行。
在check方法里會(huì)調(diào)用module.hot.check方法
function hotCheck() {
hotDownloadManifest().then(update => {
let chunkIds = Object.keys(update.c)
chunkIds.forEach(chunkId => {
hotDownloadUpdateChunk(chunkId)
})
})
}
上面也提到過webpack每次編譯都會(huì)產(chǎn)生hash值、已改動(dòng)模塊的json文件、已改動(dòng)模塊代碼的js文件,
此時(shí)先使用ajax請求Manifest即服務(wù)器這一次編譯相對于上一次編譯改變了哪些module和chunk。
然后再通過jsonp獲取這些已改動(dòng)的module和chunk的代碼。
調(diào)用hotDownloadManifest方法
function hotDownloadManifest() {
return new Promise(function (resolve) {
let request = new XMLHttpRequest()
//hot-update.json文件里存放著從上一次編譯到這一次編譯 取到差異
let requestPath = '/' + hotCurrentHash + ".hot-update.json"
request.open('GET', requestPath, true)
request.onreadystatechange = function () {
if (request.readyState === 4) {
let update = JSON.parse(request.responseText)
resolve(update)
}
}
request.send()
})
}
調(diào)用hotDownloadUpdateChunk方法通過JSONP請求獲取到最新的模塊代碼
function hotDownloadUpdateChunk(chunkId) {
let script = document.createElement('script')
script.charset = 'utf-8'
// /main.xxxx.hot-update.js
script.src = '/' + chunkId + "." + hotCurrentHash + ".hot-update.js"
document.head.appendChild(script)
}
這里解釋下為什么使用JSONP獲取而不直接利用socket獲取最新代碼?主要是因?yàn)?code style="font-size: 14px;border-radius: 4px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">JSONP獲取的代碼可以直接執(zhí)行。
調(diào)用webpackHotUpdate方法
當(dāng)客戶端把最新的代碼拉到瀏覽之后
window.webpackHotUpdate = function (chunkId, moreModules) {
// 循環(huán)新拉來的模塊
for (let moduleId in moreModules) {
// 從模塊緩存中取到老的模塊定義
let oldModule = __webpack_require__.c[moduleId]
// parents哪些模塊引用這個(gè)模塊 children這個(gè)模塊引用了哪些模塊
// parents=['./src/index.js']
let {
parents,
children
} = oldModule
// 更新緩存為最新代碼 緩存進(jìn)行更新
let module = __webpack_require__.c[moduleId] = {
i: moduleId,
l: false,
exports: {},
parents,
children,
hot: window.hotCreateModule(moduleId)
}
moreModules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
module.l = true // 狀態(tài)變?yōu)榧虞d就是給module.exports 賦值了
parents.forEach(parent => {
// parents=['./src/index.js']
let parentModule = __webpack_require__.c[parent]
// _acceptedDependencies={'./src/title.js',render}
parentModule && parentModule.hot && parentModule.hot._acceptedDependencies[moduleId] && parentModule.hot._acceptedDependencies[moduleId]()
})
hotCurrentHash = currentHash
}
}
hotCreateModule的實(shí)現(xiàn)
實(shí)現(xiàn)我們可以在業(yè)務(wù)代碼中定義需要熱更新的模塊以及回調(diào)函數(shù),將其存放在hot._acceptedDependencies中。
window.hotCreateModule = function () {
let hot = {
_acceptedDependencies: {},
dispose() {
// 銷毀老的元素
},
accept: function (deps, callback) {
for (let i = 0; i < deps.length; i++) {
// hot._acceptedDependencies={'./title': render}
hot._acceptedDependencies[deps[i]] = callback
}
}
}
return hot
}
然后在webpackHotUpdate中進(jìn)行調(diào)用
parents.forEach(parent => {
// parents=['./src/index.js']
let parentModule = __webpack_require__.c[parent]
// _acceptedDependencies={'./src/title.js',render}
parentModule && parentModule.hot && parentModule.hot._acceptedDependencies[moduleId] && parentModule.hot._acceptedDependencies[moduleId]()
})
最后調(diào)用hotApply方法進(jìn)行熱更新
客戶端調(diào)試階段
經(jīng)過上述實(shí)現(xiàn)了一個(gè)基本版的HMR,可更改代碼保存的同時(shí)查看瀏覽器并非整體刷新,而是局部更新代碼進(jìn)而更新視圖。在涉及到大量表單的需求時(shí)大大提高了開發(fā)效率。
問題
-
如何實(shí)現(xiàn)commonjs規(guī)范?
感興趣的可前往debug CommonJs規(guī)范了解其實(shí)現(xiàn)原理。
-
webpack實(shí)現(xiàn)流程以及各個(gè)生命周期的作用是什么?
webpack主要借助了
基于此我實(shí)現(xiàn)了一版簡易的webpack,源碼100+行,食用時(shí)伴著注釋很容易消化,感興趣的可前往看個(gè)思路。tapable這個(gè)庫所提供的一系列同步/異步鉤子函數(shù)貫穿整個(gè)生命周期。
-
發(fā)布訂閱的使用和實(shí)現(xiàn),并且如何實(shí)現(xiàn)一個(gè)可先訂閱后發(fā)布的機(jī)制?
上面也提到需要使用到發(fā)布訂閱模式,且只支持先發(fā)布后訂閱功能。對于一些較為復(fù)雜的場景可能需要先訂閱后發(fā)布,此時(shí)可以移步@careteen/event-emitter。其實(shí)現(xiàn)原理也挺簡單,需要維護(hù)一個(gè)離線事件棧存放還沒發(fā)布就訂閱的事件,等到訂閱時(shí)可以取出所有事件執(zhí)行。
-
為什么使用JSONP而不用socke通信獲取更新過的代碼?
因?yàn)橥ㄟ^socket通信獲取的是一串字符串需要再做處理。而通過
JSONP獲取的代碼可以直接執(zhí)行。
引用
-
模塊熱替換 - webpack官網(wǎng)
招聘
急缺前端,對搜狐焦點(diǎn)感興趣的直接簡歷發(fā)我郵件[email protected]或加我vx: Careteen
關(guān)于本文
來源:careteenL
https://segmentfault.com/a/1190000020310371
