原稿:语雀 · 实现一个 loader · 原目录:Webpack › Demo › 实现一个 loader

虽然了解了 loader 的概念,但动手实现一个哪怕再简单不过的 loader 也比单纯的纸面了解更加深印象

目标 & 初始化

目标:我们打算实现一个 txt 文件的 loader,准确说是两个:

  • reverse-loader:将 txt 文件的字符逆序
  • toUpperClass-loader:将逆序后的字符全部转为大写

最后希望输出一个 js 文件,这个 bundle 会被 index.html 通过 <script> 加载,在控制台打印处理后的字符

初始化如下,无需其他的 loader 和 plugin

# 目录创建
mkdir txt_loader
cd txt_loader

# npm 初始化
npm init -y

# 安装 webpack 相关包
cnpm install webpack webpack-cli --save-dev

创建 src/loaders,其中创建两个需要的 js 文件:reverse-loader.js,toUpperClass-loader.js

创建 dist/index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>loader</title>
  </head>
  <body>
    <script src="app.js"></script>
  </body>
</html>

单一原则:

每个 Loader 只做一件事,因此我们将两个简单任务分给两个 loader,第一个 loader 将处理后的返回值会链式传递给第二个loader ... ... 链中的最后一个 loader 应该返回 webpack 所期望的 JavaScript 代码


loader 引入

创建 webpack.config.js

const path = require("path");

module.exports = {
  mode: "production",
  entry: {
    app: "./src/test.txt",
  },
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist"),
  },
  module: {
    rules: [
      {
        test: /\.txt$/,
        use: [
          path.resolve(__dirname, "src/loaders/toUpperCase-loader.js"),
          path.resolve(__dirname, "./src/loaders/reverse-loader.js"),
        ],
      },
    ],
  }
};
  • 最重要的就是关于两个 loader 的引入,不同于 node_modules 中模块的引入,自定义的 loader 需要给出路径,也可以通过 resolveLoader 设置,这里直接使用路径
  • 其次注意调用关系,use 中先是 toUpperCase-loader,后是 reverse-loader,但实际的调用链一定是从后向前的

loader 编写

loader 运行在 Node.js 中,通常预期将 loader 模块导出为一个函数,并且编写为 Node.js 兼容的 JS 代码。loader 本事作为函数,其执行自然分同步异步,以下也分成同步和异步编写

同步

// reverse-loader.js
function loader(source){
    source = source.split('').reverse().join('');
    return source;
}

module.exports = loader;

toUpperCase-loader 作为链上最后一个 loader,应当以字符形式返回 JS 代码给 webpack

// toUpperCase-loader.js
function loader(source) {
  source = source.toUpperCase();
  return `console.log('${source}')`;
}

module.exports = loader;

npm run build 同步构建结果

image.png

异步

reverse-loader.js 不变,修改 toUpperCase-loader.js,通过 setTimeout 模拟异步

// toUpperCase-loader.jsfunction loader(source) {  const callback = this.async();  new Promise((resolve) => {    setTimeout(() => {      resolve(source.toUpperCase());    }, 1000);  }).then((source) => callback(null, `console.log('${source}')`));}module.exports = loader;

异步通过 this.async API 实现,它返回一个 callback 函数,调用时可以接收四个参数,第一个必须是 Error 或 null;第二个是一个 string 或 Buffer 对象,用于返回异步执行的结果

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

异步 loader 构建的结果:app.js 中生成了 js 代码,构建时间 1224 ms 确实说明经历了异步执行过程

image.png

完整代码见:https://github.com/NepJNQ/Front-end-Baby/tree/master/webpack/txt-loader