webpack 如何编写 loader


loader 执行顺序

分类

  • pre: 前置 loader
  • normal: 普通 loader
  • inline: 内联 loader
  • post: 后置 loader

执行顺序

4 类 loader 的执行优级为:pre > normal > inline > post
相同优先级的 loader 执行顺序为:从右到左,从下到上。
例如:

// 此时loader执行顺序:loader3 - loader2 - loader1
module: {
  rules: [
    {
      test: /\.js$/,
      loader: "loader1",
    },
    {
      test: /\.js$/,
      loader: "loader2",
    },
    {
      test: /\.js$/,
      loader: "loader3",
    },
  ],
},
// 此时loader执行顺序:loader1 - loader2 - loader3
module: {
  rules: [
    {
      enforce: "pre",
      test: /\.js$/,
      loader: "loader1",
    },
    {
      // 没有enforce就是normal
      test: /\.js$/,
      loader: "loader2",
    },
    {
      enforce: "post",
      test: /\.js$/,
      loader: "loader3",
    },
  ],
},

开发一个 loader

1. 最简单的 loader

// loaders/loader1.js
module.exports = function loader1(content) {
  console.log("hello loader");
  return content;
};

它接受要处理的源码作为参数,输出转换后的 js 代码。

2. loader 接受的参数

  • content 源文件的内容
  • map SourceMap 数据
  • meta 数据,可以是任何内容

loader 的使用方式

  • 配置方式:在 webpack.config.js 文件中指定 loader。(pre、normal、post loader)
  • inline loader
    • 用法:import Styles from ‘style-loader!css-loader?modules!./styles.css’;
    • 含义:
      • 使用 css-loader 和 style-loader 处理 styles.css 文件
      • 通过 ! 将资源中的 loader 分开
        inline loader 可以通过添加不同前缀,跳过其他类型 loader。
      • ! 跳过 normal loader。
        import Styles from ‘!style-loader!css-loader?modules!./styles.css’;
      • -! 跳过 pre 和 normal loader。
        import Styles from ‘-!style-loader!css-loader?modules!./styles.css’;
      • !! 跳过 pre、 normal 和 post loader。
        import Styles from ‘!!style-loader!css-loader?modules!./styles.css’;

loader 分类

1. 同步 loader

module.exports = function (content, map, meta) {
  return content;
};

this.callback 方法则更灵活,因为它允许传递多个参数,而不仅仅是 content

module.exports = function (content, map, meta) {
  // 传递map,让source-map不中断
  // 传递meta,让下一个loader接收到其他参数
  this.callback(null, content, map, meta);
  return; // 当调用 callback() 函数时,总是返回 undefined
};

2. 异步 loader

module.exports = function (content, map, meta) {
  const callback = this.async();
  // 进行异步操作
  setTimeout(() => {
    callback(null, result, map, meta);
  }, 1000);
};

由于同步计算过于耗时,在 Node.js 这样的单线程环境下进行此操作并不是好的方案,我们建议尽可能地使你的 loader 异步化。但如果计算量很小,同步 loader 也是可以的。

Raw Loader

默认情况下,资源文件会被转化为 UTF-8 字符串,然后传给 loader。通过设置 raw 为 true,loader 可以接收原始的 Buffer。

module.exports = function (content) {
  // content是一个Buffer数据
  return content;
};
module.exports.raw = true; // 开启 Raw Loader

Pitching Loader

module.exports = function (content) {
  return content;
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  console.log("do somethings");
};

webpack 会先从左到右执行 loader 链中的每个 loader 上的 pitch 方法(如果有),然后再从右到左执行 loader 链中的每个 loader 上的普通 loader 方法。
在这里插入图片描述
在这个过程中如果任何 pitch 有返回值,则 loader 链被阻断。webpack 会跳过后面所有的的 pitch 和 loader,直接进入上一个 loader 。
在这里插入图片描述


loader API

方法名含义用法
this.async异步回调 loader。返回 this.callbackconst callback = this.async()
this.callback可以同步或者异步调用的并返回多个结果的函数this.callback(err, content, sourceMap?, meta?)
this.getOptions(schema)获取 loader 的 optionsthis.getOptions(schema)
this.emitFile产生一个文件this.emitFile(name, content, sourceMap)
this.utils.contextify返回一个相对路径this.utils.contextify(context, request)
this.utils.absolutify返回一个绝对路径this.utils.absolutify(context, request)

更多文档,请查阅 webpack 官方 loader api 文档open in new window


三种本地开发测试 loader 的方法

1. 匹配(test)单个 loader

你可以通过在 rule 对象使用 path.resolve 指定一个本地文件:

webpack.config.js

const path = require('path');

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

2. 匹配(test)多个 loaders

你可以使用 resolveLoader.modules 配置,webpack 将会从这些目录中搜索这些 loaders。例如,如果你的项目中有一个 /loaders 本地目录:

webpack.config.js

