圖解Webpack——實現一個Loader

loader承擔的是翻譯官的職責,利用其彌補了讓webpack只能理解JavaScript和JSON文件的問題,從而能夠處理其它類型的文件,因此loader對webpack的重要性不言而喻,因此學習構建一個loader是學習webpack的必經之路。在學習編寫一個loader以前,要明確一下loader的職責: 其職責是單一的,只須要完成一種轉換。下面將逐步闡述選擇loader開發中的幾個關鍵點並實現一個loader。

1、Loader分類

loader是一個CommonJs風格的函數,接收輸入的source後可經過同步或異步的方式進行處理,而後將內容進行輸出。

1.1 同步Loader

同步loader指的是同步的返回轉換後的內容。,因爲是在Node.js這樣的單線程環境,因此轉換過程會阻塞整個構建,構建緩慢,不適用於耗時較長的環境中。對於同步loader,主要有兩種方法返回轉換後的內容:return和this.callback.
  1. return<br/>

利用return可直接返回轉換後結果。css

module.exports = function(source, map, meta){
    // ...
    // output爲處理後結果
    return output;
}
  1. this.callback<br/>

該方法相比於return更加靈活,其參數主要有四個:html

this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
);

(1)第一個參數爲沒法轉換原內容是,Webpack會返回一個Error。<br/>
(2)第二個參數即爲通過轉換後的內容(爲輸出的內容)。<br/>
(3)指與編譯後代碼所映射的源代碼,便於調試。爲了在此loader中獲取該sourceMap,則須要在建立的webpack作一下配置(以js爲例,babel-loader會將基礎ES6語法進行轉換爲ES5,經過devtool能夠開啓source-map):node

// webpack.config.js
module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [
                    'test-loader',// 該loader即爲本身構建的loader
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: [
                                '@babel/preset-env'
                            ]
                        }
                    }
                ]
            }
        ]
    },
    devtool: 'eval-source-map',
}

(4)能夠是任何東西,輸出該參數,便可在下一個loader中獲取並使用,例如經過各loader之間共享通用的AST,加速編譯時間。webpack

利用this.callback可返回傳遞多參數的結果。
module.exports = function(source, map, meta) {
    // 處理後得到的結果output
    const output = dealOperation(source);
    this.callback(null, output, map, meta);
}

1.2 異步Loader

同步loader只適合於計算量小,速度快的場景,可是對於計量算大、耗時比較長的場景(例如網絡請求),使用同步loader會阻塞整個構建過程,致使構建速度變慢,採用異步loader便可避免該問題。對於異步loader,使用this.async()能夠獲取到callback函數,該函數參數和同步loader中this.callback參數一致。
module.exports = function(content, map, meta) {
    // 獲取callback函數
    const callback = this.async();
    // 用setTimeout模擬該異步過程
    setTimeout(() => {
        // 處理後得到的結果output
    const output = dealOperation(source);
        callback(null, output, map, meta);
    }, 100)
}

2、文件轉化後類型

默認狀況下,資源文件經轉化後都是UTF-8格式編碼的字符串,可是對於圖片這樣的文件通過轉化後是二進制格式的內容,爲了讓loader支持接收二進制資源,須要設置raw(以圖片資源爲例進行展現)
// webpack.config.js
module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.(png|jpg|gif)$/,
                use: [
                    'url-loader',
                    'raw-test-loader',// 本身的loader
                ]
            }
        ]
    }
}
// raw-test-loader.js
module.exports = function(source, map, meta) {
    // 處理輸入的資源
    const output = dealOperation(source);
    return output;
}
// 經過該屬性告訴webpack該loader是否須要二進制數據
module.exports.raw = true;

3、options選項

對於webpack配置中,loader每每有一些options參數,對於本身編寫的loader中爲了獲取options參數,官方推薦使用loader-utils包,利用該包便可獲取options中參數,而後在loader中進行處理。
const loaderUtils = require('loader-utils');

module.exports = function (source, map, meta){
    // 獲取options
    const options = loaderUtils.getOptions(this);
    const output = dealOperation(source);
    
    return output;
}

4、是否緩存

對於轉換操做須要大量的計算,很是耗時,每次從新構建會讓構建過程變的很是緩慢。webpack會默認緩存全部loader的處理結果,即要處理文件和其相關依賴沒發生變化就會利用其緩存(注意loader除了this.addDependency裏指定的依賴外,不該該有任何外部依賴)。經過this.cacheable可控制其是否進行緩存。
module.exports = function(source, map, meta) {
    // 關閉緩存
    this.cacheable(false);
    return source;
}

5、實現一個loader

本節是loader實戰,編寫了一個用於字母大小寫轉換的loader,利用該loader可以實現將txt文件中字母的大小寫轉換,其loader內容及webpack.config.js相關配置以下所示(詳細代碼見 github上代碼)
// format-letters-loader.js
const loaderUtils = require('loader-utils');

const Lowercase2Uppercase = 'L2U';
const Uppercase2Lowercase = 'U2L';

module.exports = function (source, map, meta) {
    let output = '';
    // 獲取options
    const options = loaderUtils.getOptions(this);
    const { formatType } = options;
    switch(formatType) {
        case Lowercase2Uppercase: {
            output = source.toUpperCase();
            break;
        }
        case Uppercase2Lowercase: {
            output = source.toLowerCase();
            break;
        }
        default: {
            output = source;
        }
    }

    this.callback(null, output, map, meta);
};
// webpack.config.js
module.exports = {
    // ...
    module: {
        rules: [
            {
                exclude: /\.(css|js|html|png|jpg|gif)$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[name].[ext]',
                            outputPath: 'asset',
                        }
                    },
                    {
                        loader: 'format-letters-loader',
                        options: {
                            formatType: 'U2L'
                        }
                    },
                ]
            }
        ]
    },
    // 解析loader包是設置模塊如何被解析
    resolveLoader: {
        modules: ['./node_modules', './loader'],// 告訴 webpack 解析loader時應該搜索的目錄。
    },
}

注:本文只是起到拋磚引玉的做用,但願各位大佬多多指點。git

相關章節<br/>
圖解Webpack————基礎篇
圖解Webpack————優化篇

歡迎你們關注公衆號(回覆「深刻淺出Webpack」獲取深刻淺出Webpack的pdf版本,回覆「webpack04」獲取本節的思惟導圖」)
github

相關文章
相關標籤/搜索