webpack 之 loader 學習

開篇

loader不難,loader不難,loader不難。默唸三遍,而後開始。javascript

loader 簡介

loader 這個東西配置 webpack 的時候常常用到,剛開始會讓人以爲有一種神祕感。css

看了文檔以後才發現,loader 只是一個導出爲函數的 JavaScript 模塊。對,不要怕它,它只是個函數模塊。java

webpack內部用了loader-runner 這個開源庫,在webpack項目下lib\NormalModule.js中的doBuild方法中調用。loader-runner具體的內部邏輯還不是很清晰,這裏先不說了。webpack

用法

咱們常常用 loader 來對模塊的源代碼進行轉換。例如ES6+ES5,sasscss等等。git

loader 的常見用法以下:github

rules: [{
    test: 'xxx',
    use: ['a-loader', 'b-loader', 'c-loader']
}]

rules: [{
    test: 'xxx',
    use: 'xx-loader'
}]

rules: [{
    test: 'xxx',
    user: [
        { loader: 'a-loader' },
        {
            loader: 'b-loader',
            options: {}
        }
    ]
}]
複製代碼

loader的運行順序是從右到左,後面的先執行,是反着來的。web

下一個執行的loader會接收上一個執行的loader後的返回值做爲參數繼續處理。相似gulp中的pipe管道流npm

到這裏應該能理解爲何css相關的loader,都是style-loader放在最前面吧。gulp

loader 前置函數

每一個 loader 都支持加一個 Pitch 函數,我這裏把它理解成前置函數。api

loader自己是按相反順序執行,但在(從右到左)執行 loader 以前,會先從左到右調用 loader 上的 pitch 方法。

能夠參考官網給出的例子:

若是是這樣的配置的loader

rules: [{
    test: 'xxx',
    use: ['a-loader', 'b-loader', 'c-loader']
}]
複製代碼

內部的執行邏輯是這樣的:

|- 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
複製代碼

注意:若是某個loader 的 pitch 方法有返回值,那麼webpack會忽略 當前loader 和 剩下的loader,直接執行以前已經調用過 pitch 方法的loader

關於 pitch 的詳細解析,能夠參考webpack中文文檔;

寫個簡單的loader

咱們已經知道了 loader 只是一個 javascript函數模塊而已,並且有固定的參數格式(官網有介紹):

module.exports = function( content, // 資源文件的內容,多是 String 或 Buffer map, // sourceMap,能夠不寫 meta // 能夠是任何東西(例如元數據),能夠不寫 ){
        // do something
    }
複製代碼

接下來咱們就能夠本身寫個簡單的loader玩一下,

這裏我寫一個my-img-loader,用於將圖片轉爲base64編碼。

先安裝mime工具包,用來獲取資源類型

npm i -D mime
複製代碼

而後在項目根目錄下新建一個 loaders 文件夾,而後新建一個 my-img-loader.js,編寫代碼:

// 用來獲取資源的類型
const mime = require('mime');

module.exports = function (content, map, meta) {

    // 將資源內容轉換爲 Buffer
    if (typeof content === 'string') {
        content = Buffer.from(content);
    }

    // this.resourcePath 表示當前資源的絕對路徑
    let mimetype = mime.getType(this.resourcePath);

    // 生成 base64 碼
    let base64 = `data:${mimetype || ''};base64,${content.toString('base64')}`;

    // 若是是處理順序排在最後一個的 loader,那麼它的返回值將最終交給 webpack 的 require
    // 也就是說這裏要返回一串 CommonJs 規範的 JS 代碼
    // 由於我這裏只用到一個loader,因此須要這樣設置,否則的話,css裏面會出現 background: url([object Ojbect])
    // 若是不是最後一個 loader,就不須要 `module.exports` 了
    return `module.exports = ${JSON.stringify(base64)}`;
}

// 設置用 Buffer 格式 來傳遞處理結果,也就是二進制數據
// 我不清楚這裏爲何要用 Buffer 格式來傳遞,不設置的話,顯示不了base64圖片
// 有知道的讀者請告訴下我
module.exports.raw = true;
複製代碼

而後設置 webpack.config.jsrules

{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,                
    exclude: [],
    use: ['./loaders/my-img-loader']
    // use: {
    // loader: 'url-loader', 
    // options: {
    // limit: 10000,
    // name: 'img/[name].[hash:7].[ext]'
    // }
    // }
}
複製代碼

爲了方便起見,讀者能夠下載我以前提交的spa-webpack-demo來更改。

亦或者,直接把代碼複製到已有的項目嘗試。

而後 npm run dev 看效果。

代碼結構以下:

若是想要給 loader 添加 options 參數,能夠用 loader-utils工具庫來處理。

最後說下 loader 的類型
同步 loader
module.exports = function(content, map, meta) {
    // 作一些同步的操做
    let result = someSyncOperation(content);
    
    // 將資源返回
    return result;
};
複製代碼
異步 loader

異步loader須要先調用this.async(),執行這個方法後,loader-runner內部會將loader識別爲異步的,並返回一個callback

module.exports = function(content, map, meta) {
    // 執行 this.async(),告訴 webpack 這是個異步的 loader
    let callback = this.async();
    // 作一些異步的操做
    someAsyncOperation(content, function(err, result) {
        if (err) return callback(err);
        
        // 告訴 webpack,loader執行完畢,並把 result 傳入,供下一個loader 使用
        callback(null, result, map, meta);
    });
};
複製代碼

寫在最後

但願本文能對讀者有幫助。

若是有錯誤的地方,還請指出。

謝謝閱讀。

參考

webpack document

webpack 中文文檔

url-loader

手把手教你擼一個 Webpack Loader

webpack loader機制源碼解析

相關文章
相關標籤/搜索