利用 webpack 處理開發與線上環境靜態資源切換問題
前言
webpack,作為一個處理模組載入、資源依賴管理、構建化的工具,已經逐漸成為了前端工程化領域的新貴。其創造性的把每個靜態資源歸為一個 module(模組)並能被其強大的 loader 所載入的這種方式,成功的開闢了前端工程界的另一大生態。基於其官網文件的完善度較高,這篇文章就不對 webpack 的主要內容做過多的介紹,而是迴歸到本文的主題,即通過介紹幾款 webpack 相關的外掛,來解決一個常見的工程問題:如何做到靜態資源路徑可以在不同的環境下自動切換。
問題說明
這到底是個怎樣的問題?設想一下,在使用 webpack 打包編譯之後,它會生成一個 js 檔案,隨後我們需要在 html 或者模板檔案裡指定這個檔案的路徑確保其被正確的引入,
<script type="javascript" src="app.js"></script>
對於開發環境下的單入口檔案(稍後會介紹 webpack 打包到多個入口的解決方案 ),這個標籤內的引入檔案路徑完全可以寫死,而且在 webpack-dev-server 熱替換機制的幫助下,我們也無須通過對打包生成的檔案新增 hash 值來處理因瀏覽器快取的緣故引起的引用不到最新資源。
但在產品模式下,我們非常有必要在 webpack 的 output 屬性裡的 filename 裡配置一個 chunkhash 來變向的為靜態資源注入版本號,如下,
output: {
filename : [name].[chunkhash].js,
}
以便上線之後頁面可以引入版本更新後的程式碼。chunkhash 是一個基於檔案內容,通過摘要演算法(如md5)生成的一個被稱之為檔案指紋
的序號,即只有當檔案內容發生改變的時候,這個值才會相應更改。
通過給靜態資源注入 hash 值來作為版本號的好處主要有兩個:
-
實現 策略。當釋出新版本時,我們只需要更新更改了的資源。這比起將新版資源存放在例如
/v1.3/xx.js
這種帶版本號的路徑或資料夾下的部署方式會顯得更科學一點:減少手動配置版本號的額外操作、已經快取過且快取尚未過期的瀏覽器只需請求更新過的資源,確保未變更過的資源可以依舊從快取內讀取。 -
實現策略。張雲龍老師的原文中提到的這種平滑的版本升級方式更加完美的解決了靜態資源部署至CDN出現的問題。
這個時候我們再來看下線上的 script 引入,
<script type="javascript" src="http://xxx.cdn.com/app.82076244596568c8c929.js
"></script>
Fine, 也許你會說我可以手動 copy/paste 這個版本號當你需要從開發切到產品環境,額額,單個入口檔案這麼處理雖是可以,但想象下當有多個入口檔案的時候。。。(感覺我的左手大拇指肌腱炎又要犯了。。),這麼經典的問題webpack早已準備好了它的解決方案。
從 webpack 的編譯資料裡獲取開發與生產的資源路徑對應關係
這一部分的工作可以說是解決這個問題的一個核心環節,即我們需要通過 webpack 來生成類似如下一張對應關係圖:
{
'app.js': 'http://xxx.cdn.com/app.82076244596568c8c929.js'
}
像在 webpack 的 plugin 屬性裡配置如下,我們就可以通過返回 webpack 的編譯資料裡獲取到帶有 chunkhash 的檔案資訊:
// webpack.config.js
module.exports = {
...
plugins: [
function() {
this.plugin("done", function(stats) {
require("fs").writeFileSync(
path.join(__dirname, "..", "stats.json"),
JSON.stringify(stats.toJson()));
});
}
]
}
將 stats.json
require 到專案中,通過讀取 publicPath
、 assetsByChunkName
屬性,可以得到開發與線上環境資源路徑的對應關係。
切換資源路徑
接下來的工作基本上就是如何利用這個對應關係來切換對應環境下的路徑。這個還要取決於你的頁面是否會涉及到服務端的渲染。
服務端渲染資源路徑
以 node 作為服務端語言,handlebars(或者ejs)為模板語言為例,我們通過編寫模板語言的 helper 來讀取由 assets-webpack-plugin
生成的
stats.json,在不同的環境下實現路徑切換:
stats.json -- webpack 跑開發配置
{
"app": {
"js": "app.js"
}
}
stats.json -- webpack 跑生產配置
{
"app": {
"js": "http://xxx.cdn.com/app.82076244596568c8c929.js"
}
}
example.handlebars
<script type="text/javascript" src="{{app.js}}"></script>
後臺通過 require stats.json
資料並傳入到模板即可實現根據環境動態渲染資源路徑。
如果你的後臺是使用 Rails 來搭建的話,那麼這篇文章更詳細的介紹了處理這種情況下處理資源切換的問題
前端渲染頁面模板
如果你的專案不依賴任何後端渲染,那麼 html-webpack-plugin
這款外掛可以為你動態生成一個帶有 css、js 等資源路徑的
html 檔案。
html-webpack-plugin
具體的用法可以點選這裡,其中 inject
這個屬性可以讓你將
script 標籤插入到 dom 的指定位置。為了能夠更大許可權的將 webpack 編譯過的資源可以插入到 html 檔案的任意位置,我們可以在 HtmlWebpackPlugin
裡指定的
template 檔案裡寫入如下程式碼:
<% for (var css in htmlWebpackPlugin.files.css) { %>
<link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
<% } %>
<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
<script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
<% } %>
就可以同樣實現靜態資源的切換,所以對於前端渲染模板的這種情況,我們無須再生成一個 json 檔案,對於使用諸如 react、vue 這種框架,僅使用這個外掛也是極好的。
htmlWebpackPlugin
具體還有哪些屬性可以配置,可以參考下這個 default
template 檢視完整例子