在開發(fā)vue時(shí),發(fā)現(xiàn)即使使用了路由懶加載,webpack打包出來的靜態(tài)文件依然還是有點(diǎn)大,因此,在開發(fā)途中抽空出來對(duì)webpack打包進(jìn)一步探索(雖然包都是共用的,但本篇內(nèi)容是基于vue-lic腳手架的框架,請(qǐng)自行衍生)
推薦一個(gè)非常好用的可視化分析插件 webpack-bundle-analyzer
它可以將打包后的內(nèi)容束展示為方便交互的直觀樹狀圖,這樣你就可以非常直觀的看到構(gòu)建的包中引入了那些內(nèi)容,以方便你進(jìn)行優(yōu)化。
- 配置
// 常規(guī)配置
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
// vue中配置啟動(dòng)或不啟動(dòng)
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
// package.json
"analyz": "set NODE_ENV=production && set npm_config_report=true && npm run build" // window系統(tǒng)
或者
"analyz": "NODE_ENV=production && npm_config_report=true && npm run build"
- 效果圖(網(wǎng)上的動(dòng)態(tài)圖)

為 VUE 項(xiàng)目添加 全局變量
-通過DefinePlugin插件配置
new webpack.DefinePlugin({
'process.env': env,
LOCAL_SERVICE: JSON.stringify('')
})
// 即可在vue中直接引用設(shè)置的參數(shù) LOCAL_SERVICE
為 VUE 對(duì)象添加 對(duì)象變量
-在入口文件main.js中
import Vue from 'vue'
Vue.prototype.LOCAL_SERVICE = LOCAL_SERVICE ;
// 即可在vue對(duì)象中直接引用設(shè)置的參數(shù) this.LOCAL_SERVICE
在VUE項(xiàng)目中引入第三方插件或js
- 方法一:直接在主入口html文件引入(利用外部擴(kuò)展externals)
<script src="./static/jquery-1.12.4.js"></script>
// 在webpack中配置externals,防止將某些 import 的包(package)打包到 bundle 中,而是在運(yùn)行時(shí)(runtime)再去從外部獲取這些擴(kuò)展依賴(external dependencies)
externals: {
'jquery': 'jQuery'
}
// 引入
import $ from 'jquery';
方法二: 采用CDN方式,直接在主文件html中引入個(gè)全局變量/方法
方法三:利用插件webpack-require-http (不支持es6以上語法)(該方法回返回一個(gè)promise)
## 值得注意的是,該方法使用了promise,不會(huì)阻礙到后面方法的執(zhí)行
require(['https://cdn.bootcss.com/jquery/3.1.1/jquery.min.js').then(( )=> {
console.log($, 9338)
})
Promise.all([
require('https://cdn.bootcss.com/jquery/3.1.1/jquery.min.js'),
require('https://cdn.bootcss.com/jquery/3.1.1/jquery.min.js')
]).then(() => {
console.log( $, 999998)
})
- 方法四:會(huì)在后面的打包體積方法中體現(xiàn)
為 VUE 項(xiàng)目添加 PWA 解決發(fā)布后刷新報(bào)錯(cuò)問題
不管是部署在 IIS,還是 nginx,每次應(yīng)用部署后,再次訪問因?yàn)榕f的 js 已經(jīng)不存在,所以頁面訪問的時(shí)候會(huì)整個(gè)報(bào)錯(cuò),報(bào)錯(cuò)的結(jié)果就是一個(gè)白屏,為了解決這個(gè)問題,使用 PWA ,這樣就可以將 js 緩存到本地,再次發(fā)布后,service-worker.js 會(huì)使舊的 js 失效,重新請(qǐng)求并緩存 js。
1.無需客戶端,少量流量即可安裝 2.可添加到主屏并全屏運(yùn)行 3.離線功能,響應(yīng)更快,及時(shí)更新 4.PUSH能力 5.數(shù)據(jù)傳輸必須是https
這個(gè)暫時(shí)只在應(yīng)用上看過具體代碼,無實(shí)操過。推薦文章 https://www.cnblogs.com/morang/p/9622394.html
移動(dòng)web緩存(可忽略,查看資料順帶的)
把依賴庫拆分出來,解決使用代碼分割和異步加載打包后首次加載的文件過大問題
- 方法一:CommonsChunkPlugin
推薦兩篇描述CommonsChunkPlugin比較好的文章 https://segmentfault.com/a/1190000012828879和https://segmentfault.com/a/1190000014221669
- 首先說下chunk文件的種類,主要有以下三種:
webpack當(dāng)中配置的入口文件(entry)是chunk,可以理解為entry chunk
入口文件以及它的依賴文件通過code split(代碼分割)出來的也是chunk,可以理解為children chunk
通過CommonsChunkPlugin創(chuàng)建出來的文件也是chunk,可以理解為commons chunk
- CommonsChunkPlugin可配置屬性
name:可以是已經(jīng)存在的chunk(一般指入口文件)對(duì)應(yīng)的name,那么就會(huì)把公共模塊代碼合并到這個(gè)chunk上;否則,會(huì)創(chuàng)建名字為name的commons chunk進(jìn)行合并
filename:指定commons chunk的文件名
chunks:指定source chunk,即指定從哪些chunk當(dāng)中去找公共模塊,省略該選項(xiàng)的時(shí)候,默認(rèn)就是entry chunks
minChunks:既可以是數(shù)字,也可以是函數(shù),還可以是Infinity
- 直接抽取第三方庫放在一個(gè)文件上
plugins: [
// 查看依賴項(xiàng)是否來自node_modules文件夾,如果是,則將其輸出到單獨(dú)的文件commonChunks.js中
new webpack.optimize.CommonsChunkPlugin({
name: 'commonChunks',
minChunks: function (module,count) {
console.log(module.resource,`引用次數(shù)${count}`);
//"有正在處理文件" + "這個(gè)文件是 .js 后綴" + "這個(gè)文件是在 node_modules 中"
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(path.join(__dirname, './node_modules')) === 0
)
}
}),
// 抽離webpack運(yùn)行文件
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime',
filename: '[name].js',
chunks: ['vendor']
}),
]
- 由于采用了路由懶加載,實(shí)現(xiàn)了代碼分割和異步加載,因此需要用到children和async屬性,將懶加載的路由文件公用的模塊代碼,抽離打包成一個(gè)單獨(dú)的文件,并且該文件是按需加載的,如果某個(gè)路由沒有使用到這些公用模塊,是不會(huì)加載進(jìn)來的,其中包括js,vue組件,從而減少每個(gè)頁面生成的js體積,優(yōu)化切換頁面速度
#### minChunks可以設(shè)置為數(shù)字、函數(shù)和Infinity
數(shù)字:模塊被多少個(gè)chunk公共引用才被抽取出來成為commons chunk
函數(shù):接受 (module, count) 兩個(gè)參數(shù),返回一個(gè)布爾值,你可以在函數(shù)內(nèi)進(jìn)行你規(guī)定好的邏輯來決定某個(gè)模塊是否提取成為commons chunk
Infinity:只有當(dāng)入口文件(entry chunks) >= 3 才生效,用來在第三方庫中分離自定義的公共模塊
#### children
指定為true的時(shí)候,就代表source chunks是通過entry chunks(入口文件)進(jìn)行code split出來的children chunks
children和chunks不能同時(shí)設(shè)置,因?yàn)樗鼈兌际侵付╯ource chunks的
children 可以用來把 entry chunk 創(chuàng)建的 children chunks 的共用模塊合并到自身,但這會(huì)導(dǎo)致初始加載時(shí)間較長(zhǎng)
#### async:即解決children:true時(shí)合并到entry chunks自身時(shí)初始加載時(shí)間過長(zhǎng)的問題。async設(shè)為true時(shí),commons chunk 將不會(huì)合并到自身,而是使用一個(gè)新的異步的commons chunk。當(dāng)這個(gè) children chunk 被下載時(shí),自動(dòng)并行下載該commons chunk
new webpack.optimize.CommonsChunkPlugin({
name: "app", // 根入口文件
minChunks: 3,
children: true,
// deepChildren: true,
async: true,
})
- 方法二:把依賴的庫,先整體都不作處理地打包出來(值得注意的是,使用該方法,一定要合理跟CommonsChunkPlugin搭配,不建議把package.json中的依賴統(tǒng)一打包,這樣會(huì)導(dǎo)致dll文件比較大,反而起不到想要的優(yōu)化)
由于采用CommonsChunkPlugin,雖然可以拆分出很多小文件,如單獨(dú)第三方庫文件集合,自定義的公共模塊集合等,但每次打包都會(huì)全部去重新打包一次,這樣實(shí)際開發(fā)中會(huì)不友好。而實(shí)際上我們不會(huì)一直去更新我們引用的依賴庫,所以dll的做法就等于是,事先先打包好依賴庫,然后只對(duì)每次都修改的js做打包。
- 以vue項(xiàng)目為例,首先創(chuàng)建一個(gè)名為 webpack.dll.config.js 的文件
const path = require("path"),
UglifyJsPlugin = require('uglifyjs-webpack-plugin'),
AssetsPlugin = require('assets-webpack-plugin'),
webpack = require("webpack");
var vendors = [
'vue',
'vue-router',
'vuex'
];
module.exports = {
entry: {
vendor: ['vuex','vuex-persistedstate','vue-router']
//讀取package.json里的依賴,normalize.css除外,打包會(huì)報(bào)錯(cuò)
//如果使用了chrome的vue-devtool,那打包的時(shí)候把vue也排除掉,因?yàn)閴嚎s過的vue是不能使用vue-devtool的
// 我的dll通用來打第三方插件庫和自身封裝的不會(huì)修改的庫,
},
output: {
path: path.join(__dirname, '../static'),
filename: 'dll.[name]_[hash:6].js',
library: '[name]_[hash:6]'
},
// 如果依賴庫需要編譯,必須帶上module
module: {
rules: [
//vue文件解析
{
test: /\.vue$/,
loader: 'vue-loader'
},
//js文件解析
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src')],
}
]
},
// 如果依賴庫引用別的依賴文件使用的是別名,則必須配置resolve,而且配置別名,打包出來體積相對(duì)小很多
resolve: {
//忽略后綴的文件,路徑自動(dòng)查找,如果在同目錄下js和vue同名,則需要加后綴作為區(qū)分
extensions: ['.js', '.vue', '.json'],
//模塊解析
modules: [
resolve('src'),
resolve('node_modules')
],
//路徑別名
alias: {
'src': resolve('src'),
'router': resolve('src/router/index.js')
// 等等
}
},
plugins: [
// Dllplugin里的path,會(huì)輸出一個(gè)vendor-manifest.json,這是用來做關(guān)聯(lián)id的,打包的時(shí)候不會(huì)打包進(jìn)去,所以不用放到static里
new webpack.DllPlugin({
path: path.join(__dirname, '../', '[name]-manifest.json'),
name: '[name]_[hash:6]'
}),
// 對(duì)依賴文件進(jìn)行壓縮混淆
new webpack.optimize.UglifyJsPlugin({ // js、css都會(huì)壓縮
mangle: {
except: ['$super', '$', 'exports', 'require', 'module', '_']
},
compress: {
warnings: false
}
}),
// 生成bundle-config.json,用于把帶hash的dll插入到html中
new AssetsPlugin({
filename: 'bundle-config.json',
path: './'
})
]
};
- 生成dll.vendor.js(static文件夾中)、bundle-config.json(根目錄下)和vendor.manifest.json(根目錄下)文件
// 添加package.json中的運(yùn)行命令
"build:dll": "webpack -p --progress --config build/webpack.dll.conf.js"
// 運(yùn)行命令 npm run build:dll 即可生成
- 修改build/webpack.base.conf.js,添加DllReferencePlugin的配置
const manifest = require('../vendor-manifest.json')
// 關(guān)聯(lián)dll拆分出去的依賴
new webpack.DllReferencePlugin({
manifest
})
- 引入HTML中
// 修改html生成插件HtmlWebpackPlugin,添加多一個(gè)屬性
// 加載dll文件
vendorJsName: bundleConfig.vendor.js
// 在html文件的底部添加
<script src="./static/<%= htmlWebpackPlugin.options.vendorJsName %>"></script>
- 結(jié)果,很明顯 CommonsChunkPlugin和dll兩者結(jié)合起來,一個(gè)大型項(xiàng)目,也只有一個(gè)js入口文件是因?yàn)閔ighcharts而導(dǎo)致超過200k的,而頁面js更是只有幾十K。

拆分css文件,防止樣式文件過大
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var CSSSplitWebpackPlugin = require('css-split-webpack-plugin').default
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
allChunks: true,
}),
// IE9會(huì)忽略你生成的CSS包中的大于4000個(gè)選擇器的部分
new CSSSplitWebpackPlugin({
filename: utils.assetsPath('css/[name]-[part].[ext]'),
size: 1500,// size:?jiǎn)蝹€(gè)文件中允許的最大CSS規(guī)則數(shù),防止css樣式過大,即選擇器
})