创见博客
webpack loader 的 API 详解:loader context 全解析
七崽爱吃小饼干2026/09/19阅读 0专栏 webpack原理

写 loader 时,函数里总是用到 this.getOptions()、this.async()、this.addDependency() 这些东西。它们并不是全局 API,而是 webpack 在执行 loader 时注入的上下文对象。这个上下文提供了配置读取、异步控制、路径信息、依赖管理等能力。本文系统梳理 loader 可用的 API。

一、loader 里的 this 是什么

当 webpack 调用一个 loader 时,会以「caller」的形式把 loader 函数执行在一个**上下文对象(loader context)**上:

js
module.exports = function (source) {
  // 这个 this 就是 loader context
  console.log(this.resourcePath);
  return source;
};

所以:

  • 不要用箭头函数写 loader,否则拿不到 this。
  • 这个 this 由 webpack 注入,提供了下文所有 API。

二、读取配置:getOptions

this.getOptions(schema)

获取 loader 的配置项,是读取 options 的标准方式:

js
module.exports = function (source) {
  const options = this.getOptions(); // 等价于 loader 的 options
  return source.replace(/__NAME__/g, options.name);
};

它还支持 JSON Schema 校验,配置不合法时直接报错:

js
const schema = {
  type: 'object',
  properties: {
    name: { type: 'string' },
  },
  additionalProperties: false,
};

module.exports = function (source) {
  const options = this.getOptions(schema);
  // ...
};

旧代码里的 this.query 是它的前身,已不推荐使用;当 options 是字符串时它才有值。新代码一律用 this.getOptions()。

三、同步与异步控制

this.async()

声明这是一个异步 loader,返回一个 callback:

js
module.exports = function (source) {
  const callback = this.async();
  setTimeout(() => {
    callback(null, source + '\n// async');
  }, 100);
};

this.callback(err, content, sourceMap, meta)

用于同步或异步返回结果,并携带 Source Map 与元信息:

js
module.exports = function (source) {
  // 同步也可以使用 this.callback
  this.callback(null, source, null, { custom: true });
};

需要注意:

  • 调用 this.async() 后必须使用它返回的 callback,且只能调用一次。
  • 使用 this.callback 时不要再 return 内容。
  • meta 会传递给下一个 loader,可通过 this.data 之外的方式传递额外信息。

四、路径与请求信息

这些属性帮你判断「当前在处理哪个文件」:

API含义示例
this.resourcePath资源文件的绝对路径/src/style.css
this.resourceQuery资源路径的查询串?modules
this.resourceresourcePath + resourceQuery/src/style.css?modules
this.context资源所在目录/src
this.rootContext项目根目录/project
this.request完整的请求串(loaders + 资源)/a-loader!./b.css
this.remainingRequest当前 loader 右侧剩余部分css-loader!./b.css
this.previousRequest当前 loader 左侧部分a-loader
this.currentRequest当前 loader 自身的请求b-loader
this.loaders整条 loader 链的数组[{...}, {...}]
this.loaderIndex当前 loader 在链中的下标1

这些信息常用于:

  • 根据文件路径决定是否处理(如只处理 src 下的文件)。
  • 根据 resourceQuery 区分不同处理方式。
  • 在 pitch 中利用 remainingRequest / previousRequest 重组请求。

五、依赖管理(watch 相关)

当 loader 的处理依赖于其他文件时,必须把这些文件声明为依赖,否则文件变化时 webpack 不会重新构建:

API作用
this.addDependency(file)添加单个文件依赖
this.addContextDependency(dir)添加目录依赖(目录内文件变化都触发重建)
this.addMissingDependency(file)声明一个「当前不存在但未来可能创建」的文件依赖
this.clearDependencies()清空已收集的依赖
js
const fs = require('fs');

module.exports = function (source) {
  const configPath = path.resolve(__dirname, 'config.json');
  this.addDependency(configPath); // 配置变化时重新执行本 loader
  const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
  return source.replace(/__CONFIG__/g, JSON.stringify(config));
};

六、输出文件与错误提示

this.emitFile(name, content, sourceMap, assetInfo)

让 loader 直接向产物目录写出一个文件:

js
module.exports = function (source) {
  this.emitFile('generated/report.txt', 'some content');
  return source;
};

