参考 mini-webpack,使用 Rust 从零实现一个简单的 webpack,深入理解 webpack 的同时,还锻炼了 Rust 的技能,简直赢麻了!
代码地址:https://github.com/ParadeTo/rs-webpack
本文对应 Pull Request:https://github.com/ParadeTo/rs-webpack/pull/2
前文已经介绍如何使用 oxc 来解析并修改 JS 代码,解决了最核心的问题,那现在已经可以实现一个 MVP 版本了,本次 MVP 目标是实现对下面这个代码的打包,并能正常运行输出的结果:
1 | // index.js |
我们新建一个 Compiler
结构体,它包括如下属性和方法:
1 | pub struct Compiler { |
其中,run
是入口,它会调用 build_module
,build_module
首先会调用 parse
得到 JS 模块的代码和它的依赖进行返回(顺便还会对原来的代码进行转换),然后 build_module
中会递归调用自己继续对这些依赖进行处理,最终得到 modules
。modules
是一个 HashMap
,其中 key 为模块 id,即模块相对于 root 的路径,value 则是转换后的模块代码。run
最后会调用 emit_file
输出结果。完整代码请见 Pull Request,下面挑几个重点讲一下。
Tranform
前文曾给出了一个 Transform 的例子,用于解释如何修改 require
的参数,例子中我们是把参数改成了固定的 full_path_of_b
:
1 | string_literal.value = Atom::from("full_path_of_b") |
但是,实际开发的时候,这里的参数是跟当前 JS 模块的路径有关的,是动态的。我们可以用下面这个例子来说明(给 require
的参数增加一个动态的 prefix
):
1 | struct MyTransform { |
上面的代码编译不通过,会报错 new_name does not live long enough
,因为 new_name
会在函数执行完后销毁,但是 Atom::from
声明了一个生命周期 'a
。解决办法是这样:
1 | string_literal.value = ctx.ast.atom(new_name.as_str()); |
原因可以通过 ctx.ast.atom
的源码来解释:
1 |
|
可以看到,atom
方法没有声明生命周期,最终还是调用了 Atom::from
,但是里面的值是通过 String::from_str_in
生成的,其中第二个参数 self.allocator
是带生命周期的:
1 | pub struct AstBuilder<'a> { |
而 Allocator
则是基于 bumpalo 实现的内存分配的工具,Allocator
这个东西好像在实现解析器一般都要用到,可以参考这个教程,对这块还不太了解,先略过了。
Emit File
输出最终的打包文件时,用到了模板引擎 sailfish,模版如下所示:
1 | (function(modules) { |
可以看到,只需要将 entry_id
和 modules
的内容输出到模版中即可。
运行 cargo run
,得到输出结果:
1 | // out/bundle.js |
使用 Node.js 运行,如果可以正常打印 hello
,则表示 MVP 成功完成。