80行代碼寫一個(gè)Webpack插件并發(fā)布到npm
1. 前言
最近在學(xué)習(xí) Webpack 相關(guān)的原理,以前只知道 Webpack 的配置方法,但并不知道其內(nèi)部流程,經(jīng)過一輪的學(xué)習(xí),感覺獲益良多,為了鞏固學(xué)習(xí)的內(nèi)容,我決定嘗試自己動(dòng)手寫一個(gè)插件。
這個(gè)插件實(shí)現(xiàn)的功能比較簡單:
- 默認(rèn)清除
js代碼中的console.log的打印輸出; - 可通過傳入配置,實(shí)現(xiàn)移除
console的其它方法,如console.warn、console.error等;
2. Webpack 的構(gòu)建流程以及 plugin 的原理
2.1 Webpack 構(gòu)建流程
Webpack 的主要構(gòu)建流程,可以分為三個(gè)階段:
- 初始化階段:啟動(dòng)構(gòu)建,讀取與合并配置參數(shù),加載
Plugin,實(shí)例化Compiler。 - 編譯階段:從
Entry發(fā)出,針對每個(gè)Module串行調(diào)用對應(yīng)的Loader去翻譯文件內(nèi)容,再找到該Module依賴的Module,遞歸地進(jìn)行編譯處理。 - 生成階段:對編譯后的
Module組合成Chunk,把Chunk轉(zhuǎn)換成文件,輸出到文件系統(tǒng)。
如果 Webpack 打包生產(chǎn)環(huán)境文件時(shí),只會(huì)執(zhí)行一次構(gòu)建,以上階段會(huì)按順序執(zhí)行一遍。但是在開啟監(jiān)聽模式時(shí),如開發(fā)環(huán)境,Webpack 會(huì)持續(xù)的進(jìn)行構(gòu)建。

