43个版本 (22个破坏性更新)
新版本 0.25.0 | 2024年8月23日 |
---|---|
0.23.1 | 2024年8月6日 |
0.22.1 | 2024年7月28日 |
0.11.0 | 2024年3月30日 |
0.3.0 | 2023年11月6日 |
#872 在 编程语言
4,526 每月下载量
用于 2 crates
3MB
70K SLoC
Oxc transformer
我们主要基于Babel的实现来构建Oxc的转换。
转换的第一迭代通常会尽可能直接从Babel移植。然后,我们可能从这个地方开始迭代,以获得更好的性能。
我们导入Babel的转换测试,并旨在通过所有测试。
所有转换都是Traverse
特质的实现。
组合转换
我们的目标是一次性在单个AST访问过程中运行所有转换。
这将是最有效的方法,尽管它带来了一些复杂性,特别是由于不同转换对相同代码的交互。目前尚不清楚这种方法是否可行,但这是我们最初的目标,只有在单次遍历不可行的情况下,我们才会回退到多次遍历。
实现转换的样式指南
转换很复杂。请尽量使代码尽可能清晰和易于理解。
注意:到目前为止,我们编写的转换中并不遵循此样式指南中所有“规则”。我们将在有时间时更新这些转换以遵循此指南。但所有新的转换都应密切遵循此样式指南。
结构
每个转换应在其自己的文件中。
一些转换只是委托给子转换。例如,React
转换委托给ReactJsx
和ReactDisplayName
转换。
注释
为了维护和可理解的代码库,请大量添加代码注释。越多越好!
文件顶部
每个转换应在文件顶部包含注释,包括
- 转换的概述。
- 一个“之前/之后”示例。
- 指向Babel插件的链接。
- 注意我们的实现与Babel的实现有何不同,以及原因。
方法
如果是一个复杂的转换,涉及多个相互交互的访客,请添加注释说明各个部分是如何组合在一起的。
代码片段
AstBuilder
调用通常非常冗长。在每段 AstBuilder
调用之前添加一个简短的注释,说明这段代码会产生什么结果。例如:
// `let Foo;`
let declarations = {
let ident = BindingIdentifier::new(SPAN, "Foo");
let pattern_kind = self.ast.binding_pattern_identifier(ident);
let binding = self.ast.binding_pattern(pattern_kind, None, false);
let decl = self.ast.variable_declarator(SPAN, VariableDeclarationKind::Let, binding, None, false);
self.ast.new_vec_single(decl)
};
let var_decl = Declaration::VariableDeclaration(self.ast.variable_declaration(
SPAN,
kind,
declarations,
Modifiers::empty(),
));
在Babel中我们可以改进的地方
Babel相比于Oxc更不注重性能。因此,Babel的实现通常不如可能的那样高效。
在某些情况下,我们可以做得更好,但目前我们无法做到,因为更高效的实现会导致Oxc的输出和Babel的输出之间在视觉上的差异(例如,不同的变量名),这会导致Babel在Oxc的输出上运行时测试失败。
未来我们可能找到一种绕过这个问题的方法。
所以,当我们觉得Babel的实现效率低下,但我们必须目前遵循它以通过他们的测试时,添加一个 // TODO(improve-on-babel): Babel的impl效率低下因为X,我们可以通过Y做得更好
注释,这样我们可以在以后回到它。
清晰的“入口点”
“入口点”是访客调用转换的地方。
- 转换的入口点应该实现为
impl Traverse for MyTransform
。 - 这些方法必须被调用
enter_*
和exit_*
。 - 父转换将只通过这些入口点与子转换进行接口交互。
- 仅公开的外部方法应该是
new
。这应该在文件顶部。 - 入口点直接位于
new
方法定义下方。 - 内部方法在
impl MyTransform
块中较低的位置实现。 - 内部方法命名应具有描述性 -
add_id_to_function
而不是transform_function
。
即:文件布局使得逻辑从文件顶部流向底部。
例如:
struct FunctionRenamer {
prefix: String,
}
// Initialization
impl FunctionRenamer {
pub fn new(prefix: String) -> Self {
Self { prefix }
}
}
// Entry points
impl<'a> Traverse<'a> for FunctionRenamer {
fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
match stmt {
Statement::FunctionDeclaration(func) => {
self.rename_function(func, ctx);
}
Statement::ExportDefaultDeclaration(decl) => {
if let ExportDefaultDeclarationKind::FunctionDeclaration(func) = &mut decl.declaration {
self.rename_function(func, ctx);
}
}
}
}
}
// Internal methods
impl FunctionRenamer {
/// Rename the function
// This function's name describes what it does, not just `transform_function`
fn rename_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
// Do stuff
}
}
封装逻辑
每个转换的所有逻辑都应该存在于特定的文件中,不允许“泄漏”到父转换。每个转换只能通过标准的 enter_*
/exit_*
入口点调用。
唯一的例外是父转换可以检查子转换是否启用。
错误!不要这样做。
以下是一些从子转换中“泄漏”到父转换的逻辑
// src/do_stuff/mod.rs
impl<'a> Traverse<'a> for ParentTransform {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if self.child_is_enabled {
match expr {
Expression::JSXElement(e) => {
self.child.enter_jsx_element(e, ctx);
}
Expression::JSXFragment(e) => {
self.child.enter_jsx_fragment(e, ctx);
}
_ => {}
}
}
}
}
// src/do_stuff/child.rs
impl<'a> Traverse<'a> for ChildTransform {
fn enter_jsx_element(&mut self, elem: &mut JSXElement<'a>, ctx: &mut TraverseCtx<'a>) {
// Do stuff
}
fn enter_jsx_fragment(&mut self, elem: &mut JSXFragment<'a>, ctx: &mut TraverseCtx<'a>) {
// Do stuff
}
}
正确!
所有子转换的逻辑都封装在 ChildTransform
中
// src/do_stuff/mod.rs
impl<'a> Traverse<'a> for ParentTransform {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if self.child_is_enabled {
self.child.enter_expression(expr, ctx);
}
}
}
// src/do_stuff/child.rs
impl<'a> Traverse<'a> for ChildTransform {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
match expr {
Expression::JSXElement(e) => {
self.do_stuff_to_jsx_element(e, ctx);
}
Expression::JSXFragment(e) => {
self.do_stuff_to_jsx_fragment(e, ctx);
}
_ => {}
}
}
}
impl ChildTransform {
fn do_stuff_to_jsx_element(&mut self, elem: &mut JSXElement<'a>, ctx: &mut TraverseCtx<'a>) {
// Do stuff
}
fn do_stuff_to_jsx_fragment(&mut self, elem: &mut JSXFragment<'a>, ctx: &mut TraverseCtx<'a>) {
// Do stuff
}
}
依赖关系
~12-18MB
~191K SLoC