第一節:webpack打包、壓縮及相容性處理
一.前言
當我們使用vue-cli3建立專案時,會自動生成相應的webpack配置,不過明白webpack的原理和基本設定方法對我們區域性修改某些webpack配置還是很有必要的;
二.為什麼需要構建工具?
- 轉換
ES6
語法; - 轉換
JSX
; CSS
字首補全/前處理器;- 壓縮混淆;
- 圖片壓縮;
三.Webpack五個核心概念
1.入口(Entry)
入口(Entry
)指示webpack
以哪個檔案為入口起點開始打包,分析構建內部依賴圖;
2.出口(Output)
輸出(Output
)指示Webpack
打包後的資源bundles
輸出到哪裡去,以及如何命名;
3.Loader
Loader
Webpack
能夠去處理那些非JavaScript
檔案(Webpack
自身只理解JavaScript
)
4.外掛(Plugins)
外掛(Plugins
)可以用於執行範圍更廣的任務,外掛的作用範圍包括,從打包優化和壓縮,一直到重新定義環境中的變數等;
5.模式(Mode)
通過設定 mode
引數為 development
或 production
之中的一個,來啟用相應模式下 webpack
內建的優化;
module.exports = {
mode: 'production'
};
development
模式:程式碼本地除錯和執行的環境;production
模式:程式碼優化上線執行的環境;
四.原始碼倉庫
webpack
系列中使用到的演示例項原始碼已上傳至該倉庫:
示例:
建立下圖所示的檔案目錄:
其中src
表示原始檔目錄,儲存著webpack
的入口起點檔案,比如index.js
;build
用於webpack
打包處理之後輸出的目錄,其餘檔案可通過執行:
可通過下列指令建立一個演示例項:
npm init//生成package.json
webpack_test //資料夾名字
一路回車取預設值生成:
隨後執行以下命令全域性安裝webpack
,其中的-g
引數表示全域性安裝,即使已經安裝過了,也沒關係。該指令會覆蓋原來的安裝並進行版本更新。
五.npm i 和 npm install 的區別
i
為install
的縮寫;
實際使用的區別點主要如下(windows下):
-
用
npm i
安裝的模組無法用npm uninstall
刪除,用npm uninstall i
才解除安裝掉 -
npm i
會幫助檢測與當前node
版本最匹配的npm
包版本號,並匹配出來相互依賴的npm
包應該提升的版本號 -
部分
npm
包在當前node
版本下無法使用,必須使用建議版本 -
安裝報錯時
intall
肯定會出現npm-debug.log
檔案,npm i
不一定
npm i webpack webpack-cli -g
然後在本地安裝,引數-D
為--save-dev
的縮寫,表示它會將webpack
新增到package.json
中的開發依賴中:
npm i webpack webpack-cli -D
webpack
中下載的所有東西都屬於開發依賴,不屬於生產依賴,所以都使用-D
。
index.js
檔案內容:
/*
index.js:webpack入口起點檔案
1.執行指令:
開發環境指令:webpack ./src/index.js -o ./build/built.js --mode=development
翻譯:webpack會以./src/index.js為入口環境開始打包,打包後輸出 ./build/built.js;整體打包環境是開發環境;程式碼不會被壓縮,可以看清楚邏輯;
生產環境指令:webpack ./src/index.js -o ./build/built.js --mode=production
翻譯:webpack會以./src/index.js為入口環境開始打包,打包後輸出 ./build/built.js;整體打包環境是生產環境;程式碼會被壓縮,無法看清程式碼邏輯;
*/
function add(x, y){
return x + y
}
console.log(add(1, 2));
先進入02-webpack
初體驗目錄,然後打包檔案
開發環境指令:
輸入下列開發環境指令打包檔案:
webpack ./src/index.js -o ./build/built.js --mode=development
輸出結果:
- 其中的
Hash
值代表打包的結果,每次打包都會生成一個唯一的雜湊值,唯一地ID
; - 其餘的有版本,打包時間,資源
Asset
,打包後的檔案built.js
的大小等;
此時build
目錄下會多出一個built.js
檔案
生產環境指令
輸入下列生產環境指令打包檔案:
webpack ./src/index.js -o ./build/built.js --mode=production
執行結果:
檢視生成的build
目錄下的built.js
檔案,發現程式碼進行了壓縮:
這個程式碼是可執行的:
每次src
資料夾中的入口檔案index.js
引入了新依賴之後,都要重新進行打包,才能看到最新的結果;
也就是說只要指定了入口檔案index.js
,就可以在index.js
中通過import
引入很多依賴檔案,這樣
webpack
在打包入口檔案index.js
的時候就會根據其中引入關係,一起打包index.js
的依賴檔案;
那麼引入其他檔案呢?
比如在index.js
中引入css
檔案的時候:
會出現打包錯誤,打包出來的built.js
中的該部分會丟出一個錯誤:
得出結論:
webpack
能處理js/json
資源,不能處理css/img
等資源;- 生產環境和開發環境將
ES6
模組化編譯成瀏覽器能識別的模組化; - 生產環境(
production
)比開發環境(development
)多了一個壓縮js
程式碼;
六.打包樣式資源
雖然webpack
不能直接打包css
檔案,但是可以藉助於各種各樣的Loader
將webpack
不能識別的檔案轉換成它能識別的格式;
需要在根目錄的package.json.js
檔案中進行配置:
整體配置為:
const { resolve } = require('path')
module.exports= {
entry: './src/index.js',
//輸出:這是一個物件,裡面有兩個東西。filename表示輸出的檔名,path表示輸出的路徑,寫路徑的時候通常會寫一個絕對路徑,避免出錯。絕對路徑會引入一個nodejs的path模組
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules:[
{
//詳細loader配置
//匹配哪些檔案
test: /\.css$/,
//處理這些檔案
use: [
'style-loader',
'css-loader'
]
}
]
},
plugins: [
//詳細plugins配置
],
mode: 'development',
//mode: 'production'
}
首先, webpack.json.js
為webpack
的配置檔案。作用為:指示webpack
幹哪些活(當執行webpack
指令時,要載入哪些配置);
所有構建工具都是基於node.js
平臺執行的,模組化預設採用commonjs
。可以看到commonjs
會匯出一個物件,在該物件中寫webpack
的配置:
1.入口起點
entry: './src/index.js'
表示打包的入口檔案為src
目錄下的index.js
檔案;
2.輸出
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
}
這是一個物件,其中:
-
filename
表示輸出的檔名; -
path
表示輸出的路徑; -
寫路徑的時候為了避免出錯,通常會寫一個絕對路徑。需要引入一個
nodejs
的模組path
模組:const { resolve } = require('path')
其中的
resolve
是用來拼接絕對路徑的方法,格式為:path: resolve(__dirname, 'build')
傳入兩個引數:
__dirname
和當前的build
目錄;其中__dirname
是nodejs
的變數,它代表當前檔案(指webpack.config.js
)的目錄的絕對路徑:
- 該絕對路徑就是
03-
打包樣式資源,也就是說__dirname
的值就是03-打包樣式資源
,拼接上build
,再加上第一個引數filename
指明的built.js
一起表示,打包後輸出到build
目錄下的built.js
檔案中;
3.Loader配置
module: {
rules: {
//詳細loader配置
}
}
4.外掛(plugins
)
plugins: [
//詳細plugins配置
],
5.模式
mode: 'development',
//mode: 'production'
開發模式development
和生產模式production
兩種模式選一種,不能同時寫;
6.打包樣式資源
可以使用css-loader
來翻譯css
檔案:
module: {
rules: [
{
//詳細loader配置
//使用正則表示式指定匹配哪些檔案
test: /\.css$/,//該正則表示式表明,匹配以css結尾的檔案,\為轉義符
//通過test的正則表示式匹配了檔案後,這裡指定使用哪些loader進行處理
use: [
//需要使用兩個loader
//作用:建立style標籤,將js中的樣式資源插入進去,新增到head中生效
'style-loader',
//將css檔案轉換成一個commonjs模組並載入到js中,裡面內容是樣式字串
'css-loader'
]
}
]
}
注意:rules
屬性是一個數組,裡面的每一個元素都為物件,每個物件匹配並處理一類檔案。並且物件中的use
屬性也是一個數組,其中loader
的執行順序為:從右到左,從下到上依次執行。為了不用每次都安裝同樣的依賴檔案包,可以在根目錄執行下列指令安裝這些包,因為子目錄中找不到包會依次往根目錄找:
npm init //生成package.json
npm i webpack webpack-cli -D //下載webpack包,-D是--save-dev的縮寫,表示是開發時依賴
npm i css-loader style-loader -D //下載兩個loader,都是開發時依賴
進入03-打包樣式資源
目錄,執行webpack
進行打包:
開啟打包生成的built.js
,可以看到index.css
被成功打包了:
在build
目錄下新建index.html
引入生成的打包檔案built.js
:
<script src="./built.js"></script>
隨後使用瀏覽器開啟該檔案,發現樣式發生了變化,原始碼多了style
標籤
這便是style-loader
的作用。
注意:不同型別的檔案要配置不同的loader
來處理;比如為了處理less
檔案,需要webpack.config.js
中的rules
陣列中再增添一個物件:
module: {
rules:[
//匹配並處理css檔案
{
test: /\.css$/,
//處理這些檔案
use: [
'style-loader',
'css-loader'
]
},
//匹配並處理less檔案
{
test: /\.less$/,
use: [
//以style標籤的形式在head標籤中插入樣式
'style-loader',
//將css檔案裝換為js形式
'css-loader',
//將less檔案編譯成css檔案
'less-loader'
]
}
]
}
可以看到處理less
檔案需要三個loader
,注意loader
執行的順序為由下往上,由右往左:
less-loader
:將less
檔案編譯成css
檔案;css-loader
:將css
檔案內容整合到js
中;style-loader
:從js
中找到這些css
程式碼,將它傳入style
標籤,插入head
標籤中;
所以處理less
檔案需要使用三個loader
,注意:使用loader
之前要先進行下載:
npm i less less-loader -D //全域性安裝less和less-loader
注意:最好統一在根目錄下載包,這樣其他子目錄向上查詢時都能找到相應的包,避免重複下載;
安裝完依賴包,並且正確配置package.config.js
之後,執行webpack
指令,就可以成功打包less
檔案了:
七.打包html資源
1.新增基本配置
首先給webpack.config.js
新增基本的配置:
const {resolve} = require('path')
module.exports = {
entry : './src/index.js',
output : {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
//laoder的配置
]
},
plugins: [
//plugins的配置
],
//為了方便除錯,使用不壓縮程式碼的development模式
mode: 'development'
}
打包html
檔案需要使用外掛:
- 使用
loader
:下載 、使用(配置laoder
); - 使用
plugins
: 下載 、引入 、使用;
2.下載和引入外掛
首先下載外掛,同樣採用全域性下載,和開發時依賴:
npm i html-webpack-plugin -D
然後引入外掛:
const HtmlWebpackPlugin = require('html-webpack-plugin')
由於它是建構函式,直接new
一個例項就可以使用了:
plugins: [
//plugins的配置
new HtmlWebpackPlugin()
],
3.打包檔案
我們執行webpack
打包看看有什麼效果:
注意要進入專案目錄
04-打包html資源
再進行打包;
build
目錄下多了一個html
檔案:
開啟該html
檔案:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webpack App</title>
<meta name="viewport" content="width=device-width, initial-scale=1"></head>
<body>
<script src="built.js"></script></body>
</html>
發現自動引入了built.js
檔案,注意,src
目錄下的原始檔index.html
中是沒有引入過built.js
檔案的。
所以,外掛html-webpack-plugin
的作用是,建立一個空的HTML
檔案,自動引入打包輸出的所有資源(包括js/css
);
如果想要打包出有結構的html
檔案,則需要給該外掛傳入一個物件,裡面有模板引數template
:
plugins: [
//plugins的配置
new HtmlWebpackPlugin({
//複製一個/src/index.html檔案,並自動引入打包輸出的所有資源
template: './src/index.html'
})
],
此時再次執行webpack
打包,打包出來的html
檔案就擁有了src
目錄下index.html
檔案的所有結構,也就是原樣複製了一份。
4.完整配置
webpack.config.js
的完整配置:
const {resolve} = require('path')
//引入外掛
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry : './src/index.js',
output : {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
//laoder的配置
]
},
plugins: [
//plugins的配置
new HtmlWebpackPlugin({
//複製一個/src/index.html檔案,並自動引入打包輸出的所有資源
template: './src/index.html'
})
],
//為了方便除錯,使用不壓縮程式碼的development模式
mode: 'development'
}
5.總結
我們在入口檔案index.js
中並沒有引入html
檔案。這個外掛會根據template
屬性指定的路徑去尋找該檔案,並且把該檔案的內容複製進來並輸出。只不過在輸出之前會將打包生成的所有資源都引入到這個複製的html
檔案中。如果是js
檔案,就通過script
標籤引入;如果是css
檔案就通過link
標籤引入;
需要注意的是,千萬不要手動引入這些html
檔案,因為外掛幫我們自動引入了這些檔案,我們再引入就重複了。
注意,與loader
的使用不同,使用外掛時要多一步引入操作;
八.打包圖片資源
專案目錄如下:
在入口檔案index.js
中引入樣式檔案index.less
;index.html
檔案不用引入,該檔案會由外掛自動引入;在樣式檔案index.less
中又引入圖片資源small.png
和big.png
;通過配置webpack.config.js
來打包這些檔案:
1.初始配置
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path:resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader'
]
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}
)
],
mode: 'development'
}
此時沒有新增處理圖片的loader
,執行webpack
時會出現錯誤:
注意:webpack
不能識別資源的時候,第一時間就要想到loader
。新增以下loader
處理圖片資源:
rules: [
//匹配並處理圖片
{
test: /\.(jpg|png|gif)$/,
//只使用一個loader可以不用use陣列
loader: 'url-loader',
options: {
limit: 8 * 1024
}
}
]
只使用一個loader
時可以不使用use
陣列;使用多個loader
時需要使用use
陣列。還可以通過option
物件進行引數的配置;
關於options
物件中的limit
屬性:
- 規定圖片大小小於
8kb
,就會被base64
處理。這樣的好處是:減少伺服器的請求數量,減輕伺服器壓力;缺點是:圖片體積會增大,導致檔案請求速度更慢; - 所以就需要找到一個平衡點,我們規定
8~12KB
以下的圖片進行base64
處理。所以上面limit
中配置的8
就表示小於等於8KB
的圖片都進行base64
處理。開發中可根據實際需要適當調整這個閾值;
2.打包圖片資源
注意,需要下載兩個loader
:url-loader
和file-loader
,因為前者依賴於後者;
回到根目錄下,下載這兩個包,依舊是開發時依賴:
npm i url-loader file-loader -D
安裝完成後執行webpack
進行打包:
可以看到,成功地對兩張圖片進行了打包。
檢視打包檔案輸出的目錄build
,發現少了一張圖片:
開啟built.js
,可以發現,小於8KB
的small.png
被裝換為了base64
編碼,所以會少了一張圖片:
我們再開啟打包出來的index.html
檔案,可以看到,small.png
也正常顯示了;
開啟除錯工具,選中small/png
,可以看到該圖片被轉換成了base64
編碼:
3.打包html檔案中的圖片
上面是通過樣式檔案index.less
引入的圖片,那麼通過src
目錄下的index.html
檔案引入圖片能被正常打包嗎:
<img src="./big.png" alt="">
執行webpack
再次打包,發現並沒有報錯:
但是,檢視輸出資料夾下的index.html
檔案,發現圖片的引入路徑並沒有發生變化:
這顯然是不對的,打包過後,輸出資料夾build
中顯然不會存在big.png
。因此得出結論:url-loader
不能處理html
中的圖片。
我們可以新增一個html-loader
來處理:
rules: [
{
test: /\.html$/,
loader: 'html-loader'
}
]
不能對該loader
顧名思義,它是專門處理html
中的img
圖片的,負責引入img
從而能被url-loader
進行處理;
再次提醒:凡是使用到loader
都是要先下載;
npm i html-loader -D
隨後打包出來的index.html
中的圖片路徑就是正確的了:
較低版本的webpack
可能會出現解析問題:打包出來的圖片路徑為[object module]
。這是由於url-loader
採用的是es6
模組化解析,而html-loader
引入圖片時採用的是commonjs
而發生了衝突;
解決方法為:通過設定esModule: false
,來關閉url-loader
的es6
模組化,使用commonjs
解析:
rules: [
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
esModule: false
}
}
]
注意到,打包過後的圖片是一串雜湊值,比較長,可以通過name
屬性重新命名打包後的圖片:
rules: [
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]'
}
}
]
-
[hash:10]
表示:取圖片雜湊值的前十位; -
[ext]
表示:取檔案的原副檔名;
再次打包,可以看見另外生成了雜湊值只有十位的圖片:
還有一個細節:在樣式index.less
中我們引入了三張圖片,兩張是相同的;webpack
不會打包出三張圖片,它會進行識別,不打包重複的檔案,這也是webpack
的優點之一;
4.完整配置
至此webpack
的完整配置為:
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path:resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.less$/,
//要使用多個loader時使用use陣列
use: [
'style-loader',
'css-loader',
'less-loader'
]
},
//匹配並處理圖片
{
test: /\.(jpg|png|gif)$/,
//需要下載url-loader 和 file-loader
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]'
}
},
{
test: /\.html$/,
//該loader專門處理html中的img圖片
loader: 'html-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}
)
],
mode: 'development'
}
九.打包其他資源
1.安裝file-loader
所謂其他資源,指的是自定義的資源,可能是字型檔案,指令碼檔案等。我們希望這些檔案不需要進行壓縮或其他處理,直接原封不動地打包到輸出資料夾中。
比如打包iconfont
檔案,需要採用專門的laoder
進行處理:
module: {
rules: [
{
exclude: /\.(css|js|html)$/,
loader: 'file-loader',
}
]
}
打包其他資源(除了html/css/js
以外的資源),可以使用exclude
屬性排除其他資源。使用file-loader
進行打包。
使用前需要在根目錄安裝這個loader
:
npm i file-loader -D
2.打包
隨後對src
檔案下的iconfont
檔案和其他檔案進行打包:
打包出來的檔案目錄為:
執行其中的index.html
可以正常看到iconfont
,說明打包成功:
同樣也可以新增option
屬性,重新命名打包過後的檔名:
重新打包成功生成重新命名後的檔案:
3.完整配置
此時webpack
的完整配置為:
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules:[
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
//打包其他資源(除了html/css/js以外的資源),可以使用exclude屬性排除其他資源
{
exclude: /\.(css|js|html)$/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development'
}
十.devServer
當我們每次給src
目錄下新增檔案和內容時,都需要重新打包。這顯然很麻煩,webpack
也考慮到這一點,因此提供了devServer
的功能。
1.安裝devServer
開發伺服器 devServer
:用於自動化(自動編譯,自動開啟瀏覽器,自動重新整理瀏覽器);
特點:只會在記憶體中編譯打包,不會有任何輸出;
啟動devServer
指令為:webpack-dev-server
;
首先,需要在根目錄下載該包,同樣採用開發時依賴:
npm i webpack-dev-server -D
2.配置devServer
在webpack.config.js
中配置devServer
,它與五個基本配置是同一等級的:
devServer: {
//代表執行時的目錄(打包後目錄),也是使用字串拼接絕對目錄
contentBase: resolve(__dirname, 'build'),
//該引數表示啟動gzip壓縮
compress: true,
//埠號
port: 8081,
//自動開啟瀏覽器
open: true
}
3.開啟devServer
開啟時新增npx
,找到該指令:
npx webpack-dev-server
成功執行後,可通過http://localhost:8081
埠檢視執行結果:
能夠成功顯示:
此時只要改變src
目錄下的檔案,都會進行自動編譯。這樣不用頻繁輸入webpack
重新打包就可以實時看到變化了。
一旦開啟devServer
它就會一直執行,可以通過ctrl+ c
關閉它:
也可以通過改變引數port
和open
來設定埠和是否自動開啟瀏覽器,注意:只要重新配置了webpack.config.js
就需要重啟devServer
:
npx webpack-dev-server
我們將打包輸出目錄build
下的檔案都刪除,再次執行上述指令。打包過後,build
目錄下並不會生成任何檔案。這就驗證了:devServer
只會在記憶體中編譯打包,不會有任何輸出的特點。
十一.開發環境的基本配置
通過前面知識的學習,我們學會了打包樣式資源,html
資源,圖片資源和其他資源。以及學會了通過devServer
開啟熱更新。現在我們便可以開始配置基本的開發環境了;
1.開發環境基本配置
webpack.config.js
的配置如下:
/**
* 開發環境配置
*/
const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports ={
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
//loader的配置
//處理less資源
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
//處理css資源
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
//處理圖片資源
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
// esModule: false
}
},
//處理html中圖片的引入
{
test: /\.html$/,
loader: 'html-loader'
},
//處理其他資源
{
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]'
}
}
]
},
plugins: [
//plugins的配置
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true
}
}
2.優化輸出目錄
首先我們整理一下src
目錄下的檔案,並修改webpack.config.js
中的路徑,隨後使用webpack
打包。打包出來的build
目錄下的檔案還是很亂:
如果想要打包出來的build
目錄下的檔案能與src
目錄結構相同呢?
可以給webpack.config.js
中的output
中的filename
新增字首,這樣打包過後的檔案會自動建立這個指定的目錄:
在處理圖片時,只需要新增outputPath
屬性就能指定打包後的目錄結構了:
重新打包,圖片檔案被打包進了imgs
目錄:
同理,可以在處理其他資源的loader
的option
屬性中新增outputPath
屬性指定打包後的目錄結構:
注意了,主要的打包輸出目錄是由五大配置之一的output
中的path
屬性決定的,上面的這一屬性為build
。所以,之後使用outputPath
指定的路徑都要拼接在build
後面。最後形成完整的路徑。
十二.單獨打包CSS檔案
從現在開始我們將部署生成壞境;
常見錯誤:
ERROR in Entry module not found: Error: Can't resolve 'D:\webpack5\09-提取css成單獨檔案\src\index.html' in 'D:\webpack5\09-提取css成單獨檔案'
上面的這種錯誤大部分都是由於相關檔案路徑錯誤導致的。
1.安裝mini-css-extract-plugin
可通過以下指令安裝mini-css-extract-plugin
外掛,提取打包檔案built.js
中的css
檔案:
npm i mini-css-extract-plugin -D
- 第一步:為了使用該外掛:首先引入該外掛:
const miniCssExtractPlugin = require('mini-css-extract-plugin')
- 第二步:然後在
plugins
屬性中通過建立例項的方式使用:
plugins: [
new miniCssExtractPlugin()
],
- 第三步:配合相關的
loader
:
rules: [
{
test: /\.css$/,
use: [
// 'style-loader':建立style標籤,將樣式放入
//使用以下loader取代style-loader,作用:提取js中的css成單獨檔案
miniCssExtractPlugin.loader,
//將css檔案整合到js檔案中
'css-loader',
]
}
]
2.單獨打包css檔案
完成配置後,再次執行webpack
打包檔案,沒有報錯,檢視生成的打包輸出檔案目錄build
,發現其中多了一個main.css
檔案:
這個檔案就是提取出來的css
檔案;
也可以在第二步中傳入引數進行一些配置:比如對輸出路徑和檔案進行命名:
plugins: [
new miniCssExtractPlugin({
//對輸出的css檔案進行重新命名
filename: 'css/built.css'
})
],
隨後,刪除原有打包出來的build
目錄,再次執行webpack
,重新生成打包目錄build
:
可以看到,成功地設定了css
檔案存放的目錄和檔名。
3.優點
這種做法的好處是:
-
提取後的
css
檔案是通過link
標籤引入的,這樣就避免了閃屏現象: -
並且
css
檔案與js
檔案分離開了,所以js
檔案體積縮小了,解析速度也會更好一些。
十三.CSS相容性處理
css3
新增了許多的樣式屬性,但是並不是每一個瀏覽器都完全支援了這些樣式屬性,因此我們需要對某些樣式屬性進行相容性處理,通過webpack
可以很容易地實現這一點;
需要使用postcss-loader
和postcss-preset-env
外掛;
這個外掛能幫助postcss
識別某些環境,而載入指定的配置,從而使相容性做到精確到某一個瀏覽器版本;
1.安裝依賴
首先,全域性下載這個兩包,依舊是開發時依賴:
npm i postcss-loader postcss-preset-env -D
2.基本配置
配置有兩種寫法:
-
第一種:使用
loader
預設配置。直接寫:'postcss-loader'
這種方法不能修改配置,如果想要修改配置,採用第二種寫法;
-
第二種:修改配置。寫成物件的形式,在
options
屬性中修改配置:{ loader: 'postcss-loader', options: { //固定寫法 ident: 'postcss', plugins: () => [ //postcss的外掛 require('postcss-preset-env')() ] } }
該外掛的作用為:幫助postcss
找到package.json
中browserslist
裡面的配置,通過配置載入指定的css
相容性樣式。
此時需要在根目錄的package.json
中新增相關配置:
在其中新增下列配置:
"browserslist": {
//這是開發環境
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
//生產環境,
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
}
browserslist
物件中可以寫兩個引數:
development
代表開發環境的配置,值為陣列;production
代表生產環境的配置;
關於
browserslist
的配置github
上又很詳盡的介紹。
"last 1 chrome version"
代表相容chrome
最近的一個版本;其他瀏覽器也是這個格式。如果是開發模式,我們不需要配置太多瀏覽器,只需要配置除錯用到的瀏覽器就夠了;
但是,生產環境就要多寫一點;他們表示:
//表示相容大於99.8%的瀏覽器
">0.2%",
//不要已經死的瀏覽器,比如IE10
"not dead",
//還有所有的op_mini瀏覽器
"not op_mini all"
這樣就做到了絕大多數瀏覽器的相容了。
打包時,預設看生產環境,與webpack.config.js
中的mode
是沒關係的;如果想要使用開發環境,需要設定node
環境變數:
process.env.NODE_ENV = 'development'
打包前,webpack
的配置是這樣的:
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const miniCssExtractPlugin = require('mini-css-extract-plugin')
//設定nodejs環境變數
process.env.NODE_ENV = 'development'
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [
miniCssExtractPlugin.loader,
'css-loader',
/**
* css相容性處理:postcss --> postcss-loader postcss-preset-env
*/
{
loader: 'postcss-loader',
options: {
//固定寫法
ident: 'postcss',
plugins: () => [
//postcss的外掛
require('postcss-preset-env')()
]
}
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new miniCssExtractPlugin({
filename: 'css/built.css'
})
],
mode: 'development'
}
打包前,我們在css
檔案中新增兩個有相容性問題的新樣式:
使用webpack
打包後,打包後的樣式發生了變化:
說明相容性處理成功了。
這樣,我們就能專心使用各種樣式,而不需要考慮相容性問題了,webpack
會自動幫我們做好這方面的工作。
十四.壓縮CSS
這節介紹使用外掛來完成css
的壓縮,可以發現有的時候使用loader
,有的時候使用外掛;主要的區別在於:loader
處理的東西比較少於專一,外掛處理的東西比較大;
1.下載外掛
使用的外掛為:optimize-css-assets-webpack-plugin
,首先全域性下載:
npm i optimize-css-assets-webpack-plugin -D
使用外掛的時候,要先在webpack.config.js
中引入:
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
2.配置外掛
然後在plugins
中使用:
plugins: [
//壓縮css
new OptimizeCssAssetsWebpackPlugin()
]
只要引用就可以了,不需要額外配置,按照預設的配置已經可以將css
檔案壓縮了。
然後使用webpack
打包,輸出的css
檔案被壓縮成了一行:
在樣式較多的情況下,壓縮是很有必要的。能夠請求速度更快,載入樣式更快,這樣就能更快地渲染,使用者體驗也就更好。
十五.js語法檢查
1.安裝依賴
語法檢查,不僅可以檢查語法錯誤,還可以設定程式碼風格,讓整個團隊的程式碼風格都保持一致;專門做語法檢查的如eslint-loader
它依賴於eslint
庫,所以首先要在全域性下載這兩個包:
npm i eslint-loader eslint -D
2.配置eslint
注意:語法檢查只會檢查自己寫的程式碼,第三方庫不會也不用檢查;所以需要使用exclude
來排除第三方庫:
rules: [
{
test: /\.js$/,
loader: 'eslint-loader',
exclude: /node_modules/,
options: {}
}
]
此外還需要我們在package.json
中的eslintConfig
中配置檢查規則。關於規則,推薦使用airbnb
。在github
上的這個專案會詳細告訴你,應該如何配置這些規則:
npm
上有兩個將airbnb
規則應用到eslint
中的包:
- 不包含
React
相關內容的外掛也分為兩種:eslint-config-airbnb-base
:包含ES6
及以上的內容;開發中一般都會使用ES6
語法,所以使用上面這個外掛;eslint-config-airbnb-base/legacy
:包含ES5
和ES5
以下;
eslint-config-airbnb
:包含了React
的內容;
所以,我們選擇使用eslint-config-airbnb-base
,注意,該外掛依賴於eslint
和eslint-plugin-import
兩個包;上面已經下載過eslint
了,這裡只需要下載其他兩個包:
npm i eslint-config-airbnb-base eslint-plugin-import -D
package.json
中的具體配置為:
"eslintConfig": {
//通過extends欄位繼承airbnb-base就可以了
"extends": "airbnb-base"
}
隨後在入口檔案index.js
中故意寫一寫不規範的程式碼:不加空格,分號等:
然後執行webpack
指令,進行打包,發現出現了很多錯誤:
雖然可以根據錯誤提示,在airbnb
文件中查詢修改意見。但是手動修改仍然顯得麻煩,可以在webpack.config.js
的eslint-loader
中的options
裡新增fix
屬性配置,讓它自動修復:
rules: [
{
test: /\.js$/,
loader: 'eslint-loader',
exclude: /node_modules/,
options: {
//自動修復eslint的錯誤
fix: true
}
}
]
再次打包,通過自動修復就不會再顯示錯誤資訊了:
可以看到,雖然沒有顯示錯誤,但是有一個警告:不建議使用console
;在開發中可以使用console
進行除錯,但是真正的程式碼中不應該含有它;此時可以通過新增下列註釋,讓eslint
忽略下一行程式碼:
//下一行eslint所有規則都失效(即下一個行不進行eslint檢查)
//eslint-disable-next-line
console.log('123')
一個坑:安裝包的時候,最好按照依賴關係來安裝;即首先安裝下層的包,再安裝上層的包,否則可能會出現意想不到的錯誤;可以通過檢視
package.json
中的配置來觀察相應的包是否成功安裝了;
十六.js相容性處理
我們將入口檔案index.js
內容改為箭頭函式:
直接執行webpack
打包,打包出來的檔案中,該部分並沒有做相容性處理:
這樣的話,不支援ES6
的瀏覽器就會出錯;這時候就需要使用babel-loader
來進行js
的相容性處理了;
1.基本配置
webpack.config.js
中的配置為:
rules: [
/**
* js相容性處理:babel-loader @babel/preset-env @babel/core
*/
{
test: /\.js/,
//排除不需要js相容性處理的檔案
exclude: /node_modules/,
loader: 'babel-loader',
options: {
//預設:指示babel做怎樣的相容性處理,一般使用@babel/preset-env就可以了
presets: ['@babel/preset-env']
}
}
]
注意:凡是涉及到相容性處理都要使用exclude
將不需要進行相容性處理的檔案排除;否則會出錯,如下圖所示,不排除的話,依賴檔案node_module
中的js
都報錯了:
2.使用babel-loader
方法一:使用babel-loader
進行基本的相容性處理;
首先需要在全域性下載babel-loader
、@babel/preset-env
和@babel/core
:
npm i babel-loader @babel/preset-env @babel/core -D
隨後再次執行webpack
打包,這次打包出來的js
檔案中,ES6
的語法全都轉換為了ES5
的語法了,也就是做了相容性處理:
babel-loader
存在的問題
只能轉換一些基本語法,如不能轉換promise
;比如在入口檔案index.js
中新增Promise
物件:
直接打包是不會被相容性處理的。
解決方案:通過@babel/polyfill
對全部js
進行相容性處理;
3.使用@babel.polyfill
方法二:使用@babel.polyfill
,對全部js
進行相容性處理;
首先還是全域性安裝@babel/polyfill
:
npm i @babel/polyfill -D
它不是laoder
或外掛,只需要在入口檔案index.js
中通過import
引入就可以使用了:
import '@babel/polyfill';
此時再次執行webpack
打包,會發現打包出來的js
檔案變得非常大:
引入@babel/polyfill
前:built.js
只有不到4KB
:
引入後,變成了441KB
:
這是因為只要使用了@babel/polyfill
,那麼它就會將js
檔案涉及到的所有相容性問題都解決掉。
此時入口檔案中的Promise
就被相容性處理了:
@babel/polyfill
存在的問題
我只要解決部分相容性問題,但是將所有相容性程式碼全部引入,體積太大了;
解決方法:需要做相容性處理就做:按需載入(只加載指定的相容性庫),通過core-js
實現;
4.使用core-js
方法三:使用core-js
,實現按需載入。
首先全域性下載core-js
:
npm i core-js -D
隨後在webpack.config.js
中進行相應的配置:
rules: [
/**
* js相容性處理:babel-loader
*/
{
test: /\.js/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
//預設:指示babel做怎樣的相容性處理,一般使用@babel/preset-env就可以了
presets: [
[
'@babel/preset-env',
{
//按需載入
useBuiltIns: 'usage',
//指定corejs版本
corejs: {
version: 3
},
//指定相容性做到那個版本瀏覽器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
}
}
]
需要注意的是,使用第三種方法就不能使用第二種方法了,所以要在入口檔案index.js
中不再引入@babel/polyfill
:
隨後再次執行webpack
進行打包,會發現,打包出來的js
檔案相比於使用第二種方法時的441KB
,縮減到了104KB
:
結合第一種和第三種方法就能實現對所有的js
程式碼進行相容性處理;
5.完整配置
最後整個webpack.config.js
的配置如下:
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
/**
* js相容性處理:babel-loader
*/
{
test: /\.js/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
//預設:指示babel做怎樣的相容性處理,一般使用@babel/preset-env就可以了
presets: [
[
'@babel/preset-env',
{
//按需載入
useBuiltIns: 'usage',
//指定corejs版本
corejs: {
version: 3
},
//指定相容性做到那個版本瀏覽器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development'
}
十七.壓縮js和html
1.壓縮js程式碼
只需要就webpack.config.js
中的模式mode
改為生產模式,就會自動壓縮js
程式碼:
mode: 'production'
內部實現是通過外掛UglifyJsPlugin
;
執行webpack
進行打包,打包出來的js
檔案被壓縮成了一行:
不需要做html
的相容性處理,因為標籤認識就認識,不認識就不認識,不能轉換;只需要對html
進行壓縮處理即可;
2.壓縮html程式碼
只需要在配置檔案webpack.config.js
中給HtmlWebpackPlugin
外掛新增minify
屬性即可:
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
//壓縮html程式碼
minify: {
//移除空格
collapseWhitespace: true,
//移除註釋
removeComments: true
}
})
],
執行webpack
進行打包,打包出來的html
檔案被去除了所有空格並移除了註釋:
十八.生產環境基本配置
學習了前面的基本配置,現在可以彙總起來配置基本的生產環境了。
1.配置的複用
如相同的配置可以抽離出來封裝一個複用的loader
,比如css
和less
的相容性處理,唯一的不同點是多了個less-loader
將less
轉換為css
,所以其餘部分可以複用;
未複用前,存在大量的重複程式碼:
rules: [
//處理css檔案
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
//css相容性處理
{
//還需要在webpack.json中定義browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
//指定外掛
plugins: () => [
require('postcss-preset-env')()
]
}
}
]
},
//處理less檔案
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
//css相容性處理
{
//還需要在webpack.json中定義browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
//指定外掛
plugins: () => [
require('postcss-preset-env')()
]
}
},
//由於use陣列執行順序為從下往上(注意執行順序),經過less-loader轉換為css後再進行相容性處理
'less-loader'
]
}
]
複用後:
//複用loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
//css相容性處理
{
//還需要在webpack.json中定義browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
//指定外掛
plugins: () => [
require('postcss-preset-env')()
]
}
}
]
//...
rules: [
//處理css檔案
{
test: /\.css$/,
//通過擴充套件運算子使用封裝的loader
use: [...commonCssLoader]
},
//處理less檔案
{
test: /\.less$/,
//由於use陣列執行順序為從下往上(注意執行順序),經過less-loader轉換為css後再進行相容性處理
use: [...commonCssLoader,'less-loader']
}
]
//...
所以,當遇到重複程式碼的時候一定要考慮將重複程式碼抽離封裝,達到複用的效果;
2.生產環境基本配置
完整的webpack.config.js
配置如下:
//0.引入path模組解決路徑問題
const { resolve } = require('path');
//1.引入外掛提取和相容性處理css檔案
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
//2.引入壓縮css外掛
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
//3.引入處理html圖片引入的外掛
const HtmlWebpackPlugin = require('html-webpack-plugin');
//複用loader
const commonCssLoader = [
//這一行作用為將css檔案抽離出來
MiniCssExtractPlugin.loader,
'css-loader',
//css相容性處理
{
//還需要在webpack.json中定義browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
//指定外掛
plugins: () => [
require('postcss-preset-env')()
]
}
}
]
//package.json中的browserslist預設使用開發環境,若使用生產環境需要定義nodejs環境變數
process.env.NODE_ENV = 'production';
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
//1.處理css檔案
{
test: /\.css$/,
//通過擴充套件運算子使用封裝的loader
use: [...commonCssLoader]
},
//2.處理less檔案
{
test: /\.less$/,
//由於use陣列執行順序為從下往上(注意執行順序),經過less-loader轉換為css後再進行相容性處理
use: [...commonCssLoader,'less-loader']
},
/**
* 正常來說:一個檔案只能被一個loader處理
* 當一個檔案要被多個loader處理時,那麼一定要指定loader的執行順序。
* 比如先執行eslint-loader,再執行babel-loader。這是因為一旦語法出錯進行相容性處理就沒意義了。
* 如何新增順序:enforce: 'pre'
*/
//3.進行語法檢查
{
//在package.json中配置eslintConfig指定檢查規則 --> airbnb
test: /\.js$/,
//配出不需要語法檢查的檔案
exclude: /node_modules/,
//優先執行
enforce: 'pre',
loader: 'eslint-loader',
options: {
//自動修復錯誤
fix: true
}
},
//4.js相容性處理
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
//新增預設,告訴babel以哪種方式進行相容性處理
presets: [
//由於要使用方法一和三,所以使用陣列儲存
[
//簡單處理
'@babel/preset-env',
//按需載入
{
useBuiltIns: 'usage',
//指定corejs版本
corejs: {version: 3},
//指定瀏覽器版本
targets: {
chrome: '60',
firefox: '50'
}
}
]
]
}
},
//5.處理圖片
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
//通過base64編碼優化
limit: 8 * 1024,
//重新命名打包後的圖片
name: '[hash:10].[ext]',
//指定輸出路徑
outputPath: 'imgs'
}
},
//6.處理html中的圖片
{
test: /\.html$/,
loader: 'html-loader',
},
//8.處理其他檔案
{
//排除其他檔案
//正則中不加$表示只要匹配到這些詞就行,是不是字尾都可以
exclude: /\.(js|css|less|html|jpg|png|gif)/,
//原封不動地輸出檔案
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
},
plugins: [
//相容性處理css並單獨抽離css檔案
new MiniCssExtractPlugin({
//設定輸出路徑
filename: 'css/built.css',
}),
//壓縮css
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
//指定html模板
template: './src/index.html',
//7.壓縮html檔案
minify: {
//移除空格
collapseWhitespace: true,
//移除註釋
removeComments: true
}
})
],
//改為production模式自動壓縮js檔案
mode: 'production'
}