2.2 plugin 原理
Webpack 插件通常是一個(gè)帶有 apply 函數(shù)的類,其中 constructor 可以接收傳入的配置項(xiàng)。插件被安裝時(shí),apply 函數(shù)會(huì)被調(diào)用一次,并接收 Compiler 對象,然后我們可以在 Compiler 對象上監(jiān)聽不同的事件鉤子,從而進(jìn)行插件功能的開發(fā)。
// 定義一個(gè)插件
class MyPlugin {
// 構(gòu)造函數(shù),接收插件的配置項(xiàng) options
constructor(options) {
// 獲取配置項(xiàng),初始化插件
}
// 插件安裝時(shí)會(huì)調(diào)用 apply,并傳入 compiler
apply(compiler) {
// 獲取 comolier 獨(dú)享,可以監(jiān)聽事件鉤子
// 功能開發(fā) ...
}
}
2.3 compiler 和 compilation 對象
在開發(fā) Plugin 過程中最常用的兩個(gè)對象就是 Compiler 和 Compilation:
Compiler對象在Webpack啟動(dòng)時(shí)被實(shí)例化,該對象包含了Webpack環(huán)境所有的配置信息,包括options、loaders、plugins等。在整個(gè)Webpack構(gòu)建過程中,Compiler對象是全局唯一的, 它提供了很多事件鉤子回調(diào)供插件使用。Compilation對象包含了當(dāng)前的模塊資源、編譯生成資源、變化的文件等。Compilation對象在Webpack構(gòu)建過程中并不是唯一的,如果在開發(fā)模式下Webpack開啟了文件檢測功能,每當(dāng)文件變化時(shí),Webpack會(huì)重新構(gòu)建,此時(shí)會(huì)生成一個(gè)新的Compilation對象。Compilation對象也提供了很多事件回調(diào)供插件做擴(kuò)展。
3. 插件開發(fā)
3.1 項(xiàng)目目錄
該插件實(shí)現(xiàn)的功能比較簡單,文件目錄也不復(fù)雜。首先新建一個(gè)空文件夾 remove-console-Webpack-plugin,并在該文件夾目錄下運(yùn)行 npm init,根據(jù)提示來填寫 package.json 相關(guān)信息。然后再新建一個(gè) src 文件夾,插件主要代碼就放在 src/index.js 里面。如果你需要把項(xiàng)目放到 github 上,最好也添加一下 .gitignore、README.md 等文件。
// remove-console-Webpack-plugin ├─src │ └─index.js ├─.gitignore ├─package.json └─README.md
3.2 插件代碼
插件代碼邏輯也并不復(fù)雜,主要有幾點(diǎn):
- 在構(gòu)造函數(shù)中接收配置參數(shù),并對參數(shù)進(jìn)行合并,得到需要清除的
console函數(shù), 存放在removed數(shù)組中; - 在
apply函數(shù)中監(jiān)聽compiler.hook.compilation鉤子,該鉤子觸發(fā)后,拿到compilation后進(jìn)一步監(jiān)聽它的鉤子,這里Webpack4和Webpack5的鉤子不一樣,需要做兼容; - 定義
assetsHandler方法來處理js文件,利用正則表達(dá)式清除removed中包括的console函數(shù);
class RemoveConsoleWebpackPlugin {
// 構(gòu)造函數(shù)接受配置參數(shù)
constructor(options) {
let include = options && options.include;
let removed = ['log']; // 默認(rèn)清除的方法
if (include) {
if (!Array.isArray(include)) {
console.error('options.include must be an Array.');
} else if (include.includes('*')) {
// 傳入 * 表示清除所有 console 的方法
removed = Object.keys(console).filter(fn => {
return typeof console[fn] === 'function';
})
} else {
removed = include; // 根據(jù)傳入配置覆蓋
}
}
this.removed = removed;
}
// Webpack 會(huì)調(diào)用插件實(shí)例的 apply 方法,并傳入compiler 對象
apply(compiler) {
// js 資源代碼處理函數(shù)
let assetsHandler = (assets, compilation) => {
let removedStr = this.removed.reduce((a, b) => (a + '|' + b));
let reDict = {
1: [RegExp(`\\.console\\.(${removedStr})\\(\\)`, 'g'), ''],
2: [RegExp(`\\.console\\.(${removedStr})\\(`, 'g'), ';('],
3: [RegExp(`console\\.(${removedStr})\\(\\)`, 'g'), ''],
4: [RegExp(`console\\.(${removedStr})\\(`, 'g'), '(']
}
Object.entries(assets).forEach(([filename, source]) => {
// 匹配js文件
if (/\.js$/.test(filename)) {
// 處理前文件內(nèi)容
let outputContent = source.source();
Object.keys(reDict).forEach(i => {
let [re, s] = reDict[i];
outputContent = outputContent.replace(re, s);
})
compilation.assets[filename] = {
// 返回文件內(nèi)容
source: () => {
return outputContent
},
// 返回文件大小
size: () => {
return Buffer.byteLength(outputContent, 'utf8')
}
}
}
})
}
/**
* 通過 compiler.hooks.compilation.tap 監(jiān)聽事件
* 在回調(diào)方法中獲取到 compilation 對象
*/
compiler.hooks.compilation.tap('RemoveConsoleWebpackPlugin',
compilation => {
// Webpack 5
if (compilation.hooks.processAssets) {
compilation.hooks.processAssets.tap(
{ name: 'RemoveConsoleWebpackPlugin' },
assets => assetsHandler(assets, compilation)
);
} else if (compilation.hooks.optimizeAssets) {
// Webpack 4
compilation.hooks.optimizeAssets.tap(
'RemoveConsoleWebpackPlugin',
assets => assetsHandler(assets, compilation)
);
}
})
}
}
// export Plugin
module.exports = RemoveConsoleWebpackPlugin;
4. 發(fā)布到npm
希望別人能使用到你的插件,就需要把插件發(fā)布到 npm 上,發(fā)布的主要流程:
首先在 npm 官網(wǎng)上注冊賬號,然后打開命令行工具,在任意目錄下輸入 npm login 并按提示登錄;

登錄后可用 npm whoami 查看是否登錄成功;

發(fā)布前檢查一下根目錄下的 package.json 文件信息是否填寫正確,主要字段:
- name:決定用戶下載你的插件時(shí)用的名稱,不可與
npm上已有的第三方包重名,否則無法發(fā)布; - main:插件主文件入口,
Webpack引入插件時(shí),就從該目錄導(dǎo)入; - version:每次更新發(fā)布時(shí),需要與上一版本的版本號不一樣,否則上傳不成功;
- repository:如果你的插件代碼放在
github、gitee等網(wǎng)站,可以填一下; - private:不能設(shè)置為
true,否則無法發(fā)布;

一切準(zhǔn)備就緒后,切換到插件所在的目錄下,運(yùn)行 npm publish 即可上傳插件;

上傳成功后,到 npm 官網(wǎng)上搜索,看看是否能搜到插件;

5. 結(jié)尾
到此這篇關(guān)于80行代碼寫一個(gè)Webpack插件并發(fā)布到npm的文章就介紹到這了,更多相關(guān)Webpack插件發(fā)布到npm內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript控制在光標(biāo)位置插入文字適合表情的插入
使用javascript控制在光標(biāo)位置插入文字,在實(shí)現(xiàn)表情的插入時(shí)會(huì)用到的,需要的朋友可以參考下2014-06-06
一個(gè)php+js實(shí)時(shí)顯示時(shí)間問題
本文給大家分享的是解決的php+js實(shí)時(shí)顯示時(shí)間問題,主要是自己當(dāng)時(shí)的理解有問題,也許大家有和我一樣的情況,這里分享給大家2015-10-10

