在 Webpack 配置中,大多数人最先接触到的 loader 匹配字段是 test。比如用 test: /\.js$/ 把所有 JavaScript 文件交给 babel-loader 处理。这个写法确实简单,但当项目规模变大、模块来源变复杂之后,仅仅根据文件后缀或路径来筛选模块往往不够用。比如一个组件库既包含源码中的 .js 文件,又包含第三方依赖里的 .js 文件,如果希望只对源码目录做转换,就需要更精确的匹配条件。Webpack 的规则系统提供了 resource 和 issuer 两个维度,分别描述“当前文件本身是谁”和“谁引用了当前文件”,把它们组合起来可以精确划定 loader 的作用范围。

先明确一个核心概念:在 Webpack 处理模块依赖图时,每个被处理的文件都有一个 resource,也就是这个文件的绝对路径,例如 /project/src/index.js。同时,如果这个文件是被另一个模块通过 import 或 require 语句引用的,那么引用它的那个模块路径就称为 issuer。比如入口文件 /project/src/main.js 里写了一句 import './utils/helper.js',当 Webpack 处理 helper.js 时,resource 是 helper.js 的路径,issuer 是 main.js 的路径。入口文件没有上级引用者,所以它的 issuer 是 undefined。
从 test 到 resource:匹配对象的变化
常见的 test、include、exclude 字段,本质上都是针对 resource 的简写。它们并不是独立的规则维度,而是 Webpack 在内部把它们合并成了 resource 条件。也就是说下面两种写法是等价的:
// 写法一:直接使用 test
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
}
]
}
};
// 写法二:显式声明 resource
module.exports = {
module: {
rules: [
{
resource: {
test: /\.js$/
},
use: 'babel-loader'
}
]
}
};
这两种配置的结果完全一致,都表示只匹配绝对路径以 .js 结尾的模块。区别在于显式使用 resource 对象后,可以在同一个字段里写多个子条件。比如既要限制文件后缀为 .js,又要限制文件位于 src 目录下,就可以写成:
module.exports = {
module: {
rules: [
{
resource: {
test: /\.js$/,
include: path.resolve(__dirname, 'src')
},
use: 'babel-loader'
}
]
}
};
这里的 include 是路径数组或字符串,Webpack 会判断 resource 是否包含在这些路径中。如果还希望排除某些子目录,可以再加上 exclude。这种写法比在规则顶层直接写 test 和 include 更内聚,条件之间的逻辑关系也更清晰。需要明确的是,顶层 include 和 exclude 同样作用于 resource,当顶层与 resource 对象同时出现时,Webpack 会把两者合并成一个 resource 条件。
匹配值除了字符串和正则,还可以是函数。函数接收当前模块的 resource 路径作为参数,返回 true 或 false,适合处理无法用正则表达的复杂目录逻辑。例如判断路径中是否包含 /pages/ 且不包含 /test/,就可以用函数实现。
issuer:谁引用了当前模块
有些场景下,需要根据引用来源来决定是否应用 loader。最常见的就是 CSS Modules 按目录开启:只对页面组件引用的样式文件做模块化处理,而第三方库里的样式文件保持原样。此时 issuer 条件可以限定引用者的路径范围。例如:
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true
}
}
],
issuer: {
include: path.resolve(__dirname, 'src/pages')
}
},
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: false
}
}
]
}
]
}
};
第一条规则只对 issuer 路径位于 src/pages 目录下的 CSS 文件开启 CSS Modules,第二条规则作为兜底处理其他 CSS 文件。注意规则匹配有优先级,Webpack 会按数组顺序尝试匹配,第一条命中了就不会继续走第二条。这种设计可以让同一种资源在不同来源下使用完全不同的 loader 配置。
issuer 同样支持正则、字符串、数组和函数。与 resource 不同的是,issuer 的值可能为空。对于入口模块以及某些由 Webpack 内部生成的模块,issuer 可以是 undefined 或 null。如果在条件中写 issuer: /\.js$/,入口文件不会匹配这条规则,因为它的 issuer 是 undefined,正则测试会失败。如果希望专门匹配没有 issuer 的入口模块,可以使用函数来判断。
另一个容易混淆的点是:issuer 并不是依赖链上的所有上级,而是直接引用当前模块的那一个父模块。例如 a.js 引用 b.js,b.js 引用 c.js,处理 c.js 时 issuer 是 b.js 而不是 a.js。这个直接关系决定了匹配规则的粒度,也意味着无法通过单个 issuer 条件追溯多级依赖,如果需要更复杂的传播判断,通常要借助自定义函数或 Webpack 插件。
组合条件与多种匹配类型
Webpack 的规则条件不仅支持 test、include、exclude,还允许在对象中使用 and、or、not 来组合多个条件。以 resource 为例,可以表达“路径匹配 .js 且不来自 node_modules 但可以来自 src/shared”这类混合逻辑。写法如下:
module.exports = {
module: {
rules: [
{
resource: {
and: [
{ test: /\.js$/ },
{ not: /node_modules/ }
]
},
use: 'babel-loader'
}
]
}
};
and 数组中的所有条件必须同时满足,or 数组中的条件满足任意一个即可,not 则对单个条件取反。这些逻辑运算符同样适用于 issuer 和 resourceQuery。例如可以写 issuer: { or: [ /src\/pages/, /src\/components/ ] } 来匹配来自两个不同目录的引用者。
除了 resource 和 issuer,Webpack 还提供了 resourceQuery 和 resourceFragment 两个补充维度。resourceQuery 用于匹配模块路径中的查询字符串,比如 import './image.png?size=small' 中的 ?size=small。结合 oneOf 可以针对不同查询参数使用不同 loader。例如:
module.exports = {
module: {
rules: [
{
test: /\.png$/,
oneOf: [
{
resourceQuery: /size=small/,
use: 'url-loader?limit=8192'
},
{
use: 'file-loader'
}
]
}
]
}
};
resourceFragment 则匹配片段标识符,例如 import './file.js#fragment' 中的 #fragment。虽然实际项目中很少使用,但它的存在让资源匹配覆盖了 URL 的完整结构。掌握这些匹配类型后,可以把 resource 理解为“文件路径条件”,把 issuer 理解为“引用者路径条件”,把 resourceQuery 理解为“资源后缀参数条件”,三者组合起来能够覆盖绝大多数精细化控制需求。
实战:精确控制 loader 作用范围
来看一个更贴近真实项目的例子。假设有一个 Vue 项目,同时使用 vue-loader 处理 .vue 文件、babel-loader 处理 JavaScript 文件,并且所有第三方依赖都放在 node_modules 中。需求是:只对 src 目录下的 JS 文件启用 babel-loader,但要同时转换 node_modules 中某个特定包 my-es6-lib 的 JS 文件;此外,CSS 文件只在被 src/views 下的 Vue 组件引用时才启用 CSS Modules,其他情况使用全局样式。配置如下:
const path = require('path');
module.exports = {
module: {
rules: [
{
test: /\.js$/,
include: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'node_modules/my-es6-lib')
],
use: 'babel-loader'
},
{
test: /\.css$/,
oneOf: [
{
resource: {
test: /\.css$/
},
issuer: {
include: path.resolve(__dirname, 'src/views')
},
use: [
'style-loader',
{
loader: 'css-loader',
options: { modules: true }
}
]
},
{
use: [
'style-loader',
{
loader: 'css-loader',
options: { modules: false }
}
]
}
]
}
]
}
};
第一条规则中 include 同时指定了源码目录和特定第三方包,这样既避免了全量转换 node_modules 带来的性能损耗,又解决了某些发布包未转译 ES6 语法的问题。第二条规则使用 oneOf 将 CSS 处理拆成两条,第一条通过 issuer 限定只有来自 src/views 的引用才开启 CSS Modules,第二条作为兜底处理其他情况。这种结构比直接写多个平级规则更清晰,因为 oneOf 保证了同一种资源只会应用一个分支。
在使用 resource 和 issuer 时,有两个细节值得注意。第一,所有路径匹配都基于文件的绝对路径,因此在配置 include 或 exclude 时应使用 path.resolve 生成绝对路径,避免相对路径在 Windows 或不同工作目录下产生偏差。第二,条件对象中的 include 和 exclude 虽然可以用字符串或正则,但用于路径时建议使用数组或绝对路径字符串,因为正则匹配路径字符串虽然灵活,却容易因为路径分隔符的差异(如 Windows 的反斜杠)导致问题。Webpack 内部会对路径做规范化,但正则直接作用于规范化后的字符串,编写时需要考虑到反斜杠的转义。
最后总结一下:resource 回答“这个文件该不该被处理”,issuer 回答“引用它的文件符不符合条件”。把这两个维度拆开,再配合 and、or、not 以及 resourceQuery,就能在不写复杂函数的情况下实现大部分按来源、按路径、按查询参数的 loader 控制。理解这些规则后,Webpack 配置中的 module.rules 就不只是一堆 test 正则,而是一套可以精确表达模块处理策略的声明式语言。
Webpack loaderresourceissuer匹配规则修改时间:2026-09-26 03:56:07