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编程语言

Download history 121/week @ 2024-05-10 159/week @ 2024-05-17 28/week @ 2024-05-24 295/week @ 2024-05-31 575/week @ 2024-06-07 899/week @ 2024-06-14 594/week @ 2024-06-21 1217/week @ 2024-06-28 1862/week @ 2024-07-05 1459/week @ 2024-07-12 1274/week @ 2024-07-19 1303/week @ 2024-07-26 1134/week @ 2024-08-02 1174/week @ 2024-08-09 762/week @ 2024-08-16

4,526 每月下载量
用于 2 crates

MIT 许可证

3MB
70K SLoC

Oxc transformer

我们主要基于Babel的实现来构建Oxc的转换。

转换的第一迭代通常会尽可能直接从Babel移植。然后,我们可能从这个地方开始迭代,以获得更好的性能。

我们导入Babel的转换测试,并旨在通过所有测试。

所有转换都是Traverse特质的实现。

组合转换

我们的目标是一次性在单个AST访问过程中运行所有转换。

这将是最有效的方法,尽管它带来了一些复杂性,特别是由于不同转换对相同代码的交互。目前尚不清楚这种方法是否可行,但这是我们最初的目标,只有在单次遍历不可行的情况下,我们才会回退到多次遍历。

实现转换的样式指南

转换很复杂。请尽量使代码尽可能清晰和易于理解。

注意:到目前为止,我们编写的转换中并不遵循此样式指南中所有“规则”。我们将在有时间时更新这些转换以遵循此指南。但所有新的转换都应密切遵循此样式指南。

结构

每个转换应在其自己的文件中。

一些转换只是委托给子转换。例如,React转换委托给ReactJsxReactDisplayName转换。

注释

为了维护和可理解的代码库,请大量添加代码注释。越多越好!

文件顶部

每个转换应在文件顶部包含注释,包括

  • 转换的概述。
  • 一个“之前/之后”示例。
  • 指向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