如何開發webpack loader

關於webpack

做爲近段時間風頭正盛的打包工具,webpack基本佔領了前端圈。相信你都很差意思說不知道webpack。
有興趣的同窗能夠參考下我很早以前的webpack簡介 .
確實webpack萬事萬物皆模塊的思路真是極大的方便了咱們的開發,將css,圖片等文件都能打包的功能離不開形形色色的loader。
對於一個事情要知其然更要知其因此然,抱着這個心態咱們一塊兒來看下loader的相關知識及如何開發。css

學習方法

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

什麼是loader

loader是一個對面暴露一個方法的node包.當遇到某些資源須要被轉換時調用該方法。前端

簡單狀況

只有一個loader來處理某個文件時,該loader被調用時只有一個參數,這個參數是該文件的內容轉化以後的字符串。node

loader在function執行時能夠經過this context來訪問laoder API 以便更高效的開發。webpack

一個僅僅須要一個值的同步loader能夠簡單的return 本身。其餘狀況下,loader能夠經過this.callback(err, values...)返回一系列的值。error一樣傳遞給this.callback或者在loader中拋出。git

loader指望返回1-2個值,第一個是處理以後做爲string或者buffer返回的js代碼。第二個是SourceMap或者js 對象github

複雜狀況:

當多個loader被鏈式調用時,只有最後一個loader得到資源文件。
同時只有第一個loader被指望返回1-2個值(即上面提到的JavaScript和SourceMap)。
其餘loader接收值由上一個loader傳遞。web

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

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 '/*copy@ xiaoxiangdaiyu*/'+JSON.stringify(source)
    }

開發指南

loader須要遵循如下事項。
如下事項按優先級排列,第一條具備最高優先級。

1、單一任務

loaders能夠被鏈式調用,爲每一步建立一個loader而非一個loader作全部事情
也就是說,在非必要的情況下沒有必要將他們轉換爲js。

例如:經過查詢字符串將一個字符串模板轉化爲html。
若是你寫了個loader作了全部事情那麼你違背了loader的第一條要求。
你應該爲每個task建立一個loader而且經過管道來使用它們

  • jade-loader: 轉換模板爲一個module
  • apply-loader: 建立一個module並經過查詢參數來返回結果
  • html-loade: 建立一個處理html並返回一個string的模塊

2、建立moulde話的模塊,即正常的模塊

loader產出的module應該和遵循和普通的module同樣的設計原則。
舉個例子,下面這樣設計是很差的,沒有模塊化,依賴全局狀態

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

3、儘可能代表該loader是否能夠緩存

大部分loaders是cacheable,因此應該標明是否cacheable。
只須要在loader裏面調用便可

// Cacheable identity loader
module.exports = function(source) {
    this.cacheable();
    return source;
};

4、不要在運行和模塊之間保存狀態

  • 一個loader相對於其餘編譯後的模塊應該是獨立的。 除非其能夠本身處理這些狀態
  • 一個loader相對於同一模塊以前的編譯過程應該是獨立的。

5、標明依賴

若是該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);
    });
};

6、解析依賴

不少語言都提供了一些規範來聲明依賴,例如css中的 @import 和 url(...)。這些依賴應該被模塊系統所解析。

下面是兩種解決方式:
  • 一、將它們轉化成require
  • 二、 用this.resolve方法來解析路徑
下面是兩個示例
  • 一、css-loader: 將依賴轉化成require,即用require來替換@import和 url(...),解析對其餘樣式文件的依賴
  • 二、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")

7、抽離公共代碼

extract common code 我感受仍是翻譯成上面的標題比較好。其實全部語言都遵循該思想,即封裝
不要寫出來不少每一個模塊都在使用的代碼,在loader中建立一個runtime文件,將公共代碼放在其中

8、避免寫入絕對路徑

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

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

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

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

10、可編程對象做爲查詢項

有些狀況下,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查看。
另外,對於我這種英語渣渣來講,翻譯起來確實難度蠻大的。此處拋磚引玉,但願你們共同探討學習。

此文爲原創文章,轉載請註明出處!