前端的打包工具從以前的browserify、grunt、gulp到現現在的rollup、webpack,涌現出了不少優秀的打包工具,而目前最火的無疑是webpack,不管是當前熱門的框架仍是工具庫不少都選擇了它做爲打包工具,所以在開發中webpack做爲打包工具是一個很好的選擇。在最近的項目開發中我也用到了webpack,其中也碰到了很多優化方面的問題,這裏總結一下webpack打包優化的一些細節和方法。javascript
首先,此次項目用到的是vue的全家桶,在webpack的配置方面直接用的是vue-cli
生成的默認配置,項目打包完成後發現生成的vendor.js
文件體積特別大,其次打包過程至關緩慢,所以想嘗試各類方式對其進行優化。html
要想對打包體積進行優化,首先得找到體積大的模塊,在這裏咱們可使用webpack插件webpack-bundle-analyzer
來查看整個項目的體積結構對比,它是以treemap的形式展示出來,很形象直觀,還有一些具體的交互形式。既能夠查看你項目中用到的全部依賴,也能夠直觀看到各個模塊體積在整個項目中的佔比。前端
該插件的使用方法能夠直接經過npm install webpack-bundle-analyzer --save-dev
安裝,並在webpack的配置信息中的plugins: [new BundleAnalyzerPlugin()]
中添加便可。對於vue-cli
中的配置方式,默認是安裝了該插件,可是沒有啓用,找到config/index.js
文件在build
下面會有bundleAnalyzerReport
的配置,默認是process.env.npm_config_report
,這裏建議在package.json
的scripts
中添加一行"analyz": "npm_config_report=true npm run build"
,這樣每次想啓用該插件時只須要npm run analyze
便可。vue
對於webpack,它在模塊化打包上有兩點是其核心功能,一是它支持大量的模塊類型,不管是TypeScript
、CoffeeScript
仍是sass
、stylus
等語言它都支持,二是它能夠經過配置來控制打包文件的粒度,這個下面會講到。java
在開發中咱們每每會將全部的依賴庫單獨提取出來,而不與咱們的項目代碼混在一塊兒,這時咱們會用到一個webpack自帶的插件CommonsChunkPlugin
,從名字上就能夠看出它是一個提取公共模塊的插件。從它的文檔中能夠看出能夠傳入一個對象最爲參數,在使用中經常使用的三個參數分別爲:node
name
好理解,指的就是最後打包文件的名字,而若是使用的是names
的話,傳入的必須是一個字符串數組。minChunks
若是傳入的是一個數字的話,指的是若是該模塊被其餘模塊的引用次數達到了這個數值的話該模塊就會被打包。若是傳入的是一個函數的話,其返回值必須是布爾類型來指明這個模塊是否應該被打包進公共模塊。而chunks
則會指定一個字符串數組,若是設置了該參數,則打包的時候只會從其中指定的模塊中提取公共子模塊。jquery
下面經過幾個實例來講明這個插件是如何工做的。webpack
假設有兩個模塊chunk1.js
和chunk2.js
以及兩個項目文件a.js
和b.js
,結構大體以下:web
// a.js
require('./chunk1');
require('./chunk2');
require('jquery');
// b.js
require('./chunk1');
require('./chunk2');
require('vue');
// webpack配置以下
module.exports = {
entry: {
main: './main.js',
main1: './main1.js',
jquery:["jquery"],
vue:["vue"]
},
output: {
path: __dirname + '/dist',
filename: '[name].js'
},
plugins: [
new CommonsChunkPlugin({
name: ["common","jquery","vue","load"],
minChunks:2
})
] };
複製代碼
最終的打包結果是:jquery
被打包到jquery.js
,vue
被打包到vue.js
,common.js
打包的是公共模塊(chunk1和chunk2)。使用該插件打包時,會將知足minChunks
的模塊打包到name
數組的第一個塊裏,而後數組後面的依次打包,首先從entry
中找,若是沒有則產生一個空塊。name
數組中最後一個塊打包的是webpack的runtime代碼,在使用的時候必須先加載該塊。ajax
如今看一看vue-cli
對於該插件的配置文件:
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// 將node_modules中的依賴模塊所有提取到vendor文件中
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// webpack在使用CommonsChunkPlugin時會生成一段runtime代碼,而且打包進vendor中。
// 這樣即便不改變vendor代碼,每次打包時runtime會變化致使vendor的hash變化,這裏
// 把獨立的runtime代碼抽離出來來解決這個問題
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
}),
複製代碼
在項目中我經過方法必定位到幾處體積佔用較大的庫,其中一個即是moment.js
這個日期處理庫。對於一個日期處理的功能,爲什麼這個庫會佔用如此大的體積,仔細查看發現當引用這個庫的時候,全部的locale
文件都被引入,而這些文件甚至在整個庫的體積中佔了大部分,所以當webpack打包時移除這部份內容會讓打包文件的體積有所減少。
webpack自帶的兩個庫能夠實現這個功能:
IgnorePlugin
的使用方法以下:
// 插件配置
plugins: [
// 忽略moment.js中全部的locale文件
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
// 使用方式
const moment = require('moment');
// 引入zh-cn locale文件
require('moment/locale/zh-cn');
moment.locale('zh-cn');
複製代碼
ContextReplacementPlugin
的使用方法以下:
// 插件配置
plugins: [
// 只加載locale zh-cn文件
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
],
// 使用方式
const moment = require('moment');
moment.locale('zh-cn');
複製代碼
經過以上兩種方式,moment.js
的體積大體能縮減爲原來的四分之一。
在項目中我使用了lodash
這個很經常使用的工具庫,然而在代碼定位的時候發現這個庫也佔了很多的體積。仔細想一想,咱們在使用這類工具庫的時候每每只使用到了其中的不多的一部分功能,但卻把整個庫都引入了。所以這裏也能夠進一步優化,只引用須要的部分。
import {chain, cloneDeep} from 'lodash';
// 能夠改寫爲
import chain from 'lodash/chain';
import cloneDeep from 'lodash/cloneDeep';
複製代碼
這樣就能夠只打包咱們須要的部分功能。
對於一些必要的庫,但又沒法對該庫進行更好的體積優化的話,能夠嘗試經過外部引入的方式來減少打包文件的體積。採用該方法只須要在cdn站點找到須要引用的庫的外部連接,以及對webpack進行簡單配置便可。
// 在html中添加script引用
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
複製代碼
// 這裏externals的key指的是使用時須要require的包名,value指的是該庫經過script引入後在全局註冊的變量名
externals: {
jquery: 'jQuery'
}
// 使用方法
require('jquery')
複製代碼
DLLPlugin
和 DLLReferencePlugin
拆分文件若是項目過大,打包的時間會至關的長,若是頻繁更新上線則會不斷對代碼進行編譯打包,浪費不少時間。這時咱們即可以將那些不常更新的框架和庫(如vue.js等)進行單獨的編譯打包,這樣每次開發上線就只須要對咱們的開發文件進行編譯打包,這樣能夠極大地省去沒必要要的打包時間。而這種方法須要DLLPlugin
和DLLReferencePlugin
兩個插件的配合來完成。
在使用這個插件時,咱們須要單首創建一個配置文件,這裏命名爲webpack.dll.config.js
,配置以下:
module.exports = {
entry: {
lib: ['vue', 'vuex', 'vue-resource', 'vue-router']
},
output: {
path: path.resolve(__dirname, '../dist', 'dll'),
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath,
library: '[name]_library'
},
plugins: [
new webpack.DefinePlugin({
'process.env': '"production"'
}),
/** * path: manifest.json輸出文件路徑 * name: dll對象名,跟output.library保持一致 */
new webpack.DllPlugin({
context: __dirname,
path: path.resolve(__dirname, '../dist/dll', 'lib.manifest.json'),
name: '[name]_library'
})
]
}
複製代碼
這裏要注意幾點:
entry
中寫明全部要單獨打包的模塊output
的library
屬性能夠將dll包暴露出來DLLPlugin
的配置中,path
指明manifest.json
文件的生成路徑,name
暴露出dll的函數名運行該配置文件即可生成打包文件和manifest.json
文件。
對於該插件的配置,不須要像上面同樣單獨寫配置文件,只須要在生產配置文件中添加以下代碼:
new webpack.DllReferencePlugin({
context: __dirname, // 同dll配置的路徑保持一致
manifest: require('../dist/dll/lib.manifest.json') // manifest的位置
}),
複製代碼
而後運行webpack,發現打包的速度獲得了極大地提高,也不用每次更新代碼的時候重複編譯打包這些依賴庫了。
對於webpack的打包優化我大體就總結了上面的一些方法,而爲了讓頁面更快的加載,有更好的用戶體驗,咱們並不僅是從打包上優化,也能夠有其餘方面的優化,這裏我也簡單提一下我使用過的方法。
開啓gzip壓縮能夠減小HTTP傳輸的數據量和時間,從而減小客戶端請求的響應時間,因爲下降了請求時間,頁面的加載速度也會獲得提高,會有更快的渲染速度,極大地改善了用戶體驗。因爲如今基本上全部的主流瀏覽器都支持Gzip的壓縮方式,只須要對服務器進行相關設置便可,這裏就不具體講如何配置服務器。
咱們日常也會對代碼進行壓縮混淆,能夠經過UglifyJS
等工具來對js代碼進行壓縮,同時能夠去掉沒必要要的空格、註釋、console信息等,也能夠有效的減少代碼體積。
本文到這裏就結束了,主要是對webpack的打包優化部分作了些講解,固然能力和時間有限,只研究了部分方法,可能會有其餘更多的優化方法,不管是從編譯打包的體積仍是速度上都能有更好的優化。接觸了一段時間的webpack發現做爲一個打包工具實在是過於複雜,不管從開始的官方文檔仍是到新的高級特性,都很難去徹底掌握,還得須要本身不斷去實踐去深刻研究才行。