手把手教你寫一個 Webpack Loader

本文示例源代碼請戳github博客,建議你們動手敲敲代碼。html

本文不會介紹loader的一些使用方法,不熟悉的同窗請自行查看 Webpack loader

一、背景

首先咱們來看一下爲何須要loader,以及他能幹什麼?
webpack 只能理解 JavaScriptJSON 文件。loaderwebpack 可以去處理其餘類型的文件,並將它們轉換爲有效模塊,以供應用程序使用,以及被添加到依賴圖中。node

本質上來講,loader 就是一個 node 模塊,這很符合 webpack 中「萬物皆模塊」的思路。既然是 node 模塊,那就必定會導出點什麼。在 webpack 的定義中,loader 導出一個函數,loader 會在轉換源模塊resource的時候調用該函數。在這個函數內部,咱們能夠經過傳入 this 上下文給 Loader API 來使用它們。最終裝換成能夠直接引用的模塊。webpack

二、xml-Loader 實現

前面咱們已經知道,因爲 Webpack 是運行在 Node.js 之上的,一個 Loader 其實就是一個 Node.js 模塊,這個模塊須要導出一個函數。 這個導出的函數的工做就是得到處理前的原內容,對原內容執行處理後,返回處理後的內容。
一個簡單的loader源碼以下git

module.exports = function(source) {
  // source 爲 compiler 傳遞給 Loader 的一個文件的原內容
  // 該函數須要返回處理後的內容,這裏簡單起見,直接把原內容返回了,至關於該 Loader 沒有作任何轉換
  return source;
};

因爲 Loader 運行在 Node.js 中,你能夠調用任何 Node.js 自帶的 API,或者安裝第三方模塊進行調用:github

const xml2js = require('xml2js');
const parser = new xml2js.Parser();

module.exports =  function(source) {
  this.cacheable && this.cacheable();
  const self = this;
  parser.parseString(source, function (err, result) {
    self.callback(err, !err && "module.exports = " + JSON.stringify(result));
  });
};

這裏咱們事簡單實現一個xml-loader;web

注意:若是是處理順序排在最後一個的 loader,那麼它的返回值將最終交給 webpackrequire,換句話說,它必定是一段可執行的 JS 腳本 (用字符串來存儲),更準確來講,是一個 node 模塊的 JS 腳本,因此咱們須要用 module.exports =導出。

整個過程至關於這個 loader 把源文件npm

// 這裏是 source 模塊

轉化爲json

// example.js
module.exports = '這裏是 source 模塊';

而後交給 require 調用方:segmentfault

// applySomeModule.js
var source = require('example.js'); 
console.log(source); // 這裏是 source 模塊

寫完後咱們要怎麼在本地驗證呢?下面咱們來寫個簡單的demo進行驗證。api

2.一、驗證

首先咱們建立一個根目錄xml-loader,此目錄下 npm init -y生成默認的package.json文件 ,在文件中配置打包命令

"scripts": {
    "dev": "webpack-dev-server"
  },

以後npm i -D webpack webpack-cli,安裝完webpack,在根目錄 建立配置文件webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.xml$/,
        use: ['xml-loader'],
      }
    ]
  },
  resolveLoader: {
    modules: [path.join(__dirname, '/src/loader')]
  },
  devServer: {
    contentBase: './dist',
    overlay: {
      warnings: true,
      errors: true
    },
    open: true
  }
}

在根目錄建立一個src目錄,裏面建立index.js,

import data from './foo.xml';

function component() {
  var element = document.createElement('div');
  element.innerHTML = data.note.body;
  element.classList.add('header');
  console.log(data);
  return element;
}

document.body.appendChild(component());

同時還有一個foo.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<note>
    <to>Mary</to>
    <from>John</from>
    <heading>Reminder  dd</heading>
    <body>Call Cindy on Tuesday dd</body>
</note>

最後把上面的xml-loader放到src/loader文件夾下。
完整的demo源碼請看
最終咱們的運行效果以下圖
圖片描述

至此一個簡單的webpack loader就實現完成了。固然最終使用你能夠發佈到npm上。

三、一些議論知識補充

3.一、得到 Loader 的 options

當咱們配置loader時咱們常常會看到有這樣的配置

ules: [{
    test: /\.html$/,
    use: [ {
      loader: 'html-loader',
      options: {
        minimize: true
      }
    }],
  }]

那麼咱們在loader中怎麼獲取這寫配置信息呢?答案是loader-utils。這個由webpack提供的工具。下面咱們來看下使用方法

const loaderUtils = require('loader-utils');
module.exports = function(source) {
  // 獲取到用戶給當前 Loader 傳入的 options
  const options = loaderUtils.getOptions(this);
  return source;
};

沒錯就是這麼簡單。

3.二、加載本地 Loader

一、path.resolve
能夠簡單經過在 rule 對象設置 path.resolve 指向這個本地文件

{
  test: /\.js$/
  use: [
    {
      loader: path.resolve('path/to/loader.js'),
      options: {/* ... */}
    }
  ]
}

二、ResolveLoader
這個就是上面我用到的方法。ResolveLoader 用於配置 Webpack 如何尋找 Loader。 默認狀況下只會去 node_modules 目錄下尋找,爲了讓 Webpack 加載放在本地項目中的 Loader 須要修改 resolveLoader.modules
假如本地的 Loader 在項目目錄中的 ./loaders/loader-name 中,則須要以下配置:

module.exports = {
  resolveLoader:{
    // 去哪些目錄下尋找 Loader,有前後順序之分
    modules: ['node_modules','./loaders/'],
  }
}

加上以上配置後, Webpack 會先去 node_modules 項目下尋找 Loader,若是找不到,會再去 ./loaders/ 目錄下尋找。
三、npm link
npm link 專門用於開發和調試本地 npm 模塊,能作到在不發佈模塊的狀況下,把本地的一個正在開發的模塊的源碼連接到項目的 node_modules 目錄下,讓項目能夠直接使用本地的 npm 模塊。 因爲是經過軟連接的方式實現的,編輯了本地的 Npm 模塊代碼,在項目中也能使用到編輯後的代碼。

完成 npm link 的步驟以下:

  • 確保正在開發的本地 npm 模塊(也就是正在開發的 Loader)的 package.json 已經正確配置好;
  • 在本地 npm 模塊根目錄下執行 npm link,把本地模塊註冊到全局;
  • 在項目根目錄下執行 npm link loader-name,把第2步註冊到全局的本地 Npm 模塊連接到項目的 node_moduels 下,其中的 loader-name 是指在第1步中的package.json 文件中配置的模塊名稱。

連接好 Loader 到項目後你就能夠像使用一個真正的 Npm 模塊同樣使用本地的 Loader 了。(npm link不是很熟,複製被人的)

3.三、緩存加速

在有些狀況下,有些轉換操做須要大量計算很是耗時,若是每次構建都從新執行重複的轉換操做,構建將會變得很是緩慢。 爲此,Webpack 會默認緩存全部 Loader 的處理結果,也就是說在須要被處理的文件或者其依賴的文件沒有發生變化時, 是不會從新調用對應的 Loader 去執行轉換操做的。

若是你想讓 Webpack 不緩存該 Loader 的處理結果,能夠這樣:

module.exports = function(source) {
  // 關閉該 Loader 的緩存功能
  this.cacheable(false);
  return source;
};

3.四、處理二進制數據

在默認的狀況下,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;

以上代碼中最關鍵的代碼是最後一行 module.exports.raw = true;,沒有該行 Loader 只能拿到字符串。

3.五、同步與異步

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);
    });
};

參考

編寫一個webpack loader

相關文章
相關標籤/搜索