Webpack4不求人系列(4)——自定義Loader

在前面的內容中,咱們學習了Webpack的基本知識、經常使用腳手架和性能優化,雖說大部分的開發場景社區已經又成熟的模塊給咱們使用,可是遇到特殊狀況仍是須要本身有獨立開發的能力,所以今天咱們一塊兒來學習如何編寫自定義Loader。javascript

基本Loader

Webpack中loader是一個CommonJs風格的函數,接收輸入的源碼,經過同步或異步的方式替換源碼後進行輸出。java

module.exports = function(source, sourceMap, meta) {
  
}
  • source是輸入的內容
  • sourceMap是可選的
  • meta是模塊的元數據,也是可選的

須要注意的是,該導出函數必須使用function,不能使用箭頭函數,由於loader編寫過程當中會常常使用到this訪問選項和其餘方法。node

咱們先編寫一個基本的Loader,完成的工做很簡單,那就是把輸出的字符串進行替換。webpack

1.新建loader-example目錄,執行npm初始化,並安裝webpackweb

mkdir loader-example
cd loadeer-example
npm init -y
npm install webpack webpack-cli

2.構建項目目錄npm

|----loader # loader目錄
        |----replace-loader.js # 替換字符串的Loader
|----src   # 應用源碼
        |----index.js # 首頁
|----package.json
|----webpack.config.js

3.編寫loader/replace-loader.jsjson

module.exports = function(source) {
  return source.replace(/World/g, 'Loader');
};

本例中咱們Loader只是簡單的將源碼中的」World「替換成了」Loader「。性能優化

4.編寫src/index.jsbash

console.log('Hello World');

5.編寫webpack.config.jsbabel

const path = require('path');

module.exports = {
  entry: './src/index',
  target: 'node', // 咱們編譯爲Node.js環境下的JS,等下直接使用Node.js執行編譯完成的文件
  output:{
    path: path.resolve(__dirname, 'build'),
    filename: '[name].js'
  },
  module:{
    rules:[
      {
        test:/\.js$/,
        use: 'replace-loader'
      }
    ]
  },
  resolveLoader: {
      modules: ['./node_modules', './loader'] // 配置loader的查找目錄
  }
};

6.編寫package.json

{
  "scripts":{
    "build":"webpack"
  }
}

7.執行構建

npm run build

8.構建完成後,執行build/main.js

node build/main.js

此時終端輸出以下,咱們編寫的Loader工做正常。

Hello Loader

Loader選項

咱們使用第三方loader時常常能夠看到傳遞選項的狀況:

{
  test:/\.js$/,
  use:[
    {
      loader:'babel-loader',
      options:{
        plugins:['@babel/transform-runtime'],
        presets:['@babel/env']
      }
    }
  ]
}

在Loader編寫時,Webpack中官方推薦經過loader-utils來讀取配置選項,咱們須要先安裝。

npm install loader-utils

咱們給剛纔編寫的replace-loader傳遞一個選項,容許自定義替換結果。

const loaderUtils = require('loader-utils');

module.exports = function(source) {
    const options = loaderUtils.getOptions(this);
  return source.replace(/World/g, options.text);
};

接下來編輯webpack.config.js,給replace-loader傳遞選項。

module.exports = {
  module:{
    rules:[
      {
        test:/\.js$/,
        use:[
          {
            loader:'replace-loader',
            options:{
              text: 'Webpack4'
            }
          }
        ]
      }
    ]
  },
  resolveLoader:{
    modules: ['./node_modules', './loader']
  }
};

執行構建以後用Node.js執行build/main.js,能夠看到輸出的內容已經發生變化了。

Hello Webpack4

異步Loader

在Loader中,若是存在異步調用,那麼就沒法直接經過return返回構建後的結果了,此時須要使用到Webpack提供的回調函數將數據進行回調。

Webpack4給Loader提供了this.async()函數,調用以後返回一個callback,callback的簽名以下:

function callback(
  err: Error|null,
  content: string|Buffer,
  sourceMap?:SourceMap,
  meta?: any
)

例如咱們須要在loader中調用setTimeout進行等待,則相應的代碼以下:

module.exports = function(source) {
  const callback = this.async();
  setTimeout(() => {
    const output = source.replace(/World/g, 'Webpack4');
    callback(null, output);
  }, 1000);
}

執行構建,Webpack會等待一秒,而後再輸出構建內容,經過Node.js執行構建後的文件,輸出以下

Hello Webpack4

"Raw" Loader

默認狀況下,資源文件會被轉化爲 UTF-8 字符串,而後傳給 loader。經過設置 raw,loader 能夠接收原始的 Buffer。好比處理非文本文件時(如圖片等等)。

module.exports = function(source) {
  assert(source instanceof Buffer);
  return someSyncOperation(source);
};
module.exports.raw = true; // 設置當前Loader爲raw loader, webpack會將原始的Buffer對象傳入

讀取loader配置文件

babel-loader在使用時能夠加載.babelrc配置文件來配置plugins和presets,減小了webpack.config.js的代碼量,便於維護。接下來咱們編寫一個i18n-loader,經過讀取語言配置文件完成語言轉換。

項目結構

|----loader
        |----i18n-loader.js # loader
|----i18n
        |----zh.json # 中文語言包
|----src
        |----index.js # 入口文件
|----webpack.config.js

i18n/zh.json

{
    "hello": "你好",
    "today": "今天"
}

loader/i18n-loader.js

const loaderUtils = require('loader-utils');
const path = require('path');

module.exports = function (source) {
    const options = loaderUtils.getOptions(this);
    const locale = options ? options.locale : null;

    // 讀取語言配置文件
    let json = null;
    if (locale) {
        const filename = path.resolve(__dirname, '..', 'i18n', `${locale}.json`);
        json = require(filename);
    }

    // 讀取語言標記 {{}}
    const matches = source.match(/\{\{\w+\}\}/g); 
    for (const match of matches) {
        const name = match.match(/\{\{(\w+)\}\}/)[1].toLowerCase();
        if (json !== null && json[name] !== undefined) {
            source = source.replace(match, json[name]);
        } else {
            source = source.replace(match, name);
        }
    }
    return source;
}

src/index.js

console.log('{{Hello}}, {{Today}} is a good day.');

webpack.config.js

const path = require('path');

module.exports = {
    entry: './src/index',
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: '[name].js'
    },
    target: 'node',
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [
                    {
                        loader: 'i18n-loader',
                        options: { // 傳遞選項
                            locale: 'zh'
                        }
                    }
                ]
            }
        ]
    },
    resolveLoader: {
        modules: ['./node_modules', './loader']
    }
};

package.json

{
  "scripts":{
    "build":"webpack"
  }
}

執行構建

npm run build

構建完畢後使用Node.js執行build/main.js輸出以下:

你好, 今天 is a good day.

能夠看到i18n-loader成功讀取了配置文件。

小結

本文簡要介紹了Webpack中如何編寫一個自定義的loader,權當拋磚引玉,更多的用法等待讀者在實際工做中去挖掘,要想掌握Webpack的高級知識,Loader是必不可少的技能,有時候若是社區找不到合適的Loader,你們能夠根據須要本身進行開發。

0

相關文章
相關標籤/搜索