Webpack 2 中一些常見的優化措施
Webpack 是一款強大的前端構建工具, 社區對其介紹的相關文章已經很多了, 本文不再贅述. 基于 Webpack 2, 本文是對我在搭建團隊前端腳手架的過程中, 搜羅的 Webpack 2 常見的優化措施的一個總結.
如果你還不了解 Webpack 2, 可以先看下 Webpack 2 快速入門
1. 分離第三方依賴
在 開發環境 下, 通常會采取 HMR 模式來提高開發效率. 但一般情況下, 我們只會更改自身的業務文件, 不會去更改第三方的依賴, 但 webpack 在 rebuild 的時候, 依舊會 build 所有的依賴. 因而, 為減少 rebuild 的時間, 我們可以分離第三方依賴, 在項目啟動之前, 將其單獨打包和引入.
這要借助 DllPlugin 插件.
我們定義一份生成 dll 的配置文件:
## webpacl.dll.config.js
module.exports = {
entry: {
vendor: [
'vue', 'vuex', 'vue-router', 'vuex-router-sync', 'babel-polyfill', '...'
]
},
output: {
path: path.join(__dirname, './public/', 'dist'),
filename: '[name].dll.js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, './public/', 'dist', '[name]-manifest.json'),
name: '[name]_library'
})
]
}
生成 dll 文件之后, 可以根據環境變量在頁面的靜態文件中引入:
這樣, 在每次 rebuild 的時候, webpack 都不會去重新 build vendor, 能極大減少 rebuild 的時間, 提升開發效率.
僅在開發環境下使用
2. 多進程構建
Webpack的構建過程是單進程的, 利用 HappyPack 可讓 loader 對文件進行多進程處理, 其原理圖如下:
在業務文件依賴越多和復雜的情況下, HappyPack 對 Webpack 構建效率的提升會越明顯. 下圖是我在項目使用 HappyPack 前后的一張構建時間對比圖:
HappyPack 會充分利用系統的資源來提升 Webpack 的構建效率, 所以系統本身的硬件配置會對 HappyPack 的使用有一定的影響.
HappyPack 不限于處理 js 文件, 也可以同時處理 css/vue 等其它類型文件. HappyPack 支持多個實例, 可以創建多個實例來分別處理不同的類型文件:
let HappyPack = require('happypack');
let os = require('os');
let happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
...
plugins: [
new HappyPack({
id: 'vue',
threadPool: happyThreadPool,
cache: true,
verbose: true,
loaders: ['vue-loader'],
}),
new HappyPack({
id: 'js',
threadPool: happyThreadPool,
cache: true,
verbose: true,
loaders: ['babel-loader'],
})
# others
]
...
}
此外, HappyPack 同時還利用緩存來使得 rebuild 更快.
開發環境和生產環境下均可使用. 關于其原理分析, 請看 HappyPack 原理解析
3. 提取公共的依賴模塊
無論是單頁還是多頁應用, 在 生產環境 下, 通常都會利用 CommonsChunkPlugin 插件來提供公共的依賴模塊:
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: ({resource}) => {
resource &&
resource.indexOf('node_modules') &&
resource.match(/.js$/)
}
}),
上述的配置會提取 node_modules 下的所有模塊, 打包出來的結果可能是這樣的:
打包結果分析圖由 webpack-bundle-analyzer 提供
這樣提取了公共模塊之后, 的確會減少業務包的大小, 但是, 這種方式會導致兩個問題:
- 業務越復雜, 三方依賴會越多, vendor 包會越大
- 沒有隔離業務路由組件, 所有的路由都有可能會去加載 vendor, 但并不是所有的路由組件都依賴 node_modules 下的所有模塊
所以, 上述提取公共依賴的方式不可取. 我們應該去分析業務依賴和路由, 盡可能將所有路由組件的公共依賴提取出來:
entry: {
app: path.resolve(__dirname, '../src/page/index.js'),
vendor: [
'vue', 'vuex', 'vue-router', 'vuex-router-sync', 'babel-polyfill',
'axios', '....'
]
},
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
filename: "vendor.js"
}),
前后兩種方式打包出來的 vendor 大小對比:
既要去提取公共依賴, 也要避免 vendor 包過于太大.
4. 文件分離
文件分離主要是將圖片和 CSS 從 js 中分離. 圖片和 CSS 都是 Webpack 需要構建的資源, 通過某種配置, 圖片可以以 base64 的方式混淆在 js 文件中, 這會增加最終的 bundle 文件的大小. 在 生產環境 下, 應該將圖片和 CSS 從 js 中分離:
- 在生產環境下, 通過自定義插件, 將圖片的本地引用替換為 CDN 的鏈接
- 在生產環境下, 通過 ExtractTextPlugin 來 提取 CSS.
5. 資源混淆和壓縮
Webpack提供的 UglifyJS 插件由于采用單線程壓縮, 速度比較慢,
可以使用 Parallel 插件進行優化:
let ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
let os = require('os');
new ParallelUglifyPlugin({
workerCount: os.cpus().length,
cacheDir: '.cache/',
uglifyJS: {
compress: {
warnings: false,
drop_debugger: true,
drop_console: true
},
comments: false,
sourceMap: true,
mangle: true
}
})
6. Gzip 壓縮
在 生產環境 下, 如果想進一步減小 bundle 文件的大小, 可以使用 Gzip 壓縮.
let CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
plugins: [
new CompressionPlugin({
asset: "[path].gz[query]",
algorithm: "gzip",
test: /.(js|html)$/,
threshold: 10240,
minRatio: 0.8
})
]
}
Gzip 壓縮能有效減少 bundle 的文件大小:
部署上線時, 服務端也需要開啟 gzip 壓縮
7. 按需加載
在單頁應用中, 一個應用可能會對應很多路由, 每個路由都會對應一個組件; 如果將這些組件全部全部放進一個 bundle, 會導致最終的 bundle 文件比較大(看上圖的 app bundle 文件). 因而, 我們需要利用 Webpack 的 Code Splitting 功能, 將代碼進行分割, 實現路由的按需加載.
在 Vue 中, 利用 vue-router 的 懶加載 功能, 是比較容易實現按需加載的:
當訪問首頁時, 會去加載 Index 組件, 此時并不會加載 Info 組件; 只有當路由切換為 /info 時, Info 組件才會被加載.
以上是個人的一些總結, 如有不足請指正, 如有遺漏, 歡迎補充.
vue-startup 是基于上述的一些優化措施寫的一個 Vue 的腳手架, 歡迎 star.
來自:http://web.jobbole.com/91414/