this.emitWarning(warning) / this.emitError(error)

输出警告或错误。emitWarning 不中断构建,emitError 会记录错误:

js
this.emitWarning(new Error('这里有个可疑的写法'));
this.emitError(new Error('必须修复的错误'));

this.getLogger(name)

获取一个 logger,输出结构化的日志:

js
const logger = this.getLogger('my-loader');
logger.warn('构建警告');
logger.error('构建错误');

七、解析与加载其他模块

有时 loader 需要在内部解析路径或加载别的模块。

this.resolve(context, request, callback)

解析一个请求的绝对路径:

js
module.exports = function (source) {
  const callback = this.async();
  this.resolve(this.context, './config.js', (err, resolvedPath) => {
    if (err) return callback(err);
    callback(null, `/* resolved: ${resolvedPath} */\n${source}`);
  });
};

不传 callback 时返回 Promise。this.getResolve(options) 可以拿到一个带自定义解析选项的 resolve 函数。

this.loadModule(request, callback)

加载并编译另一个模块,拿到其源码:

js
this.loadModule('./other.js', (err, source, sourceMap, module) => {
  // ...
});

this.importModule(request, options, callback)

以模块方式导入,支持 layer 等高级选项;不传 callback 时返回 Promise。

八、环境与编译信息

API含义
this.mode当前模式:'development' / 'production' / 'none'
this.target编译目标(如 'web'、'node')
this.sourceMap是否需要生成 Source Map
this.hot是否开启了热更新
this.environment目标环境支持的语法特性信息
this.webpack是否为 webpack 环境(布尔值)
this.versionwebpack 版本号
this.fs输入文件系统,用于读取文件
this.data在 pitch 与 normal 之间共享的数据对象

this.data 在上一篇文章讲 pitch 时提过,是同一个 loader 的 pitch 与 normal 阶段之间传递数据的容器:

js
module.exports = function (source) {
  return `/* start: ${this.data.start} */\n${source}`;
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  data.start = Date.now();
};

this.cacheable() 在早期版本用于声明结果可缓存,webpack 5 中已基本废弃,通常无需调用。

九、一个综合示例

下面这个 loader 组合使用了多个 API——读取配置、声明依赖、异步解析、输出警告:

js
const fs = require('fs');
const path = require('path');

module.exports = function (source) {
  const callback = this.async();
  const options = this.getOptions();
  const configPath = path.resolve(this.rootContext, options.config || 'app.config.json');

  // 声明依赖:配置文件变化时重新构建
  this.addDependency(configPath);

  this.resolve(this.context, options.template || './banner.txt', (err, bannerPath) => {
    let banner = '';
    if (!err) {
      this.addDependency(bannerPath);
      banner = fs.readFileSync(bannerPath, 'utf-8');
    } else {
      this.emitWarning(new Error('未找到 banner 模板,已跳过'));
    }

    const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
    const result = `${banner}\n/* env: ${config.env} */\n${source}`;
    callback(null, result);
  });
};

十、注意事项

  1. 不要用箭头函数写 loader,否则 this 不是 loader context。
  2. 读取选项用 this.getOptions(),不要再用 this.query。
  3. 异步 loader 必须调用 callback 且仅一次,否则构建挂起。
  4. 依赖文件一定要 addDependency,否则 watch 模式下改动不触发重建。
  5. 只用公开 API,this._compiler、this._module 等下划线属性属于内部实现,随时可能变。
  6. 保持 loader 无副作用,写产物文件用 emitFile 这类受控方式,而不是直接操作文件系统写输出。

小结

类别代表 API
读取配置getOptions
同步/异步async、callback
路径信息resourcePath、resourceQuery、context、request
依赖管理addDependency、addContextDependency、addMissingDependency
输出与提示emitFile、emitWarning、emitError、getLogger
解析加载resolve、getResolve、loadModule、importModule
环境信息mode、target、sourceMap、hot、webpack、version
阶段传值data

loader context 是 webpack 给 loader 的「能力接口」。记住它大致分为「读配置、控异步、拿路径、报依赖、做输出、解模块」几类,写 loader 时就不会面对一个空白的函数不知所措了。

评论
0/100