从零开始用Rust实现Webpack(五):支持自定义JS插件
参考了 mini-webpack,我用 Rust 从零开始实现了一个简单的 Webpack。这让我对 Webpack 的理解更加深入,同时也提升了我的 Rust 技能。真是两全其美!
代码库:https://github.com/ParadeTo/rs-webpack
本文对应的 Pull Request 为:https://github.com/ParadeTo/rs-webpack/pull/6
上一篇文章实现了 Rust 端的插件系统,但如何将用户用 JavaScript 开发的插件集成到 rs-webpack 中的问题尚未解决。本文将介绍如何实现。
如果我开发了一个 JavaScript 插件,怎样才能让它正常工作?
module.exports = class MyPlugin {
apply(compiler) {
compiler.hooks.beforeRun.tap('myplugin', (compiler) => {
console.log("在运行前,编译器对象", compiler)
})
}
}
点击进入全屏。退出全屏。
接下来,既然我们之前已经提到过 rspack,那么我们就继续沿用它的思路。研究之后,我发现它的方法大致如图所示。
我们的自定义 JS 插件 myPlugin
将从 rs-webpack-cli 传递给 rs-webpack-core。在 rs-webpack-core 中,它使用由 rspack 团队开发的 @rspack/lite-tapable
库来创建一个 beforeRun
钩子。
export class Compiler {
// bindingRsWebpack 是一个 BindingRsWebpack 实例
bindingRsWebpack: BindingRsWebpack
// hooks 是一个包含各种钩子的配置对象
hooks: {
// beforeRun 是一个 SyncHook,用于在运行前执行同步操作
beforeRun: liteTapable.SyncHook<[string]>;
}
...
}
进入全屏,退出全屏
在初始化 Compiler
时,它会遍历所有插件并执行它们的 apply
方法:
constructor(props: RawConfig) {
const {plugins} = props
plugins.forEach(plugin => {
plugin.apply(this)
})
}
切换到全屏 退出全屏
然后,通过一系列操作,它将一个名为 register_before_run_taps
的函数进行包装,并将这个函数传递给 Rust。register_before_run_taps
这个函数封装了调用 beforeRun
钩子的 call
函数。如下:
this.registers = {
registerBeforeRunTaps: this.#createHookRegisterTaps(
RegisterJsTapKind.BeforeRun,
() => this.hooks.beforeRun,
queried => (native: string) => {
// beforeRun.call
queried.call(native);
}
),
}
// 创建 BindingRsWebpack 实例,传入 props 和 this.registers 对象
this.bindingRsWebpack = new BindingRsWebpack(props, this.registers)
进入全屏 退出全屏
执行完此函数后,返回一个数组,数组中的每个元素都可以充当 Rust 里 before_run
钩子的拦截器(仅实现了 call
方法这一部分)。
#[async_trait]
impl Interceptor<BeforeRunHook> for RegisterBeforeRunTaps {
async fn call(
&self,
hook: &BeforeRunHook,
) -> rswebpack_error::Result<Vec<<BeforeRunHook as Hook>::Tap>> {
// 如果某些不可跳过的注册存在,并且这些注册不是不可跳过的,返回一个空的Vec。
if let Some(non_skippable_registers) = &self.inner.non_skippable_registers {
if !non_skippable_registers.is_non_skippable(&RegisterJsTapKind::BeforeRun) {
return Ok(Vec::new());
}
}
// 调用内部的call_register方法,获取js_taps。
let js_taps = self.inner.call_register(hook).await?;
// 将获取到的js_taps进行处理,创建一个新的Vec。
let js_taps = js_taps
.iter()
.map(|t| Box::new(BeforeRunTap::new(t.clone())) as <BeforeRunHook as Hook>::Tap)
.collect();
Ok(js_taps)
}
}
全屏 退出
在 rswebpack_binding 中,这些拦截器是通过 JsHooksAdapterPlugin
应用的。
impl Plugin for JsHooksAdapterPlugin {
fn 名称(&self) -> &'static str {
"rspack.JsHooksAdapterPlugin"
}
fn 应用(&self, _ctx: PluginContext<&mut ApplyContext>) -> rswebpack_error::Result<()> {
_ctx
.context
.compiler_hooks
.before_run
.intercept(self.register_before_run_taps.clone());
}
}
全屏显示;退出全屏
PS:每次Hook的call
函数被调用时,拦截器中的call
函数也会被执行。例如,在以下示例中:
const hook = new SyncHook(['arg1', 'arg2'])
hook.tap('test', (...args) => {
控制台输出('打印 "test" 和参数', ...args)
})
hook.intercept({
// 在调用时触发
call: (...args) => {
控制台输出('执行拦截器调用', ...args)
},
})
hook.call('a1', 'a2')
// 输出
执行拦截器调用 a1 a2
打印 "test" 和参数 a1 a2
全屏 退出全屏
当 Rust 中的 before_run
调用 call
时,这些拦截器的 call
函数也会被执行,然后这些拦截器包裹的 beforeRun.call
也会在 JS 侧被执行,从而触发 myPlugin
中相应 Tap 函数的执行。
通过这些步骤,整个插件系统就完成了。完整的更改可以在这里查看 here。我不会一一讲解代码,但是按照我们之前提到的顺序,你应该就能理解了。
这套文章的初衷是通过重新实现来加深对 webpack 的理解程度。然而,我发现我的 Rust 技能还不够熟练,还不足以实现 Plugin 系统。大部分时间都花在了 Rspack 的集成上。
在这个过程中,我意识到有很多地方我理解得不是很透彻,我打算把这些地方记下来,以后再学学。
- Napi,比如ThreadsafeFunction。把Napi和Node.js结合可以做很多事情。以后我会试着找一些例子。
- Rust的异步处理以及tokio。
- Rust中的并发编程:多线程、通道等。
- Rust中的宏编程编写和调试都很费劲。
麻烦您给我点个赞,非常感谢!
共同学习,写下你的评论
评论加载中...
作者其他优质文章