一、Webpack 簡(jiǎn)介
1.1 webpack 是什么
webpack 是一種前端資源構(gòu)建工具粹断,一個(gè)靜態(tài)模塊打包器(module bundler)稿湿。
在webpack 看來(lái), 前端的所有資源文件(js/json/css/img/less/...)都會(huì)作為模塊處理。
它將根據(jù)模塊的依賴關(guān)系進(jìn)行靜態(tài)分析族铆,打包生成對(duì)應(yīng)的靜態(tài)資源(bundle)将硝。
1.2 webpack 五個(gè)核心概念
1.2.1 Entry
入口(Entry):指示 webpack 以哪個(gè)文件為入口起點(diǎn)開始打包一屋,分析構(gòu)建內(nèi)部依賴圖。
1.2.2 Output
輸出(Output):指示 webpack 打包后的資源 bundles 輸出到哪里去袋哼,以及如何命名。
1.2.3 Loader
Loader:讓 webpack 能夠去處理那些非 JS 的文件闸衫,比如樣式文件涛贯、圖片文件(webpack 自身只理解
JS)
1.2.4 Plugins
插件(Plugins):可以用于執(zhí)行范圍更廣的任務(wù)。插件的范圍包括蔚出,從打包優(yōu)化和壓縮弟翘,
一直到重新定義環(huán)境中的變量等。
1.2.5 Mode
模式(Mode):指示 webpack 使用相應(yīng)模式的配置骄酗。
表頭 | 表頭 | 特點(diǎn) |
---|---|---|
development | 會(huì)將 DefinePlugin 中 process.env.NODE_ENV 的值設(shè)置為 development稀余。 | 能讓代碼本地調(diào)試運(yùn)行的環(huán)境 |
production | 會(huì)將 DefinePlugin 中 process.env.NODE_ENV 的值設(shè)置為 production。啟用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 TerserPlugin趋翻。 | 能讓代碼優(yōu)化上線運(yùn)行的環(huán)境 |
二睛琳、Webpack 初體驗(yàn)
2.1 初始化配置
初始化 package.json:npm init
-
下載安裝webpack:(webpack4以上的版本需要全局/本地都安裝webpack-cli)
全局安裝:cnpm i webpack webpack-cli -g
本地安裝:cnpm i webpack webpack-cli -D
2.2 編譯打包應(yīng)用
創(chuàng)建 src 下的 js 等文件后,不需要配置 webpack.config.js 文件,在命令行就可以編譯打包师骗。
指令:
-
開發(fā)環(huán)境:webpack ./src/index.js -o ./build/built.js --mode=development
webpack會(huì)以 ./src/index.js 為入口文件開始打包历等,打包后輸出到 ./build/built.js 整體打包環(huán)境,是開發(fā)環(huán)境
-
生產(chǎn)環(huán)境:webpack ./src/index.js -o ./build/built.js --mode=production
webpack會(huì)以 ./src/index.js 為入口文件開始打包辟癌,打包后輸出到 ./build/built.js 整體打包環(huán)境寒屯,是生產(chǎn)環(huán)境
結(jié)論:
webpack 本身能處理 js/json 資源,不能處理 css/img 等其他資源
生產(chǎn)環(huán)境和開發(fā)環(huán)境將 ES6 模塊化編譯成瀏覽器能識(shí)別的模塊化黍少,但是不能處理 ES6 的基本語(yǔ)法轉(zhuǎn)化為 ES5(需要借助 loader)
生產(chǎn)環(huán)境比開發(fā)環(huán)境多一個(gè)壓縮 js 代碼
三寡夹、Webpack 開發(fā)環(huán)境的基本配置
webpack.config.js 是 webpack 的配置文件。
作用: 指示 webpack 干哪些活(當(dāng)你運(yùn)行 webpack 指令時(shí)厂置,會(huì)加載里面的配置)
所有構(gòu)建工具都是基于 nodejs 平臺(tái)運(yùn)行的菩掏,模塊化默認(rèn)采用 commonjs。
開發(fā)環(huán)境配置主要是為了能讓代碼運(yùn)行农渊。主要考慮以下幾個(gè)方面:
- 打包樣式資源
- 打包 html 資源
- 打包圖片資源
- 打包其他資源
- devServer
下面是一個(gè)簡(jiǎn)單的開發(fā)環(huán)境webpack.confg.js配置文件
// resolve用來(lái)拼接絕對(duì)路徑的方法
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin') // 引用plugin
module.exports = {
// webpack配置
entry: './src/js/index.js', // 入口起點(diǎn)
output: {
// 輸出
// 輸出文件名
filename: 'js/build.js',
// __dirname是nodejs的變量患蹂,代表當(dāng)前文件的目錄絕對(duì)路徑
path: resolve(__dirname, 'build'), // 輸出路徑,所有資源打包都會(huì)輸出到這個(gè)文件夾下
},
// loader配置
module: {
rules: [
// 詳細(xì)的loader配置
// 不同文件必須配置不同loader處理
{
// 匹配哪些文件
test: /\.less$/,
// 使用哪些loader進(jìn)行處理
use: [
// use數(shù)組中l(wèi)oader執(zhí)行順序:從右到左砸紊,從下到上传于,依次執(zhí)行(先執(zhí)行css-loader)
// style-loader:創(chuàng)建style標(biāo)簽,將js中的樣式資源插入進(jìn)去醉顽,添加到head中生效
'style-loader',
// css-loader:將css文件變成commonjs模塊加載到j(luò)s中沼溜,里面內(nèi)容是樣式字符串
'css-loader',
// less-loader:將less文件編譯成css文件,需要下載less-loader和less
'less-loader'
],
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
// url-loader:處理圖片資源游添,問(wèn)題:默認(rèn)處理不了html中的img圖片
test: /\.(jpg|png|gif)$/,
// 需要下載 url-loader file-loader
loader: 'url-loader',
options: {
// 圖片大小小于8kb系草,就會(huì)被base64處理,優(yōu)點(diǎn):減少請(qǐng)求數(shù)量(減輕服務(wù)器壓力)唆涝,缺點(diǎn):圖片體積會(huì)更大(文件請(qǐng)求速度更慢)
// base64在客戶端本地解碼所以會(huì)減少服務(wù)器壓力找都,如果圖片過(guò)大還采用base64編碼會(huì)導(dǎo)致cpu調(diào)用率上升,網(wǎng)頁(yè)加載時(shí)變卡
limit: 8 * 1024,
// 給圖片重命名廊酣,[hash:10]:取圖片的hash的前10位能耻,[ext]:取文件原來(lái)擴(kuò)展名
name: '[hash:10].[ext]',
// 問(wèn)題:因?yàn)閡rl-loader默認(rèn)使用es6模塊化解析,而html-loader引入圖片是conmonjs亡驰,解析時(shí)會(huì)出問(wèn)題:[object Module]
// 解決:關(guān)閉url-loader的es6模塊化晓猛,使用commonjs解析
esModule: false,
outputPath: 'imgs',
},
},
{
test: /\.html$/,
// 處理html文件的img圖片(負(fù)責(zé)引入img,從而能被url-loader進(jìn)行處理)
loader: 'html-loader',
},
// 打包其他資源(除了html/js/css資源以外的資源)
{
// 排除html|js|css|less|jpg|png|gif文件
exclude: /\.(html|js|css|less|jpg|png|gif)/,
// file-loader:處理其他文件
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media',
},
},
],
},
// plugin的配置
plugins: [
// html-webpack-plugin:默認(rèn)會(huì)創(chuàng)建一個(gè)空的html文件凡辱,自動(dòng)引入打包輸出的所有資源(JS/CSS)
// 需要有結(jié)構(gòu)的HTML文件可以加一個(gè)template
new HtmlWebpackPlugin({
// 復(fù)制這個(gè)./src/index.html文件戒职,并自動(dòng)引入打包輸出的所有資源(JS/CSS)
template: './src/index.html',
}),
],
// 模式
mode: 'development', // 開發(fā)模式
// 開發(fā)服務(wù)器 devServer:用來(lái)自動(dòng)化,不用每次修改后都重新輸入webpack打包一遍(自動(dòng)編譯透乾,自動(dòng)打開瀏覽器洪燥,自動(dòng)刷新瀏覽器)
// 特點(diǎn):只會(huì)在內(nèi)存中編譯打包磕秤,不會(huì)有任何輸出(不會(huì)像之前那樣在外面看到打包輸出的build包,而是在內(nèi)存中蚓曼,關(guān)閉后會(huì)自動(dòng)刪除)
// 啟動(dòng)devServer指令為:npx webpack-dev-server
devServer: {
// 項(xiàng)目構(gòu)建后路徑
contentBase: resolve(__dirname, 'build'),
// 啟動(dòng)gzip壓縮
compress: true,
// 端口號(hào)
port: 3000,
// 自動(dòng)打開瀏覽器
open: true,
},
}
- 運(yùn)行項(xiàng)目的兩個(gè)指令:
webpack 會(huì)將打包結(jié)果輸出出去(build文件夾)
npx webpack-dev-server 只會(huì)在內(nèi)存中編譯打包亲澡,沒(méi)有輸出 - loader 和 plugin 的不同:(plugin 一定要先引入才能使用)
? loader:1. 下載 2. 使用(配置 loader)
? plugins:1.下載 2. 引入 3. 使用
四、Webpack 生產(chǎn)環(huán)境的基本配置
而生產(chǎn)環(huán)境的配置需要考慮以下幾個(gè)方面:
提取 css 成單獨(dú)文件
css 兼容性處理
壓縮 css
js 語(yǔ)法檢查
js 兼容性處理
js 壓縮
-
html 壓縮
下面是一個(gè)基本的生產(chǎn)環(huán)境下的webpack.config.js配置
const { resolve } = require('path')
const MiniCssExtractorPlugin = require('mini-css-extract-plugin')
const OptimiziCssAssetsWebpackPlugin = require('optimizi-css-assets-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 定義node.js的環(huán)境變量纫版,決定使用browserslist的哪個(gè)環(huán)境
process.env.NODE_ENV = 'production'
// 復(fù)用loader的寫法
const commonCssLoader = [
// 這個(gè)loader取代style-loader床绪。作用:提取js中的css成單獨(dú)文件然后通過(guò)link加載
MiniCssExtractPlugin.loader,
// css-loader:將css文件整合到j(luò)s文件中
// 經(jīng)過(guò)css-loader處理后,樣式文件是在js文件中的
// 問(wèn)題:1.js文件體積會(huì)很大2.需要先加載js再動(dòng)態(tài)創(chuàng)建style標(biāo)簽其弊,樣式渲染速度就慢癞己,會(huì)出現(xiàn)閃屏現(xiàn)象
// 解決:用MiniCssExtractPlugin.loader替代style-loader
'css-loader',
/*
postcss-loader:css兼容性處理:postcss --> 需要安裝:postcss-loader postcss-preset-env
postcss需要通過(guò)package.json中browserslist里面的配置加載指定的css兼容性樣式
在package.json中定義browserslist:
"browserslist": {
// 開發(fā)環(huán)境 --> 設(shè)置node環(huán)境變量:process.env.NODE_ENV = development
"development": [ // 只需要可以運(yùn)行即可
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
// 生產(chǎn)環(huán)境。默認(rèn)是生產(chǎn)環(huán)境
"production": [ // 需要滿足絕大多數(shù)瀏覽器的兼容
">0.2%",
"not dead",
"not op_mini all"
]
},
*/
{
loader: 'postcss-loader',
options: {
ident: 'postcss', // 基本寫法
plugins: () => [
// postcss的插件
require('postcss-preset-env')(),
],
},
},
]
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.css$/,
use: [...commonCssLoader],
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader'],
},
/*
正常來(lái)講梭伐,一個(gè)文件只能被一個(gè)loader處理
當(dāng)一個(gè)文件要被多個(gè)loader處理痹雅,那么一定要指定loader執(zhí)行的先后順序
先執(zhí)行eslint再執(zhí)行babel(用enforce)
*/
{
/*
js的語(yǔ)法檢查: 需要下載 eslint-loader eslint
注意:只檢查自己寫的源代碼,第三方的庫(kù)是不用檢查的
airbnb(一個(gè)流行的js風(fēng)格) --> 需要下載 eslint-config-airbnb-base eslint-plugin-import
設(shè)置檢查規(guī)則:
package.json中eslintConfig中設(shè)置
"eslintConfig": {
"extends": "airbnb-base"糊识, // 繼承airbnb的風(fēng)格規(guī)范
"env": {
"browser": true // 可以使用瀏覽器中的全局變量(使用window不會(huì)報(bào)錯(cuò))
}
}
*/
test: /\.js$/,
exclude: /node_modules/, // 忽略node_modules
enforce: 'pre', // 優(yōu)先執(zhí)行
loader: 'eslint-loader',
options: {
// 自動(dòng)修復(fù)
fix: true,
},
},
/*
js兼容性處理:需要下載 babel-loader @babel/core
1. 基本js兼容性處理 --> @babel/preset-env
問(wèn)題:只能轉(zhuǎn)換基本語(yǔ)法绩社,如promise高級(jí)語(yǔ)法不能轉(zhuǎn)換
2. 全部js兼容性處理 --> @babel/polyfill
問(wèn)題:只要解決部分兼容性問(wèn)題,但是將所有兼容性代碼全部引入赂苗,體積太大了
3. 需要做兼容性處理的就做:按需加載 --> core-js
*/
{
// 第三種方式:按需加載
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
// 預(yù)設(shè):指示babel做怎樣的兼容性處理
presets: [
'@babel/preset-env', // 基本預(yù)設(shè)
{
useBuiltIns: 'usage', //按需加載
corejs: { version: 3 }, // 指定core-js版本
targets: { // 指定兼容到什么版本的瀏覽器
chrome: '60',
firefox: '50',
ie: '9',
safari: '10',
edge: '17'
},
},
],
},
},
{
// 圖片處理
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false, // 關(guān)閉url-loader默認(rèn)使用的es6模塊化解析
},
},
// html中的圖片處理
{
test: /\.html$/,
loader: 'html-loader',
},
// 處理其他文件
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media',
},
},
],
},
plugins: [
new MiniCssExtractPlugin({
// 對(duì)輸出的css文件進(jìn)行重命名
filename: 'css/built.css',
}),
// 壓縮css
new OptimiziCssAssetsWebpackPlugin(),
// HtmlWebpackPlugin:html文件的打包和壓縮處理
// 通過(guò)這個(gè)插件會(huì)自動(dòng)將單獨(dú)打包的樣式文件通過(guò)link標(biāo)簽引入
new HtmlWebpackPlugin({
template: './src/index.html',
// 壓縮html代碼
minify: {
// 移除空格
collapseWhitespace: true,
// 移除注釋
removeComments: true,
},
}),
],
// 生產(chǎn)環(huán)境下會(huì)自動(dòng)壓縮js代碼
mode: 'production',
}
五愉耙、Webpack 優(yōu)化配置
5.1 開發(fā)環(huán)境性能優(yōu)化
5.1.1 HMR(模塊熱替換)
HMR: hot module replacement 熱模塊替換 / 模塊熱替換
作用:一個(gè)模塊發(fā)生變化,只會(huì)重新打包構(gòu)建這一個(gè)模塊(而不是打包所有模塊) 拌滋,極大提升構(gòu)建速度
代碼:只需要在 devServer 中設(shè)置 hot 為 true朴沿,就會(huì)自動(dòng)開啟HMR功能(只能在開發(fā)模式下使用)
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
// 開啟HMR功能
// 當(dāng)修改了webpack配置,新配置要想生效败砂,必須重啟webpack服務(wù)
hot: true
}
每種文件實(shí)現(xiàn)熱模塊替換的情況:
- 樣式文件:可以使用HMR功能赌渣,因?yàn)殚_發(fā)環(huán)境下使用的 style-loader 內(nèi)部默認(rèn)實(shí)現(xiàn)了熱模塊替換功能
- js 文件:默認(rèn)不能使用HMR功能(修改一個(gè) js 模塊所有 js 模塊都會(huì)刷新)
--> 實(shí)現(xiàn) HMR 需要修改 js 代碼(添加支持 HMR 功能的代碼)
// 綁定
if (module.hot) {
// 一旦 module.hot 為true,說(shuō)明開啟了HMR功能昌犹。 --> 讓HMR功能代碼生效
module.hot.accept('./print.js', function() {
// 方法會(huì)監(jiān)聽 print.js 文件的變化坚芜,一旦發(fā)生變化,只有這個(gè)模塊會(huì)重新打包構(gòu)建斜姥,其他模塊不會(huì)货岭。
// 會(huì)執(zhí)行后面的回調(diào)函數(shù)
print();
});
}
注意:HMR 功能對(duì) js 的處理,只能處理非入口 js 文件的其他文件疾渴。
- html 文件: 默認(rèn)不能使用 HMR 功能(html 不用做 HMR 功能,因?yàn)橹挥幸粋€(gè) html 文件屯仗,不需要再優(yōu)化)
使用 HMR 會(huì)導(dǎo)致問(wèn)題:html 文件不能熱更新了(不會(huì)自動(dòng)打包構(gòu)建)
解決:修改 entry 入口搞坝,將 html 文件引入(這樣 html 修改整體刷新)
entry: ['./src/js/index.js', './src/index.html']
5.1.2 source-map
source-map:一種提供源代碼到構(gòu)建后代碼的映射的技術(shù) (如果構(gòu)建后代碼出錯(cuò)了,通過(guò)映射可以追蹤源代碼錯(cuò)誤)
參數(shù):[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
代碼:
devtool: 'eval-source-map'
可選方案:[生成source-map的位置|給出的錯(cuò)誤代碼信息]
source-map:外部魁袜,錯(cuò)誤代碼準(zhǔn)確信息 和 源代碼的錯(cuò)誤位置
inline-source-map:內(nèi)聯(lián)桩撮,只生成一個(gè)內(nèi)聯(lián) source-map敦第,錯(cuò)誤代碼準(zhǔn)確信息 和 源代碼的錯(cuò)誤位置
hidden-source-map:外部,錯(cuò)誤代碼錯(cuò)誤原因店量,但是沒(méi)有錯(cuò)誤位置(為了隱藏源代碼)芜果,不能追蹤源代碼錯(cuò)誤,只能提示到構(gòu)建后代碼的錯(cuò)誤位置
eval-source-map:內(nèi)聯(lián)融师,每一個(gè)文件都生成對(duì)應(yīng)的 source-map右钾,都在 eval 中,錯(cuò)誤代碼準(zhǔn)確信息 和 源代碼的錯(cuò)誤位
nosources-source-map:外部聚磺,錯(cuò)誤代碼準(zhǔn)確信息某残,但是沒(méi)有任何源代碼信息(為了隱藏源代碼)
cheap-source-map:外部氧猬,錯(cuò)誤代碼準(zhǔn)確信息 和 源代碼的錯(cuò)誤位置,只能把錯(cuò)誤精確到整行脆烟,忽略列
-
cheap-module-source-map:外部,錯(cuò)誤代碼準(zhǔn)確信息 和 源代碼的錯(cuò)誤位置房待,module 會(huì)加入 loader 的 source-map
內(nèi)聯(lián) 和 外部的區(qū)別:1. 外部生成了文件邢羔,內(nèi)聯(lián)沒(méi)有 2. 內(nèi)聯(lián)構(gòu)建速度更快
開發(fā)/生產(chǎn)環(huán)境可做的選擇:
開發(fā)環(huán)境:需要考慮速度快,調(diào)試更友好
-
速度快( eval > inline > cheap >... )
- eval-cheap-souce-map
- eval-source-map
-
調(diào)試更友好
- souce-map
- cheap-module-souce-map
- cheap-souce-map
最終得出最好的兩種方案 --> eval-source-map(完整度高桑孩,內(nèi)聯(lián)速度快) / eval-cheap-module-souce-map(錯(cuò)誤提示忽略列但是包含其他信息拜鹤,內(nèi)聯(lián)速度快)
生產(chǎn)環(huán)境:需要考慮源代碼要不要隱藏,調(diào)試要不要更友好
內(nèi)聯(lián)會(huì)讓代碼體積變大洼怔,所以在生產(chǎn)環(huán)境不用內(nèi)聯(lián)
-
隱藏源代碼
- nosources-source-map 全部隱藏
- hidden-source-map 只隱藏源代碼署惯,會(huì)提示構(gòu)建后代碼錯(cuò)誤信息
最終得出最好的兩種方案 --> source-map(最完整) / cheap-module-souce-map(錯(cuò)誤提示一整行忽略列)
5.2 生產(chǎn)環(huán)境性能優(yōu)化
5.2.1 優(yōu)化打包構(gòu)建速度
5.2.1.1 oneOf
oneOf:匹配到 loader 后就不再向后進(jìn)行匹配,優(yōu)化生產(chǎn)環(huán)境的打包構(gòu)建速度
代碼:
module: {
rules: [
{
// js 語(yǔ)法檢查
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只會(huì)匹配一個(gè)(匹配到了后就不會(huì)再往下匹配了)
// 注意:不能有兩個(gè)配置處理同一種類型文件(所以把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'
}
}
]
}
]
},
5.2.1.2 babel 緩存
babel 緩存:類似 HMR镣隶,將 babel 處理后的資源緩存起來(lái)(哪里的 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)建時(shí)安岂,會(huì)讀取之前的緩存
cacheDirectory: true
}
},
文件資源緩存
文件名不變轻猖,就不會(huì)重新請(qǐng)求,而是再次用之前緩存的資源
1.hash: 每次 wepack 打包時(shí)會(huì)生成一個(gè)唯一的 hash 值域那。
? 問(wèn)題:重新打包咙边,所有文件的 hsah 值都改變,會(huì)導(dǎo)致所有緩存失效次员。(可能只改動(dòng)了一個(gè)文件)
2.chunkhash:根據(jù) chunk 生成的 hash 值败许。來(lái)源于同一個(gè) chunk的 hash 值一樣
? 問(wèn)題:js 和 css 來(lái)自同一個(gè)chunk,hash 值是一樣的(因?yàn)?css-loader 會(huì)將 css 文件加載到 js 中淑蔚,所以同屬于一個(gè)chunk)
3.contenthash: 根據(jù)文件的內(nèi)容生成 hash 值市殷。不同文件 hash 值一定不一樣(文件內(nèi)容修改,文件名里的 hash 才會(huì)改變)
修改 css 文件內(nèi)容刹衫,打包后的 css 文件名 hash 值就改變醋寝,而 js 文件沒(méi)有改變 hash 值就不變搞挣,這樣 css 和 js 緩存就會(huì)分開判斷要不要重新請(qǐng)求資源 --> 讓代碼上線運(yùn)行緩存更好使用
5.2.1.3 多進(jìn)程打包
多進(jìn)程打包:某個(gè)任務(wù)消耗時(shí)間較長(zhǎng)會(huì)卡頓,多進(jìn)程可以同一時(shí)間干多件事音羞,效率更高囱桨。
優(yōu)點(diǎn)是提升打包速度,缺點(diǎn)是每個(gè)進(jìn)程的開啟和交流都會(huì)有開銷(babel-loader消耗時(shí)間最久嗅绰,所以使用thread-loader針對(duì)其進(jìn)行優(yōu)化)
{
test: /\.js$/,
exclude: /node_modules/,
use: [
/*
thread-loader會(huì)對(duì)其后面的loader(這里是babel-loader)開啟多進(jìn)程打包舍肠。
進(jìn)程啟動(dòng)大概為600ms,進(jìn)程通信也有開銷办陷。(啟動(dòng)的開銷比較昂貴貌夕,不要濫用)
只有工作消耗時(shí)間比較長(zhǎng),才需要多進(jìn)程打包
*/
{
loader: 'thread-loader',
options: {
workers: 2 // 進(jìn)程2個(gè)
}
},
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
// 開啟babel緩存
// 第二次構(gòu)建時(shí)民镜,會(huì)讀取之前的緩存
cacheDirectory: true
}
}
]
},
5.2.1.4 externals
externals:讓某些庫(kù)不打包啡专,通過(guò) cdn 引入
webpack.config.js 中配置:
externals: {
// 拒絕jQuery被打包進(jìn)來(lái)(通過(guò)cdn引入,速度會(huì)快一些)
// 忽略的庫(kù)名 -- npm包名
jquery: 'jQuery'
}
需要在 index.html 中通過(guò) cdn 引入:
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
5.2.1.5 dll
dll:讓某些庫(kù)單獨(dú)打包制圈,后直接引入到 build 中们童。可以在 code split 分割出 node_modules 后再用 dll 更細(xì)的分割鲸鹦,優(yōu)化代碼運(yùn)行的性能慧库。
webpack.dll.js 配置:(將 jquery 單獨(dú)打包)
/*
node_modules的庫(kù)會(huì)打包到一起,但是很多庫(kù)的時(shí)候打包輸出的js文件就太大了
使用dll技術(shù)馋嗜,對(duì)某些庫(kù)(第三方庫(kù):jquery齐板、react、vue...)進(jìn)行單獨(dú)打包
當(dāng)運(yùn)行webpack時(shí)葛菇,默認(rèn)查找webpack.config.js配置文件
需求:需要運(yùn)行webpack.dll.js文件
--> webpack --config webpack.dll.js(運(yùn)行這個(gè)指令表示以這個(gè)配置文件打包)
*/
const { resolve } = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
// 最終打包生成的[name] --> jquery
// ['jquery] --> 要打包的庫(kù)是jquery
jquery: ['jquery']
},
output: {
// 輸出出口指定
filename: '[name].js', // name就是jquery
path: resolve(__dirname, 'dll'), // 打包到dll目錄下
library: '[name]_[hash]', // 打包的庫(kù)里面向外暴露出去的內(nèi)容叫什么名字
},
plugins: [
// 打包生成一個(gè)manifest.json --> 提供jquery的映射關(guān)系(告訴webpack:jquery之后不需要再打包和暴露內(nèi)容的名稱)
new webpack.DllPlugin({
name: '[name]_[hash]', // 映射庫(kù)的暴露的內(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哪些庫(kù)不參與打包,同時(shí)使用時(shí)的名稱也得變
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 將某個(gè)文件打包輸出到build目錄下眯停,并在html中自動(dòng)引入該資源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
5.2.2 優(yōu)化代碼運(yùn)行的性能
5.2.2.1 緩存
5.2.2.2 tree shaking(樹搖)
tree shaking:去除無(wú)用代碼
前提:1. 必須使用 ES6 模塊化 2. 開啟 production 環(huán)境 (這樣就自動(dòng)會(huì)把無(wú)用代碼去掉)
作用:減少代碼體積
在 package.json 中配置:
"sideEffects": false
表示所有代碼都沒(méi)有副作用(都可以進(jìn)行 tree shaking)
這樣會(huì)導(dǎo)致的問(wèn)題:可能會(huì)把 css / @babel/polyfill 文件干掉(副作用)
所以可以配置:"sideEffects": ["*.css", "*.less"]
不會(huì)對(duì)css/less文件tree shaking處理
5.2.2.3 code split(代碼分割)
代碼分割济舆。將打包輸出的一個(gè)大的 bundle.js 文件拆分成多個(gè)小文件,這樣可以并行加載多個(gè)文件莺债,比加載一個(gè)文件更快滋觉。
1.多入口拆分
entry: {
// 多入口:有一個(gè)入口,最終輸出就有一個(gè)bundle
index: './src/js/index.js',
test: './src/js/test.js'
},
output: {
// [name]:取文件名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
2.optimization:
optimization: {
splitChunks: {
chunks: 'all'
}
},
- 將 node_modules 中的代碼單獨(dú)打包(大小超過(guò)30kb)
- 自動(dòng)分析多入口chunk中齐邦,有沒(méi)有公共的文件椎侠。如果有會(huì)打包成單獨(dú)一個(gè)chunk(比如兩個(gè)模塊中都引入了jquery會(huì)被打包成單獨(dú)的文件)(大小超過(guò)30kb)
3.import 動(dòng)態(tài)導(dǎo)入語(yǔ)法:
/*
通過(guò)js代碼,讓某個(gè)文件被單獨(dú)打包成一個(gè)chunk
import動(dòng)態(tài)導(dǎo)入語(yǔ)法:能將某個(gè)文件單獨(dú)打包(test文件不會(huì)和index打包在同一個(gè)文件而是單獨(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('文件加載失敗~');
});
5.2.2.4 lazy loading(懶加載/預(yù)加載)
1.懶加載:當(dāng)文件需要使用時(shí)才加載(需要代碼分割)措拇。但是如果資源較大肺蔚,加載時(shí)間就會(huì)較長(zhǎng),有延遲。
2.正常加載:可以認(rèn)為是并行加載(同一時(shí)間加載多個(gè)文件)沒(méi)有先后順序宣羊,先加載了不需要的資源就會(huì)浪費(fèi)時(shí)間。
3.預(yù)加載 prefetch(兼容性很差):會(huì)在使用之前汰蜘,提前加載仇冯。等其他資源加載完畢,瀏覽器空閑了族操,再偷偷加載這個(gè)資源苛坚。這樣在使用時(shí)已經(jīng)加載好了,速度很快色难。所以在懶加載的基礎(chǔ)上加上預(yù)加載會(huì)更好泼舱。
代碼:
document.getElementById('btn').onclick = function() {
// 將import的內(nèi)容放在異步回調(diào)函數(shù)中使用,點(diǎn)擊按鈕枷莉,test.js才會(huì)被加載(不會(huì)重復(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.2.2.5 pwa(離線可訪問(wèn)技術(shù))
pwa:離線可訪問(wèn)技術(shù)(漸進(jìn)式網(wǎng)絡(luò)開發(fā)應(yīng)用程序)娇昙,使用 serviceworker 和 workbox 技術(shù)。優(yōu)點(diǎn)是離線也能訪問(wèn)笤妙,缺點(diǎn)是兼容性差冒掌。
webpack.config.js 中配置:
const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); // 引入插件
// plugins中加入:
new WorkboxWebpackPlugin.GenerateSW({
/*
1. 幫助serviceworker快速啟動(dòng)
2. 刪除舊的 serviceworker
生成一個(gè) serviceworker 配置文件
*/
clientsClaim: true,
skipWaiting: true
})
index.js 中還需要寫一段代碼來(lái)激活它的使用:
/*
1. eslint不認(rèn)識(shí) window、navigator全局變量
解決:需要修改package.json中eslintConfig配置
"env": {
"browser": true // 支持瀏覽器端全局變量
}
2. sw代碼必須運(yùn)行在服務(wù)器上
--> nodejs
或-->
npm i serve -g
serve -s build 啟動(dòng)服務(wù)器蹲盘,將打包輸出的build目錄下所有資源作為靜態(tài)資源暴露出去
*/
if ('serviceWorker' in navigator) { // 處理兼容性問(wèn)題
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js') // 注冊(cè)serviceWorker
.then(() => {
console.log('sw注冊(cè)成功了~');
})
.catch(() => {
console.log('sw注冊(cè)失敗了~');
});
});
}
六股毫、Webpack 配置詳情
6.1 entry
entry: 入口起點(diǎn)
string --> './src/index.js',單入口
打包形成一個(gè) chunk召衔。 輸出一個(gè) bundle 文件铃诬。此時(shí) chunk 的名稱默認(rèn)是 mainarray --> ['./src/index.js', './src/add.js'],多入口
所有入口文件最終只會(huì)形成一個(gè) chunk苍凛,輸出出去只有一個(gè) bundle 文件趣席。
(一般只用在 HMR 功能中讓 html 熱更新生效)-
object,多入口
有幾個(gè)入口文件就形成幾個(gè) chunk毫深,輸出幾個(gè) bundle 文件吩坝,此時(shí) chunk 的名稱是 key 值--> 特殊用法:
entry: {
// 最終只會(huì)形成一個(gè)chunk, 輸出出去只有一個(gè)bundle文件。
index: ['./src/index.js', './src/count.js'],
// 形成一個(gè)chunk哑蔫,輸出一個(gè)bundle文件钉寝。
add: './src/add.js'
}
6.2 output
output: {
// 文件名稱(指定名稱+目錄)
filename: 'js/[name].js',
// 輸出文件目錄(將來(lái)所有資源輸出的公共目錄)
path: resolve(__dirname, 'build'),
// 所有資源引入公共路徑前綴 --> 'imgs/a.jpg' --> '/imgs/a.jpg'
publicPath: '/',
chunkFilename: 'js/[name]_chunk.js', // 指定非入口chunk的名稱
library: '[name]', // 打包整個(gè)庫(kù)后向外暴露的變量名
libraryTarget: 'window' // 變量名添加到哪個(gè)上 browser:window
// libraryTarget: 'global' // node:global
// libraryTarget: 'commonjs' // conmmonjs模塊 exports
},
6.3 module
module: {
rules: [
// loader的配置
{
test: /\.css$/,
// 多個(gè)loader用use
use: ['style-loader', 'css-loader']
},
{
test: /\.js$/,
// 排除node_modules下的js文件
exclude: /node_modules/,
// 只檢查src下的js文件
include: resolve(__dirname, 'src'),
enforce: 'pre', // 優(yōu)先執(zhí)行
// enforce: 'post', // 延后執(zhí)行
// 單個(gè)loader用loader
loader: 'eslint-loader',
options: {} // 指定配置選項(xiàng)
},
{
// 以下配置只會(huì)生效一個(gè)
oneOf: []
}
]
},
6.4 resolve
// 解析模塊的規(guī)則
resolve: {
// 配置解析模塊路徑別名: 優(yōu)點(diǎn):當(dāng)目錄層級(jí)很復(fù)雜時(shí),簡(jiǎn)寫路徑闸迷;缺點(diǎn):路徑不會(huì)提示
alias: {
$css: resolve(__dirname, 'src/css')
},
// 配置省略文件路徑的后綴名(引入時(shí)就可以不寫文件后綴名了)
extensions: ['.js', '.json', '.jsx', '.css'],
// 告訴 webpack 解析模塊應(yīng)該去找哪個(gè)目錄
modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
}
這樣配置后嵌纲,引入文件就可以這樣簡(jiǎn)寫:import '$css/index';
6.5 dev server
devServer: {
// 運(yùn)行代碼所在的目錄
contentBase: resolve(__dirname, 'build'),
// 監(jiān)視contentBase目錄下的所有文件,一旦文件變化就會(huì)reload
watchContentBase: true,
watchOptions: {
// 忽略文件
ignored: /node_modules/
},
// 啟動(dòng)gzip壓縮
compress: true,
// 端口號(hào)
port: 5000,
// 域名
host: 'localhost',
// 自動(dòng)打開瀏覽器
open: true,
// 開啟HMR功能
hot: true,
// 不要顯示啟動(dòng)服務(wù)器日志信息
clientLogLevel: 'none',
// 除了一些基本信息外腥沽,其他內(nèi)容都不要顯示
quiet: true,
// 如果出錯(cuò)了逮走,不要全屏提示
overlay: false,
// 服務(wù)器代理,--> 解決開發(fā)環(huán)境跨域問(wèn)題
proxy: {
// 一旦devServer(5000)服務(wù)器接收到/api/xxx的請(qǐng)求今阳,就會(huì)把請(qǐng)求轉(zhuǎn)發(fā)到另外一個(gè)服務(wù)器3000
'/api': {
target: 'http://localhost:3000',
// 發(fā)送請(qǐng)求時(shí)师溅,請(qǐng)求路徑重寫:將/api/xxx --> /xxx (去掉/api)
pathRewrite: {
'^/api': ''
}
}
}
}
其中茅信,跨域問(wèn)題:同源策略中不同的協(xié)議、端口號(hào)墓臭、域名就會(huì)產(chǎn)生跨域蘸鲸。
正常的瀏覽器和服務(wù)器之間有跨域,但是服務(wù)器之間沒(méi)有跨域窿锉。代碼通過(guò)代理服務(wù)器運(yùn)行酌摇,所以瀏覽器和代理服務(wù)器之間沒(méi)有跨域,瀏覽器把請(qǐng)求發(fā)送到代理服務(wù)器上嗡载,代理服務(wù)器替你轉(zhuǎn)發(fā)到另外一個(gè)服務(wù)器上窑多,服務(wù)器之間沒(méi)有跨域,所以請(qǐng)求成功洼滚。代理服務(wù)器再把接收到的響應(yīng)響應(yīng)給瀏覽器埂息。這樣就解決開發(fā)環(huán)境下的跨域問(wèn)題。
6.6 optimization
contenthash 緩存會(huì)導(dǎo)致一個(gè)問(wèn)題:修改 a 文件導(dǎo)致 b 文件 contenthash 變化判沟。
因?yàn)樵?index.js 中引入 a.js耿芹,打包后 index.js 中記錄了 a.js 的 hash 值,而 a.js 改變挪哄,其重新打包后的 hash 改變吧秕,導(dǎo)致 index.js 文件內(nèi)容中記錄的 a.js 的 hash 也改變,從而重新打包后 index.js 的 hash 值也會(huì)變迹炼,這樣就會(huì)使緩存失效砸彬。(改變的是a.js文件但是 index.js 文件的 hash 值也改變了)
解決辦法:runtimeChunk --> 將當(dāng)前模塊記錄其他模塊的 hash 單獨(dú)打包為一個(gè)文件 runtime,這樣 a.js 的 hash 改變只會(huì)影響 runtime 文件斯入,不會(huì)影響到 index.js 文件
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build'),
chunkFilename: 'js/[name].[contenthash:10]_chunk.js' // 指定非入口文件的其他chunk的名字加_chunk
},
optimization: {
splitChunks: {
chunks: 'all',
/* 以下都是splitChunks默認(rèn)配置砂碉,可以不寫
miniSize: 30 * 1024, // 分割的chunk最小為30kb(大于30kb的才分割)
maxSize: 0, // 最大沒(méi)有限制
minChunks: 1, // 要提取的chunk最少被引用1次
maxAsyncRequests: 5, // 按需加載時(shí)并行加載的文件的最大數(shù)量為5
maxInitialRequests: 3, // 入口js文件最大并行請(qǐng)求數(shù)量
automaticNameDelimiter: '~', // 名稱連接符
name: true, // 可以使用命名規(guī)則
cacheGroups: { // 分割chunk的組
vendors: {
// node_modules中的文件會(huì)被打包到vendors組的chunk中,--> vendors~xxx.js
// 滿足上面的公共規(guī)則刻两,大小超過(guò)30kb增蹭、至少被引用一次
test: /[\\/]node_modules[\\/]/,
// 優(yōu)先級(jí)
priority: -10
},
default: {
// 要提取的chunk最少被引用2次
minChunks: 2,
prority: -20,
// 如果當(dāng)前要打包的模塊和之前已經(jīng)被提取的模塊是同一個(gè),就會(huì)復(fù)用磅摹,而不是重新打包
reuseExistingChunk: true
}
} */
},
// 將index.js記錄的a.js的hash值單獨(dú)打包到runtime文件中
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
},
minimizer: [
// 配置生產(chǎn)環(huán)境的壓縮方案:js/css
new TerserWebpackPlugin({
// 開啟緩存
cache: true,
// 開啟多進(jìn)程打包
parallel: true,
// 啟用sourceMap(否則會(huì)被壓縮掉)
sourceMap: true
})
]
}
七滋迈、Webpack5 介紹和使用
此版本重點(diǎn)關(guān)注以下內(nèi)容:
通過(guò)持久緩存提高構(gòu)建性能.
使用更好的算法和默認(rèn)值來(lái)改善長(zhǎng)期緩存.
通過(guò)更好的樹搖和代碼生成來(lái)改善捆綁包大小.
清除處于怪異狀態(tài)的內(nèi)部結(jié)構(gòu),同時(shí)在 v4 中實(shí)現(xiàn)功能而不引入任何重大更改.
-
通過(guò)引入重大更改來(lái)為將來(lái)的功能做準(zhǔn)備户誓,以使我們能夠盡可能長(zhǎng)時(shí)間地使用 v5.
下載
npm i webpack@next webpack-cli -D
自動(dòng)刪除 Node.js Polyfills
早期饼灿,webpack 的目標(biāo)是允許在瀏覽器中運(yùn)行大多數(shù) node.js 模塊,但是模塊格局發(fā)生了變化帝美,許多模塊用途現(xiàn)在主要是為前端目的而編寫的碍彭。webpack <= 4 附帶了許多 node.js 核心模塊的 polyfill,一旦模塊使用任何核心模塊(即 crypto 模塊),這些模塊就會(huì)自動(dòng)應(yīng)用庇忌。
盡管這使使用為 node.js 編寫的模塊變得容易舞箍,但它會(huì)將這些巨大的 polyfill 添加到包中。在許多情況下皆疹,這些 polyfill 是不必要的创译。
webpack 5 會(huì)自動(dòng)停止填充這些核心模塊,并專注于與前端兼容的模塊墙基。
遷移:
盡可能嘗試使用與前端兼容的模塊。
可以為 node.js 核心模塊手動(dòng)添加一個(gè) polyfill刷喜。錯(cuò)誤消息將提示如何實(shí)現(xiàn)該目標(biāo)残制。
Chunk 和模塊 ID
添加了用于長(zhǎng)期緩存的新算法。在生產(chǎn)模式下默認(rèn)情況下啟用這些功能掖疮。
chunkIds: "deterministic", moduleIds: "deterministic"
Chunk ID
你可以不用使用 import(/* webpackChunkName: "name" */ "module")
在開發(fā)環(huán)境來(lái)為 chunk 命名初茶,生產(chǎn)環(huán)境還是有必要的
webpack 內(nèi)部有 chunk 命名規(guī)則,不再是以 id(0, 1, 2)命名了
Tree Shaking
- webpack 現(xiàn)在能夠處理對(duì)嵌套模塊的 tree shaking
// inner.js
export const a = 1;
export const b = 2;
// module.js
import * as inner from './inner';
export { inner };
// user.js
import * as module from './module';
console.log(module.inner.a);
在生產(chǎn)環(huán)境中, inner 模塊暴露的 b
會(huì)被刪除
- webpack 現(xiàn)在能夠多個(gè)模塊之前的關(guān)系
import { something } from './something';
function usingSomething() {
return something;
}
export function test() {
return usingSomething();
}
當(dāng)設(shè)置了"sideEffects": false
時(shí)浊闪,一旦發(fā)現(xiàn)test
方法沒(méi)有使用恼布,不但刪除test
,還會(huì)刪除"./something"
- webpack 現(xiàn)在能處理對(duì) Commonjs 的 tree shaking
Output
SplitChunk
// webpack4
minSize: 30000;
// webpack5
minSize: {
javascript: 30000,
style: 50000,
}
webpack 4 默認(rèn)只能輸出 ES5 代碼
webpack 5 開始新增一個(gè)屬性 output.ecmaVersion, 可以生成 ES5 和 ES6 / ES2015 代碼.
如:output.ecmaVersion: 2015
Caching
// 配置緩存
cache: {
// 磁盤存儲(chǔ)
type: "filesystem",
buildDependencies: {
// 當(dāng)配置修改時(shí)搁宾,緩存失效
config: [__filename]
}
}
緩存將存儲(chǔ)到 node_modules/.cache/webpack
監(jiān)視輸出文件
之前 webpack 總是在第一次構(gòu)建時(shí)輸出全部文件折汞,但是監(jiān)視重新構(gòu)建時(shí)會(huì)只更新修改的文件。
此次更新在第一次構(gòu)建時(shí)會(huì)找到輸出文件看是否有變化盖腿,從而決定要不要輸出全部文件爽待。
默認(rèn)值
entry: "./src/index.js
output.path: path.resolve(__dirname, "dist")
output.filename: "[name].js"