Webpack學習-Plugin

什麼是Plugin?

Webpack學習-工做原理(上)一文中咱們就已經介紹了Plugin的基本概念,同時知道了webpack其實很像一條生產線,要通過一系列處理流程後才能將源文件轉換成咱們理想的輸出結果。而webpack構建過程當中,會在特定的時機廣播對應的事件,插件能夠監聽這些事件的發生,Plugin在webpack構建流程中就是這樣的一個角色。同時咱們也介紹了不少整個構建流程會廣播的事件,那麼這篇文章咱們一塊兒詳細地學習一下如何編寫Pluginjavascript

其實Plugin本質上就是一個class,一個最基礎的Plugin代碼以下:css

class BasePlugin {
        // 構造函數,接收options配置
        constructor(options) {
            ...
        }
        apply(compiler) {
            // 在此處去監聽webpack廣播的全部事件
            compiler.plugin('compilation', function(compilation) {
                ...
            });
        }
    }

    moudle.exports = BasePlugin;

咱們能夠再看看,webpack會怎麼配置Plugin,java

module.exports = {
        plugins: [
            new BasePlugin(options)
        ]
    }

咱們回憶一下,Webpack學習-工做原理(上)文章中們介紹過webpack的構建詳細流程,初始化的時候會去new Plugin(),那麼即是會去實例化webpack配置plugins全部的插件,那麼第一步插件實例化就有了,而插件中的apply方法會在開始編譯時依次被調用,而且傳入Compiler對象(後面會深刻介紹),而後調用Compiler.run()開始編譯。webpack

瞭解Compiler 和 Compilation

  • Compiler對象包含了Webpack環境全部的配置信息,包含options,loaders,plugins這些信息,這個對象在webpack啓動時被實例化,全局惟一,能夠簡單理解成就是webpack實例
  • Compilation表明着一次新的編譯,包含當前的模塊資源、編譯生成的資源,變化的文件,以前咱們瞭解到compilation事件中compilation對象也會提供不少事件給插件作擴展,同時不少事件的的回調中都會將compilation傳入,以便使用
  • Webpack的事件機制應用了觀察者模式,Compiler和Compilation同時繼承Taptable,因此能夠直接在Compiler和Compilation對象廣播和監聽事件,廣播事件[Compiler | Compilation].apply('event-name', params),監聽事件[Compiler | Compilation].plugin('event-name', function(params){...}),event-name不能和現有的事件重名

開發插件需注意的點

  • 只要能拿到Compiler或是Compilation對象,就能廣播新的事件,供其餘插件使用
  • Compiler或是Compilation對象爲同一個引用,一旦修改就會影響後面的插件
  • 若是事件是異步的,會帶兩個參數,第二個參數爲回調函數,在插件處理完任務時須要調用回調函數通知webpack,纔會進入下一個處理流程。如:
compiler.plugin('emit',function(compilation, callback) {
    // 支持處理邏輯

    // 處理完畢後執行 callback 以通知 Webpack 
    // 若是不執行 callback,運行流程將會一直卡在這不往下執行 
    callback();
  });

經常使用api

讀取輸出資源、代碼塊、模塊及其依賴

class Plugin {
  apply(compiler) {
    compiler.plugin('emit', function (compilation, callback) {
      // compilation.chunks 存放全部代碼塊,是一個數組
      compilation.chunks.forEach(function (chunk) {
        // chunk 表明一個代碼塊
        // 代碼塊由多個模塊組成,經過 chunk.forEachModule 能讀取組成代碼塊的每一個模塊
        chunk.forEachModule(function (module) {
          // module 表明一個模塊
          // module.fileDependencies 存放當前模塊的全部依賴的文件路徑,是一個數組
          module.fileDependencies.forEach(function (filepath) {
          });
        });

        // Webpack 會根據 Chunk 去生成輸出的文件資源,每一個 Chunk 都對應一個及其以上的輸出文件
        // 例如在 Chunk 中包含了 CSS 模塊而且使用了 ExtractTextPlugin 時,
        // 該 Chunk 就會生成 .js 和 .css 兩個文件
        chunk.files.forEach(function (filename) {
          // compilation.assets 存放當前全部即將輸出的資源
          // 調用一個輸出資源的 source() 方法能獲取到輸出資源的內容
          let source = compilation.assets[filename].source();
        });
      });

      // 這是一個異步事件,要記得調用 callback 通知 Webpack 本次事件監聽處理結束。
      // 若是忘記了調用 callback,Webpack 將一直卡在這裏而不會日後執行。
      callback();
    })
  }
}

