1. 程式人生 > >如何開發webpack loader

如何開發webpack loader

關於webpack

作為近段時間風頭正盛的打包工具,webpack基本佔領了前端圈。相信你都不好意思說不知道webpack。 有興趣的同學可以參考下我很早之前的webpack簡介 . 確實webpack萬事萬物皆模組的思路真是極大的方便了我們的開發,將css,圖片等檔案都能打包的功能離不開形形色色的loader。 對於一個事情要知其然更要知其所以然,抱著這個心態我們一起來看下loader的相關知識及如何開發。

學習方法

對於一個新事物最好的學習方法,我認為是其官方文件。對於loader,將其官方文件看一遍,就知道如何開發最簡單的loader了。 只是其官方文件是英文的,我就順手翻譯了一下,一方面加深自己理解。另一方面為其他同學提供個參考。 我相信看完文件你就知道如何開發一個loader了。

什麼是loader

loader是一個對面暴露一個方法的node包.當遇到某些資源需要被轉換時呼叫該方法。

簡單情況

只有一個loader來處理某個檔案時,該loader被呼叫時只有一個引數,這個引數是該檔案的內容轉化之後的字串。

loader在function執行時可以通過this context來訪問laoder API 以便更高效的開發。

一個僅僅需要一個值的同步loader可以簡單的return 自己。其他情況下,loader可以通過this.callback(err, values...)返回一系列的值。error同樣傳遞給this.callback或者在loader中丟擲。

loader期望返回1-2個值,第一個是處理之後作為string或者buffer返回的js程式碼。第二個是SourceMap或者js 物件

複雜情況:

當多個loader被鏈式呼叫時,只有最後一個loader獲得資原始檔。 同時只有第一個loader被期望返回1-2個值(即上面提到的JavaScript和SourceMap)。 其他loader接收值由上一個loader傳遞。

換句話說,鏈式loader執行順序從右至左或者自下而上。 舉個栗子:下面這段程式碼的執行順序就是自下而上 foo-loader==>bar-loader

module: {
  loaders: [
    {
      test: /\.js/,
      loaders: [
        'bar-loader',
        'foo-loader'
      ]
    }
  ]
}複製程式碼

注意:當前weboack只會在nodemodules資料夾下面搜尋你指定的loader

如果你的資料夾不在該目錄下需要在config下面增加一項配置: 即預設訪問node_modules,你的資料夾不在的話就需要手動在配置檔案里加上了。

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

溫馨提示

ps:經過自身實踐發現這樣寫是錯的,不需要通過path去解析,直接將檔案目錄寫入即可。 一般來說loader都會發布到npm上進行管理,這種狀況不用擔心,但是開發階段如果要自行測試,就面對這種情況了。 例如,我手寫的myloader在loaders下面,例子如下。

     resolveLoader:{
        modules: ['node_modules','loader']
    }複製程式碼

Examples

就這麼簡單就是個普通的loader

    module.exports = function(source,map){
    this.cacheable && this.cacheable()
    this.value = source
    return '/*[email protected] xiaoxiangdaiyu*/'+JSON.stringify(source)
    }複製程式碼

開發指南

loader需要遵循以下事項。 以下事項按優先順序排列,第一條具有最高優先順序。

一、單一任務

loaders可以被鏈式呼叫,為每一步建立一個loader而非一個loader做所有事情 也就是說,在非必要的狀況下沒有必要將他們轉換為js。

例如:通過查詢字串將一個字串模板轉化為html。 如果你寫了個loader做了所有事情那麼你違背了loader的第一條要求。 你應該為每一個task建立一個loader並且通過管道來使用它們

  • jade-loader: 轉換模板為一個module
  • apply-loader: 建立一個module並通過查詢引數來返回結果
  • html-loade: 建立一個處理html並返回一個string的模組

二、建立moulde話的模組,即正常的模組

loader產出的module應該和遵循和普通的module一樣的設計原則。 舉個例子,下面這樣設計是不好的,沒有模組化,依賴全域性狀態

    require("any-template-language-loader!./xyz.atl");
    var html = anyTemplateLanguage.render("xyz");複製程式碼

