本篇文章將介紹一些webpack
的進階用法,演示內容繼承自上一篇文章的內容,所以沒看過上一篇文章的建議先學習上一篇內容再閱讀此篇內容,會更有利於此篇的學習~
檔案指紋指的是打包輸出的檔名字尾,一般用來做版本管理、快取等
webpack的指紋策略有三種:hash
、chunkhash
、contenthash
,它們之間最主要的區別就是每種hash影響的範圍不同。
webpack提供預留位置用於將特定資訊附加在打包輸出的檔案上
名稱 | 含義 |
---|---|
[ext] | 資源字尾名 |
[id] | 檔案識別符號 |
[name] | 檔名稱 |
[path] | 檔案的相對路徑 |
[folder] | 檔案所在的資料夾 |
[hash] | 模組識別符號的 hash,預設是 md5 生成 |
[chunkhash] | chunk 內容的 hash,預設是 md5 生成 |
[contenthash] | 檔案內容的 hash,預設是 md5 生成 |
[query] | 檔案的 query,例如,檔名 ? 後面的字串 |
[emoji] | 一個隨機的指代檔案內容的 emoji |
我們可以使用特定的語法,對 hash
、 chunkhash
、contenthash
進行切片:[chunkhash:4]
,像 8c4cbfdb91ff93f3f3c5
這樣的雜湊會最後會變為 8c4c
。
與整個專案的構建有關,只要專案內檔案有修改,整個專案構建的hash值就會改變
我們使用多入口打包來體驗一下:
// webpack.config.js
module.exports = {
entry: {
main: './src/main.js',
index: './src/index.js'
},
output: {
filename: '[name].[hash:6].js',
path: __dirname + '/dist',
clean: true
},
// ...
}
此時我們使用了預留位置來設定檔案指紋[name].[hash:6].js
代表的是檔名+6位hash
此時我們執行npm run build
,看打包出來的內容如下:
此時兩個js檔案的hash
都是207495
我們修改一下index.js
的內容,再打包一次
我們會發現此時兩個js檔案的hash
都變成了9f0e2d
chunkhash 是和 webpack 打包的模組相關,每一個 entry 作為一個模組,會產生不同的 Chunkhash 值,檔案改變時只會影響當前chunk組的hash值
我們再來看看chunkhash
// webpack.config.js
module.exports = {
entry: {
main: './src/main.js',
index: './src/index.js'
},
output: {
filename: '[name].[chunkhash:6].js',
path: __dirname + '/dist',
clean: true
},
// ...
}
還是延用上面的例子,這次我們只修改main.js
檔案內容
修改前兩個檔案的hash值如下:
修改後:
此時只有main.js
的打包產物的hash發生了變化
contenthash 是和根據檔案內容相關,單個檔案發生變化,只會引起此檔案的hash值
這裡我們使用miniCssExtractPlugin
將CSS內容提取成檔案,併為它設定contenthash
// webpack.config.js
const miniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: {
main: './src/main.js',
index: './src/index.js'
},
output: {
filename: '[name].[contenthash:6].js',
path: __dirname + '/dist',
clean: true
},
mudole: {
rules: [
{
test: /\.css$/,
use: [miniCssExtractPlugin.loader, 'css-loader']
},
// ...
]
},
plugins: [
// ...
new miniCssExtractPlugin({
filename: 'css/[name].[contenthash:6].css'
}),
]
// ...
}
然後打包看一下此時的hash:
我們修改index.css
內容再打包一次
此時只有index.css
的打包產物hash值發生了變化。
根據不同的檔案型別一般選擇不同的檔案指紋策略,通常情況下:
目前最成熟的JavaScript程式碼壓縮工具是UglifyJS
,它能夠分析JavaScript語法樹,理解程式碼含義,從而能做到諸如去掉無效程式碼、去掉紀錄檔輸出程式碼、縮短變數名等優化。但很遺憾的是UglifyJS
不再維護,並且它不支援 ES6 + 。
現在推薦使用的是Terser
,它在 UglifyJS 基礎上增加了 ES6 語法支援,並重構程式碼解析、壓縮演演算法,使得執行效率與壓縮率都有較大提升,並且Webpack5.0 後預設使用 Terser 作為 JavaScript 程式碼壓縮器
簡單實用:
// webpack.config.js
module.exports = {
//...
optimization: {
minimize: true
}
}
需要注意的是在生產模式中構建時,Terser壓縮是預設開啟的
當然它也允許你通過提供一個或多個客製化過的TerserPlugin範例,覆蓋預設的壓縮工具,實現更精細的壓縮功能
// webpack.config.js
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
//...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
// https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
},
})
]
}
}
在Webpack4中 預設使用 uglifyjs-webpack-plugin
壓縮程式碼,也可以通過 minimizer
陣列替換為 Terser 外掛
CSS程式碼同樣也可以使用webpack來進行壓縮,比較常見的CSS壓縮工具有:cssnano
、css-minimizer-webpack-plugin
對於 webpack5 或更高版本,官方推薦使用 CssMinimizerWebpackPlugin
,該外掛是使用 cssnano
優化和壓縮 CSS,支援快取和並行模式下執行。
安裝:
npm i css-minimizer-webpack-plugin
設定:
// webpack.config.js
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 用壓縮css
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 用來提取css成單獨的檔案
module.exports = {
//...
module: {
rules: [
{
test: /.css$/,
// 注意,MiniCssExtractPlugin.loader 與 style-loader不能同時使用
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
optimization: {
minimize: true,
minimizer: [
// Webpack5 之後,約定使用 '...' 字面量保留預設 minimizer 設定
"...",
new CssMinimizerPlugin(),
],
},
plugins: [new MiniCssExtractPlugin()],
};
⚠️這裡需要注意的是需要使用 mini-css-extract-plugin
將 CSS 程式碼抽取為單獨的 CSS 產物檔案,這樣才能命中 css-minimizer-webpack-plugin
預設的 test
邏輯。
我們之前使用的html-webpack-plugin
,它除了可以生成html模版,也可以用來對html進行壓縮。
htmlWebpackPlugin常見引數
template
:模板的路徑,預設會去尋找 src/index.ejs
是否存在。
filename
:輸出檔案的名稱,預設為 index.html
。
inject
:是否將資源注入到模版中,預設為 true
。
minify
:壓縮引數。在生產模式下(production
),預設為 true
;否則,預設為false
。
// webpack.config.js
module.exports = {
// ...
plugins: [
// ...
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html',
minify: true
}),
]
}
生成的 HTML 將使用 html-minifier-terser
和以下選項進行壓縮,所以它實際上的壓縮功能其實是html-minifier-terser
來實現的,更多設定可以檢視這個工具檔案
{
collapseWhitespace: true,
keepClosingSlash: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
}
經過上面這些設定後,我發現了一個奇怪的問題,那就是每個bundle
產物都多了一個同名的LICENSE.txt
檔案,開啟一看裡面都是一些註釋內容。
為什麼會生成這些檔案,帶著疑惑我去翻了下官方檔案,Webpack5 預設壓縮程式碼工具為terser-webpack-plugin
,那就先從它入手吧。
在它的設定中找到了extractComments
引數,預設值為true
,表示將註釋剝離到單獨的檔案中。
如果我們不想要,直接關掉該設定就行了
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new cssMinimizerPlugin(),
new terserPlugin({
extractComments: false, // 關閉註釋剝離功能
}),
'...'
]
},
}
前端最頭疼的問題莫過於處理相容性,因為前端的執行環境並不固定,可以在各種瀏覽器以及各種webview中執行,並且每個瀏覽器廠商對CSS的寫法也各不相同,這就勢必會導致出現一些問題。
比如為了相容各種瀏覽器核心,圓角屬性應該這樣寫:
.container {
-moz-border-radius: 16px;
-webkit-border-radius: 16px;
-o-border-radius: 16px;
border-radius: 16px;
}
試想一下如果在開發中需要你這樣寫,那是不是太不合理了?
我們一般都會通過webpack設定外掛來幫我們解決這個問題,處理CSS我們首先會想到postcss
,沒錯webpack也有使用postcss處理CSS的loader --- postcss-loader
,然後我們還需要使用postcss
的外掛autoprefixer
來幫我們自動新增瀏覽器字首。
安裝:
npm i postcss postcss-loader autoprefixer
修改設定:
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
//...
{
test: /\.css$/,
use: [miniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: ['autoprefixer']
}
}
}]
},
]
}
//...
}
⚠️這裡需要注意的是,如果你想自定義轉換的規則,最好是將 autoprefixer 的 browsers
選項替換為 browserslist
設定。在 package.json
或。Browserslistrc
檔案。使用 browsers
選項可能導致錯誤,並且browserslist
設定可以用於 babel、 autoprefixer、 postcss-norize 等工具。
比如package.json中設定browserslist
:
// package.json
{
//...
"browserslist": [
"last 10 Chrome versions",
"last 5 Firefox versions",
"Safari >= 6",
"ie> 8"
]
}
此時我們打包的CSS的產物就會自動新增瀏覽器字首
假如我們需要在html中參照一些不需要打包處理的資源,比如下面這種情況
在index.html
中引入了一些紀錄檔的工具函數,這時候我們直接跑起來會發現這個檔案直接404了,這是怎麼回事?
首先我們寫的路徑肯定是沒問題的,問題在於我們打包後這個utils
檔案肯定是不在這個位置了,所以會報404
所以這裡我們需要使用copy-webpack-plugin
將檔案拷貝至dist
目錄下
// webpack.config.js
const copyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
// ...
plugins: [
new copyWebpackPlugin({
patterns: [
{from: 'module', to: __dirname + '/dist/module/'}
]
}),
]
}
此時再打包,我們會發現dist
目錄下已經有了module/utils.js
,並且頁面也不會再報404了
SourceMap 就是一個資訊檔案,裡面儲存著程式碼的位置資訊。這種檔案主要用於開發偵錯,現在程式碼都會經過壓縮混淆,這樣報錯提示會很難定位程式碼。通過 SourceMap 能快速定位到原始碼,並進行偵錯。
比如我們沒有開啟sourcemap
,然後開發過程中報錯了,它的報錯資訊是這樣的:
定位過去是打包後的內容,這樣的話對我們排查報錯非常不方便。
當我們開啟sourcemap
,再來看看這個同樣的報錯是怎樣的:
// webpack.config.js
module.exports = {
// ...
devtool: 'eval-cheap-module-source-map',
}
此時的報錯指向就非常清晰了~
devtool的值有20多種,並且都是由以下七種關鍵字的一個或多個組成
eval
關鍵字當 devtool
值包含 eval
時,生成的模組程式碼會被包裹進一段 eval
函數中,且模組的 Sourcemap 資訊通過 //# sourceURL
直接掛載在模組程式碼內
source-map
關鍵字當 devtool
包含 source-map
時,Webpack 才會生成 Sourcemap 內容
cheap
關鍵字當 devtool
包含 cheap
時,生成的 Sourcemap 內容會拋棄列維度的資訊,這就意味著瀏覽器只能對映到程式碼行維度
module
關鍵字module
關鍵字只在 cheap
場景下生效,例如 cheap-module-source-map
、eval-cheap-module-source-map
。當 devtool
包含 cheap
時,Webpack 根據 module
關鍵字判斷按 loader 聯調處理結果作為 source,還是按處理之前的程式碼作為 source
nosources
關鍵字當 devtool
包含 nosources
時,生成的 Sourcemap 內容中不包含原始碼內容 —— 即 sourcesContent
欄位
inline
關鍵字當 devtool
包含 inline
時,Webpack 會將 Sourcemap 內容編碼為 Base64 DataURL,直接追加到產物檔案中
hidden
關鍵字通常,產物中必須攜帶 //# sourceMappingURL=
指令,瀏覽器才能正確找到 Sourcemap 檔案,當 devtool
包含 hidden
時,編譯產物中不包含 //# sourceMappingURL=
指令
devtool的值以及各自的功能可以在webpack檔案上檢視
eval
:速度極快,但只能看到原始檔案結構,看不到打包前的程式碼內容;cheap-eval-source-map
:速度比較快,可以看到打包前的程式碼內容,但看不到 loader 處理之前的原始碼;cheap-module-eval-source-map
:速度比較快,可以看到 loader 處理之前的原始碼,不過定位不到列級別;eval-source-map
:初次編譯較慢,但定位精度最高;source-map
:資訊最完整,但安全性最低,外部使用者可輕易獲取到壓縮、混淆之前的原始碼,慎重使用;hidden-source-map
:資訊較完整,安全性較低,外部使用者獲取到 .map
檔案地址時依然可以拿到原始碼,慎重使用;nosources-source-map
:原始碼資訊缺失,但安全性較高,需要配合 Sentry 等工具實現完整的 Sourcemap 對映。在開發過程中,我們勢必會遇到跨域問題,對於本地開發我們一般可以通過設定代理來解決
我們先來簡單寫一個介面:
const express = require('express')
const app = express()
app.get('/api/getInfo', (req, res) => {
res.json({
code: 200,
data: {
name: 'nanjiu',
age: 18
}
})
})
app.listen(3000, () => {
console.log('服務已啟動~')
})
然後把服務跑起來,再到vue專案中去呼叫
const getInfo = async () => {
try {
const res = await axios.get('http://localhost:3000/api/getInfo')
console.log(res)
} catch(err) {
console.log(err)
}
}
這時候你會發現介面呼叫跨域了
接著我們再來通過webpack設定代理解決跨域問題,由於我們本地使用了webpack-dev-server
,所以我們可以直接通過它來設定
// webpack.config.js
module.exports = {
// ...
devServer: {
hot: true,
open: true,
proxy: {
'/api': 'http://localhost:3000'
}
}
}
這個時候我們的介面請求就正常了
由於篇幅問題,這篇文章就介紹到這裡了,後面會接著更新webpack
更多高階用法。
如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖
第一時間獲取最新文章~
-------------------------------------------
如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖 第一時間獲取最新的文章~
掃描下方二維條碼關注公眾號,回覆進群,拉你進前端學習交流群(WX),這裡有一群志同道合的前端小夥伴,交流技術、生活、內推、面經、摸魚,這裡都有哈,快來加入我們吧~ 回覆資料,獲取前端大量精選前端電子書及學習視訊~