webpack打包優化分爲兩部分,一部分是大小優化,另一部分是速度優化。
大小優化
1.CommonsChunk
前端構建項目中,爲了提高打包效率,往往將第三庫與業務邏輯代碼分開打包,因爲第三方庫往往不需要經常打包更新。webpack建議使用CommonsChunk 來單獨打包第三方庫:
module.exports = {
entry: {
vendor: ['react','react-dom'],
app: "./main",
},
output: {
path: './build',
filename: '[name].js',
library: '[name]_library'
},
plugins: [
new CommonsChunkPlugin({
name: "vendor",
}),
]
};
CommonsChunk雖然可以減少包的大小,但存在問題是:即使代碼不更新,每次重新打包,vendor都會重新生成,不符合我們分離第三方包的初衷。
2.Externals
相比於前者,webpack 提供Externals的方法,可以通過外部引用的方法,引入第三方庫: index.html
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
webpack.config.js
module.exports = {
externals: {
jquery: 'jQuery'
}
};
業務邏輯,如index.js
import $ from 'jquery';
$('.my-element').animate(...);
webpack打包時,發現jquery定義在externals,則不會打包jquery代碼。由於不需要打包jquery,所以也減少打包時間。 不過externals雖然解決了外部引用問題,但是無法解決以下問題:
import xxx from 'react/src/xx';
webpack遇到此問題時,會重新打包react代碼。 參考:https://gold.xitu.io/entry/57996222128fe1005411c649
3.DLL & DllReference
相比於前者,通過前置這些依賴包的構建,來提高真正的build和rebuild構建效率。也就是說只要第三方庫沒有變化,之後的每次build都只需要去打包自己的業務代碼,解決Externals多次引用問題。 webpack通過webpack.DllPlugin與webpack.DllReferencePlugin兩個內嵌插件實現此功能。
1、新建webpack.dll.config.js
const webpack = require('webpack');
module.exports = {
entry: {
bundle: [
'react',
'react-dom',
//其他庫
],
},
output: {
path: './build',
filename: '[name].js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: './build/bundle.manifest.json',
name: '[name]_library',
})
]
};
webpack.DllPlugin選項:
- path:manifest.json文件的輸出路徑,這個文件會用於後續的業務代碼打包;
- name:dll暴露的對象名,要跟output.library保持一致;
- context:解析包路徑的上下文,這個要跟接下來配置的 webpack.config.js 一致。
運行:
npm run webpack-dll
生成兩個文件,一個是打包好的bundlejs,另外一個是bundle.mainifest.json,大致內容如下:
{
"name": "bundle_library",
"content": {
"./node_modules/react/react.js": 1,
"./node_modules/react/lib/React.js": 2,
"./node_modules/process/browser.js": 3,
"./node_modules/object-assign/index.js": 4,
"./node_modules/react/lib/ReactChildren.js": 5,
"./node_modules/react/lib/PooledClass.js": 6,
"./node_modules/react/lib/reactProdInvariant.js": 7,
//其他引用
}
2、配置webpack.config.js
const webpack = require('webpack');
var path = require('path');
module.exports = {
entry: {
main: './main.js',
},
output: {
path: path.join(__dirname, "build"),
publicPath: './',
filename: '[name].js'
},
module: {
loaders:[
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'},
{
test: /\.jsx?$/,
loaders: ['babel-loader?presets[]=es2015&presets[]=react'],
include: path.join(__dirname, '.')
}
]
},
plugins: [
new webpack.DllReferencePlugin({
context: '.',
manifest: require("./build/bundle.manifest.json"),
}),
]
};
webpack.DllReferencePlugin的選項中:
- context:需要跟之前保持一致,這個用來指導webpack匹配manifest.json中庫的路徑;
- manifest:用來引入剛纔輸出的manifest.json文件。
速度優化
1.優化loader配置
1.1 縮小文件匹配範圍(include/exclude)
通過排除node_modules下的文件 從而縮小了loader加載搜索範圍 高概率命中文件
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/, // 排除不處理的目錄
include: path.resolve(dirname, 'src') // 精確指定要處理的目錄
}
]
}
1.2 緩存loader的執行結果(cacheDirectory)
cacheDirectory是loader的一個特定的選項,默認值是false。指定的目錄(use: ‘babel-loader?cacheDirectory=cacheLoader’)將用來緩存loader的執行結果,減少webpack構建時Babel重新編譯過程。如果設置一個空值(use: ‘babel-loader?cacheDirectory’) 或true(use: ‘babel-loader?cacheDirectory=true’) 將使用默認的緩存目錄(node_modules/.cache/babel-loader),如果在任何根目錄下都沒有找到 node_modules 目錄,將會降級回退到操作系統默認的臨時文件目錄。
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader?cacheDirectory', // 緩存loader執行結果 發現打包速度已經明顯提升了
exclude: /node_modules/,
include: path.resolve(dirname, 'src')
}
]
}
2.resolve優化配置
2.1 優化模塊查找路徑 resolve.modules
Webpack的resolve.modules配置模塊庫(即 node_modules)所在的位置,在 js 裏出現 import ‘vue’ 這樣不是相對、也不是絕對路徑的寫法時,會去 node_modules 目錄下找。但是默認的配置,會採用向上遞歸搜索的方式去尋找,但通常項目目錄裏只有一個 node_modules,且是在項目根目錄,爲了減少搜索範圍,可以直接寫明 node_modules 的全路徑;同樣,對於別名(alias)的配置,亦當如此:
const path = require('path');
function resolve(dir) { // 轉換爲絕對路徑
return path.join(dirname, dir);
}
resolve: {
modules: [ // 優化模塊查找路徑
path.resolve('src'),
path.resolve('node_modules') // 指定node_modules所在位置 當你import 第三方模塊時 直接從這個路徑下搜索尋找
]
}
配置好src目錄所在位置後,由於util目錄是在src裏面 所以可以用下面方式引入util中的工具函數
// main.js
import dep1 from 'util/dep1';
import add from 'util/add';
2.2 resolve.alias 配置路徑別名
創建 import 或 require 的路徑別名,來確保模塊引入變得更簡單。配置項通過別名來把原導入路徑映射成一個新的導入路徑 此優化方法會影響使用Tree-Shaking去除無效代碼。
例如,一些位於 src/ 文件夾下的常用模塊:
alias: {
Utilities: path.resolve(dirname, 'src/utilities/'),
Templates: path.resolve(dirname, 'src/templates/')
}
現在,替換「在導入時使用相對路徑」這種方式,就像這樣:
import Utility from '../../utilities/utility';
你可以這樣使用別名:
import Utility from 'Utilities/utility';
resolve: {
alias: { // 別名配置 通過別名配置 可以讓我們引用變的簡單
'vue$': 'vue/dist/vue.common.js', // $表示精確匹配
src: resolve('src') // 當你在任何需要導入src下面的文件時可以 import moduleA from 'src/moduleA' src會被替換爲resolve('src') 返回的絕對路徑 而不需要相對路徑形式導入
}
}
也可以在給定對象的鍵後的末尾添加 $,以表示精準匹配:
alias: {
util$: resolve('src/util/add.js')
}
這將產生以下結果:
import Test1 from 'util'; // 精確匹配,所以 src/util/add.js 被解析和導入
import Test2 from 'util/dep1.js'; // 精確匹配,觸發普通解析 util/dep1.js
2.3resolve.extensions
當引入模塊時不帶文件後綴 webpack會根據此配置自動解析確定的文件後綴
後綴列表儘可能小
頻率最高的往前放
導出語句儘可能帶上後綴
resolve: {
extensions: ['.js', '.vue']
}
3.module.noParse
用了noParse的模塊將不會被loaders解析,所以當我們使用的庫如果太大,並且其中不包含import require、define的調用,我們就可以使用這項配置來提升性能, 讓 Webpack 忽略對部分沒采用模塊化的文件的遞歸解析處理。
// 忽略對jquery lodash的進行遞歸解析
module: {
// noParse: /jquery|lodash/
// 從 webpack 3.0.0 開始
noParse: function(content) {
return /jquery|lodash/.test(content)
}
}
4.HappyPack
HappyPack是讓webpack對loader的執行過程,從單一進程形式擴展爲多進程模式,也就是將任務分解給多個子進程去併發的執行,子進程處理完後再把結果發送給主進程。從而加速代碼構建 與 DLL動態鏈接庫結合來使用更佳。
npm i happypack@next -D
webpack.config.js
const HappyPack = require('happypack');
const os = require('os'); // node 提供的系統操作模塊
// 根據我的系統的內核數量 指定線程池個數 也可以其他數量
const happyThreadPool = HappyPack.ThreadPool({size: os.cpus().lenght})
module: {
rules: [
{
test: /\.js$/,
use: 'happypack/loader?id=babel',
exclude: /node_modules/,
include: path.resolve(dirname, 'src')
}
]
},
plugins: [
new HappyPack({ // 基礎參數設置
id: 'babel', // 上面loader?後面指定的id
loaders: ['babel-loader?cacheDirectory'], // 實際匹配處理的loader
threadPool: happyThreadPool,
// cache: true // 已被棄用
verbose: true
});
]
happypack提供的loader,是對文件實際匹配的處理loader。這裏happypack提供的loader與plugin的銜接匹配,則是通過id=happypack來完成。
5.ParallelUglifyPlugin
這個插件可以幫助有很多入口點的項目加快構建速度。把對JS文件的串行壓縮變爲開啓多個子進程並行進行uglify。
cnpm i webpack-parallel-uglify-plugin -D
// webpck.config.js
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
plugins: [
new ParallelUglifyPlugin({
workerCount: 4,
uglifyJS: {
output: {
beautify: false, // 不需要格式化
comments: false // 保留註釋
},
compress: { // 壓縮
warnings: false, // 刪除無用代碼時不輸出警告
drop_console: true, // 刪除console語句
collapse_vars: true, // 內嵌定義了但是隻有用到一次的變量
reduce_vars: true // 提取出出現多次但是沒有定義成變量去引用的靜態值
}
}
});
]
執行壓縮
webpack --mode production
6.Tree Shaking
剔除JavaScript中用不上的代碼。它依賴靜態的ES6模塊化語法,例如通過impot和export導入導出