Webpack-Plugin

先附上原文鏈接
原文

Webpack 通過 Plugin 機(jī)制讓其更加靈活剃斧。 在 Webpack 運(yùn)行的生命周期中會(huì)廣播出許多事件,Plugin 可以監(jiān)聽這些事件昔字,在合適的時(shí)機(jī)通過 Webpack 提供的 API 改變輸出結(jié)果。

先來看一個(gè)基礎(chǔ)的plugin

class BasicPlugin{
  // 在構(gòu)造函數(shù)中獲取用戶給該插件傳入的配置
  constructor(options){
  }
  
  // Webpack 會(huì)調(diào)用 BasicPlugin 實(shí)例的 apply 方法給插件實(shí)例傳入 compiler 對(duì)象
  apply(compiler){
    compiler.plugin('compilation',function(compilation) {
    })
  }
}

// 導(dǎo)出 Plugin
module.exports = BasicPlugin;

使用

const BasicPlugin = require('./BasicPlugin.js');
module.export = {
  plugins:[
    new BasicPlugin(options),
  ]
}

Webpack 啟動(dòng)后岂傲,在讀取配置的過程中會(huì)先執(zhí)行 new BasicPlugin(options) 初始化一個(gè) BasicPlugin 獲得其實(shí)例痰洒。
在初始化 compiler 對(duì)象后,再調(diào)用 basicPlugin.apply(compiler) 給插件實(shí)例傳入 compiler 對(duì)象瘟裸。
插件實(shí)例在獲取到 compiler 對(duì)象后客叉,就可以通過 compiler.plugin(事件名稱, 回調(diào)函數(shù)) 監(jiān)聽到 Webpack 廣播出來的事件。

Compiler 和 Compilation

  • Compiler 對(duì)象包含了 Webpack 環(huán)境所有的的配置信息话告,包含 options兼搏,loaders,plugins 這些信息沙郭,這個(gè)對(duì)象在 Webpack 啟動(dòng)時(shí)候被實(shí)例化佛呻,它是全局唯一的,可以簡(jiǎn)單地把它理解為 Webpack 實(shí)例病线;

  • Compilation 對(duì)象包含了當(dāng)前的模塊資源吓著、編譯生成資源、變化的文件等送挑。當(dāng) Webpack 以開發(fā)模式運(yùn)行時(shí)绑莺,每當(dāng)檢測(cè)到一個(gè)文件變化,一次新的 Compilation 將被創(chuàng)建惕耕。Compilation 對(duì)象也提供了很多事件回調(diào)供插件做擴(kuò)展纺裁。通過 Compilation 也能讀取到 Compiler 對(duì)象。

Compiler 和 Compilation 的區(qū)別在于:Compiler 代表了整個(gè) Webpack 從啟動(dòng)到關(guān)閉的生命周期司澎,而 Compilation 只是代表了一次新的編譯对扶。

事件流

Webpack 就像一條生產(chǎn)線区赵,要經(jīng)過一系列處理流程后才能將源文件轉(zhuǎn)換成輸出結(jié)果。
這條生產(chǎn)線上的每個(gè)處理流程的職責(zé)都是單一的浪南,多個(gè)流程之間有存在依賴關(guān)系笼才,只有完成當(dāng)前處理后才能交給下一個(gè)流程去處理。
插件就像是一個(gè)插入到生產(chǎn)線中的一個(gè)功能络凿,在特定的時(shí)機(jī)對(duì)生產(chǎn)線上的資源做處理骡送。

Webpack 在運(yùn)行過程中會(huì)廣播事件,插件只需要監(jiān)聽它所關(guān)心的事件絮记,就能加入到這條生產(chǎn)線中摔踱,去改變生產(chǎn)線的運(yùn)作。 Webpack 的事件流機(jī)制保證了插件的有序性怨愤,使得整個(gè)系統(tǒng)擴(kuò)展性很好派敷。

Webpack 的事件流機(jī)制應(yīng)用了觀察者模式

可以直接在 Compiler 和 Compilation 對(duì)象上廣播和監(jiān)聽事件,方法如下:

/**
* 廣播出事件
* event-name 為事件名稱撰洗,注意不要和現(xiàn)有的事件重名
* params 為附帶的參數(shù)
*/
compiler.apply('event-name',params);

