写完 Rspack 插件才发现,webpack 那套经验全都能直接用——官方文档里没写清楚的那几个关键点
写完了,构建产物里混进了不该有的测试代码,每次都要手动删——这个痛我相信做过二次开发的前端工程师都懂。后来我自己写了个 Rspack 插件把它自动化了,才发现 Rspack 的插件系统其实比想象中简单太多。
Rspack 兼容 webpack 插件生态,你平时在 Webpack 里怎么写插件,搬到 Rspack 里基本不用改。但它也有自己的一套 plugin API,搞清楚几个核心概念就能动手了。
先说最基础的插件结构。Rspack 插件只需要一个 apply 方法,接收 compiler 实例:
const PLUGIN_NAME = 'MyPlugin';
export class MyPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
console.log('构建开始了!');
});
}
}
如果你用 TypeScript,可以直接从 @rspack/core 导入类型,IDE 补全会舒服很多:
import type { Compiler, RspackPluginInstance } from '@rspack/core';
const PLUGIN_NAME = 'MyPlugin';
export class MyPlugin implements RspackPluginInstance {
apply(compiler: Compiler) {
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
console.log('构建开始了!');
});
}
}
这就是全部了。一个 apply 方法,一个 compiler 对象,搞定。
compiler 对象上有五大类 hooks,这是 Rspack 插件系统的核心:Compiler hooks 管整个构建生命周期,Compilation hooks 管单次构建过程,RuntimePlugin hooks 管运行时代码生成,NormalModuleFactory hooks 管模块创建,Stats hooks 管统计信息输出。
最常用的是 Compiler hooks 和 Compilation hooks。
Compiler hooks 里,按执行顺序排列,核心这几个:
environment:初始化插件环境后触发,做初始化操作entryOption:入口配置处理完毕后触发,可以读取 entry 配置afterPlugins:内部插件注册完成后触发beforeRun/run:开始构建前触发,beforeRun 只在 rspack build 命令下触发,watch 模式用 watchRuncompile:每次新编译前触发,在 beforeCompile 之后thisCompilation/compilation:创建 compilation 对象后触发,这里可以注册 Compilation hooksmake:开始构建模块图前触发,是异步并行 hook,适合做耗时操作finishMake:模块图构建完成后触发afterCompile:seal 阶段前触发shouldEmit:返回 true/false 控制是否输出产物emit:输出产物到目录前触发afterEmit:输出完成后触发done:整个构建完成后触发failed:构建失败时触发
我之前遇到的问题——构建产物里混了测试代码——就是在 emit 这个 hook 里解决的。因为这时候所有产物已经生成好了,但还没有写入磁盘,我可以在这里对 compilation.assets 做最后处理。
apply(compiler) {
compiler.hooks.emit.tap('RemoveTestAssets', (compilation) => {
Object.keys(compilation.assets).forEach(filename => {
if (filename.includes('.test.') || filename.includes('__tests__')) {
delete compilation.assets[filename];
}
});
});
}
Compilation hooks 更细粒度,主要管单个构建的过程。常用的:
buildModule:某个模块开始构建前,可以修改模块succeedModule:模块构建成功后,可以做一些统计finishModules:所有模块构建完成,所有模块没有错误seal:停止接受新模块,开始优化阶段optimizeModules/afterOptimizeModules:模块优化前后optimizeTree:优化依赖树optimizeChunkModules:优化 chunk 模块additionalTreeRuntimeRequirements:添加运行时依赖
有一个关键点要记住:Rspack 的核心编译逻辑跑在 Rust 侧,JS 这边的 hooks 大部分是”只读”的——你在 hook 里修改的对象变化不会同步回 Rust 侧。所以如果你想修改编译行为,需要找到正确的 hook 位置,而不是在 seal 阶段乱改。
比如我想给构建产物加一个 hash 信息,我知道该用 additionalTreeRuntimeRequirements 这个 hook,因为它可以向运行时需求集合里添加 RuntimeGlobals:
apply(compiler) {
const { RuntimeGlobals } = compiler.rspack;
compiler.hooks.thisCompilation.tap('HashPlugin', (compilation) => {
compilation.hooks.additionalTreeRuntimeRequirements.tap(
'HashPlugin',
(chunk, set) => {
set.add(RuntimeGlobals.getFullHash);
}
);
});
}
这样运行时代码里就能用 __webpack_require__.h 获取当前构建的 hash。
还有一个很实用的场景:想让插件同时兼容 Rspack 和 webpack。Rspack 官方文档里给了一个判断方法:
apply(compiler) {
if (compiler.rspack) {
// 在 Rspack 里运行的逻辑
} else {
// 在 webpack 里运行的逻辑
}
}
这个 compiler.rspack 属性是 Rspack 特有的,webpack 里不存在。
最后说一下怎么发布插件。如果你的插件只给 Rspack 用,在 package.json 里声明 @rspack/core 为可选依赖:
{
"peerDependencies": {
"@rspack/core": "^1.0.0 || ^2.0.0"
},
"peerDependenciesMeta": {
"@rspack/core": {
"optional": true
}
}
}
如果要同时兼容 webpack 和 Rspack,两个都声明。
写到这里,我发现 Rspack 插件开发的门槛其实很低——你只需要理解 compiler 和 compilation 两个对象,以及它们上面的 hooks 体系。剩下的就是根据你的业务场景,选对 hook 位置,填业务逻辑。
下一步你可以从自己项目的痛点出发,比如构建速度慢、产物需要二次处理、想在构建时注入环境变量……这些都可以用插件解决。官方文档的 Plugin API 部分列了完整的 hook 清单,有需要可以对着文档查。
评论区
登录后可评论。