1. oneOf
對(duì)于某種類型的文件,webpack 會(huì)從上至下匹配所有的 loader,也就是,所有的 loader 都會(huì)去處理這種文件。但是,webpack 這種匹配方式在有些場(chǎng)景反而影響了效率。比如說,一個(gè) webpack 只有 babel-loader 處理了 js 文件,那么當(dāng)它匹配完了以后,完全沒有必要再匹配一些針對(duì) css 文件的 loader。所以,這就是為什么,我們使用 oneOf,匹配到了,立刻跳出循環(huán)。因此 oneOf 可以優(yōu)化構(gòu)建速度
module: {
rules: [
{
oneOf: [
{
test: /\.(png|jpg|jpeg)$/,
use: ['file-loader'],
exclude: /(node_modules|bower_components|dist)/
}
]
}
]
}
2. babel 緩存
babel 緩存的意思是,瀏覽器第一次拿資源文件的時(shí)候,會(huì)從服務(wù)器上走 http 拉取,但是當(dāng)刷新頁面再次請(qǐng)求的時(shí)候,瀏覽器會(huì)直接從緩存(可以是內(nèi)存,也可以是硬盤,自己配置)當(dāng)中拿同名文件,因此省去了發(fā)送 http 請(qǐng)求拿資源的時(shí)間。
3. 多進(jìn)程打包
業(yè)內(nèi)有兩種解決方案比較常用,一個(gè)是 thread-loader,一個(gè)是 happy-pack,但是 happy-pack 的維護(hù)者現(xiàn)在對(duì)這個(gè)庫不再維護(hù)了,因此,推薦使用 thread-loader(多進(jìn)程打包,不是線程)。
{
rules:
[
{
test: /\.(js|jsx)$/,
exclude: /(node_modules|bower_components|dist)/,
use: [
/**
* 開啟多進(jìn)程打包,打開進(jìn)程一般 600 ms,
* 通信也有開銷。
*/
{
loader: "thread-loader",
options: {
workers: 3
}
},
{
loader: "babel-loader",
options: {
cacheDirectory: true
}
}
]
}
]
}
需要在 options 字段下配置 workers 也就是進(jìn)程的個(gè)數(shù)。注意 thread-loader 并不是開的進(jìn)程數(shù)越多就越好,假如你的 js 代碼量很少,開多核反而會(huì)降低性能,這是因?yàn)榇蜷_進(jìn)程有比較大的開銷(600 ms 左右),進(jìn)程間通信也有開銷。
4. tree shaking
tree shaking 的意思是你的項(xiàng)目里面有些代碼可能是從來沒被引入的,比方說你定義了一個(gè)函數(shù)但是你從來沒有引用到它。這個(gè)時(shí)候 tree shaking 可以在打包的時(shí)候幫你干掉這些代碼。
前提
使用 ES6 module
production 默認(rèn)開啟
需要配合 package.json 里面 sideEffects: ["*.css"] 一同使用,否則可能會(huì)干掉打包好的 css 文件。
5. code split
code split 直接從字面上理解即可,就是代碼分割技術(shù),因?yàn)?html 里面的靜態(tài)資源文件是并行加載的,即發(fā)送 http 請(qǐng)求并且把文件放到內(nèi)存里這個(gè)過程是并行的。所以適當(dāng)?shù)拇a分割技術(shù)可以讓我們的項(xiàng)目運(yùn)行性能更好。另外,代碼分割也可以幫助我們?cè)诙嗦酚傻膱?chǎng)景進(jìn)行性能優(yōu)化。
多入口
entry: {
main: './src/js/index.js', // 入口1
test: './src/js/test.js' // 入口2
},
output: {
// [name]是webpack命名規(guī)則,使用chunk的name作為輸出的文件名。
// 什么是chunk?打包的資源就是chunk,輸出出去叫bundle。
// chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名無關(guān)。
// 為什么需要這樣命名呢?如果還是之前寫法main.js,那么打包生成兩個(gè)js文件都會(huì)叫做main.js會(huì)發(fā)生覆蓋。(實(shí)際上會(huì)直接報(bào)錯(cuò)的)
filename: 'js/[name].[contenthash:10].js'
path: resolve(__dirname, 'build')
}
// optimization 代碼分割配置
optimization: {
splitChunks:{
chunks: 'all' // 對(duì)所有模塊都進(jìn)行分割
// 以下是默認(rèn)值
// minSize: 20000, // 分割代碼最小的大小
// minRemainingSize: 0, // 類似于minSize,最后確保提取的文件大小不能為0
// minChunks: 1, // 至少被引用的次數(shù),滿足條件才會(huì)代碼分割
// maxAsyncRequests: 30, // 按需加載時(shí)并行加載的文件的最大數(shù)量
// maxInitialRequests: 30, // 入口js文件最大并行請(qǐng)求數(shù)量
// enforceSizeThreshold: 50000, // 超過50kb一定會(huì)單獨(dú)打包(此時(shí)會(huì)忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
// cacheGroups: { // 組,哪些模塊要打包到一個(gè)組
// defaultVendors: { // 組名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模塊
// priority: -10, // 權(quán)重(越大越高)
// reuseExistingChunk: true, // 如果當(dāng)前 chunk 包含已從主 bundle 中拆分出的模塊,則它將被重用,而不是生成新的模塊
// },
// default: { // 其他沒有寫的配置會(huì)使用上面的默認(rèn)值
// minChunks: 2, // 這里的minChunks權(quán)重更大
// priority: -20,
// reuseExistingChunk: true,
// },
// },
// 修改配置
// cacheGroups: {
// 組,哪些模塊要打包到一個(gè)組
// defaultVendors: { // 組名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模塊
// priority: -10, // 權(quán)重(越大越高)
// reuseExistingChunk: true, // 如果當(dāng)前 chunk 包含已從主 bundle 中拆分出的模塊,則它將被重用,而不是生成新的模塊
// },
}
}
上面這塊配置可以把你做兩件事:
node_modules 中代碼單獨(dú)打包成一個(gè) chunk
自動(dòng)分析多入口 chunk 中,有沒有公共文件,如果有會(huì)打包成一個(gè)單獨(dú) chunk
6. 懶加載和預(yù)加載
懶加載
使用 import 函數(shù),同上。回調(diào)函數(shù)中使用 import 函數(shù)。
document.getElementById('btn').onclick = () => {
import('./test').then(({mul})=> {
console.log(mul(4,5))
})
)
預(yù)加載
// eslint會(huì)對(duì)動(dòng)態(tài)導(dǎo)入語法報(bào)錯(cuò),需要修改eslint配置文件
// webpackChunkName: "math":這是webpack動(dòng)態(tài)導(dǎo)入模塊命名的方式
// "math"將來就會(huì)作為[name]的值顯示。
// webpackPrefetch 開啟預(yù)加載
document.getElementById('btn').onclick = () => {
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({mul})=> {
console.log(mul(4,5))
})
)
7. pwa
適用情景,我們希望在用戶離線時(shí)也可以訪問我們的頁面。對(duì)于 webpack 而言,我需要使用的是 workbox-webpack-plugin。配置成功以后,瀏覽器在離線后可以從 service-worker 里拉取靜態(tài)資源。
// webpack
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
// 刪除舊的 serviceWorker 且快速啟動(dòng)
clientsClaim: true,
skipWaiting: true
})
]
// js
if('serviceWorker' in navigator){
window.addEventListenr('load', ()=>{
navigator.serviceWorker.register.register('./service-worker.js')
.then(()=>{
console.log('sw 注冊(cè)成功了')
})
.catch(()=>{
console.log('sw 注冊(cè)失敗')
})
})
}
8. CDN
CDN稱之為內(nèi)容分發(fā)網(wǎng)絡(luò)(Content Delivery Network或Content Distribution Network,縮寫:CDN), 它是指通過相互連接的網(wǎng)絡(luò)系統(tǒng),利用最靠近每個(gè)用戶的服務(wù)器; 更快、更可靠地將音樂、圖片、視頻、應(yīng)用程序及其他文件發(fā)送給用戶; 來提供高性能、可擴(kuò)展性及低成本的網(wǎng)絡(luò)內(nèi)容傳遞給用戶;
在開發(fā)中,我們使用CDN主要是兩種方式:
方式一:打包的所有靜態(tài)資源,放到CDN服務(wù)器, 用戶所有資源都是通過CDN服務(wù)器加載的;
方式二:一些第三方資源放到CDN服務(wù)器上
9. JS代碼壓縮
Terser
optimization: {
minimize: true, // 是否要啟用壓縮,默認(rèn)情況下,生產(chǎn)環(huán)境會(huì)自動(dòng)開啟
minimizer: [
// 壓縮時(shí)使用的插件,可以有多個(gè)
new TerserPlugin(), // js壓縮插件
new OptimizeCSSAssetsPlugin() // css壓縮插件
],
}
10. gizp
瀏覽器發(fā)送請(qǐng)求時(shí),會(huì)在請(qǐng)求頭中設(shè)置Accept-Encoding:gzip,deflate,br。表明瀏覽器支持gzip。服務(wù)器收到瀏覽器發(fā)送的請(qǐng)求之后,判斷瀏覽器是否支持gizp,如果支持gzip,則向?yàn)g覽器傳送壓縮過的內(nèi)容,不支持則向?yàn)g覽器發(fā)送未經(jīng)壓縮的內(nèi)容。一般情況下,瀏覽器和服務(wù)器都支持gzip,響應(yīng)頭返回包含content-encoding:gzip。瀏覽器接收到服務(wù)器的響應(yīng)之后判斷內(nèi)容是否被壓縮,如果被壓縮則解壓縮顯示頁面內(nèi)容
使用compression-webpack-plugin插件對(duì)打包結(jié)果進(jìn)行預(yù)壓縮,可以移除服務(wù)器的壓縮時(shí)間。
const CmpressionWebpackPlugin = require("compression-webpack-plugin")
module.exports = {
plugins: [
new CmpressionWebpackPlugin({
// filename: "[file].gzip"
test: /\.js/, //針對(duì)需要預(yù)壓縮的文件
minRatio: 0.5 //壓縮比率
})
]
};