/**
* 監(jiān)聽名稱為 event-name 的事件篮愉,當(dāng) event-name 事件發(fā)生時(shí),函數(shù)就會(huì)被執(zhí)行差导。
* 同時(shí)函數(shù)中的 params 參數(shù)為廣播事件時(shí)附帶的參數(shù)试躏。
*/
compiler.plugin('event-name',function(params) {
  
});

同理设褐,compilation.apply 和 compilation.plugin 使用方法和上面一致颠蕴。

在開發(fā)插件時(shí),你可能會(huì)不知道該如何下手助析,因?yàn)槟悴恢涝摫O(jiān)聽哪個(gè)事件才能完成任務(wù)犀被。

在開發(fā)插件時(shí),還需要注意以下兩點(diǎn):

  • 只要能拿到 Compiler 或 Compilation 對(duì)象外冀,就能廣播出新的事件弱判,所以在新開發(fā)的插件中也能廣播出事件,給其它插件監(jiān)聽使用锥惋。

  • 傳給每個(gè)插件的 Compiler 和 Compilation 對(duì)象都是同一個(gè)引用昌腰。也就是說在一個(gè)插件中修改了 Compiler 或 Compilation 對(duì)象上的屬性,會(huì)影響到后面的插件膀跌。

  • 有些事件是異步的遭商,這些異步的事件會(huì)附帶兩個(gè)參數(shù),第二個(gè)參數(shù)為回調(diào)函數(shù)捅伤,在插件處理完任務(wù)時(shí)需要調(diào)用回調(diào)函數(shù)通知 Webpack劫流,才會(huì)進(jìn)入下一處理流程。例如

compiler.plugin('emit',function(compilation, callback) {
  // 支持處理邏輯

  // 處理完畢后執(zhí)行 callback 以通知 Webpack 
  // 如果不執(zhí)行 callback,運(yùn)行流程將會(huì)一直卡在這不往下執(zhí)行 
  callback();
});

常用 API

讀取輸出資源祠汇、代碼塊仍秤、模塊及其依賴

有些插件可能需要讀取 Webpack 的處理結(jié)果,例如輸出資源可很、代碼塊诗力、模塊及其依賴,以便做下一步處理我抠。

emit 事件發(fā)生時(shí)苇本,代表源文件的轉(zhuǎn)換和組裝已經(jīng)完成,在這里可以讀取到最終將輸出的資源菜拓、代碼塊瓣窄、模塊及其依賴,并且可以修改輸出資源的內(nèi)容纳鼎。

class Plugin {
  apply(compiler) {
    compiler.plugin('emit', function (compilation, callback) {
      // compilation.chunks 存放所有代碼塊俺夕,是一個(gè)數(shù)組
      compilation.chunks.forEach(function (chunk) {
        // chunk 代表一個(gè)代碼塊
        // 代碼塊由多個(gè)模塊組成,通過 chunk.forEachModule 能讀取組成代碼塊的每個(gè)模塊
        chunk.forEachModule(function (module) {
          // module 代表一個(gè)模塊
          // module.fileDependencies 存放當(dāng)前模塊的所有依賴的文件路徑贱鄙,是一個(gè)數(shù)組
          module.fileDependencies.forEach(function (filepath) {
          });
        });

        // Webpack 會(huì)根據(jù) Chunk 去生成輸出的文件資源劝贸,每個(gè) Chunk 都對(duì)應(yīng)一個(gè)及其以上的輸出文件
        // 例如在 Chunk 中包含了 CSS 模塊并且使用了 ExtractTextPlugin 時(shí),
        // 該 Chunk 就會(huì)生成 .js 和 .css 兩個(gè)文件
        chunk.files.forEach(function (filename) {
          // compilation.assets 存放當(dāng)前所有即將輸出的資源
          // 調(diào)用一個(gè)輸出資源的 source() 方法能獲取到輸出資源的內(nèi)容
          let source = compilation.assets[filename].source();
        });
      });

      // 這是一個(gè)異步事件贰逾,要記得調(diào)用 callback 通知 Webpack 本次事件監(jiān)聽處理結(jié)束悬荣。
      // 如果忘記了調(diào)用 callback菠秒,Webpack 將一直卡在這里而不會(huì)往后執(zhí)行疙剑。
      callback();
    })
  }
}
### 監(jiān)聽文件變化

Webpack 會(huì)從配置的入口模塊出發(fā),依次找出所有的依賴模塊践叠,當(dāng)入口模塊或者其依賴的模塊發(fā)生變化時(shí)言缤, 就會(huì)觸發(fā)一次新的 Compilation。