監聽文件變化

// 當依賴的文件發生變化時會觸發 watch-run 事件
compiler.plugin('watch-run', (watching, callback) => {
    // 獲取發生變化的文件列表
    const changedFiles = watching.compiler.watchFileSystem.watcher.mtimes;
    // changedFiles 格式爲鍵值對,鍵爲發生變化的文件路徑。
    if (changedFiles[filePath] !== undefined) {
      // filePath 對應的文件發生了變化
    }
    callback();
});

爲了監聽 HTML 文件的變化,咱們須要把 HTML 文件加入到依賴列表中,能夠怎麼作:

compiler.plugin('after-compile', (compilation, callback) => {
  // 把 HTML 文件添加到文件依賴列表,好讓 Webpack 去監聽 HTML 模塊文件,在 HTML 模版文件發生變化時從新啓動一次編譯
    compilation.fileDependencies.push(filePath);
    callback();
});

修改輸出資源

// 設置 compilation.assets 的代碼以下:
compiler.plugin('emit', (compilation, callback) => {
  // 設置名稱爲 fileName 的輸出資源
  compilation.assets[fileName] = {
    // 返回文件內容
    source: () => {
      // fileContent 既能夠是表明文本文件的字符串,也能夠是表明二進制文件的 Buffer
      return fileContent;
      },
    // 返回文件大小
      size: () => {
      return Buffer.byteLength(fileContent, 'utf8');
    }
  };
  callback();
});
// 讀取 compilation.assets 的代碼以下:
compiler.plugin('emit', (compilation, callback) => {
  // 讀取名稱爲 fileName 的輸出資源
  const asset = compilation.assets[fileName];
  // 獲取輸出資源的內容
  asset.source();
  // 獲取輸出資源的文件大小
  asset.size();
  callback();
});

判斷webpack使用了哪些插件

// 判斷當前配置使用使用了 ExtractTextPlugin,
// compiler 參數即爲 Webpack 在 apply(compiler) 中傳入的參數
function hasExtractTextPlugin(compiler) {
  // 當前配置全部使用的插件列表
  const plugins = compiler.options.plugins;
  // 去 plugins 中尋找有沒有 ExtractTextPlugin 的實例
  return plugins.find(plugin=>plugin.__proto__.constructor === ExtractTextPlugin) != null;
}

對於上面經常使用api的講解,咱們能夠知道compiler和compilation在plugin中佔據着舉足輕重的做用,那麼具體它們長什麼樣子的,咱們編寫個例子打印出來看看,下面以extract-text-webpack-plugin插件進行斷點調試的截圖,能夠來看看這兩個分別打印出來的東西,

compiler
compilation

總結

通常狀況下,咱們是不須要去寫Plugin,可是有時候咱們有些業務需求是沒有插件能夠知足的,那麼咱們便得須要本身去寫Plugin,那瞭解Plugin的一些相關知識點就是有必要的,咱們不必定要每一個鉤子或是API都至關熟,可是咱們須要思路,瞭解如何編寫Plugin,也是有必要的,Plugin中最重要的compiler和compilation,一個Plugin插件也就是圍繞着這個去擴展,對應詳細內容能夠去webpack官網瞭解,compiler連接compilation連接web

相關文章
相關標籤/搜索