webpack編譯優化

NO IMAGE

如果是多頁項目,修改入口 entry,區分開發和生產環境

用我正在開發的項目舉例,修改之前 entry 沒有區分開發和生產環境,入口對象裡包含 pages 目錄下所有頁面的入口 js

webpack.base.conf.js

entry: utils.getEntry()

utils.js

exports.getEntry = function () {
var entry = {}
Object.keys(config.pages).forEach(function (name) {
entry[name] = config.pages[name].entry
})
return entry
}

config.pages 是前面封裝好的一個對象,存儲著所有頁面的信息,在這裡交給 Object.keys() 方法去枚舉它的屬性並返回一個數組,再將該數組交給 forEach 循環遍歷,循環裡的 name 就是 config.pages 對象的 keyconfig.pages 如下:

{
home: {
name: 'home',
entry: 'F:/path.../src/pages/home/main.js',
template: 'F:/path.../src/pages/home/index.html'
},
'product-details': {
name: 'product-details',
entry: 'F:/path.../src/pages/product-details/main.js',
template: 'F:/path.../src/pages/product-details/index.html'
}
...
}

修改之後:

utils.js

exports.getEntry = function () {
var entry = {}
Object.keys(config.pages).forEach(function (name) {
if (process.env.NODE_ENV === 'production') {
entry[name] = config.pages[name].entry
} else {
var target_pages = !!JSON.parse(process.env.npm_config_argv).original[3] ? JSON.parse(process.env.npm_config_argv).original[3] : 'home'
var target_pages_arr = target_pages.split('!')
target_pages_arr.forEach(function (target_name) {
if (name === target_name) entry[name] = config.pages[name].entry
})
}
})
return entry
}

首先通過 process.env.NODE_ENV 判斷當前環境,如果是開發環境,就將所有頁面的入口 js 添加進 entry (這裡視你的情況而定,我開發的這個項目需要將所有的頁面打包上線,所以 entry 裡添加了所有頁面的入口 js),如果是生產環境,則從執行的命令(npm run dev -- pageName | npm run dev == pageNameA!pageNameB!pageNameC)中獲取當前正在開發的頁面,再將這些頁面的入口 js 添加進 entry,這樣可以大大減少開發時每次編譯所消耗的時間。

使用 HappyPack

webpack 構建時需要解析和處理大量的文件,但運行在 Node.js 上的 webpack 是單線程模型的,這就導致了 webpack 構建的時間開銷比較大,項目越大問題越嚴重。HappyPack 可以幫我們分解任務並管理線程,它將任務分解給多個子進程併發執行,子進程處理完之後再將結果發送給主進程,我們只需要按照它的用法和配置正確接入即可。

webpack.base.conf.js

const HappyPack = require('happypack')
const happyThreadPool = HappyPack.ThreadPool({ size: 5 })
module.exports = {
module: {
rules: [{
test: /\.js$/,
use: ['happypack/loader?id=babel'],
include: [resolve('src')]
}]
},
plugins: [
new HappyPack({
id: 'babel',
loaders: ['babel-loader?cacheDirectory'],
threadPool: happyThreadPool
})
]
}
  • 對於 Loader,將文件的處理交給 happypack/loaderhappypack/loader後面的 querystring — ?id=babel 會告訴它該選擇插件中的哪個 HappyPack 實例來處理這些文件。

  • 在插件中 new 了一個 HappyPack 的實例,作用是告訴 happypack/loader 怎麼處理 js 後綴的文件, id 的值和 Loader 中的 ?id=babel 對應, loaders 屬性的值即原 Loader 配置,注意 loaders 屬性需是數組類型。

  • HappyPack 實例中的 threadPool 參數表示共享進程池,多個 HappyPack 實例都使用同一個共享進程池中的子進程去處理任務,可以防止資源佔用過多,該例中構造出來的共享進程池 (const happyThreadPool = HappyPack.ThreadPool({ size: 5 })) 包含5個子進程。

配置完之後安裝新依賴:

npm i -D happypack

安裝完之後重新進行構建即可。

構建時控制檯會輸出類似於下面的日誌:

webpack編譯優化

這是 happypack 輸出的,如果想關閉它,在實例化 HappyPack 插件時增加 verbose 參數並將它設置為 false 即可。

使用 DllPlugin 和 DllReferencePlugin

DllPlugin 和 DllReferencePlugin 的作用原理類似於 windows 系統中的動態鏈接庫,即 .dll 後綴的文件。動態鏈接庫中包含著供其它模塊調用的函數和數據。

在 Web 項目中,可以將源代碼中依賴的所有基礎模塊提取出來,統一打包到一個單獨的動態鏈接庫裡,當需要導入的模塊存在於這個提前生成的動態鏈接庫裡時,該模塊就不能被再次打包,而是直接從動態鏈接庫裡面獲取。

