引入問題
在簡單的專案裡因為只輸出了一個 bundle.js
檔案,所以手寫了一個 index.html
檔案去引入這個 bundle.js
,才能讓應用在瀏覽器中執行起來。
在實際專案中遠比這複雜,一個頁面常常有很多資源要載入。接下來舉一個實戰中的例子,要求如下:
- 專案採用 ES6 語言加 React 框架。
- 給頁面加入 Google Analytics,這部分程式碼需要內嵌進 HEAD 標籤裡去。
- 給頁面加入 Disqus 使用者評論,這部分程式碼需要非同步載入以提升首屏載入速度。
- 壓縮和分離 JavaScript 和 CSS 程式碼,提升載入速度。
在開始前先來看看該應用最終釋出到線上的程式碼:
<html>
<head>
<meta charset="UTF-8">
<!--注入 Chunk app 依賴的 CSS-->
<style rel="stylesheet">h1{color:red}</style>
<!--內嵌 google_analytics 中的 JavaScript 程式碼-->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<!--非同步載入 Disqus 評論-->
<script async="" src="https://dive-into-webpack.disqus.com/embed.js"></script>
</head>
<body>
<div id="app"></div>
<!--匯入 app 依賴的 JS-->
<script src="app_746f32b2.js"></script>
<!--Disqus 評論容器-->
<div id="disqus_thread"></div>
</body>
</html>
複製程式碼
HTML 應該是被壓縮過的,這裡為了方便大家閱讀而格式化了 HTML,並且加入了註釋。
構建出的目錄結構為:
dist
├── app_792b446e.js
└── index.html
複製程式碼
可以看到部分程式碼被內嵌進了 HTML 的 HEAD 標籤中,部分檔案的檔名稱被打上根據檔案內容算出的 Hash 值,並且載入這些檔案的 URL 地址也被正常的注入到了 HTML 中。
如果你還採用手寫 index.html
檔案去完成以上要求,這就會使工作變得複雜、易錯,專案難以維護。
本節教你如何自動化的生成這個符合要求的 index.html
。
解決方案
推薦一個用於方便的解決以上問題的 Webpack 外掛 web-webpack-plugin。 該外掛已經被社群上許多人使用和驗證,解決了大家的痛點獲得了很多好評,下面具體介紹如何用它來解決上面的問題。
首先,修改 Webpack 配置為如下:
const path = require('path');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const { WebPlugin } = require('web-webpack-plugin');
module.exports = {
entry: {
app: './main.js'// app 的 JavaScript 執行入口檔案
},
output: {
filename: '[name]_[chunkhash:8].js',// 給輸出的檔名稱加上 Hash 值
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
// 排除 node_modules 目錄下的檔案,
// 該目錄下的檔案都是採用的 ES5 語法,沒必要再通過 Babel 去轉換
exclude: path.resolve(__dirname, 'node_modules'),
},
{
test: /\.css/,// 增加對 CSS 檔案的支援
// 提取出 Chunk 中的 CSS 程式碼到單獨的檔案中
use: ExtractTextPlugin.extract({
use: ['css-loader?minimize'] // 壓縮 CSS 程式碼
}),
},
]
},
plugins: [
// 使用本文的主角 WebPlugin,一個 WebPlugin 對應一個 HTML 檔案
new WebPlugin({
template: './template.html', // HTML 模版檔案所在的檔案路徑
filename: 'index.html' // 輸出的 HTML 的檔名稱
}),
new ExtractTextPlugin({
filename: `[name]_[contenthash:8].css`,// 給輸出的 CSS 檔名稱加上 Hash 值
}),
new DefinePlugin({
// 定義 NODE_ENV 環境變數為 production,以去除原始碼中只有開發時才需要的部分
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
// 壓縮輸出的 JavaScript 程式碼
new UglifyJsPlugin({
// 最緊湊的輸出
beautify: false,
// 刪除所有的註釋
comments: false,
compress: {
// 在UglifyJs刪除沒有用到的程式碼時不輸出警告
warnings: false,
// 刪除所有的 `console` 語句,可以相容ie瀏覽器
drop_console: true,
// 內嵌定義了但是隻用到一次的變數
collapse_vars: true,
// 提取出出現多次但是沒有定義成變數去引用的靜態值
reduce_vars: true,
}
}),
],
};
複製程式碼
以上配置中,大多數都是按照前面已經講過的內容增加的配置,例如:
- 增加對 CSS 檔案的支援,提取出 Chunk 中的 CSS 程式碼到單獨的檔案中,壓縮 CSS 檔案;
- 定義
NODE_ENV
環境變數為production
,以去除原始碼中只有開發時才需要的部分; - 給輸出的檔名稱加上 Hash 值;
- 壓縮輸出的 JavaScript 程式碼。
但最核心的部分在於 plugins
裡的:
new WebPlugin({
template: './template.html', // HTML 模版檔案所在的檔案路徑
filename: 'index.html' // 輸出的 HTML 的檔名稱
})
複製程式碼
其中 template: './template.html'
所指的模版檔案 template.html
的內容是:
<html>
<head>
<meta charset="UTF-8">
<!--注入 Chunk app 中的 CSS-->
<link rel="stylesheet" href="app?_inline">
<!--注入 google_analytics 中的 JavaScript 程式碼-->
<script src="./google_analytics.js?_inline"></script>
<!--非同步載入 Disqus 評論-->
<script src="https://dive-into-webpack.disqus.com/embed.js" async></script>
</head>
<body>
<div id="app"></div>
<!--匯入 Chunk app 中的 JS-->
<script src="app"></script>
<!--Disqus 評論容器-->
<div id="disqus_thread"></div>
</body>
</html>
複製程式碼
該檔案描述了哪些資源需要被以何種方式加入到輸出的 HTML 檔案中。
以 <link rel="stylesheet" href="app?_inline">
為例,按照正常引入 CSS 檔案一樣的語法來引入 Webpack 生產的程式碼。
href
屬性中的 app?_inline
可以分為兩部分,前面的 app
表示 CSS 程式碼來自名叫 app
的 Chunk 中,後面的 _inline
表示這些程式碼需要被內嵌到這個標籤所在的位置。
同樣的 <script src="./google_analytics.js?_inline"></script>
表示 JavaScript 程式碼來自相對於當前模版檔案 template.html
的本地檔案 ./google_analytics.js
,
而且檔案中的 JavaScript 程式碼也需要被內嵌到這個標籤所在的位置。
也就是說資源連結 URL 字串裡問號前面的部分表示資源內容來自哪裡,後面的 querystring 表示這些資源注入的方式。
除了 _inline
表示內嵌外,還支援以下屬性:
_dist
只有在生產環境下才引入該資源_dev
只有在開發環境下才引入該資源_ie
只有IE瀏覽器才需要引入的資源,通過[if IE]>resource<![endif]
註釋實現
這些屬性之間可以搭配使用,互不衝突。例如 app?_inline&_dist
表示只在生產環境下才引入該資源,並且需要內嵌到 HTML 裡去。
WebPlugin
外掛還支援一些其它更高階的用法,詳情可以訪問該專案主頁閱讀文件。
本例項提供專案完整程式碼