一.Webpack是什么
Webpack是一種前端資源構(gòu)建工具蛹找,一個靜態(tài)模塊打包器,在Webpack看來莉御,前端的所有資源文件都會作為模塊處理俗孝,它將根據(jù)模塊的依賴關(guān)系進(jìn)行靜態(tài)分析,打包生成對應(yīng)的靜態(tài)資源浮声。
二.Webpack的核心概念
entry(入口):指示 webpack 以哪個文件為入口起點(diǎn)開始打包虚婿,分析構(gòu)建內(nèi)部依賴圖。
output(輸出):指示webpack打包后的資源bundles輸出到哪里去泳挥,以及命名
loader:讓 webpack 能夠去處理那些非 JS 的文件塑荒,比如樣式文件勃痴、圖片文件,webpack只能理解Javascript和JSON文件
plugins(插件):loader 用于轉(zhuǎn)換某些類型的模塊,而插件則可以用于執(zhí)行范圍更廣的任務(wù)揍拆。包括:打包優(yōu)化,資源管理新症,注入環(huán)境變量
mode(模式):指示webpack使用相應(yīng)模式的配置
瀏覽器兼容性(browser compatibility):webpack 支持所有符合 ES5 標(biāo)準(zhǔn)的瀏覽器(不支持 IE8 及以下版本)筑凫。webpack 的 import()
和 require.ensure()
需要 Promise
。如果你想要支持舊版本瀏覽器吨艇,在使用這些表達(dá)式之前躬它,還需要 提前加載 polyfill
環(huán)境(environment):webpack運(yùn)行的環(huán)境
三.webpack優(yōu)化配置
1.開發(fā)環(huán)境性能優(yōu)化
(1)HMR(模塊熱替換)
HMR: hot module replacement 熱模塊替換 / 模塊熱替換
作用:一個模塊發(fā)生變化,只會重新打包構(gòu)建這一個模塊(而不是打包所有模塊) 东涡,極大提升構(gòu)建速度
代碼:只需要在 devServer 中設(shè)置 hot 為 true冯吓,就會自動開啟HMR功能
(只能在開發(fā)者模式下使用)
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
// 開啟HMR功能
// 當(dāng)修改了webpack配置,新配置要想生效疮跑,必須重啟webpack服務(wù)
hot: true
}
每種文件實現(xiàn)熱模塊替換的情況:
樣式文件:可以使用HMR功能组贺,因為開發(fā)環(huán)境下使用的 style-loader 內(nèi)部默認(rèn)實現(xiàn)了熱模塊替換功能
js 文件:默認(rèn)不能使用HMR功能(修改一個 js 模塊所有 js 模塊都會刷新)
--> 實現(xiàn) HMR 需要修改 js 代碼(添加支持 HMR 功能的代碼)
// 綁定
if (module.hot) {
// 一旦 module.hot 為true,說明開啟了HMR功能祖娘。 --> 讓HMR功能代碼生效
module.hot.accept('./print.js', function() {
// 方法會監(jiān)聽 print.js 文件的變化失尖,一旦發(fā)生變化,只有這個模塊會重新打包構(gòu)建渐苏,其他模塊不會掀潮。
// 會執(zhí)行后面的回調(diào)函數(shù)
print();
});
}
注意:HMR 功能對 js 的處理,只能處理非入口 js 文件的其他文件整以。
- html文件:默認(rèn)不能使用 HMR 功能(html 不用做 HMR 功能胧辽,因為只有一個 html 文件,不需要再優(yōu)化)
使用 HMR 會導(dǎo)致問題:html 文件不能熱更新了(不會自動打包構(gòu)建)
解決:修改 entry 入口公黑,將 html 文件引入(這樣 html 修改整體刷新)
entry: ['./src/js/index.js', './src/index.html']
(2)source-map
source-map:一種提供源代碼到構(gòu)建后代碼的映射的技術(shù) (如果構(gòu)建后代碼出錯了邑商,通過映射可以追蹤源代碼錯誤)
參數(shù):[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
代碼:
devtool: 'eval-source-map'
可選方案:[生成source-map的位置|給出的錯誤代碼信息]
source-map:外部摄咆,錯誤代碼準(zhǔn)確信息 和 源代碼的錯誤位置
inline-source-map:內(nèi)聯(lián),只生成一個內(nèi)聯(lián) source-map人断,錯誤代碼準(zhǔn)確信息 和 源代碼的錯誤位置
hidden-source-map:外部吭从,錯誤代碼錯誤原因,但是沒有錯誤位置(為了隱藏源代碼)恶迈,不能追蹤源代碼錯誤涩金,只能提示到構(gòu)建后代碼的錯誤位置
eval-source-map:內(nèi)聯(lián),每一個文件都生成對應(yīng)的 source-map暇仲,都在 eval 中步做,錯誤代碼準(zhǔn)確信息 和 源代碼的錯誤位
nosources-source-map:外部,錯誤代碼準(zhǔn)確信息奈附,但是沒有任何源代碼信息(為了隱藏源代碼)
cheap-source-map:外部全度,錯誤代碼準(zhǔn)確信息 和 源代碼的錯誤位置,只能把錯誤精確到整行斥滤,忽略列
cheap-module-source-map:外部将鸵,錯誤代碼準(zhǔn)確信息 和 源代碼的錯誤位置,module 會加入 loader 的 source-map
內(nèi)聯(lián) 和 外部的區(qū)別:1. 外部生成了文件佑颇,內(nèi)聯(lián)沒有 2. 內(nèi)聯(lián)構(gòu)建速度更快
開發(fā)/生產(chǎn)環(huán)境可做的選擇:
開發(fā)環(huán)境:需要考慮速度快顶掉,調(diào)試更友好
- 速度快( eval > inline > cheap >... )
(1)eval-cheap-souce-map
(2)eval-source-map - 調(diào)試更友好
(1)souce-map
(2)cheap-module-souce-map
(3)cheap-souce-map
最終得出最好的兩種方案 --> eval-source-map(完整度高,內(nèi)聯(lián)速度快) / eval-cheap-module-souce-map(錯誤提示忽略列但是包含其他信息挑胸,內(nèi)聯(lián)速度快)
生產(chǎn)環(huán)境:需要考慮源代碼要不要隱藏痒筒,調(diào)試要不要更友好
- 內(nèi)聯(lián)會讓代碼體積變大,所以在生產(chǎn)環(huán)境不用內(nèi)聯(lián)
- 隱藏源代碼
(1)nosources-source-map 全部隱藏
(2)hidden-source-map 只隱藏源代碼嗜暴,會提示構(gòu)建后代碼錯誤信息
最終得出最好的兩種方案 --> source-map(最完整) / cheap-module-souce-map(錯誤提示一整行忽略列)
2.生產(chǎn)環(huán)境性能優(yōu)化
優(yōu)化打包構(gòu)建速度
(1)oneOf
oneOf:匹配到loader后就不再向后進(jìn)行匹配凸克,優(yōu)化生產(chǎn)環(huán)境的打包構(gòu)建速度
代碼
module: {
rules: [
{
// js 語法檢查
test: /\.js$/,
exclude: /node_modules/,
// 優(yōu)先執(zhí)行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
// oneOf 優(yōu)化生產(chǎn)環(huán)境的打包構(gòu)建速度
// 以下loader只會匹配一個(匹配到了后就不會再往下匹配了)
// 注意:不能有兩個配置處理同一種類型文件(所以把eslint-loader提取出去放外面)
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
{
// js 兼容性處理
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {version: 3},
targets: {
chrome: '60',
firefox: '50'
}
}
]
]
}
},
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
}
]
},
注意:如果oneOf中有相同的loader,一定要只在oneOf中只留一個loader
(2)babel緩存:類似 HMR闷沥,將 babel 處理后的資源緩存起來(哪里的 js 改變就更新哪里,其他 js 還是用之前緩存的資源)咐容,讓第二次打包構(gòu)建速度更快
代碼
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
// 開啟babel緩存
// 第二次構(gòu)建時舆逃,會讀取之前的緩存
cacheDirectory: true
}
}
文件資源緩存:文件名不變,就不會重新請求戳粒,而是再次用之前緩存的資源
1.hash:每次wepack 打包時會生成一個唯一的 hash 值路狮。
問題:重新打包,所有文件的 hsah 值都改變蔚约,會導(dǎo)致所有緩存失效奄妨。(可能只改動了一個文件)
2.chunkhash:根據(jù) chunk 生成的 hash 值。來源于同一個 chunk的 hash 值一樣
問題:js 和 css 來自同一個chunk苹祟,hash 值是一樣的(因為 css-loader 會將 css 文件加載到 js 中砸抛,所以同屬于一個chunk)
3.contenthash: 根據(jù)文件的內(nèi)容生成 hash 值评雌。不同文件 hash 值一定不一樣(文件內(nèi)容修改,文件名里的 hash 才會改變)
修改 css 文件內(nèi)容直焙,打包后的 css 文件名 hash 值就改變景东,而 js 文件沒有改變 hash 值就不變,這樣 css 和 js 緩存就會分開判斷要不要重新請求資源 --> 讓代碼上線運(yùn)行緩存更好使用
(3)多進(jìn)程打包
多進(jìn)程打包:某個任務(wù)消耗時間較長會卡頓奔誓,多進(jìn)程可以同一時間干多件事斤吐,效率更高。
優(yōu)點(diǎn)是提升打包速度厨喂,缺點(diǎn)是每個進(jìn)程的開啟和交流都會有開銷(babel-loader消耗時間最久和措,所以使用thread-loader針對其進(jìn)行優(yōu)化)
{
test: /\.js$/,
exclude: /node_modules/,
use: [
/*
thread-loader會對其后面的loader(這里是babel-loader)開啟多進(jìn)程打包。
進(jìn)程啟動大概為600ms蜕煌,進(jìn)程通信也有開銷臼婆。(啟動的開銷比較昂貴,不要濫用)
只有工作消耗時間比較長幌绍,才需要多進(jìn)程打包
*/
{
loader: 'thread-loader',
options: {
workers: 2 // 進(jìn)程2個
}
},
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
// 開啟babel緩存
// 第二次構(gòu)建時颁褂,會讀取之前的緩存
cacheDirectory: true
}
}
]
}
(4) externals
externals:讓某些庫不打包,通過 cdn 引入
webpack.config.js 中配置
externals: {
// 拒絕jQuery被打包進(jìn)來(通過cdn引入傀广,速度會快一些)
// 忽略的庫名 -- npm包名
jquery: 'jQuery'
}
需要在 index.html 中通過 cdn 引入:
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
(5)dll
dll:讓某些庫單獨(dú)打包颁独,后直接引入到 build 中∥北可以在 code split 分割出 node_modules 后再用 dll 更細(xì)的分割誓酒,優(yōu)化代碼運(yùn)行的性能。
webpack.dll.js 配置:(將 jquery 單獨(dú)打包)
/*
node_modules的庫會打包到一起贮聂,但是很多庫的時候打包輸出的js文件就太大了
使用dll技術(shù)靠柑,對某些庫(第三方庫:jquery、react吓懈、vue...)進(jìn)行單獨(dú)打包
當(dāng)運(yùn)行webpack時歼冰,默認(rèn)查找webpack.config.js配置文件
需求:需要運(yùn)行webpack.dll.js文件
--> webpack --config webpack.dll.js(運(yùn)行這個指令表示以這個配置文件打包)
*/
const { resolve } = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
// 最終打包生成的[name] --> jquery
// ['jquery] --> 要打包的庫是jquery
jquery: ['jquery']
},
output: {
// 輸出出口指定
filename: '[name].js', // name就是jquery
path: resolve(__dirname, 'dll'), // 打包到dll目錄下
library: '[name]_[hash]', // 打包的庫里面向外暴露出去的內(nèi)容叫什么名字
},
plugins: [
// 打包生成一個manifest.json --> 提供jquery的映射關(guān)系(告訴webpack:jquery之后不需要再打包和暴露內(nèi)容的名稱)
new webpack.DllPlugin({
name: '[name]_[hash]', // 映射庫的暴露的內(nèi)容名稱
path: resolve(__dirname, 'dll/manifest.json') // 輸出文件路徑
})
],
mode: 'production'
};
webpack.config.js 配置:(告訴 webpack 不需要再打包 jquery,并將之前打包好的 jquery 跟其他打包好的資源一同輸出到 build 目錄下)
// 引入插件
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
// plugins中配置:
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
// 告訴webpack哪些庫不參與打包耻警,同時使用時的名稱也得變
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 將某個文件打包輸出到build目錄下隔嫡,并在html中自動引入該資源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
]
優(yōu)化代碼運(yùn)行的性能
(1)緩存
(2)tree shaking(樹搖)
tree shaking:去除無用代碼
前提:1. 必須使用 ES6 模塊化 2. 開啟 production 環(huán)境 (這樣就自動會把無用代碼去掉)
作用:減少代碼體積
在 package.json 中配置:
"sideEffects": false 表示所有代碼都沒有副作用(都可以進(jìn)行 tree shaking)
這樣會導(dǎo)致的問題:可能會把 css / @babel/polyfill 文件干掉(副作用)
所以可以配置:"sideEffects": [".css", ".less"] 不會對css/less文件tree shaking處理
(3)code split(代碼分割)
代碼分割:將打包輸出的一個大的 bundle.js 文件拆分成多個小文件,這樣可以并行加載多個文件甘穿,比加載一個文件更快腮恩。
多入口拆分
entry: {
// 多入口:有一個入口,最終輸出就有一個bundle
index: './src/js/index.js',
test: './src/js/test.js'
},
output: {
// [name]:取文件名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
optimization
- 將 node_modules 中的代碼單獨(dú)打包(大小超過30kb)
- 自動分析多入口chunk中温兼,有沒有公共的文件秸滴。如果有會打包成單獨(dú)一個chunk(比如兩個模塊中都引入了jquery會被打包成單獨(dú)的文件)(大小超過30kb)
optimization: {
splitChunks: {
chunks: 'all'
}
},
import動態(tài)引入語法
/*
通過js代碼,讓某個文件被單獨(dú)打包成一個chunk
import動態(tài)導(dǎo)入語法:能將某個文件單獨(dú)打包(test文件不會和index打包在同一個文件而是單獨(dú)打包)
webpackChunkName:指定test單獨(dú)打包后文件的名字
*/
import(/* webpackChunkName: 'test' */'./test')
.then(({ mul, count }) => {
// 文件加載成功~
// eslint-disable-next-line
console.log(mul(2, 5));
})
.catch(() => {
// eslint-disable-next-line
console.log('文件加載失敗~');
});
(4)lazy loading(懶加載/預(yù)加載)
1.懶加載:當(dāng)文件需要使用時才加載(需要代碼分割)募判。但是如果資源較大荡含,加載時間就會較長咒唆,有延遲。
2.正常加載:可以認(rèn)為是并行加載(同一時間加載多個文件)沒有先后順序内颗,先加載了不需要的資源就會浪費(fèi)時間钧排。
3.預(yù)加載 prefetch(兼容性很差):會在使用之前,提前加載均澳。等其他資源加載完畢恨溜,瀏覽器空閑了,再偷偷加載這個資源找前。這樣在使用時已經(jīng)加載好了糟袁,速度很快。所以在懶加載的基礎(chǔ)上加上預(yù)加載會更好躺盛。
代碼
document.getElementById('btn').onclick = function() {
// 將import的內(nèi)容放在異步回調(diào)函數(shù)中使用项戴,點(diǎn)擊按鈕,test.js才會被加載(不會重復(fù)加載)
// webpackPrefetch: true表示開啟預(yù)加載
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
import('./test').then(({ mul }) => {
console.log(mul(2, 5))
})
};
(5)pwa(離線可訪問技術(shù))
pwa:離線可訪問技術(shù)(漸進(jìn)式網(wǎng)絡(luò)開發(fā)應(yīng)用程序)槽惫,使用 serviceworker 和 workbox 技術(shù)周叮。優(yōu)點(diǎn)是離線也能訪問,缺點(diǎn)是兼容性差界斜。
webpack.config.js 中配置:
const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); // 引入插件
// plugins中加入:
new WorkboxWebpackPlugin.GenerateSW({
/*
1. 幫助serviceworker快速啟動
2. 刪除舊的 serviceworker
生成一個 serviceworker 配置文件
*/
clientsClaim: true,
skipWaiting: true
})
index.js 中還需要寫一段代碼來激活它的使用:
/*
1. eslint不認(rèn)識 window仿耽、navigator全局變量
解決:需要修改package.json中eslintConfig配置
"env": {
"browser": true // 支持瀏覽器端全局變量
}
2. sw代碼必須運(yùn)行在服務(wù)器上
--> nodejs
或-->
npm i serve -g
serve -s build 啟動服務(wù)器,將打包輸出的build目錄下所有資源作為靜態(tài)資源暴露出去
*/
if ('serviceWorker' in navigator) { // 處理兼容性問題
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js') // 注冊serviceWorker
.then(() => {
console.log('sw注冊成功了~');
})
.catch(() => {
console.log('sw注冊失敗了~');
});
});
}
文章學(xué)習(xí)地址:Webpack入門-學(xué)習(xí)總結(jié) | Woc12138
視頻學(xué)習(xí)地址:尚硅谷最新版Webpack5實戰(zhàn)教程(從入門到精通)_嗶哩嗶哩_bilibili