天天看點

如何編寫一個 Webpack Loader

前言

在平時自己由零搭建項目時,雖然基礎配置都比較熟悉,比如配置 file-loader, url-loader, css-loader 等,配置不難,但究竟是怎麼起作用的呢,今天就來說說如何編寫一個 Webpack Loader。

Loader 作用

按我自己的簡單了解,loader 通常指打包的方案,即按什麼方式來處理打包,打包的時候它可以拿到子產品源代碼,經過特定 loader 的轉換後傳回新的結果。

比如 sass-loader 可以把 SCSS 代碼轉換成 CSS 代碼

編寫 Loader

保持功能單一

我們項目中可能會配置很多,但要記住,要保持一個 Loader 的功能單一,避免做多種功能,隻需完成一種功能轉換即可。

是以如 less 檔案轉換成 css 檔案,也不是一步到位,而是 less-loader, css-loader, style-loader 幾個 loader 的鍊式調用才能完成轉換。

子產品

因為 Webpack 本身是運作在 Node.js 之上的,一個 loader 其實就是一個 node 子產品,這個子產品導出的是一個函數,即:

module.exports = function (source) {
  // source 為 compiler 傳遞給 Loader 的一個檔案的原内容
  // 處理...
  return source // 需要傳回處理後的内容
}
複制代碼
           

這個導出的函數的工作就是獲得處理前的原内容,對原内容執行處理後,傳回處理後的内容。

替換字元串的 loader

比如我們打包時,想要替換源檔案的字元串,這時可以考慮使用 Loader,因為 loader 就是獲得源檔案内容然後對其進行處理,再傳回。

比如 src 目錄下有三個檔案:

src/msg1.js

export const msg1 = '學習架構'
複制代碼
           

src/msg2.js

export const msg2 = '深入了解JS'
複制代碼
           

src/index.js

import { msg1 } from './msg1'
import { msg2 } from './msg2'

function print() {
  console.log(`輸出:${msg1}, ${msg2}`)
}

print()
複制代碼
           

做的事情則是把 msg1 和 msg2 兩個檔案導入,然後輸出兩個字元串。

我們要做的事也很簡單,把"架構"轉為"React 架構", "JS"轉為"JavaScript"。

建立

src/loaders/replaceLoader.js

檔案,

module.exports = function (source) {
  const handleContent = source.replace('架構', 'React架構').replace('JS', 'JavaScript')
  return handleContent
}
複制代碼
           

就這樣,loader 寫完了!!!

上面我們講到,source 是源檔案内容,如果列印的話,則是:

如何編寫一個 Webpack Loader

使用 Loader

接下來,我們要來使用它,在根目錄下建立檔案 webpack.config.js

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  module: {
    rules: [
      {
        test: /\.js$/,
        use: './src/loaders/replaceLoader.js',
      },
    ],
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
  },
}
複制代碼
           

執行

npx webpack

, 檢視打包結果

dist/main.js

(()=>{"use strict";console.log("輸出:學習React架構, 深入了解JavaScript")})();
複制代碼
           

替換成功!

需要注意的是,

use

裡面填寫的 loader 是去

node_modules

目錄裡面找的,由于我們是自定義的 loader,是以不能直接寫

use: 'replaceLoader'

,但直接寫路徑的方式未免難看點,我們可以通過 webpack 來配置:

module.exports = {
  resolveLoader: {
    modules: ['node_modules', './src/loaders'], // node_modules找不到,就去./src/loaders找
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'replaceLoader',
      },
    ],
  },
}
複制代碼
           

擷取 loader 的 options

寫完之後,讓我們來想想,其實就是寫一個功能函數嘛。

當然,這隻是最簡單的例子,如果 loader 可以傳入參數呢,比如:

module: {
  rules: [
    {
      test: /\.js$/,
      use: {
        loader: 'replaceLoader',
        options: {
          params: 'replaceString',
        },
      },
    },
  ],
},
複制代碼
           

這個時候可以使用

this.query

來擷取,通過

this.query.params

就能拿到,這裡需要注意的是,this 上下文是有用的,是以這個 loader 導出函數不能是箭頭函數。

但 webpack 更推薦

loader-utils

子產品來擷取,它提供了許多有用的工具,最常用的一種工具是擷取傳遞給 loader 的選項。

首先要安裝

npm i -D loader-utils
複制代碼
           

修改

src/loaders/replaceLoader.js

const { getOptions } = require('loader-utils')

module.exports = function (source) {
  console.log(getOptions(this)) // { params: 'replaceString' }
  console.log(this.query.params) // replaceString
  const handleContent = source.replace('架構', 'React架構').replace('JS', 'JavaScript')
  return handleContent
}
複制代碼
           

這裡需要注意的是,

getOptions(this)

參數傳入的是 this,也就是說

列印結果:

{ params: 'replaceString' }
{ params: 'replaceString' }
{ params: 'replaceString' }
複制代碼
           

this.callback()

上面都是傳回原來内容轉換後的内容,但有些場景下還需要傳回其他東西比如 sourceMap

module.exports = function (source) {
  // 告訴 Webpack 傳回的結果
  this.callback(null, source, sourceMaps)
}
複制代碼
           

另外也不需要 return 了,是以也可使用此 API 替代 return

const { getOptions } = require('loader-utils')

module.exports = function (source) {
  const handleContent = source.replace('架構', 'React架構').replace('JS', 'JavaScript')
  this.callback(null, handleContent)
}
複制代碼
           

自定義 loader 應用場景

  1. 在所有 function 外面加一層 try catch 代碼塊捕獲錯誤,避免手動繁瑣添加。
  2. 實作中英文替換:可以将文字用占位符如

    {{ title }}

    包裹,檢測到占位符則根據環境變量替換為中英文。

繼續閱讀