在開發(fā)插件時(shí)經(jīng)常需要知道是哪個(gè)文件發(fā)生變化導(dǎo)致了新的 Compilation禁灼,為此可以使用如下代碼:

// 當(dāng)依賴的文件發(fā)生變化時(shí)會(huì)觸發(fā) watch-run 事件
compiler.plugin('watch-run', (watching, callback) => {
    // 獲取發(fā)生變化的文件列表
    const changedFiles = watching.compiler.watchFileSystem.watcher.mtimes;
    // changedFiles 格式為鍵值對(duì)管挟,鍵為發(fā)生變化的文件路徑。
    if (changedFiles[filePath] !== undefined) {
      // filePath 對(duì)應(yīng)的文件發(fā)生了變化
    }
    callback();
});

默認(rèn)情況下 Webpack 只會(huì)監(jiān)視入口和其依賴的模塊是否發(fā)生變化弄捕,在有些情況下項(xiàng)目可能需要引入新的文件僻孝,例如引入一個(gè) HTML 文件。
由于 JavaScript 文件不會(huì)去導(dǎo)入 HTML 文件守谓,Webpack 就不會(huì)監(jiān)聽 HTML 文件的變化穿铆,編輯 HTML 文件時(shí)就不會(huì)重新觸發(fā)新的 Compilation。
為了監(jiān)聽 HTML 文件的變化斋荞,我們需要把 HTML 文件加入到依賴列表中荞雏,為此可以使用如下代碼:

compiler.plugin('after-compile', (compilation, callback) => {
  // 把 HTML 文件添加到文件依賴列表,好讓 Webpack 去監(jiān)聽 HTML 模塊文件,在 HTML 模版文件發(fā)生變化時(shí)重新啟動(dòng)一次編譯
    compilation.fileDependencies.push(filePath);
    callback();
});
修改輸出資源

需要監(jiān)聽 emit 事件凤优,因?yàn)榘l(fā)生 emit 事件時(shí)所有模塊的轉(zhuǎn)換和代碼塊對(duì)應(yīng)的文件已經(jīng)生成好悦陋, 需要輸出的資源即將輸出,因此 emit 事件是修改 Webpack 輸出資源的最后時(shí)機(jī)筑辨。

所有需要輸出的資源會(huì)存放在 compilation.assets 中俺驶,compilation.assets 是一個(gè)鍵值對(duì),鍵為需要輸出的文件名稱挖垛,值為文件對(duì)應(yīng)的內(nèi)容痒钝。

設(shè)置:

compiler.plugin('emit', (compilation, callback) => {
  // 設(shè)置名稱為 fileName 的輸出資源
  compilation.assets[fileName] = {
    // 返回文件內(nèi)容
    source: () => {
      // fileContent 既可以是代表文本文件的字符串,也可以是代表二進(jìn)制文件的 Buffer
      return fileContent;
    },
    // 返回文件大小
    size: () => {
      return Buffer.byteLength(fileContent, 'utf8');
    }
  };
  callback();
});

讀攘《尽:

compiler.plugin('emit', (compilation, callback) => {
  // 讀取名稱為 fileName 的輸出資源
  const asset = compilation.assets[fileName];
  // 獲取輸出資源的內(nèi)容
  asset.source();
  // 獲取輸出資源的文件大小
  asset.size();
  callback();
});
判斷 Webpack 使用了哪些插件
// 判斷當(dāng)前配置使用使用了 ExtractTextPlugin送矩,
// compiler 參數(shù)即為 Webpack 在 apply(compiler) 中傳入的參數(shù)
function hasExtractTextPlugin(compiler) {
  // 當(dāng)前配置所有使用的插件列表
  const plugins = compiler.options.plugins;
  // 去 plugins 中尋找有沒有 ExtractTextPlugin 的實(shí)例
  return plugins.find(plugin=>plugin instanceof ExtractTextPlugin) != null;
}

demo

EndWebpackPlugin,作用是在 Webpack 即將退出時(shí)再附加一些額外的操作哪替,例如在 Webpack 成功編譯和輸出了文件后執(zhí)行發(fā)布操作把輸出的文件上傳到服務(wù)器栋荸。 同時(shí)該插件還能區(qū)分 Webpack 構(gòu)建是否執(zhí)行成功。

