更多文章請點選Jade
在前一篇文章最後,我們已經鎖定問題——熱更新過程中導致入口檔案module.exports輸出的物件無法被正確掛載到window上。而至於真實情況是就是沒有掛載還是掛載後被沖掉(複寫)了,這個就是這篇文章中需要探索的。
探索前務必要先看一下Webpack熱更新的原始碼知道其原理。有關Webpack熱更新原理在此將不再贅述,推薦兩篇相關文章:
Webpack HMR 原理解析
Webpack 熱更新實現原理分析
在此,我再用通俗的語言來闡述一下Webpack熱更新的原理。
不得不再提一下我們的專案架構:本專案以express搭建服務端,本地開發配合webpack-dev-middleware、wepack-hot-middleware、webpack.HotModuleReplacementPlugin實現熱載入更新。我們看到有webpack-dev-middlerware、webpack-hot-middleware、webpack.HotModuleReplacementPlugin各種工具,但是我們要明白一點,打包這個活兒本質上還是webpack乾的,並不是加了熱更新相關的外掛或者中介軟體後,打包這項活就是外掛或者中介軟體幹了。外掛和中介軟體的作用僅僅是在webpack編譯打包核心的外圍做了一些事兒(手腳)。所以在看待這幾個外掛的時候,我們只要抓住熱更新這一個關鍵功能就可以了。
以下這個圖來自文章-Webpack HMR 原理解析
這樣理解起來就簡化不少,熱更新的機制本質也是client-server的機制,client就在我們的瀏覽器端,它需要知道檔案變化了並且做出一系列的動作然後把最終結果通過瀏覽器讓我們看到,那server端的職責就很明確了,它需要監控模組檔案的變化,如果變化了就要讓webpack(webpack對外暴露的API)重新打包,期間還要和client保持通訊,把自己這邊的狀況告訴client,client根據資訊採取動作。
以上就是進入問題分析前的準備工作。
深入實際問題分析
為了便於看清問題,實驗的例子越簡單越好。所以我們仍舊採用上一篇文章中使用的例子。
入口檔案 – examples/test/index.js
const Test = {
text: `hello world!`
}
module.exports = Test
複製程式碼
html – example/test/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>test</title>
</head>
<body>
Test
<!-- <script src="../../dist/components/test.js"></script> -->
<script src="/examples/test.js"></script>
<script>
console.log(test)
console.log(test.text)
</script>
</body>
</html>
複製程式碼
然後用生產的webpack打包配置和本地開發的打包配置分別打包出test_s.js(這個是直接打到物理磁碟的,即為釋出包)和test_f.js(開發包是打在記憶體的,但是瀏覽器能訪問到,我們直接右鍵另存為即可),test_s.js能夠按照預期執行,test_f.js無法拿到預期入口。
我們看下test_s.js和test_f.js檔案內容,我們可以在註釋中找到這麼一句話『Load entry module and return exports』,沒錯,這就是最終打包後暴露出來的方法。看截圖:
不管是test_s.js還是test_f.js,入口都是一個叫做341的模組,這沒毛病,畢竟是同樣的一份程式碼嘛。繼續看。
test_s.js是生產包,摒除了熱載入,本身test.js也沒啥程式碼,所以打包後的程式碼也很少,一眼就能看到 341是入口,341裡頭_webpack_require_(342),而342就是我們test.js的程式碼內容,成功找到了Test。對於_webpack_require_有疑問的同學可以檢視(這篇文章)[https://github.com/ShowJoy-com/showjoy-blog/issues/39],在這裡只要瞭解 在入口模組中呼叫了 webpack_require(342),就會得到342這個模組返回的 module.exports,所以就能將Test找到,並且賦給module.exports,所以341作為入口檔案,最終暴露出來的就是Test。
同樣在test_f.js中,我們也能找到入口引用,看下圖:
341作為入口模組,341中_webpack_require_(342),342模組就是我們的test.js,出乎意料的是341中同時引入了_webpack_require_(32),而且module.exports上掛載的不是342是32!!!。至此問題徹底明瞭,熱載入過程中導致入口檔案module.exports輸出的物件無法被正確掛載到window上。事實情況是隻引入了我們的入口檔案並沒有對輸出進行掛載(不是沖掉)掛載的是32模組。
那這是不是bug呢?是bug我就去提issue啦,看來還是太天真:)。
繼續看32模組。
如果你認真看過最前面推薦的文章——Webpack 熱更新實現原理分析,並且瀏覽過HRM原始碼,就會有深刻印象。這是HRM的client模組程式碼,核心功能是通過EventSource,將與HRM server建立連線,進行模組更新通訊。看一下程式碼註釋/* WEBPACK VAR INFECTION */,沒錯,就是這段注入程式碼,成了真正的入口函式。
再回頭看一下 webpack-develop.config.js:
entry: {
test: [path.resolve(__dirname, `examples/test/index.js`), `webpack-hot-middleware/client?path=/examples/__what&reload=true`],
},
複製程式碼
entry test的陣列依次是index.js,然後是 webpack-hot-middleware。所以index.js的包引到了,但是入口確是後者 webpack-hot-middleware。
最終解決方法
webpack-develop.config.js:
entry: {
// test: [path.resolve(__dirname, `examples/test/index.js`), `webpack-hot-middleware/client?path=/examples/__what&reload=true`],
// 更換順序,正確書寫entry
test: [`webpack-hot-middleware/client?path=/examples/__what&reload=true`, path.resolve(__dirname, `examples/test/index.js`)],
},
output: {
filename: `[name].js`,
publicPath: `/examples/`,
library: `[name]`,
libraryTarget: `umd`,
},
複製程式碼
至此,該問題完美解決。
後記:依樣畫葫蘆的坑有時候更大呀,而且極難理出排查思路。「別的元件入口都沒問題,偏偏這個元件有問題」。有種情況叫負負得正了:)。