webpack-loader詳解

Macchiato發表於2018-04-10

webpack-loader是什麼?

webpack-loader詳解

loader是一個函式,有一個引數是原始檔內容,有內部的this,最後的loader必須有返回值否則會報錯:Final loader didn't return a Buffer or String

原因:

如果是處理順序排在最後一個的 Loader,那麼它的返回值將最終交給webpack的require,換句話說,它一定是一段用字串來儲存可執行的JavaScript指令碼

如何配置webpack-loader?

配置單個loader
    {
      test: /\.js$/
      use: [
        {
          loader: path.resolve(__dirname, 'src', 'loaders', 'first-loader.js'),
          options: {/*這裡是給loader傳入的引數會有一個專門的庫來處理這個引數,待會下面會講到*/}
        }
      ]
    }
複製程式碼
配置多個loader

如果把我們自己寫的loader當做一個模組來載入的時候,webpack預設的會去查詢node_modules下面的模組,但是我們自己寫的loader沒有在node_modules中 這時候我們怎麼樣讓他也查詢我們自己寫的loader呢?這時候我們就需要設定一個屬性 resolveLoader他是一個物件,裡面有一個modules屬性 該屬性的值是一個陣列 陣列中 的每個值就是要查詢的loader存放地

    resolveLoader: {
       modules: [
    		path.resolve('node_modules'),
    	    path.resolve(__dirname, 'src', 'loaders')
    	]
    },
複製程式碼

再配置loader

    {
        test: /\.less$/,
        use: ['style-loader','less-loader']
    }
複製程式碼

loader的用法準則

單一職責,一個loader只做一件事情,這樣設計的原因是因為,職責越單一,組合性就強,可配置性就好。

從右到左,鏈式執行,上一個loader的處理結果傳給下一個loader接著處理

模組化,一個loader就是一個模組,單獨存在不依賴其他的任何模組 無狀態,就類似於純函式。

loader有實用工具loader-utils解析loader引數的和schema-utils校驗格式的

loader的依賴

loader相關API

1、快取 webpack充分地利用快取來提高編譯效率

    this.cacheable();
複製程式碼

2、非同步 當一個 Loader 無依賴,可非同步的時候我想都應該讓它不再阻塞地去非同步

    // 讓 Loader 快取
    module.exports = function(source) {
        var callback = this.async();
        // 做非同步的事
        doSomeAsyncOperation(content, function(err, result) {
            if(err) return callback(err);
            callback(null, result);
        });
    };
複製程式碼

3、傳入的原始檔以buffer的形式

預設的情況原始檔是以 UTF-8 字串的形式傳入給 Loader,設定module.exports.raw = true可使用 buffer 的形式進行處理

    module.exports.raw = true
複製程式碼

4、獲取loader的options

    const loaderUtils = require('loader-utils');
    module.exports = function (source) {
    	// 獲取當前使用者給當前loader傳入的引數物件options
        const options = loaderUtils.getOptions(this);
    	return source;
    }
複製程式碼

5、校驗傳入的options的值是不是符合要求

    const loaderUtils = require('loader-utils');
    const validate = require('schema-utils');
    let json = {
        "type": "object",
        "properties": {
            "content": {
                "type": "string",
            }
        }
    }
    module.exports = function (source) {
        const options = loaderUtils.getOptions(this);
    	// 第一個引數是校驗的json 第二個引數是loader傳入的options 第三個引數是當前loader的名稱
        validate(json, options, 'first-loader');
        console.log(options.content)
    }
    // 當前規定 傳入的options必須是一個物件 他裡面的content的值必須是一個字串 如果不滿足要求則會有好的報錯  xxx應該是一個什麼型別
複製程式碼

6、loader的返回其他結果

Loader有些場景下還需要返回除了內容之外的東西。

    module.exports = function(source) {
      // 通過 this.callback 告訴 Webpack 返回的結果
      this.callback(null, source, sourceMaps);
      // 當你使用 this.callback 返回內容時,該 Loader 必須返回 undefined,
      // 以讓 Webpack 知道該 Loader 返回的結果在 this.callback 中,而不是 returnreturn;
    };
複製程式碼
    this.callback(
        // 當無法轉換原內容時,給 Webpack 返回一個 Error
        err: Error | null,
        // 原內容轉換後的內容
        content: string | Buffer,
        // 用於把轉換後的內容得出原內容的 Source Map,方便除錯
        sourceMap?: SourceMap,
        // 如果本次轉換為原內容生成了 AST 語法樹,可以把這個 AST 返回,
        // 以方便之後需要 AST 的 Loader 複用該 AST,以避免重複生成 AST,提升效能
        abstractSyntaxTree?: AST
    );