module.exports = {
  plugins:[
    // 在初始化 EndWebpackPlugin 時(shí)傳入了兩個(gè)參數(shù)凭舶,分別是在成功時(shí)的回調(diào)函數(shù)和失敗時(shí)的回調(diào)函數(shù)晌块;
    new EndWebpackPlugin(() => {
      // Webpack 構(gòu)建成功,并且文件輸出了后會(huì)執(zhí)行到這里帅霜,在這里可以做發(fā)布文件操作
    }, (err) => {
      // Webpack 構(gòu)建失敗匆背,err 是導(dǎo)致錯(cuò)誤的原因
      console.error(err);        
    })
  ]
}

需要借助兩個(gè)事件:

  • done:在成功構(gòu)建并且輸出了文件后,Webpack 即將退出時(shí)發(fā)生身冀;

  • failed:在構(gòu)建出現(xiàn)異常導(dǎo)致構(gòu)建失敗钝尸,Webpack 即將退出時(shí)發(fā)生;

class EndWebpackPlugin {

  constructor(doneCallback, failCallback) {
    // 存下在構(gòu)造函數(shù)中傳入的回調(diào)函數(shù)
    this.doneCallback = doneCallback;
    this.failCallback = failCallback;
  }

  apply(compiler) {
    compiler.plugin('done', (stats) => {
        // 在 done 事件中回調(diào) doneCallback
        this.doneCallback(stats);
    });
    compiler.plugin('failed', (err) => {
        // 在 failed 事件中回調(diào) failCallback
        this.failCallback(err);
    });
  }
}
// 導(dǎo)出插件 
module.exports = EndWebpackPlugin;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末搂根,一起剝皮案震驚了整個(gè)濱河市珍促,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剩愧,老刑警劉巖猪叙,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異仁卷,居然都是意外死亡穴翩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門锦积,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芒帕,“玉大人,你說我怎么就攤上這事充包「鼻” “怎么了遥椿?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)淆储。 經(jīng)常有香客問我冠场,道長(zhǎng),這世上最難降的妖魔是什么本砰? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任碴裙,我火速辦了婚禮,結(jié)果婚禮上点额,老公的妹妹穿的比我還像新娘舔株。我一直安慰自己,他們只是感情好还棱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布载慈。 她就那樣靜靜地躺著,像睡著了一般珍手。 火紅的嫁衣襯著肌膚如雪办铡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天琳要,我揣著相機(jī)與錄音寡具,去河邊找鬼。 笑死稚补,一個(gè)胖子當(dāng)著我的面吹牛童叠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播课幕,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼厦坛,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了撰豺?” 一聲冷哼從身側(cè)響起粪般,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤拼余,失蹤者是張志新(化名)和其女友劉穎污桦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匙监,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凡橱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了亭姥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稼钩。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖达罗,靈堂內(nèi)的尸體忽然破棺而出坝撑,到底是詐尸還是另有隱情静秆,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布巡李,位于F島的核電站抚笔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏侨拦。R本人自食惡果不足惜殊橙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狱从。 院中可真熱鬧膨蛮,春花似錦、人聲如沸季研。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)与涡。三九已至制肮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間递沪,已是汗流浹背豺鼻。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留款慨,地道東北人儒飒。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像檩奠,于是被迫代替她去往敵國(guó)和親桩了。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 2埠戳、webpack核心模塊——tapable tapable 是 webpack 的一個(gè)核心工具井誉,但也可用于其他地...
    liuxiaojie93閱讀 453評(píng)論 0 0
  • 編寫 Loader Loader就像是一個(gè)翻譯員,能把源文件經(jīng)過轉(zhuǎn)化后輸出新的結(jié)果整胃,并且一個(gè)文件還可以鏈?zhǔn)降慕?jīng)過多...
    oWSQo閱讀 7,588評(píng)論 0 8
  • Loader 一個(gè)Loader的職責(zé)是單一的颗圣,只需要完成一種轉(zhuǎn)換。如果一個(gè)源文件需要經(jīng)歷多步轉(zhuǎn)換才能正常使用屁使,就通...
    Upcccz閱讀 518評(píng)論 0 2
  • 打包過程都做了什么 1在岂、Webpack 的運(yùn)行流程是一個(gè)串行的過程,從啟動(dòng)到結(jié)束會(huì)依次執(zhí)行以下流程蛮寂,1蔽午、初始化參數(shù)...
    看到這朵小fa了么閱讀 314評(píng)論 0 0
  • 前言 Plugin(插件) 是 webpack 生態(tài)的的一個(gè)關(guān)鍵部分。它為社區(qū)提供了一種強(qiáng)大的方法來擴(kuò)展 webp...
    champyin閱讀 1,874評(píng)論 0 3