参考 mini-webpack,使用 Rust 从零实现一个简单的 webpack,深入理解 webpack 的同时,还锻炼了 Rust 的技能,简直赢麻了!
代码地址:https://github.com/ParadeTo/rs-webpack
本文对应 Pull Request:https://github.com/ParadeTo/rs-webpack/pull/5
我们知道,Webpack 中使用了 Tapable 来实现插件系统,既然如此,那我们仿照着写一个 Rust 版本的不就可以了。话虽如此,但是实现起来发现并不是那么的简单,以 SyncHook
为例,一个非常简单的版本可以像如下方式用 JS 实现:
1 | class SyncHook { |
我们尝试用 Rust 来实现,可能会写出这样的代码:
1 | struct SyncHook<Arg: Copy, R> { |
注意,Arg
必须约束为可 Copy
,否则调用 tap(a)
的时候会报错。上述代码可以正常运行,但是只能支持 call
传入一个参数的情形,不过这个可以通过宏来解决,我们可以预先生成一批支持不同个数参数的 struct:
1 | struct SyncHook1<Arg1: Copy, R> { |
不过,还有一个问题,它不支持传入 &mut T
类型的参数:
1 |
|
上述代码会报 “the trait Copy
is not implemented for&mut Compiler
“ 的错误。
看来实现类似的功能目前对于我这个初学者来说应该是个比较难的问题,那还是抄作业吧,看看 Rspack 是怎么做的。
查阅发现,Rspack 中没有通用的 SyncHook
,它通过宏为每个 Hook 单独进行了定义,接下来我们尝试把他接过来。
首先,我们把 Rspack 源码中的 crates/rspack_macros
和 crates/rspack_hook
的代码都拷贝到 rs-webpack 的 crates 目录下,并修改目录名:
1 | ├── crates |
然后,新增 crates/rswebpack_error
作为我们统一的错误处理模块:
1 | // lib.rs |
最后,把这些库里面的依赖项也做相应的修改,比如 rswebpack_hook
中的 Result
要从原来的 rspack_error::Result
改成 rswebpack_error::Result
。
我们写个 Demo 测试下:
1 | use rswebpack_macros::{define_hook, plugin, plugin_hook}; |
稍微解释一下上面的代码,首先通过 define_hook!(Test: SyncSeries(people: &mut People));
定义了一个 Hook,它展开其实是这样的:
1 | pub trait Test { |
可以看到 TestHook
跟我们之前的实现方式是有点类似的,同时它还生成了 TestHook
的 tap
的类型,即 Test
。
之后,还是通过宏来实现一个 Test
的 Tap
:
1 |
|
它展开其实是这样的:
1 | struct TestHookTap1 { |
这里面有点绕来绕去的,不过多看几遍还是能看明白的。
这样,我们就在 Rust 中实现了类似 Tapable 的功能,不过目前只演示了最简单的 SyncHook 的用法,后面遇到其他钩子时再介绍吧。
接下来,我们就基于这个来实现插件系统。我们知道,实现一个 webpack 的插件一般是这样的做法:
1 | const pluginName = 'ConsoleLogOnBuildWebpackPlugin' |
我们也依样画葫芦,先来定义一个 Plugin
的 trait 来规定插件应具备的特性:
1 |
|
可以看到,我们会把 before_run
这个钩子作为 context 创给 Plugin。
然后,我们来定义一个 PluginDriver
来驱动 Plugin
:
1 | pub struct PluginDriver { |
这里初始化了传入每个 Plugin
的参数,然后遍历这些 Plugin
并调用他们的 apply
方法。
PluginDriver
最后会在 Compiler
中进行使用:
1 | impl Compiler { |
我们暂时修改一下 Compiler
的 run
:
1 | pub fn run(&mut self) { |
然后,写个 Demo 测试一下:
1 | use rswebpack_core::compiler::Compiler; |
大功告成,不过实际开发中,自定义 Plugin
都是用 JS 开发的,怎么把这些 Plugin 集成进来呢,下篇再揭晓答案吧。