複製程式碼

7、同步與非同步

Loader 有同步和非同步之分,上面介紹的 Loader 都是同步的 Loader,因為它們的轉換流程都是同步的,轉換完成後再返回結果。 但在有些場景下轉換的步驟只能是非同步完成的,例如你需要通過網路請求才能得出結果,如果採用同步的方式網路請求就會阻塞整個構建,導致構建非常緩慢。

    module.exports = function(source) {
        // 告訴 Webpack 本次轉換是非同步的,Loader 會在 callback 中回撥結果
        var callback = this.async();
        someAsyncOperation(source, function(err, result, sourceMaps, ast) {
            // 通過 callback 返回非同步執行後的結果
            callback(err, result, sourceMaps, ast);
        });
    };
複製程式碼

8、處理二進位制資料

在預設的情況下,Webpack 傳給 Loader 的原內容都是 UTF-8 格式編碼的字串。 但有些場景下 Loader 不是處理文字檔案,而是處理二進位制檔案,例如 file-loader,就需要 Webpack 給 Loader 傳入二進位制格式的資料。 為此,你需要這樣編寫 Loader:

    module.exports = function(source) {
        // 在 exports.raw === true 時,Webpack 傳給 Loader 的 source 是 Buffer 型別的
        source instanceof Buffer === true;
        // Loader 返回的型別也可以是 Buffer 型別的
        // 在 exports.raw !== true 時,Loader 也可以返回 Buffer 型別的結果
        return source;
    };
    // 通過 exports.raw 屬性告訴 Webpack 該 Loader 是否需要二進位制資料 
    module.exports.raw = true;
複製程式碼

9、快取

在有些情況下,有些轉換操作需要大量計算非常耗時,如果每次構建都重新執行重複的轉換操作,構建將會變得非常緩慢。 為此,Webpack 會預設快取所有 Loader 的處理結果,也就是說在需要被處理的檔案或者其依賴的檔案沒有發生變化時, 是不會重新呼叫對應的 Loader 去執行轉換操作的。

    module.exports = function(source) {
     // 關閉該 Loader 的快取功能
       this.cacheable(false);
       return source;
    };
複製程式碼

10、其它 Loader API

this.context:當前處理檔案的所在目錄,假如當前 Loader 處理的檔案是 /src/main.js,則 this.context 就等於 /src。

this.resource:當前處理檔案的完整請求路徑,包括 querystring,例如 /src/main.js?name=1。

this.resourcePath:當前處理檔案的路徑,例如 /src/main.js。

this.resourceQuery:當前處理檔案的 querystring。

this.target:等於 Webpack 配置中的 Target

this.loadModule:但 Loader 在處理一個檔案時,如果依賴其它檔案的處理結果才能得出當前檔案的結果時, 就可以通過 this.loadModule(request: string, callback: function(err, source, sourceMap, module)) 去獲得 request 對應檔案的處理結果。

this.resolve:像 require 語句一樣獲得指定檔案的完整路徑,使用方法為 resolve(context: string, request: string, callback: function(err, result: string))。

this.addDependency:給當前處理檔案新增其依賴的檔案,以便再其依賴的檔案發生變化時,會重新呼叫 Loader 處理該檔案。使用方法為 addDependency(file: string)。

this.addContextDependency:和 addDependency 類似,但 addContextDependency 是把整個目錄加入到當前正在處理檔案的依賴中。使用方法為 addContextDependency(directory: string)。

this.clearDependencies:清除當前正在處理檔案的所有依賴,使用方法為 clearDependencies()。

this.emitFile:輸出一個檔案,使用方法為 emitFile(name: string, content: Buffer|string, sourceMap: {...})。 完整API

手寫簡單的style-loader 和less-loader

    // 建立一個style標籤裡面放上原始檔的樣式字串 插到頁面上
    module.exports = function (source) {
        let script = (`
            let style = document.createElement("style");
            style.innerText = ${JSON.stringify(source)};
            document.head.appendChild(style);
        `);
        return script;
    }
複製程式碼
    let less = require('less');
    module.exports = function (source) {
        this.cacheable();
        less.render(source, (err,result)=>{
            console.log(result.css);
            this.callback(err,result.css)
        })
    }
複製程式碼

不要厭煩熟悉的事物,每天都進步一點;不要畏懼陌生的事物,每天都學習一點;