webpack原理解析(二)實現一個簡單的Loader

Loader是什麼

webpack中 loader 是一個很是重要的概念,loader 能夠簡單的理解成一個文件處理器,webpack使用 loader 來處理各種文件,好比 .scss轉成成 css文件, 小圖片轉換成base64圖片。css

本質上講,loader 只是導出爲函數的 JavaScript 模塊,webpack打包的時候,會調用這個函數,把上一個loader產生的結果或資源文件(resource file)傳入進去。webpack

此次我打算開發一個把普通圖片轉換成 .webp 格式的 Loader,以達到減少圖片大小的目的。web

Loader 工具庫

開發 loader 以前,先要了解兩個 loader開發的工具包——loader-utilsschema-utilsnpm

  • loader-utils 用於獲取 loader 的 options
  • schema-utils 用於校驗 loader 的 optionsJSON Schema 結構定義的是否一致。
import { getOptions } from 'loader-utils';
import { validateOptions } from 'schema-utils';
const schema = {
  // ...
}
export default function(source) {
  // 獲取 options
  const options = getOptions(this);
  // 檢驗loader的options是否合法
  validateOptions(schema, options, 'Demo Loader');

  // 在這裏寫轉換 loader 的邏輯
  // ...
};
複製代碼

Loader 開發準則

  • 單一職責:一個 loader 只處理一件事情,保證簡單和可維護,複雜場景能夠用多個 loader 組合完成。
  • 模塊化:保證 loader 是模塊化的。loader 生成模塊須要遵循和普通模塊同樣的設計原則。
  • 無狀態:在屢次模塊的轉化之間,咱們不該該在 loader 中保留狀態。每一個 loader 運行時應該確保與其餘編譯好的模塊保持獨立,一樣也應該與前幾個 loader 對相同模塊的編譯結果保持獨立。

總之一句話,咱們在寫一個 loader 的時候,保持其職責單一,只須要關心輸入和輸出就好。json

同步 loader 和 異步 loader

loader之因此有同步和異步之分,是由於有些資源的處理比較耗時,須要異步處理,等待處理完成後再繼續執行。loader 用 this.callback() 返回處理結果,以下:babel

this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
);
複製代碼
  1. 第一個參數必須是 Error 或者 null
  2. 第二個參數是一個 string 或者 Buffer。
  3. 可選的:第三個參數必須是一個能夠被這個模塊解析的 source map。
  4. 可選的:第四個選項,會被 webpack 忽略,能夠是任何東西(例如一些元數據)。
// 同步loader 返回多個處理結果
module.exports = function(content, map, meta) {
  this.callback(null, someSyncOperation(content), map, meta);
};
複製代碼

同步模式下,若是返回結果只有一個,也能夠直接使用 return 返回結果。異步

// 同步loader 只返回一個處理結果
module.exports = function(content, map, meta) {
  return someSyncOperation(content);
};
複製代碼

異步模式下使用 this.async 來獲取 callback 函數async

module.exports = function(content, map, meta) {
  var callback = this.async();
  someAsyncOperation(content, function(err, result, sourceMaps, meta) {
    if (err) return callback(err);
    callback(null, result, sourceMaps, meta);
  });
};
複製代碼

開發本身的 Loader

項目目錄結構

|--src
| |--cjs.js //commonJs 入口
| |--index.js // loader 主文件
| |--options.json // loader options 定義文件
|--babel.config.js
|--package.json
|--readme.md模塊化

定義 options

options 裏只有一個屬性 quality, 用來控制生成 .webp 文件的質量,定義以下:函數

{
  "additionalProperties": true,
  "properties": {
    "quality": {
      "description": "quality factor (0:small..100:big), default=75",
      "type": "number"
    }
  },
  "type": "object"
}
複製代碼

圖片格式轉換

cwebp 這個js模塊提供了普通圖片和.webp 之間相互轉換的功能。

const CWebp = require('cwebp').CWebp

/** * 普通圖片轉 .webp圖片 * @param {string | buffer} img 圖片絕對路徑或二進制流 * @param {number} quality 生成 webp 圖片的質量,默認75 * @returns .webp 文件流 */
async function convertToWebp (img, quality = 75) {
  let encoder = new CWebp(img)
  encoder.quality = quality
  let buffer = await encoder.toBuffer()
  return buffer
}
複製代碼

編寫 loader

import schema from './options.json'

export default async function loader (content) {
  // 異步模式
  let callback = this.async()
  // 獲取 options
  const options = loaderUtils.getOptions(this) || {}
  // 驗證 options
  validateOptions(schema, options, {
    name: 'webp Loader',
    baseDataPath: 'options'
  })
  try {
    // 普通圖片轉 .webp
    let buffer = await convertToWebp(content, options.quality)
    callback(null, buffer)
  } catch (err) {
    callback(err)
  }
}
// loader 接收文件流
export const raw = true
複製代碼

到這裏這個 Loader 就基本完成了。

使用

// webpack.config.js
module.exports = {
  // 其餘配置
  // ...
  module: {
    rules: [{
      test: /\.(png|jpg|gif)$/,
      use: [{
        loader: 'file-loader',
        options: {
          name: '[name].[ext].webp'
        }
      },{
        loader: 'webp-loader',
        options: {
          quality: 70 
        }
      }]
    }]
  }
}
複製代碼

總結

其實寫一個 Loader 並無想象中的那麼複雜,只要掌握了基本要點,你也能夠擁有本身的 Loader。

關注咱們

相關文章
相關標籤/搜索