80行代碼寫(xiě)一個(gè)Webpack插件并發(fā)布到npm
1. 前言
最近在學(xué)習(xí) Webpack
相關(guān)的原理,以前只知道 Webpack 的配置方法,但并不知道其內(nèi)部流程,經(jīng)過(guò)一輪的學(xué)習(xí),感覺(jué)獲益良多,為了鞏固學(xué)習(xí)的內(nèi)容,我決定嘗試自己動(dòng)手寫(xiě)一個(gè)插件。
這個(gè)插件實(shí)現(xiàn)的功能比較簡(jiǎn)單:
- 默認(rèn)清除
js
代碼中的console.log
的打印輸出; - 可通過(guò)傳入配置,實(shí)現(xiàn)移除
console
的其它方法,如console.warn
、console.error
等;
2. Webpack 的構(gòu)建流程以及 plugin 的原理
2.1 Webpack 構(gòu)建流程
Webpack
的主要構(gòu)建流程,可以分為三個(gè)階段:
- 初始化階段:?jiǎn)?dòng)構(gòu)建,讀取與合并配置參數(shù),加載
Plugin
,實(shí)例化Compiler
。 - 編譯階段:從
Entry
發(fā)出,針對(duì)每個(gè)Module
串行調(diào)用對(duì)應(yīng)的Loader
去翻譯文件內(nèi)容,再找到該Module
依賴(lài)的Module
,遞歸地進(jìn)行編譯處理。 - 生成階段:對(duì)編譯后的
Module
組合成Chunk
,把Chunk
轉(zhuǎn)換成文件,輸出到文件系統(tǒng)。
如果 Webpack
打包生產(chǎn)環(huán)境文件時(shí),只會(huì)執(zhí)行一次構(gòu)建,以上階段會(huì)按順序執(zhí)行一遍。但是在開(kāi)啟監(jiān)聽(tīng)模式時(shí),如開(kāi)發(fā)環(huán)境,Webpack 會(huì)持續(xù)的進(jìn)行構(gòu)建。
2.2 plugin 原理
Webpack
插件通常是一個(gè)帶有 apply
函數(shù)的類(lèi),其中 constructor
可以接收傳入的配置項(xiàng)。插件被安裝時(shí),apply
函數(shù)會(huì)被調(diào)用一次,并接收 Compiler
對(duì)象,然后我們可以在 Compiler
對(duì)象上監(jiān)聽(tīng)不同的事件鉤子,從而進(jìn)行插件功能的開(kāi)發(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)聽(tīng)事件鉤子 // 功能開(kāi)發(fā) ... } }
2.3 compiler 和 compilation 對(duì)象
在開(kāi)發(fā) Plugin
過(guò)程中最常用的兩個(gè)對(duì)象就是 Compiler
和 Compilation
:
Compiler
對(duì)象在Webpack
啟動(dòng)時(shí)被實(shí)例化,該對(duì)象包含了Webpack
環(huán)境所有的配置信息,包括options
、loaders
、plugins
等。在整個(gè)Webpack
構(gòu)建過(guò)程中,Compiler
對(duì)象是全局唯一的, 它提供了很多事件鉤子回調(diào)供插件使用。Compilation
對(duì)象包含了當(dāng)前的模塊資源、編譯生成資源、變化的文件等。Compilation
對(duì)象在Webpack
構(gòu)建過(guò)程中并不是唯一的,如果在開(kāi)發(fā)模式下Webpack
開(kāi)啟了文件檢測(cè)功能,每當(dāng)文件變化時(shí),Webpack
會(huì)重新構(gòu)建,此時(shí)會(huì)生成一個(gè)新的Compilation
對(duì)象。Compilation
對(duì)象也提供了很多事件回調(diào)供插件做擴(kuò)展。
3. 插件開(kāi)發(fā)
3.1 項(xiàng)目目錄
該插件實(shí)現(xiàn)的功能比較簡(jiǎn)單,文件目錄也不復(fù)雜。首先新建一個(gè)空文件夾 remove-console-Webpack-plugin
,并在該文件夾目錄下運(yùn)行 npm init
,根據(jù)提示來(lái)填寫(xiě) 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ù),并對(duì)參數(shù)進(jìn)行合并,得到需要清除的
console
函數(shù), 存放在removed
數(shù)組中; - 在
apply
函數(shù)中監(jiān)聽(tīng)compiler.hook.compilation
鉤子,該鉤子觸發(fā)后,拿到compilation
后進(jìn)一步監(jiān)聽(tīng)它的鉤子,這里Webpack4
和Webpack5
的鉤子不一樣,需要做兼容; - 定義
assetsHandler
方法來(lái)處理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 對(duì)象 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') } } } }) } /** * 通過(guò) compiler.hooks.compilation.tap 監(jiān)聽(tīng)事件 * 在回調(diào)方法中獲取到 compilation 對(duì)象 */ 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)上注冊(cè)賬號(hào),然后打開(kāi)命令行工具,在任意目錄下輸入 npm login
并按提示登錄;
登錄后可用 npm whoami
查看是否登錄成功;
發(fā)布前檢查一下根目錄下的 package.json
文件信息是否填寫(xiě)正確,主要字段:
- name:決定用戶(hù)下載你的插件時(shí)用的名稱(chēng),不可與
npm
上已有的第三方包重名,否則無(wú)法發(fā)布; - main:插件主文件入口,
Webpack
引入插件時(shí),就從該目錄導(dǎo)入; - version:每次更新發(fā)布時(shí),需要與上一版本的版本號(hào)不一樣,否則上傳不成功;
- repository:如果你的插件代碼放在
github
、gitee
等網(wǎng)站,可以填一下; - private:不能設(shè)置為
true
,否則無(wú)法發(fā)布;
一切準(zhǔn)備就緒后,切換到插件所在的目錄下,運(yùn)行 npm publish
即可上傳插件;
上傳成功后,到 npm
官網(wǎng)上搜索,看看是否能搜到插件;
5. 結(jié)尾
到此這篇關(guān)于80行代碼寫(xiě)一個(gè)Webpack插件并發(fā)布到npm的文章就介紹到這了,更多相關(guān)Webpack插件發(fā)布到npm內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
滑動(dòng)門(mén),簡(jiǎn)潔,新手上路制作篇 (小鴿子系列)
滑動(dòng)門(mén),簡(jiǎn)潔,新手上路制作篇 (小鴿子系列)...2007-04-04javascript控制在光標(biāo)位置插入文字適合表情的插入
使用javascript控制在光標(biāo)位置插入文字,在實(shí)現(xiàn)表情的插入時(shí)會(huì)用到的,需要的朋友可以參考下2014-06-06一個(gè)php+js實(shí)時(shí)顯示時(shí)間問(wèn)題
本文給大家分享的是解決的php+js實(shí)時(shí)顯示時(shí)間問(wèn)題,主要是自己當(dāng)時(shí)的理解有問(wèn)題,也許大家有和我一樣的情況,這里分享給大家2015-10-10