CircularDependencyRspackPlugin
检测运行时模块之间的循环导入依赖关系。当运行时调用的函数使用时,循环引用通常不表示存在错误,但当在初始化期间使用导入时,可能会导致错误或错误。此插件不会区分这两者,但可用于在遇到错误后帮助识别和解决循环。
new rspack.CircularDependencyRspackPlugin(options);
对比
在使用 rspack.CircularDependencyRspackPlugin
之前,请注意 rspack.CircularDependencyRspackPlugin
和社区 circular-dependency-plugin
存在一些差异。
性能
由于 rspack.CircularDependencyRspackPlugin
是基于 Rust 实现的,因此他能直接直接和 rspack 模块图进行集成,避免了昂贵的复制以及序列化成本。最重要的是,rspack.CircularDependencyRspackPlugin
这个插件对每个 entry 的模块图进行一次遍历来识别所有的循环引用,而不是单独去检查每个模块。
综合来看,这意味着 rspack.CircularDependencyRspackPlugin
性能要更好,甚至可以成为 hot reload 周期的一部分,而不会对 hot reload 时间产生任何的影响,即使对于拥有数十万个模块和导入的超大项目来说也是如此。
功能
rspack.CircularDependencyRspackPlugin
尽量保证与 circular-dependency-plugin
功能兼容,并进行了修改用于更精细地去控制循环依赖的检测与行为。
两者之间的显著区别在于 Rspack 中的循环依赖项使用了模块标识符而非相对路径。其中模块标识符代表整个 bundle module 的唯一名称,包括处理该模块的 loader 集合、绝对路径以及导入时提供的任何请求参数。
虽然仍然可以只匹配对应 module 的路径,但标识符同样也允许匹配 loader 以及对应的 loader rule 集合。
本插件还提供了一个新的配置项 ignoredConnections
, 用来更加精细地控制是否忽略循环依赖。原始 webpack 插件中的 exclude
配置项同样支持,但会导致完全忽略包含模块的循环依赖。当只需要忽略特定的循环依赖时,ignoredConnections
可以指定需要匹配的 from
和 to
配置,用于忽略两个模块之间存在的明确的循环依赖引入。
示例
- 检测构建编译中存在的所有循环依赖,忽略掉
node_modules
中的一些包,并且为每个循环依赖都抛出编译报错。
rspack.config.mjs
import { rspack } from '@rspack/core';
export default {
entry: './src/index.js',
plugins: [
new rspack.CircularDependencyRspackPlugin({
failOnError: true,
exclude: /node_modules/,
}),
],
};
- 忽略两个模块之间的特定循环依赖,类似通过
to
的方式配置。
rspack.config.mjs
import { rspack } from '@rspack/core';
export default {
entry: './src/index.js',
plugins: [
new rspack.CircularDependencyRspackPlugin({
ignoredConnections: [
['some/module/a.js', 'some/module/b.js'],
['', /modules\/.*Store.js/],
],
}),
],
};
- 允许异步循环(例如
import('some/module')
) 并手动处理所有检测到的循环
rspack.config.mjs
import { rspack } from '@rspack/core';
export default {
entry: './src/index.js',
plugins: [
new rspack.CircularDependencyRspackPlugin({
allowAsyncCycles: true,
onDetected(entrypoint, modules, compilation) {
compilation.errors.push(
new Error(`Cycle in ${entrypoint}: ${modules.join(' -> ')}`),
);
},
}),
],
};
选项
failOnError
当设置为 true
时,检测到的循环依赖将生成错误级别的诊断而不是警告。在 dev
模式下不会产生明显的影响,在 build
模式下触发会直接导致构建失败。
rspack.config.mjs
import { rspack } from '@rspack/core';
export default {
plugins: [
new rspack.CircularDependencyRspackPlugin({
failOnError: true,
}),
],
};
allowAsyncCycles
允许异步导入造成的循环依赖被忽略。异步导入包括 import()
调用、以及用于 lazy compilation 的弱引入、热模块连接等等。
rspack.config.mjs
import { rspack } from '@rspack/core';
export default {
plugins: [
new rspack.CircularDependencyRspackPlugin({
allowAsyncCycles: true,
}),
],
};
exclude
- 类型:
RegExp
- 默认值:
undefined
和 webpack 插件 circular-dependency-plugin
里的 exclude
配置类似,检测到的任何包含该模式匹配的模块标识符的循环依赖都会被忽略。
rspack.config.mjs
import { rspack } from '@rspack/core';
export default {
plugins: [
new rspack.CircularDependencyRspackPlugin({
// Ignore any cycles involving external packages from `node_modules`
exclude: /node_modules/,
}),
],
};
ignoredConnections
- 类型:
Array<[string | RegExp, string | RegExp]>
- 默认值:
[]
忽略该配置中列出的显示循环依赖模块的循环依赖。该配置数组中的每个条目都通过 [from, to]
的方式来形成,匹配所有的 from
依赖于 to
的连接。
rspack.config.mjs
import { rspack } from '@rspack/core';
export default {
plugins: [
new rspack.CircularDependencyRspackPlugin({
ignoredConnections: [
// Ignore a specific connection between modules
['some/module/a', 'some/module/b'],
// Ignore any connection depending on `b`
['', 'some/module/b'],
// Ignore any connection between "Store-like" modules
[/.*Store\.js/, /.*Store\.js/],
],
}),
],
};
每个 pattern 都可以写成纯字符串或者 RegExp
。纯字符串将作为子字符串与连接部分的模块标识符去做匹配,而 RegExp
则会匹配整个标识符中的任何位置。例如:
- 字符串
'some/module/'
将匹配 some/module
目录中的任何模块,如 some/module/a
和 some/module/b
。
- 正则表达式
!file-loader!.*\.mdx
将匹配由 file-loader
处理的任何 .mdx
模块。
- 空字符串实际上可以匹配任何模块,因为空字符串始终是其他任何字符串的子串
onDetected
- 类型:
(entrypoint: string, modules: string[], compilation: Compilation) => void
- 默认值:
undefined
每次检测到循环时都会调用该处理函数。该处理函数实际上会覆盖掉添加诊断的默认行为,意味着 failOnError
的值实际上不会被使用。
rspack.config.mjs
import { rspack } from '@rspack/core';
export default {
plugins: [
new rspack.CircularDependencyRspackPlugin({
onDetected(entrypoint, modules, compilation) {
console.log(`Found a cycle in ${entrypoint}: ${modules.join(' -> ')}`);
},
}),
],
};
onDetected
函数可用于在向编译发出警告或错误之前进一步处理检测到的循环,或以插件未直接实现的任何其他方式处理它们。
entrypoint
是检测到此循环的条目的名称。由于循环检测的入口点遍历方式,可能会多次检测同一循环,每个入口点检测一次。
modules
是循环中包含的模块标识符列表,其中列表的第一个和最后一个条目始终是同一个模块,并且是列表中出现多次的唯一模块。
compilation
是完整的 Compilation 对象,允许处理程序根据需要发出错误或检查包的任何其他部分。
onIgnored
- 类型:
(entrypoint: string, modules: string[], compilation: Compilation) => void
- 默认值:
undefined
处理每个被故意忽略的检测到的循环,无论是通过 exclude
模式、ignoredConnection
的任何匹配,还是任何其他可能的原因。
rspack.config.mjs
import { rspack } from '@rspack/core';
const ignoredCycles = [];
export default {
plugins: [
new rspack.CircularDependencyRspackPlugin({
onIgnored(entrypoint, modules, compilation) {
ignoredCycles.push({ entrypoint, modules });
},
}),
],
};
onStart
- 类型:
(compilation: Compilation) => void
- 默认值:
undefined
在循环检测开始之前立即调用的 hook 函数,用于设置在 onDetected
处理程序或记录进度中使用的临时状态。
rspack.config.mjs
import { rspack } from '@rspack/core';
export default {
plugins: [
new rspack.CircularDependencyRspackPlugin({
onStart(compilation) {
console.log('Starting circular dependency detection');
},
}),
],
};
onEnd
- 类型:
(compilation: Compilation) => void
- 默认值:
undefined
循环检测完成后立即调用的钩子函数,用于清理临时状态或记录进度。
rspack.config.mjs
import { rspack } from '@rspack/core';
export default {
plugins: [
new rspack.CircularDependencyRspackPlugin({
onEnd(compilation) {
console.log('Finished detecting circular dependencies');
},
}),
],
};