玩轉webpack之loader開發

  • 2019 年 12 月 4 日
  • 筆記

本文作者:IMWeb hx856082 原文出處:IMWeb社區 未經同意,禁止轉載

webpack提倡一切皆模塊,所有類型的文件都可以經過文件加載器處理變成我們可加載的模塊,那麼這個文件加載器便是loader。

那麼我們如何開發一個webpack loader呢,讓我們一起探索探索吧~

一、loader執行順序 在開發loader之前,我們先了解一下webpack loader的執行順序。

webpack是支持loader的鏈式調用的,即一個文件可以經多個loader處理。當一個文件使用多個loader處理時,他的處理順序是倒序,即傳入loader數組的從右到左執行。

例如,對於scss文件,我們的配置如下,那麼它的執行順序是sass-loader -》 css-loader -》 postcss-loader -》style-loader:

module: { rules: [ { test: /.scss|.css/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2, }, }, 'postcss-loader', loader: 'sass-loader', ], } } 二、loader開發 那麼如何來開發一個loader呢?讓我們慢慢來揭開webpack loader 的什麼面紗~

loader其實是一個導出為函數的 JavaScript模塊,是不是看起來很簡單?實際呢,loader開發也很簡單。即

test.loader.js內容如下:

module.exports = function(content) { return transform(content); // 對content進行處理並返回給webpack } 1、loader的傳入參數 既然我們說了所謂 loader 只是一個導出為函數的 JavaScript模塊,那麼它的傳入是什麼呢?

content: string | Buffer, // 文件內容 sourceMap?: SourceMap, // 上一個loader解析完後生成的 source map meta?: any // 會被 webpack 忽略,可以是任何東西(例如一些元數據) 顯然loader就是對文件進行處理的,那麼這裡的content便是文件內容。

默認情況下,資源文件會被轉化為 UTF-8 字符串,然後傳給 loader。通過設置 raw,loader 可以接收原始的 Buffer。我們常用的file-loader就設置了raw為true,以告訴webpack傳入原始的二進制數據

module.exports.raw = true; 需要注意的是:第一個 loader 的傳入參數只有一個:資源文件的內容content。其他都是經過loader處理後可選擇傳遞給下一個loader的。

2、loader處理結果 loader返回的處理結果應該和傳入一樣是 String 或者 Buffer。 上面有講到除了content,loader其實還接受兩個可選的入參,返回也一樣。所以當我們如果是單個處理結果,可以在函數中直接返回。但是如果有多個處理結果,我們則必須通過this.callback()將處理結果傳遞給下一個loader。

module.exports = function(content) { const newContent = transform(content); // 對content進行處理 this.callback(null,newContent, sourceMaps, meta); return; // 當使用this.callback時函數應該return undefined } 這裡的this既不是webpack實例,也不是compiler、compilation、normalModule等這些實例。而是loader-runner構造的loaderContext對象,提供了各種loader API(具體API可見 https://webpack.js.org/api/loaders/ )。

對於meta參數,一般傳入抽象語法樹(abstract syntax tree – AST),這樣可以在多個 loader 之間共享通用的 AST,這樣做有助於加速編譯時間。

所以總結來說,loader的工作流程是:

最後的 loader 最早調用,將會傳入原始資源內容。 第一個 loader 最後調用,期望值是傳出 JavaScript和 source map(可選)。 中間的 loader 執行時,會傳入前一個 loader 傳出的結果。 3、獲取用戶自定義參數 到這裡基本已經清楚了loader的整個工作流程。我們在使用loader時,經常會傳入一些自定義的options,那麼loader怎麼獲取這些options呢?

webpack 提供了loader-utils包和schema-utils 包。loader-utils提供了許多有用的工具,但最常用的一種工具是獲取傳遞給 loader 的選項。schema-utils 包配合 loader-utils,用於保證 loader 選項,進行與 JSON Schema 結構一致的校驗。

const loaderUtils = 'loader-utils'; module.exports = function(content) { const options = loaderUtils.getOptions(this); // 用戶傳入的options return transform(content); // 對content進行處理並返回給webpack } 4、控制loader執行 前面講到過,webpack的loader執行順序是從後往前。有些時候我們希望選擇性的越過後續loader執行,webpack給每個loader提供了pitch方法進行設置。

根據webpack官網給出的案例,對於下面的配置:

module.exports = { //… module: { rules: [ { //… use: [ 'a-loader', 'b-loader', 'c-loader' ] } ] } }; webpack 在實際(從右到左)執行 loader 之前,會先從左到右調用 loader 上的 pitch 方法。所以實際執行順序如下:

|- a-loader pitch |- b-loader pitch |- c-loader pitch |- requested module is picked up as a dependency |- c-loader normal execution |- b-loader normal execution |- a-loader normal execution pitch方法若有返回值,則會跳過後續的loader。比如上面如果 b-loader 的 pitch 方法有返回值,那麼此時loader的執行流程是:

|- a-loader pitch |- b-loader pitch returns a module |- a-loader normal execution 三、舉個栗子 接下來我們來開發一個自己的loader

比如現在有個場景,要求我們給所有的apng 請求url加上參數?nowebp=1。loader代碼如下:

apng-url-resolve.js

module.exports = function(content) { return content.replace(/.apng(.*.png)?/, '.apng$1?nowebp=1') } webpack loader 配置:

const path = require('path'); modules: { rules: [ { test: /.js$/ use: path.resolve(__dirname, 'build/loaders/apng-url-resolve.js' ) } ] }