三、儘量表明該loader是否可以快取

大部分loaders是cacheable,所以應該標明是否cacheable。 只需要在loader裡面呼叫即可

    // Cacheable identity loader
module.exports = function(source) {
    this.cacheable();
    return source;
};複製程式碼

四、不要在執行和模組之間儲存狀態

  • 一個loader相對於其他編譯後的模組應該是獨立的。 除非其可以自己處理這些狀態
  • 一個loader相對於同一模組之前的編譯過程應該是獨立的。

五、標明依賴

如果該loader引用了其他資源(例如檔案系統), 必須宣告它們。這些資訊用來是快取的loader失效並且重新編譯它們

    var path = require("path");
    module.exports = function(source) {
    this.cacheable();
    var callback = this.async();
    var headerPath = path.resolve("header.js");
    this.addDependency(headerPath);
    fs.readFile(headerPath, "utf-8", function(err, header) {
        if(err) return callback(err);
        callback(null, header + "\n" + source);
    });
};複製程式碼

六、解析依賴

很多語言都提供了一些規範來宣告依賴,例如css中的 @import 和 url(...)。這些依賴應該被模組系統所解析。

下面是兩種解決方式:

  • 1、將它們轉化成require
  • 2、 用this.resolve方法來解析路徑

下面是兩個示例

  • 1、css-loader: 將依賴轉化成require,即用require來替換@import和 url(...),解析對其他樣式檔案的依賴
  • 2、less-loader: 不能像css-loader那樣做,因為所有的less檔案需要一起編譯來解析變數和mixins。因此其通過一個公共的路徑邏輯來擴充套件less編譯過程。這個公共的邏輯使用this.resolve來解析帶有module系統配置項的檔案。例如aliasing, custom module directories等。

如果語言僅僅接受相對urls(如css中url(file) 總是代表./file),使用~來說明成模組依賴.

    url(file) -> require("./file")
    url(~module) -> require("module")複製程式碼

七、抽離公共程式碼

extract common code 我感覺還是翻譯成上面的標題比較好。其實所有語言都遵循該思想,即封裝 不要寫出來很多每個模組都在使用的程式碼,在loader中建立一個runtime檔案,將公共程式碼放在其中

八、避免寫入絕對路徑

不要把絕對路徑寫入到模組程式碼中。它們將會破壞hash的過程當專案的根目錄發生改變的時候。應該使用loader-utils的 stringifyRequest方法來絕對路徑轉化為相對路徑。 例子:

    var loaderUtils = require("loader-utils");
    return "var runtime = require(" +
    loaderUtils.stringifyRequest(this, "!" + require.resolve("module/runtime")) +
  ");";複製程式碼

九、使用peerDependencies來指明依賴的庫

使用peerDependency允許應用開發者去在package.json裡說明依賴的具體版本。這些依賴應該是相對開放的允許工具庫升級而不需要重新發布loader版本。簡而言之,對於peerDependency依賴的庫應該是鬆耦合的,當工具庫版本變化的時候不需要重新變更loader版本。

十、可程式設計物件作為查詢項

有些情況下,loader需要某些可程式設計的物件但是不能作為序列化的query引數被方法解析。例如less-loader通過具體的less-plugin提供了這種可能。這種情況下,loader應該允許擴充套件webpack的options物件去獲得具體的option。為了避免名字衝突,基於loader的名稱空間來命名是很必要的。

     // webpack.config.js
    module.exports = {
        ...
    lessLoader: {
        lessPlugins: [
        new LessPluginCleanCSS({advanced: true})
        ]
    }
};複製程式碼

結束語

至此,如何開發一個webpack loader 我相信大家已經知道了,如果還不太清楚的話,可以移步w-loader檢視。 另外,對於我這種英語渣渣來說,翻譯起來確實難度蠻大的。此處拋磚引玉,希望大家共同探討學習。

作者:瀟湘待雨 連結:https://juejin.im/post/59e6a5de518825469c7461da 來源:掘金 著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。