想了很久暇唾,在之前的webpack教程中,也僅僅只是教了大家独泞,如何使用webapck4手?jǐn)]一份vue可用的配置呐矾,但是在我的實際工作中,會遇到很多需要優(yōu)化的地方懦砂,例如:cdn加速蜒犯, gzip等這些操作,再加上荞膘,原本的那一份教程罚随,在css和css預(yù)處理器的操作上,有很多的不足和細(xì)微的問題∮鹱剩現(xiàn)在一點點的捋出來淘菩,希望能夠幫到大家。
- 首先就是關(guān)于css和css預(yù)處理器的設(shè)置上屠升,由于當(dāng)時時間問題潮改,很草率地就直接自己寫上了,問題是沒有問題腹暖,但是相比vue-cli2生成的那個配置汇在,我還是有很多的不足,現(xiàn)在我就在vue-cli2的基礎(chǔ)上脏答,對其進(jìn)行修改糕殉,接上篇的配置信息亩鬼,我們還需要在utlis.js中添加對css的處理代碼如下
關(guān)于部分css類的loader的優(yōu)化方案
// 將css類loader放到這里一并處理,不再寫在dev和prod中了
exports.cssLoaders = function (options) {
options = options || {}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
// 這里就是生成loader和其對應(yīng)的配置
function generateLoaders(loader, loaderOptions) {
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// 當(dāng)配置信息中開啟此項時阿蝶,啟用css分離壓縮
// 這一項在生產(chǎn)環(huán)境時雳锋,是默認(rèn)開啟的
if (options.extract) {
return [MiniCssPlugin.loader].concat(loaders)
} else {
// 如果不開啟則讓vue-style-loader來處理
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// 根據(jù)上面的函數(shù)遍歷出來的各個css預(yù)處理器的loader進(jìn)行最后的拼裝
exports.styleLoaders = function (options) {
const output = []
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
- 完整的utils.js呢,我也放上來吧羡洁,以免有的朋友不大知道這段代碼該加在什么位置
'use strict'
const path = require('path')
const packageConfig = require('../package.json')
const config = require('../config')
// 這里為什么要引入這個呢玷过,由于webpack4的原因,需要用到這個插件的loader
const MiniCssPlugin = require('mini-css-extract-plugin');
// 將資源文件合并到config文件中的配置項中
exports.assetsPath = function (_path) {
const assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
// 將css類loader放到這里一并處理焚廊,不再寫在dev和prod中了
exports.cssLoaders = function (options) {
options = options || {}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
// 這里就是生成loader和其對應(yīng)的配置
function generateLoaders(loader, loaderOptions) {
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// 當(dāng)配置信息中開啟此項時冶匹,啟用css分離壓縮
// 這一項在生產(chǎn)環(huán)境時,是默認(rèn)開啟的
if (options.extract) {
return [MiniCssPlugin.loader].concat(loaders)
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
// 返回對應(yīng)的loader
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// 根據(jù)上面的函數(shù)遍歷出來的各個css預(yù)處理器的loader進(jìn)行最后的拼裝
// 這里的options就是在調(diào)用的時候向內(nèi)傳遞一些loader的配置信息咆瘟,比如map文件等
exports.styleLoaders = function (options) {
const output = []
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
// 當(dāng)出現(xiàn)報錯時嚼隘,彈出系統(tǒng)彈窗告知出現(xiàn)錯誤
exports.createNotifierCallback = () => {
const notifier = require('node-notifier')
return (severity, errors) => {
if (severity !== 'error') return
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
// 這里是設(shè)置當(dāng)出現(xiàn)錯誤時,給你的袒餐,標(biāo)題飞蛹,錯誤信息,錯誤文件地址灸眼,以及圖標(biāo)
notifier.notify({
title: packageConfig.name,
message: severity + ': ' + error.name,
subtitle: filename || '',
// icon: path.join(__dirname, 'logo.png')
})
}
}
// 獲取本地局域網(wǎng)ip
exports.getNetworkIp = () => {
const os = require('os');
let needHost = ''; // 打開的host
try {
// 獲得網(wǎng)絡(luò)接口列表
let network = os.networkInterfaces();
for (let dev in network) {
let iface = network[dev];
for (let i = 0; i < iface.length; i++) {
let alias = iface[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
needHost = alias.address;
}
}
}
} catch (e) {
needHost = 'localhost';
}
return needHost;
}
- 然后我們需要在原webpack.dev.js和webpack.prod.js中去修改我們之前的rules:
// 開發(fā)環(huán)境下
...
mode: 'development',
devtool: config.dev.devtool,
module: {
// 將之前的rules數(shù)組直接替換成下面的樣子
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
output: {
filename: '[name].js',
}
...
// 生產(chǎn)環(huán)境下
mode: 'production',
devtool: config.build.productionSourceMap ? config.build.devtool : false,
module: {
// 將之前的rules數(shù)組直接替換成下面的樣子
rules: utils.styleLoaders({
// 是否開啟css的map映射
sourceMap: config.build.productionSourceMap,
// 該選項是是否開啟css分離壓縮的選項
extract: true,
// 是否使用postcss-loader添加瀏覽器前綴
usePostCSS: true
})
},
output: {
path: config.build.assetsRoot,
},
...
- 好啦卧檐,大家應(yīng)該就很清楚了,在這里我們使用了
utlis
中的styleLoaders
函數(shù)焰宣,由上面的代碼我們就就可以知道霉囚,該函數(shù)接收至多三個對象sourceMap
,extract
匕积,usePostCSS
盈罐;他們均是一個布爾值,從左到右分別表示闪唆,是否打開css的map文件盅粪,是否開啟css抽取壓縮,是否為一些css樣式添加瀏覽器前綴悄蕾。至此票顾,我們就可以徹底的放心大膽的使用市面上已有的css預(yù)處理器,因為在utlis
中的cssLoaders
函數(shù)中帆调,我們看到奠骄,它會返回你設(shè)置好的所有的預(yù)處理器loader,這樣我們就不用再兩個webpack文件中都要去設(shè)置番刊,只需要當(dāng)出了新的css預(yù)處理器的時候含鳞,加到這里就好了。
當(dāng)依賴庫十分龐大時撵枢,dev緩慢的問題
很多時候民晒,項目的依賴非常的多,這就導(dǎo)致了每次更改之后熱更新需要等很久的時候锄禽,那么此時潜必,我們就需要使用dll來解決這個問題了,當(dāng)然沃但,和win上的dll文件是有很大的區(qū)別的磁滚,我們需要使用的是 webpack自帶的
DllPlugin
插件,現(xiàn)在附上我的代碼:
const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpackbar = require('webpackbar')
module.exports = {
mode: 'development',
entry: {
// 這里就是添加
vendor: ['axios', 'element-ui', 'vue', 'vue-router', 'echarts']
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, 'dist'),
library: '_dll_[name]',
},
plugins: [
new CleanWebpackPlugin(),//clean dist
new webpack.DllPlugin({
name: '_dll_[name]',
path: path.join(__dirname, 'dist', '[name].manifest.json'),
}),
new webpackbar({
name: "抽取依賴中",
})
],
};
- 然后我們在package.json中添加腳本
"dll":"webpack --config webpack.vendor.js"
在每次添加了大型依賴之前可以將其加入vendor數(shù)組內(nèi)宵晚,并且執(zhí)行npm run dll
垂攘,這樣每次編譯的時候,就不會帶上那些大型依賴淤刃,也就不會導(dǎo)致每次更改一個字編譯都要等很久的囧場景了~
關(guān)于cdn加速的一些建議
隨著需求越來越多晒他,各種依賴庫也越來越多,如果是跨平臺應(yīng)用還好逸贾,資源加載都在本地陨仅,不需要用戶從我們服務(wù)器下載,但是铝侵,線上的站怎么辦灼伤,我們的服務(wù)器帶寬有限,壓根就沒法顧及這些咪鲜,此時狐赡,cdn會給我們一些幫助,使用cdn可以將我們的一些依賴放在他們的服務(wù)器中分發(fā)疟丙,好處不言而喻颖侄,在去除了依賴庫的情況下,我們的代碼再加上進(jìn)行了壓縮隆敢,分包等一系列處理发皿,不僅僅只是縮短了首屏啟動的時間,那么現(xiàn)在來貼代碼:
// 在原打包分析下添加或者除配置對象中的任意位置
// 這里有涉及到config文件夾中index文件的build對象
// 那么我們肯定也要在config文件中做對應(yīng)的設(shè)置了
if (config.build.cdn) {
// 此處是從config中讀取NeedCdnModuleName
options.externals = config.build.NeedCdnModuleName
// 這里按需加載了一個依賴拂蝎,所以穴墅,您需要安裝它
const WebpackCdnPlugin = require('webpack-cdn-plugin');
options.plugins.push(new WebpackCdnPlugin({
modules: config.build.NeedCdnModuleAddress
}))
}
- 關(guān)于config文件夾中index文件的內(nèi)容則是在其build對象下新添代碼
build:{
// 此處配置可使用插件自動配置需要cdn加速的模塊
// 你可能需要執(zhí)行 npm i webpack-cdn-plugin -D 命令來安裝這個依賴
cdn: false,
// 需要說明的是,這個里温自,用于配置需要被webpack忽略的依賴包名
// 左邊是npm安裝時的名字玄货,而右邊則是該包對全局暴漏的名字
// 如果不知道全局名稱怎么辦,
// 詳情配置請參考:https://webpack.docschina.org/configuration/externals/
// 該插件默認(rèn)走的是https://unpkg.com cdn服務(wù)器
NeedCdnModuleName: {
vue: 'Vue',
'vue-router': 'VueRouter',
},
// 這里則是被webpack忽略之后悼泌,加載cdn
// name表示該模塊在npm中的命名松捉,var表示該模塊全局變量名稱,應(yīng)當(dāng)和上面右側(cè)設(shè)置一致
// path則代表是該模塊在unpkg中的文件路徑馆里,如果不清楚清去該站點自行查詢隘世。
// 詳情配置請參考:https://www.npmjs.com/package/webpack-cdn-plugin
NeedCdnModuleAddress: [{
name: 'vue',
var: 'Vue',
path: 'dist/vue.runtime.min.js',
}, {
name: 'vue-router',
var: 'VueRouter',
path: 'dist/vue-router.min.js',
}],
}
這里需要提一下可柿,在vue中,當(dāng)你在main中引入了依賴庫的css時丙者,如果你沒有判斷就直接使用import的話复斥,css則部分時沒有加入cdn的,這里需要注意一下械媒,在demo中目锭,我會追加跟多本文中額外的信息,畢竟很多東西只看文章還是太抽象纷捞,結(jié)合代碼學(xué)習(xí)痢虹,是最快的。
關(guān)于啟用gzip
有時候主儡,我們即使使用了cdn加速奖唯,剔除了所有的依賴,但是我們站本身的代碼就非常的龐大缀辩,分包之后依舊還有100k以上的大小時臭埋,(可能你們會說100k沒什么嘛,可以忽略了臀玄,那么這個就可以忽略查看)我們可以啟用gzip瓢阴,將我們的代碼放到壓縮包中在傳到用戶計算機上時,由瀏覽器解壓執(zhí)行健无;此時我們需要使用
compression-webpack-plugin
荣恐,該插件支持三種壓縮算法brotliCompress
和gzip
還有zopfli
至于哪種更好,就不在本文的闡述范圍內(nèi)了累贤,上代碼:
// 您可以選擇添加在cdn下面
// 當(dāng)config中對應(yīng)項為true時,啟用gzip功能
if (config.build.gzip) {
const CompressionPlugin = require('compression-webpack-plugin');
options.plugins.push(new CompressionPlugin({
// 這里如果算法為brotliCompress的話,需要將gz改為br
filename: '[path].gz[query]',
// 壓縮算法,這里的話,你可以選擇brotliCompress和gzip還有zopfli,當(dāng)然這一切要取決于用戶的瀏覽器支持哪種壓縮模式
// 若要使用brotliCompress算法,需要nodejs11.7以上.并且后綴名為.br
algorithm: 'gzip',
// 根據(jù)config文件設(shè)置的情況對什么文件進(jìn)行壓縮
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
// 超過該大小就會壓縮(單位:字節(jié))
threshold: 10240,
// 壓縮比,官方給的解釋設(shè)置0.8是最佳了.至于計算公式,就是minRatio = 壓縮后的大小 / 原來的大小
// 當(dāng)壓縮比比這個低的話,就會進(jìn)行壓縮處理,如果設(shè)置成1的話,就是全部都處理了.
minRatio: 0.8,
// 是否刪除原文件(默認(rèn)也是false)
// 這這里并不推薦刪除源文件,因為需要照顧到不支持gzip的瀏覽器
deleteOriginalAssets: false,
})
)
}
- 同樣附上config中的代碼
// 此處配置是是否使用gzip功能對代碼進(jìn)行高強度的壓縮(值得注意的是gzip功能需要nginx做出對應(yīng)的配置,謹(jǐn)慎開啟)
// 你可能需要執(zhí)行 npm i compression-webpack-plugin -D 命令來安裝這個依賴
// 因為本包中可能會不帶這項依賴叠穆,畢竟不是每個人都需要
gzip: false,
// 這里設(shè)置需要被壓縮的文件后綴默認(rèn)是只壓縮css和js
productionGzipExtensions: ['js', 'css']
- 這里,注釋中也說明了臼膏,需要nginx或者您所知道您項目中所使用承載頁面具備
httpServer
的服務(wù)器中去配置硼被,開啟gzip或是其他的壓縮選項。
關(guān)于控制臺的友好化輸出
在Nuxt中我發(fā)現(xiàn)其控制臺的進(jìn)度條不僅僅可以給開發(fā)者一個進(jìn)度告知渗磅,并且還會顯示當(dāng)前正在加載什么嚷硫,幸運的是,他們將這個插件開源了出來始鱼,廢話不多說仔掸,開整!
- 首先是安裝該插件
npm i webpackbar -D
然后在我們的webpack公共配置中加入
const webpackbar = require('webpackbar')
/*這里是其他代碼*/
// 在插件數(shù)組中加入
plugins: [
// 該插件支持一個名稱医清,可用于控制臺輸出
new webpackbar({
name: "",
})
],
然后我們重啟一次webpack起暮,就看到對應(yīng)的效果了。其實還有更多的webpack的操作臺輸出插件会烙,但是它們在win上表現(xiàn)非常差负懦,所以我就不再介紹~