如果動態鏈接庫裡包含較多複用率比較高的模塊,便會大大減少項目構建所需要的時間,因為這些模塊被編譯一次之後就不會再被重複編譯,在以後的構建過程中用到這些模塊的話就會直接從動態鏈接庫中獲取。

動態鏈接庫中包含的大多是一些常用的第三方模塊,如 vue、vuex 等,只要不升級這些模塊,就不用重新編譯動態鏈接庫。

配置方法

步驟1 創建配置文件 webpack.dll.config.js

首先新建一個配置文件 webpack.dll.config.js,該文件的位置視你的項目結構而定,最好和其它 webpack 配置文件放在同一目錄下,該文件內容如下:

const path = require("path")
const webpack = require("webpack")
module.exports = {
entry: {
vendor: [
'iscroll',
'mint-ui',
'vue/dist/vue.common.js',
'vue-iscroll-view',
'vue-lazyload',
'vue-scroller',
'whatwg-fetch'
]
},
output: {
path: path.join(__dirname, '../static/js'),
filename: '[name].dll.js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, '..', '[name]-manifest.json'),
name: '[name]_library',
context: __dirname
})
]
};

entry.vendor 是要放進動態鏈接庫的模塊的數組。

output.path 表示輸出文件放置的目錄。

output.filename 表示輸出的動態鏈接庫文件名稱,[name] 代表當前動態鏈接庫的名稱,拿到的是 entry 配置項的 key,這裡就是 vendor。也可以生成多個動態鏈接庫,將模塊按類型進行區分,比如可以將項目依賴的第三方模塊 vuevuex 等放入一個庫中,將依賴的 polyfill 放入另外一個庫中,這時 entry 就是下面這個樣子:

entry: {
vue: [
'mint-ui',
'vue/dist/vue.common.js',
...
],
polyfill: [
'whatwg-fetch',
...
]
}

output.library 用於存放動態鏈接庫的全局變量名稱,比如對於 vendor 這個庫來說名稱就是 vendor_library

實例化 webpack.DllPlugin 時傳遞的參數 path 表示描述動態鏈接庫的 *-manifest.json 文件輸出的路徑。

參數 name 表示動態鏈接庫的全局變量名稱,需和 output.library 的值保持一致。該字段的值也是輸出的 *-manifest.json 中 name 字段的值。

參數 context 是 manifest 文件中請求的上下文,選填,默認是當前 webpack 文件的上下文。

步驟2 生成動態鏈接庫

配置好 webpack.dll.config.js 之後執行命令 webpack --config build/webpack.dll.config.js 就可以構建動態鏈接庫文件了,我們將這句命令加到 package.json 的 scripts 裡:

"scripts": {
"build:dll": "webpack --config build/webpack.dll.config.js"
}

執行 npm run build:dll 即可。

執行完之後就會生成 vendor.dll.js 和 vendor-manifest.json,vendor.dll.js 在 static 目錄下的 js 文件夾裡,它就是“動態鏈接庫文件”,vendor-manifest.json 在項目的根目錄下。

步驟3 引用動態鏈接庫

配置 DllReferencePlugin 插件,使用 vendor-manifest.json 來引用動態鏈接庫。

webpack.base.conf.js

plugins: [
...
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('../vendor-manifest.json')
})
]

context 需與 Dllplugin 裡 context 參數所指向的上下文保持一致。
manifest 參數用來引入之前生成的 vendor-manifest.json。

步驟4 頁面模板文件引入動態鏈接庫

在頁面的模板文件裡手動引入動態鏈接庫文件。

<script src="./static/js/vendor.dll.js"></script>

到這一步所有配置就已完成,根據動態鏈接庫中包含的模塊數量和複用率不同,構建所減少的時間也會有所不同。

PS1

如果動態鏈接庫所包含的模塊有所變化,就需要重新構建生成一份動態鏈接庫文件。

PS2

生成的 js 文件是未經壓縮的,可以在 webpack.dll.config.js 中添加插件將生成的動態鏈接庫文件進行壓縮:

plugins: [
...
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]

babel-loader 使用 cacheDirectory

'babel-loader?cacheDirectory'

用於緩存 babel 的編譯結果,加快重新編譯的速度。

縮小文件的查找範圍

module: {
rules: [{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory',
include: path.resolve(__dirname, '../src')
}]
}

include 也可以是數組,路徑視具體項目結構而定。用於命中指定目錄下的文件,加快 webpack 的搜索速度。

相關文章

Android架構組件讓天下沒有難做的App

帶你走進Android之基礎篇文件解壓縮

你知道支付寶容器化架構是怎麼搭建的嗎?

關於三次握手與四次揮手的那些事,你真的明白了嗎?不看後悔系列