何爲插件(Plugin)?專注處理 webpack 在編譯過程中的某個特定的任務的功能模塊,可以稱爲插件。
Plugin 是一個擴展器,它豐富了 webpack 本身,針對是 loader 結束後,webpack 打包的整個過程,它並不直接操作文件,而是基於事件機制工作,會監聽 webpack 打包過程中的某些節點,執行廣泛的任務。
Plugin 的特點
-
是一個獨立的模塊
-
模塊對外暴露一個 js 函數
-
函數的原型
(prototype)
上定義了一個注入compiler
對象的apply
方法apply
函數中需要有通過compiler
對象掛載的webpack
事件鉤子,鉤子的回調中能拿到當前編譯的compilation
對象,如果是異步編譯插件的話可以拿到回調callback
-
完成自定義子編譯流程並處理
complition
對象的內部數據 -
如果異步編譯插件的話,數據處理完成後執行
callback
回調。
下面介紹 18 個常用的 webpack 插件。
本文在gitthub做了收錄:github.com/Michael-lzg…
HotModuleReplacementPlugin
模塊熱更新插件。Hot-Module-Replacement
的熱更新是依賴於 webpack-dev-server
,後者是在打包文件改變時更新打包文件或者 reload 刷新整個頁面,HRM
是隻更新修改的部分。
HotModuleReplacementPlugin
是webpack
模塊自帶的,所以引入webpack
後,在plugins
配置項中直接使用即可。
const webpack = require('webpack')
plugins: [
new webpack.HotModuleReplacementPlugin(), // 熱更新插件
]
html-webpack-plugin
生成 html 文件。將 webpack 中entry
配置的相關入口 chunk
和 extract-text-webpack-plugin
抽取的 css 樣式 插入到該插件提供的template
或者templateContent
配置項指定的內容基礎上生成一個 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, // 壓縮內聯css
},
inject: true,
}),
]
inject 有四個選項值
-
true:默認值,
script
標籤位於html
文件的body
底部 -
body:
script
標籤位於html
文件的body
底部(同 true) -
head:
script
標籤位於head
標籤內 -
false:不插入生成的 js 文件,只是單純的生成一個
html
文件
多頁應用打包
有時,我們的應用不一定是一個單頁應用,而是一個多頁應用,那麼如何使用 webpack 進行打包呢。
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', //打包後的文件名
}),
],
}
如果需要配置多個 HtmlWebpackPlugin
,那麼 filename
字段不可缺省,否則默認生成的都是 index.html
。
但是有個問題,index.html
和 login.html
會發現,都同時引入了 index.f7d21a.js
和 login.f7d21a.js
,通常這不是我們想要的,我們希望 index.html
中只引入 index.f7d21a.js
,login.html
只引入 login.f7d21a.js
。
HtmlWebpackPlugin
提供了一個 chunks
的參數,可以接受一個數組,配置此參數僅會將數組中指定的 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'],
}),
],
}
這樣執行 npm run build
,可以看到 index.html
中僅引入了 index 的 js 文件,而 login.html
中也僅引入了 login 的 js 文件。
clean-webpack-plugin
clean-webpack-plugin
用於在打包前清理上一次項目生成的 bundle 文件,它會根據 output.path
自動清理文件夾;這個插件在生產環境用的頻率非常高,因爲生產環境經常會通過 hash 生成很多 bundle 文件,如果不進行清理的話每次都會生成新的,導致文件夾非常龐大。
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, '/index.html'),
}),
new CleanWebpackPlugin(), // 所要清理的文件夾名稱
]
extract-text-webpack-plugin
將 css 成生文件,而非內聯 。該插件的主要是爲了抽離 css 樣式,防止將樣式打包在 js 中引起頁面樣式加載錯亂的現象
const ExtractTextPlugin = require('extract-text-webpack-plugin')
plugins: [
// 將css分離到/dist文件夾下的css文件夾中的index.css
new ExtractTextPlugin('css/index.css'),
]
mini-css-extract-plugin
將 CSS 提取爲獨立的文件的插件,對每個包含 css 的 js 文件都會創建一個 CSS 文件,支持按需加載 css 和 sourceMap
。只能用在 webpack4 中,對比另一個插件 extract-text-webpack-plugin 有以下特點:
-
異步加載
-
不重複編譯,性能更好
-
更容易使用
-
只針對 CSS
這個插件應該只用在生產環境配置,並且在 loaders
鏈中不使用 style-loader
, 而且這個插件暫時不支持 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
有時候我們 css 寫得多了或者重複了,這就造成了多餘的代碼,我們希望在生產環境進行去除。
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
默認使用的壓縮代碼方式,用來對 js 文件進行壓縮,從而減小 js 文件的大小,加速 load 速度。它使用的是單線程壓縮代碼,打包時間較慢,所以可以在開發環境將其關閉,生產環境部署時再把它打開。
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
plugins: [
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
sourceMap: true, //是否啓用文件緩存
parallel: true //使用多進程並行運行來提高構建速度
})
ParallelUglifyPlugin
開啓多個子進程,把對多個文件壓縮的工作分別給多個子進程去完成,每個子進程其實還是通過 UglifyJS
去壓縮代碼,但是變成了並行執行。
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 默認是使用 terser-webpack-plugin
這個壓縮插件,在此之前是使用 uglifyjs-webpack-plugin
,兩者的區別是後者對 ES6 的壓縮不是很好,同時我們可以開啓 parallel
參數,使用多進程壓縮,加快壓縮。
const TerserPlugin = require('terser-webpack-plugin') // 壓縮js代碼
optimization: {
minimizer: [
new TerserPlugin({
parallel: 4, // 開啓幾個進程來處理壓縮,默認是 os.cpus().length - 1
cache: true, // 是否緩存
sourceMap: false,
}),
]
}
NoErrorsPlugin
報錯但不退出 webpack 進程。編譯出現錯誤時,使用 NoEmitOnErrorsPlugin
來跳過輸出階段。這樣可以確保輸出資源不會包含錯誤。
plugins: [new webpack.NoEmitOnErrorsPlugin()]
compression-webpack-plugin
所有現代瀏覽器都支持 gzip
壓縮,啓用 gzip
壓縮可大幅縮減傳輸資源大小,從而縮短資源下載時間,減少首次白屏時間,提升用戶體驗。
gzip 對基於文本格式文件的壓縮效果最好(如:CSS、JavaScript 和 HTML),在壓縮較大文件時往往可實現高達 70-90% 的壓縮率,對已經壓縮過的資源(如:圖片)進行 gzip 壓縮處理,效果很不好。
const CompressionPlugin = require('compression-webpack-plugin')
plugins: [
new CompressionPlugin({
// gzip壓縮配置
test: /\.js$|\.html$|\.css/, // 匹配文件名
threshold: 10240, // 對超過10kb的數據進行壓縮
deleteOriginalAssets: false, // 是否刪除原文件
}),
]
當然,這個方法還需要後端配置支持。
DefinePlugin
我們可以通過 DefinePlugin
可以定義一些全局的變量,我們可以在模塊當中直接使用這些變量,無需作任何聲明,DefinePlugin
是 webpack
自帶的插件。
plugins: [
new webpack.DefinePlugin({
DESCRIPTION: 'This Is The Test Text.',
}),
]
// 直接引用
console.log(DESCRIPTION)
ProvidePlugin
自動加載模塊。任何時候,當 identifier
被當作未賦值的變量時, module 就會自動被加載,並且 identifier
會被這個 module 輸出的內容所賦值。這是 webpack 自帶的插件。
module.exports = {
resolve: {
alias: {
jquery: './lib/jquery',
},
},
plugins: [
//提供全局的變量,在模塊中使用無需用require引入
new webpack.ProvidePlugin({
$: 'jquery',
React: 'react',
}),
],
}
DLLPlugin
這是在一個額外的獨立的 webpack 設置中創建一個只有 dll 的 bundle(dll-only-bundle)
。這個插件會生成一個名爲 manifest.json
的文件,這個文件是用來讓 DLLReferencePlugin
映射到相關的依賴上去的。
「使用步驟如下」
1、在 build 下創建 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包會比較大。
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"
}
生產環境打包的時候先npm run build:dll
命令會在打包目錄下生成 vendor-manifest.json
文件與 vendor.dll.js 文件。然後npm run build
生產其他文件。
4、根目錄下的入口 index.html
加入引用
<script type="text/javascript" src="./vendor.dll.js"></script>
HappyPack
HappyPack
能讓 webpack 把任務分解給多個子進程去併發的執行,子進程處理完後再把結果發送給主進程。要注意的是 HappyPack
對 file-loader
、url-loader
支持的不友好,所以不建議對該 loader 使用。
1、HappyPack 插件安裝
npm i -D happypack
2、webpack.base.conf.js
文件對 module.rules 進行配置
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、在生產環境 webpack.prod.conf.js
文件進行配置
const HappyPack = require('happypack')
// 構造出共享進程池,在進程池中包含5個子進程
const HappyPackThreadPool = HappyPack.ThreadPool({ size: 5 })
plugins: [
new HappyPack({
// 用唯一的標識符id,來代表當前的HappyPack是用來處理一類特定的文件
id: 'babel',
// 如何處理.js文件,用法和Loader配置中一樣
loaders: ['babel-loader?cacheDirectory'],
threadPool: HappyPackThreadPool,
}),
new HappyPack({
id: 'vue', // 用唯一的標識符id,來代表當前的HappyPack是用來處理一類特定的文件
loaders: [
{
loader: 'vue-loader',
options: vueLoaderConfig,
},
],
threadPool: HappyPackThreadPool,
}),
]
「注意,當項目較小時,多線程打包反而會使打包速度變慢。」
copy-webpack-plugin
我們在 public/index.html
中引入了靜態資源,但是打包的時候 webpack 並不會幫我們拷貝到 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 內置插件,它的作用是:忽略第三方包指定目錄,讓這些指定目錄不要被打包進去。
比如我們要使用 moment
這個第三方依賴庫,該庫主要是對時間進行格式化,並且支持多個國家語言。雖然我設置了語言爲中文,但是在打包的時候,是會將所有語言都打包進去的。這樣就導致包很大,打包速度又慢。對此,我們可以用 IgnorePlugin
使得指定目錄被忽略,從而使得打包變快,文件變小。
const Webpack = require('webpack')
plugins: [
//moment這個庫中,如果引用了./locale/目錄的內容,就忽略掉,不會打包進去
new Webpack.IgnorePlugin(/\.\/locale/, /moment/),
]
我們雖然按照上面的方法忽略了包含’./locale/'
該字段路徑的文件目錄,但是也使得我們使用的時候不能顯示中文語言了,所以這個時候可以手動引入中文語言的目錄。
import moment from 'moment'
//手動引入所需要的語言包
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
let r = moment().endOf('day').fromNow()
console.log(r)
推薦文章
-
搭建一個 vue-cli4+webpack 移動端框架(開箱即用)
-
從零構建到優化一個類似vue-cli的腳手架
-
封裝一個toast和dialog組件併發布到npm
-
從零開始構建一個webpack項目
-
總結幾個webpack打包優化的方法
-
總結vue知識體系之高級應用篇
-
總結vue知識體系之實用技巧
-
總結vue知識體系之基礎入門篇
❝
作者:lzg9527
https://segmentfault.com/a/1190000022956602
❞