const path = require('path');

module.exports = {
  //...
  resolveLoader: {
    modules: ['node_modules', path.resolve(__dirname, 'loaders')],
  },
};

3. npm link

如果你已经为 loader 创建了独立的库和包,你可以使用 npm link 来将其链接到你要测试的项目。


参数

1. 当一个 loader 在资源中使用,这个 loader 只能传入一个参数 - 一个包含资源文件内容 字符串


2. loader 会返回一个或者两个值。第一个值的类型是 JavaScript 代码的字符串或者 buffer


3. loader 可以被链式调用意味着不一定要输出 JavaScript。只要下一个 loader 可以处理这个输出,这个 loader 就可以返回任意类型的模块(对外开源的loader一定要返回字符串或者 buffer


项目目录

让我们实现一个简易的raw-loader,我们将其命名为 my-raw-loader。这个loader的功能就是支持javascript引用文件原始文本内容

├── loaders                     # loader目录
├── src                         # 业务代码
│   │── happy-new-year.txt
│   │── index.html
│   └── index.js										
├── .gitignore
├── package.json
├── package-lock.json
└── webpack.config.js			# webpack 配置文件

搭建项目

mkdir my-loader
cd my-loader
npm init -y
npm i -D webpack webpack-cli html-webpack-plugin webpack-dev-server loader-utils

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
  mode: 'development',
  plugins: [
    new HtmlWebpackPlugin({
      title: '自定义 webpack loader',
      template: './src/index.html',
    }),
  ],
};

src/index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>

</body>
</html>

src/index.js

import text from './happy-new-year.txt';

const textDom = document.createElement('p');
textDom.style.cssText = 'width: 200px;height: 200px;background-color: pink;';
textDom.innerText = text;
document.body.appendChild(textDom);

src/happy-new-year.txt

🎉🎉🎆🎆🧨🧨
新年快乐!大吉大利!
🎉🎉🎆🎆🧨🧨

执行 npx webpack-dev-server,会发现编译报错了

在这里插入图片描述

那么下面我们就实现 my-raw-loader 来抛砖引玉!


my-raw-loader

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /.txt$/,
        use: [
          {
            loader: path.resolve(__dirname, 'loaders/my-raw-loader'),
            options: {
              esModule: true,
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: '自定义 webpack loader',
      template: './src/index.html',
    }),
  ],
};

loaders/my-raw-loader.js

function myRawLoader(source) {
  console.log('source', source);
}

module.exports = myRawLoader;

执行 npx webpack-dev-server 可以看到打印结果,这个参数是一个字符串

在这里插入图片描述


简单实现一个 loader

修改 loaders/my-raw-loader.js

function myRawLoader(source) {
  // 提取给定的 loader 选项,
  // 从 webpack 5 开始,this.getOptions 可以获取到 loader 上下文对象。它用来替代来自 loader-utils 中的 getOptions 方法。
  const { esModule } = this.getOptions();
  console.log('esModule:', esModule);

  // 这里一定要返回字符串或者 buffer
  if (!esModule) {
    return `module.exports = ${JSON.stringify(source)}`;
  }
  return `export default ${JSON.stringify(source)}`;
}

module.exports = myRawLoader;

执行 npx webpack-dev-server可以看到通过 this.getOptions() 获取到了当前 loader 的配置,并且编译未报错,访问 http://localhost:8080/ 页面得偿所愿!成功读取并渲染了原始文本内容。

在这里插入图片描述

在这里插入图片描述


schema-utils

schema-utils 由webpack 官方提供, 它配合 loader-utils,用于保证 loader 选项,进行与 JSON Schema 结构一致的校验

const { validate } = require('schema-utils');
const schema = {
  type: 'object',
  properties: {
    esModule: {
      type: 'boolean',
    }
  },
  "additionalProperties": false // 是否允许不存在的选项传入
};

function myRawLoader(source) {
  const options = this.getOptions();
  validate(schema, options, {
    name: 'my-raw-loader',
    baseDataPath: 'options',
  });

  // 提取给定的 loader 选项,
  // 从 webpack 5 开始,this.getOptions 可以获取到 loader 上下文对象。它用来替代来自 loader-utils 中的 getOptions 方法。
  console.log('esModule:', options.esModule);

  // 这里一定要返回字符串或者 buffer
  if (!options.esModule) {
    return `module.exports = ${JSON.stringify(source)}`;
  }
  return `export default ${JSON.stringify(source)}`;
}

module.exports = myRawLoader;

如果传入未定义的选项,则会发生编译报错

{
  test: /.txt$/,
  use: [
    {
      loader: path.resolve(__dirname, 'loaders/my-raw-loader'),
      options: {
        esModule2: true,
      },
    },
  ],
},

在这里插入图片描述



源码地址:https://gitee.com/yanhuakang/webpack-demos/tree/master/proficient/step_1-custom-loader

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__畫戟__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值