【面試】815- 面試官常問的 webpack 插件

Plugin
?何為插件(Plugin)?專注處理 webpack 在編譯過程中的某個(gè)特定的任務(wù)的功能模塊,可以稱為插件。plugin 是一個(gè)擴(kuò)展器,它豐富了 webpack 本身,針對(duì)是 loader 結(jié)束后,webpack 打包的整個(gè)過程,它并不直接操作文件,而是基于事件機(jī)制工作,會(huì)監(jiān)聽 webpack 打包過程中的某些節(jié)點(diǎn),執(zhí)行廣泛的任務(wù)。
?
Plugin 的特點(diǎn)
-
是一個(gè)獨(dú)立的模塊 -
模塊對(duì)外暴露一個(gè) js 函數(shù) -
函數(shù)的原型 (prototype)上定義了一個(gè)注入compiler對(duì)象的apply方法apply函數(shù)中需要有通過compiler對(duì)象掛載的webpack事件鉤子,鉤子的回調(diào)中能拿到當(dāng)前編譯的compilation對(duì)象,如果是異步編譯插件的話可以拿到回調(diào)callback -
完成自定義子編譯流程并處理 complition對(duì)象的內(nèi)部數(shù)據(jù) -
如果異步編譯插件的話,數(shù)據(jù)處理完成后執(zhí)行 callback回調(diào)。
下面介紹 18 個(gè)常用的 webpack 插件
HotModuleReplacementPlugin
模塊熱更新插件。Hot-Module-Replacement 的熱更新是依賴于 webpack-dev-server,后者是在打包文件改變時(shí)更新打包文件或者 reload 刷新整個(gè)頁面,HRM 是只更新修改的部分。
HotModuleReplacementPlugin是webpack模塊自帶的,所以引入webpack后,在plugins配置項(xiàng)中直接使用即可。
const webpack = require('webpack')
plugins: [
new webpack.HotModuleReplacementPlugin(), // 熱更新插件
]
html-webpack-plugin
生成 html 文件。將 webpack 中entry配置的相關(guān)入口 chunk 和 extract-text-webpack-plugin抽取的 css 樣式 插入到該插件提供的template或者templateContent配置項(xiàng)指定的內(nèi)容基礎(chǔ)上生成一個(gè) html 文件,具體插入方式是將樣式link插入到head元素中,script插入到head或者body中。
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.join(__dirname, '/index.html'),
minify: {
// 壓縮HTML文件
removeComments: true, // 移除HTML中的注釋
collapseWhitespace: true, // 刪除空白符與換行符
minifyCSS: true, // 壓縮內(nèi)聯(lián)css
},
inject: true,
}),
]
inject 有四個(gè)選項(xiàng)值
-
true:默認(rèn)值, script標(biāo)簽位于html文件的body底部 -
body: script標(biāo)簽位于html文件的body底部(同 true) -
head: script標(biāo)簽位于head標(biāo)簽內(nèi) -
false:不插入生成的 js 文件,只是單純的生成一個(gè) html文件
多頁應(yīng)用打包
有時(shí),我們的應(yīng)用不一定是一個(gè)單頁應(yīng)用,而是一個(gè)多頁應(yīng)用,那么如何使用 webpack 進(jìn)行打包呢。
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js',
login: './src/login.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash:6].js',
},
//...
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html', //打包后的文件名
}),
new HtmlWebpackPlugin({
template: './public/login.html',
filename: 'login.html', //打包后的文件名
}),
],
}
如果需要配置多個(gè) HtmlWebpackPlugin,那么 filename 字段不可缺省,否則默認(rèn)生成的都是 index.html。
但是有個(gè)問題,index.html 和 login.html 會(huì)發(fā)現(xiàn),都同時(shí)引入了 index.f7d21a.js 和 login.f7d21a.js,通常這不是我們想要的,我們希望 index.html 中只引入 index.f7d21a.js,login.html 只引入 login.f7d21a.js。
HtmlWebpackPlugin 提供了一個(gè) chunks 的參數(shù),可以接受一個(gè)數(shù)組,配置此參數(shù)僅會(huì)將數(shù)組中指定的 js 引入到 html 文件中
module.exports = {
//...
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html', //打包后的文件名
chunks: ['index'],
}),
new HtmlWebpackPlugin({
template: './public/login.html',
filename: 'login.html', //打包后的文件名
chunks: ['login'],
}),
],
}
這樣執(zhí)行 npm run build,可以看到 index.html 中僅引入了 index 的 js 文件,而 login.html 中也僅引入了 login 的 js 文件。
clean-webpack-plugin
clean-webpack-plugin 用于在打包前清理上一次項(xiàng)目生成的 bundle 文件,它會(huì)根據(jù) output.path 自動(dòng)清理文件夾;這個(gè)插件在生產(chǎn)環(huán)境用的頻率非常高,因?yàn)樯a(chǎn)環(huán)境經(jīng)常會(huì)通過 hash 生成很多 bundle 文件,如果不進(jìn)行清理的話每次都會(huì)生成新的,導(dǎo)致文件夾非常龐大。
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, '/index.html'),
}),
new CleanWebpackPlugin(), // 所要清理的文件夾名稱
]
extract-text-webpack-plugin
將 css 成生文件,而非內(nèi)聯(lián) 。該插件的主要是為了抽離 css 樣式,防止將樣式打包在 js 中引起頁面樣式加載錯(cuò)亂的現(xiàn)象
const ExtractTextPlugin = require('extract-text-webpack-plugin')
plugins: [
// 將css分離到/dist文件夾下的css文件夾中的index.css
new ExtractTextPlugin('css/index.css'),
]
mini-css-extract-plugin
將 CSS 提取為獨(dú)立的文件的插件,對(duì)每個(gè)包含 css 的 js 文件都會(huì)創(chuàng)建一個(gè) CSS 文件,支持按需加載 css 和 sourceMap。只能用在 webpack4 中,對(duì)比另一個(gè)插件 extract-text-webpack-plugin 有以下特點(diǎn):
-
異步加載 -
不重復(fù)編譯,性能更好 -
更容易使用 -
只針對(duì) CSS
這個(gè)插件應(yīng)該只用在生產(chǎn)環(huán)境配置,并且在 loaders 鏈中不使用 style-loader, 而且這個(gè)插件暫時(shí)不支持 HMR
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
module: {
rules: [
{
test: /\.(le|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
},
},
'css-loader',
'postcss-loader',
'less-loader',
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[id].[contenthash:8].css',
}),
],
}
purifycss-webpack
有時(shí)候我們 css 寫得多了或者重復(fù)了,這就造成了多余的代碼,我們希望在生產(chǎn)環(huán)境進(jìn)行去除。
const path = require('path')
const PurifyCssWebpack = require('purifycss-webpack') // 引入PurifyCssWebpack插件
const glob = require('glob') // 引入glob模塊,用于掃描全部html文件中所引用的css
module.exports = merge(common, {
plugins: [
new PurifyCssWebpack({
paths: glob.sync(path.join(__dirname, 'src/*.html')),
}),
],
})
optimize-css-assets-webpack-plugin
我們希望減小 css 打包后的體積,可以用到 optimize-css-assets-webpack-plugin。
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin") // 壓縮css代碼
optimization: {
minimizer: [
// 壓縮css
new OptimizeCSSAssetsPlugin({})
]
UglifyJsPlugin
uglifyJsPlugin 是 vue-cli 默認(rèn)使用的壓縮代碼方式,用來對(duì) js 文件進(jìn)行壓縮,從而減小 js 文件的大小,加速 load 速度。它使用的是單線程壓縮代碼,打包時(shí)間較慢,所以可以在開發(fā)環(huán)境將其關(guān)閉,生產(chǎn)環(huán)境部署時(shí)再把它打開。
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
plugins: [
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
sourceMap: true, //是否啟用文件緩存
parallel: true //使用多進(jìn)程并行運(yùn)行來提高構(gòu)建速度
})
ParallelUglifyPlugin
開啟多個(gè)子進(jìn)程,把對(duì)多個(gè)文件壓縮的工作分別給多個(gè)子進(jìn)程去完成,每個(gè)子進(jìn)程其實(shí)還是通過 UglifyJS 去壓縮代碼,但是變成了并行執(zhí)行。
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
plugins: [
new ParallelUglifyPlugin({
//cacheDir 用于配置緩存存放的目錄路徑。
cacheDir: '.cache/',
sourceMap: true,
uglifyJS: {
output: {
comments: false,
},
compress: {
warnings: false,
},
},
}),
]
terser-webpack-plugin
Webpack4.0 默認(rèn)是使用 terser-webpack-plugin 這個(gè)壓縮插件,在此之前是使用 uglifyjs-webpack-plugin,兩者的區(qū)別是后者對(duì) ES6 的壓縮不是很好,同時(shí)我們可以開啟 parallel 參數(shù),使用多進(jìn)程壓縮,加快壓縮。
const TerserPlugin = require('terser-webpack-plugin') // 壓縮js代碼
optimization: {
minimizer: [
new TerserPlugin({
parallel: 4, // 開啟幾個(gè)進(jìn)程來處理壓縮,默認(rèn)是 os.cpus().length - 1
cache: true, // 是否緩存
sourceMap: false,
}),
]
}
NoErrorsPlugin
報(bào)錯(cuò)但不退出 webpack 進(jìn)程。編譯出現(xiàn)錯(cuò)誤時(shí),使用 NoEmitOnErrorsPlugin 來跳過輸出階段。這樣可以確保輸出資源不會(huì)包含錯(cuò)誤。
plugins: [new webpack.NoEmitOnErrorsPlugin()]
compression-webpack-plugin
所有現(xiàn)代瀏覽器都支持 gzip 壓縮,啟用 gzip 壓縮可大幅縮減傳輸資源大小,從而縮短資源下載時(shí)間,減少首次白屏?xí)r間,提升用戶體驗(yàn)。
gzip 對(duì)基于文本格式文件的壓縮效果最好(如:CSS、JavaScript 和 HTML),在壓縮較大文件時(shí)往往可實(shí)現(xiàn)高達(dá) 70-90% 的壓縮率,對(duì)已經(jīng)壓縮過的資源(如:圖片)進(jìn)行 gzip 壓縮處理,效果很不好。
const CompressionPlugin = require('compression-webpack-plugin')
plugins: [
new CompressionPlugin({
// gzip壓縮配置
test: /\.js$|\.html$|\.css/, // 匹配文件名
threshold: 10240, // 對(duì)超過10kb的數(shù)據(jù)進(jìn)行壓縮
deleteOriginalAssets: false, // 是否刪除原文件
}),
]
當(dāng)然,這個(gè)方法還需要后端配置支持。
DefinePlugin
我們可以通過 DefinePlugin 可以定義一些全局的變量,我們可以在模塊當(dāng)中直接使用這些變量,無需作任何聲明,DefinePlugin 是 webpack 自帶的插件。
plugins: [
new webpack.DefinePlugin({
DESCRIPTION: 'This Is The Test Text.',
}),
]
// 直接引用
console.log(DESCRIPTION)
ProvidePlugin
自動(dòng)加載模塊。任何時(shí)候,當(dāng) identifier 被當(dāng)作未賦值的變量時(shí), module 就會(huì)自動(dòng)被加載,并且 identifier 會(huì)被這個(gè) module 輸出的內(nèi)容所賦值。這是 webpack 自帶的插件。
module.exports = {
resolve: {
alias: {
jquery: './lib/jquery',
},
},
plugins: [
//提供全局的變量,在模塊中使用無需用require引入
new webpack.ProvidePlugin({
$: 'jquery',
React: 'react',
}),
],
}
DLLPlugin
這是在一個(gè)額外的獨(dú)立的 webpack 設(shè)置中創(chuàng)建一個(gè)只有 dll 的 bundle(dll-only-bundle)。這個(gè)插件會(huì)生成一個(gè)名為 manifest.json 的文件,這個(gè)文件是用來讓 DLLReferencePlugin 映射到相關(guān)的依賴上去的。
「使用步驟如下」
1、在 build 下創(chuàng)建 webpack.dll.config.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
vendor: [
'vue-router',
'vuex',
'vue/dist/vue.common.js',
'vue/dist/vue.js',
'vue-loader/lib/component-normalizer.js',
'vue',
'axios',
'echarts',
],
},
output: {
path: path.resolve('./dist'),
filename: '[name].dll.js',
library: '[name]_library',
},
plugins: [
new webpack.DllPlugin({
path: path.resolve('./dist', '[name]-manifest.json'),
name: '[name]_library',
}),
// 建議加上代碼壓縮插件,否則dll包會(huì)比較大。
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
},
}),
],
}
2、在 webpack.prod.conf.js 的 plugin 后面加入配置
new webpack.DllReferencePlugin({
manifest: require('../dist/vendor-manifest.json'),
})
3、package.json文件中添加快捷命令(build:dll)
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"lint": "eslint --ext .js,.vue src",
"build": "node build/build.js",
"build:dll": "webpack --config build/webpack.dll.conf.js"
}
生產(chǎn)環(huán)境打包的時(shí)候先npm run build:dll命令會(huì)在打包目錄下生成 vendor-manifest.json 文件與 vendor.dll.js 文件。然后npm run build生產(chǎn)其他文件。
4、根目錄下的入口 index.html 加入引用
<script type="text/javascript" src="./vendor.dll.js">script>
HappyPack
HappyPack 能讓 webpack 把任務(wù)分解給多個(gè)子進(jìn)程去并發(fā)的執(zhí)行,子進(jìn)程處理完后再把結(jié)果發(fā)送給主進(jìn)程。要注意的是 HappyPack 對(duì) file-loader、url-loader 支持的不友好,所以不建議對(duì)該 loader 使用。
1、HappyPack 插件安裝
npm i -D happypack
2、webpack.base.conf.js 文件對(duì) module.rules 進(jìn)行配置
module: {
rules: [
{
test: /\.js$/,
use: ['happypack/loader?id=babel'],
include: [resolve('src'), resolve('test')],
exclude: path.resolve(__dirname, 'node_modules'),
},
{
test: /\.vue$/,
use: ['happypack/loader?id=vue'],
},
]
}
3、在生產(chǎn)環(huán)境 webpack.prod.conf.js 文件進(jìn)行配置
const HappyPack = require('happypack')
// 構(gòu)造出共享進(jìn)程池,在進(jìn)程池中包含5個(gè)子進(jìn)程
const HappyPackThreadPool = HappyPack.ThreadPool({ size: 5 })
plugins: [
new HappyPack({
// 用唯一的標(biāo)識(shí)符id,來代表當(dāng)前的HappyPack是用來處理一類特定的文件
id: 'babel',
// 如何處理.js文件,用法和Loader配置中一樣
loaders: ['babel-loader?cacheDirectory'],
threadPool: HappyPackThreadPool,
}),
new HappyPack({
id: 'vue', // 用唯一的標(biāo)識(shí)符id,來代表當(dāng)前的HappyPack是用來處理一類特定的文件
loaders: [
{
loader: 'vue-loader',
options: vueLoaderConfig,
},
],
threadPool: HappyPackThreadPool,
}),
]
「注意,當(dāng)項(xiàng)目較小時(shí),多線程打包反而會(huì)使打包速度變慢。」
copy-webpack-plugin
我們?cè)?public/index.html 中引入了靜態(tài)資源,但是打包的時(shí)候 webpack 并不會(huì)幫我們拷貝到 dist 目錄,因此 copy-webpack-plugin 就可以很好地幫我做拷貝的工作了。
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: 'public/js/*.js',
to: path.resolve(__dirname, 'dist', 'js'),
flatten: true,
},
],
}),
],
}
IgnorePlugin
這是 webpack 內(nèi)置插件,它的作用是:忽略第三方包指定目錄,讓這些指定目錄不要被打包進(jìn)去。
比如我們要使用 moment 這個(gè)第三方依賴庫(kù),該庫(kù)主要是對(duì)時(shí)間進(jìn)行格式化,并且支持多個(gè)國(guó)家語言。雖然我設(shè)置了語言為中文,但是在打包的時(shí)候,是會(huì)將所有語言都打包進(jìn)去的。這樣就導(dǎo)致包很大,打包速度又慢。對(duì)此,我們可以用 IgnorePlugin 使得指定目錄被忽略,從而使得打包變快,文件變小。
const Webpack = require('webpack')
plugins: [
//moment這個(gè)庫(kù)中,如果引用了./locale/目錄的內(nèi)容,就忽略掉,不會(huì)打包進(jìn)去
new Webpack.IgnorePlugin(/\.\/locale/, /moment/),
]
我們雖然按照上面的方法忽略了包含’./locale/'該字段路徑的文件目錄,但是也使得我們使用的時(shí)候不能顯示中文語言了,所以這個(gè)時(shí)候可以手動(dòng)引入中文語言的目錄。
import moment from 'moment'
//手動(dòng)引入所需要的語言包
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
let r = moment().endOf('day').fromNow()
console.log(r)
前端學(xué)習(xí)筆記?
?最近花了點(diǎn)時(shí)間把筆記整理到語雀上了,方便同學(xué)們閱讀:公眾號(hào)回復(fù)筆記或者簡(jiǎn)歷
?
轉(zhuǎn)載自:https://juejin.cn/post/6844904193589772301

回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~
點(diǎn)擊“閱讀原文”查看 100+ 篇原創(chuàng)文章
