如何编写自定义webpack loader?

2021-06-22  本文已影响0人  努力学习的小丸子

https://webpack.docschina.org/contribute/writing-a-loader/

书写规范

loader 需导出一个函数

监听文件实时编译

const path = require("path");
const fs = require("fs");
export default function (source) {
  var callback = this.async();
  var headerPath = path.resolve("header.js");
  // 当header.js文件发生改变时会触发编译
  this.addDependency(headerPath);

  fs.readFile(headerPath, "utf-8", function (err, header) {
    if (err) return callback(err);
    callback(null, header + "\n" + source);
  });
}
webpack 5 新特性

webpack 5 发布后,在 loader 的上下文中,会带有内置的 this.getOptions 方法。这对于那些使用之前推荐 schema-utils 中的 getOptions 方法的 loader 而言,这是一个重大更新。

模块依赖

根据模块的类型,可以使用不同的模式来指定依赖项。例如在CSS中,使用@import和url(…)语句。这些依赖关系应该由模块系统来解决。
这可以通过两种方式之一来实现:

对于 less-loader ,它不能将每个@import转换为require,因为所有的.less文件必须一次编译一次,用于变量和mixin跟踪。因此,less loader使用自定义路径解析逻辑扩展了less compiler。然后它利用了第二种方法this.resolve来解析依赖。

绝对路径

不要在模块代码中插入绝对路径,因为当项目的根被移动时,它们会破坏散列。loader-utils中有一个stringifyRequest方法,可以用来将绝对路径转换为相对路径。

jtest 测试

使用webpack和memfs来避免输出文件到磁盘

// compiler.js
import path from 'path';
import webpack from 'webpack';
import { createFsFromVolume, Volume } from 'memfs';
export default (fixture, options = {}) => {
    const compiler = webpack({
        context: __dirname,
        entry: `./${fixture}`,
        output: {
            path: path.resolve(__dirname),
            filename: 'bundle.js',
        },
        module: {
            rules: [
                {
                    test: /\.js$/,
                    use: {
                        loader: 'replace-loader',
                        options,
                    },
                },
            ],
        },
        resolveLoader: {
            modules: ["../loader"], // 配置loader的查找目录
        },
    });
    compiler.outputFileSystem = createFsFromVolume(new Volume());
    compiler.outputFileSystem.join = path.join.bind(path);
    return new Promise((resolve, reject) => {
        compiler.run((err, stats) => {
            if (err) reject(err);
            if (stats.hasErrors()) reject(stats.toJson().errors);
            resolve(stats);
        });
    });
};
// loader.test.js
import compiler from './compiler.js';

test('Inserts name and outputs JavaScript', async () => {
    const stats = await compiler('example.js', { name: 'Alice' });
    const output = stats.toJson({ source: true }).modules[0].source;
    console.log(output);
    expect(output).toBe("console.log('hello Alice');");
});
// example.js
console.log('hello world');
// package.json
"test": "jest"

其他代码:

// loader/replace-loader.js
// 注意导出的函数不能是箭头函数
// webpack 5 发布后,在 loader 的上下文中,会带有内置的 this.getOptions 方法。
// 这对于那些使用之前推荐 schema-utils 中的 getOptions 方法的 loader 而言,这是一个重大更新:
import { getOptions } from 'loader-utils';
import { validate } from 'schema-utils';
const schema = {
    type: 'object',
    properties: {
        test: {
            type: 'string'
        }
    }
};
export default function(source) {
    const options = getOptions(this);
    validate(schema, options, {
        name: 'Example Loader',
        baseDataPath: 'options'
    });
    return source.replace(/world/g, options.text);
}
// vue.config.js
configureWebpack: {
    module: {
      rules: [
        {
          test: /\.js$/i,
          use: ["replace-loader"],
        },
      ],
    },
    resolveLoader: {
      modules: ["./node_modules", "./loader"], // 配置loader的查找目录
    },
  }
上一篇下一篇

猜你喜欢

热点阅读