diff --git a/crates/oxc_transformer/src/common/helper_loader.rs b/crates/oxc_transformer/src/common/helper_loader.rs index 536b5579e737dc..60a783e2679a6e 100644 --- a/crates/oxc_transformer/src/common/helper_loader.rs +++ b/crates/oxc_transformer/src/common/helper_loader.rs @@ -156,7 +156,7 @@ pub enum Helper { } impl Helper { - const fn name(self) -> &'static str { + pub const fn name(self) -> &'static str { match self { Self::AwaitAsyncGenerator => "awaitAsyncGenerator", Self::AsyncGeneratorDelegate => "asyncGeneratorDelegate", @@ -184,6 +184,7 @@ pub struct HelperLoaderStore<'a> { mode: HelperLoaderMode, /// Loaded helpers, determined what helpers are loaded and what imports should be added. loaded_helpers: RefCell>>, + pub(crate) used_helpers: RefCell>, } impl<'a> HelperLoaderStore<'a> { @@ -192,6 +193,7 @@ impl<'a> HelperLoaderStore<'a> { module_name: options.module_name.clone(), mode: options.mode, loaded_helpers: RefCell::new(FxHashMap::default()), + used_helpers: RefCell::new(FxHashMap::default()), } } } @@ -238,9 +240,12 @@ impl<'a> TransformCtx<'a> { /// Load a helper function and return a callee expression. pub fn helper_load(&self, helper: Helper, ctx: &mut TraverseCtx<'a>) -> Expression<'a> { let helper_loader = &self.helper_loader; + let source = helper_loader.get_runtime_source(helper, ctx); + helper_loader.used_helpers.borrow_mut().entry(helper).or_insert_with(|| source.to_string()); + match helper_loader.mode { HelperLoaderMode::Runtime => { - helper_loader.transform_for_runtime_helper(helper, self, ctx) + helper_loader.transform_for_runtime_helper(helper, source, self, ctx) } HelperLoaderMode::External => { HelperLoaderStore::transform_for_external_helper(helper, ctx) @@ -257,32 +262,25 @@ impl<'a> HelperLoaderStore<'a> { fn transform_for_runtime_helper( &self, helper: Helper, + source: Atom<'a>, transform_ctx: &TransformCtx<'a>, ctx: &mut TraverseCtx<'a>, ) -> Expression<'a> { let mut loaded_helpers = self.loaded_helpers.borrow_mut(); let binding = loaded_helpers .entry(helper) - .or_insert_with(|| self.get_runtime_helper(helper, transform_ctx, ctx)); + .or_insert_with(|| Self::get_runtime_helper(helper, source, transform_ctx, ctx)); binding.create_read_expression(ctx) } fn get_runtime_helper( - &self, helper: Helper, + source: Atom<'a>, transform_ctx: &TransformCtx<'a>, ctx: &mut TraverseCtx<'a>, ) -> BoundIdentifier<'a> { let helper_name = helper.name(); - // Construct string directly in arena without an intermediate temp allocation - let len = self.module_name.len() + "/helpers/".len() + helper_name.len(); - let mut source = ArenaString::with_capacity_in(len, ctx.ast.allocator); - source.push_str(&self.module_name); - source.push_str("/helpers/"); - source.push_str(helper_name); - let source = Atom::from(source.into_bump_str()); - let flag = if transform_ctx.source_type.is_module() { SymbolFlags::Import } else { @@ -295,6 +293,17 @@ impl<'a> HelperLoaderStore<'a> { binding } + // Construct string directly in arena without an intermediate temp allocation + fn get_runtime_source(&self, helper: Helper, ctx: &mut TraverseCtx<'a>) -> Atom<'a> { + let helper_name = helper.name(); + let len = self.module_name.len() + "/helpers/".len() + helper_name.len(); + let mut source = ArenaString::with_capacity_in(len, ctx.ast.allocator); + source.push_str(&self.module_name); + source.push_str("/helpers/"); + source.push_str(helper_name); + Atom::from(source.into_bump_str()) + } + fn transform_for_external_helper(helper: Helper, ctx: &mut TraverseCtx<'a>) -> Expression<'a> { static HELPER_VAR: &str = "babelHelpers"; diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 0a89e107d3b764..9c8a75bb6d9084 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -48,10 +48,11 @@ use es2021::ES2021; use es2022::ES2022; use jsx::Jsx; use regexp::RegExp; +use rustc_hash::FxHashMap; use typescript::TypeScript; pub use crate::{ - common::helper_loader::{HelperLoaderMode, HelperLoaderOptions}, + common::helper_loader::{Helper, HelperLoaderMode, HelperLoaderOptions}, compiler_assumptions::CompilerAssumptions, es2015::{ArrowFunctionsOptions, ES2015Options}, jsx::{JsxOptions, JsxRuntime, ReactRefreshOptions}, @@ -63,10 +64,14 @@ pub use crate::{ typescript::{RewriteExtensionsMode, TypeScriptOptions}, }; +#[non_exhaustive] pub struct TransformerReturn { pub errors: std::vec::Vec, pub symbols: SymbolTable, pub scopes: ScopeTree, + /// Helpers used by this transform. + #[deprecated = "Internal usage only"] + pub helpers_used: FxHashMap, } pub struct Transformer<'a> { @@ -128,7 +133,9 @@ impl<'a> Transformer<'a> { }; let (symbols, scopes) = traverse_mut(&mut transformer, allocator, program, symbols, scopes); - TransformerReturn { errors: self.ctx.take_errors(), symbols, scopes } + let helpers_used = self.ctx.helper_loader.used_helpers.borrow_mut().drain().collect(); + #[allow(deprecated)] + TransformerReturn { errors: self.ctx.take_errors(), symbols, scopes, helpers_used } } } diff --git a/napi/transform/index.d.ts b/napi/transform/index.d.ts index 2daf11c18c15fe..c5bd41e8e6ad9c 100644 --- a/napi/transform/index.d.ts +++ b/napi/transform/index.d.ts @@ -313,6 +313,18 @@ export interface TransformResult { * {@link TransformOptions#sourcemap sourcemap} are set to `true`. */ declarationMap?: SourceMap + /** + * Helpers used. + * + * @internal + * + * Example: + * + * ```text + * { "_objectSpread": "@babel/runtime/helpers/objectSpread2" } + * ``` + */ + helpersUsed: Record /** * Parse and transformation errors. * diff --git a/napi/transform/src/transformer.rs b/napi/transform/src/transformer.rs index c0aabdf0c93b8f..1ed28f8735a993 100644 --- a/napi/transform/src/transformer.rs +++ b/napi/transform/src/transformer.rs @@ -1,7 +1,10 @@ // NOTE: Types must be aligned with [@types/babel__core](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/b5dc32740d9b45d11cff9b025896dd333c795b39/types/babel__core/index.d.ts). #![allow(rustdoc::bare_urls)] -use std::path::{Path, PathBuf}; +use std::{ + ops::ControlFlow, + path::{Path, PathBuf}, +}; use napi::Either; use napi_derive::napi; @@ -50,6 +53,18 @@ pub struct TransformResult { /// {@link TransformOptions#sourcemap sourcemap} are set to `true`. pub declaration_map: Option, + /// Helpers used. + /// + /// @internal + /// + /// Example: + /// + /// ```text + /// { "_objectSpread": "@babel/runtime/helpers/objectSpread2" } + /// ``` + #[napi(ts_type = "Record")] + pub helpers_used: FxHashMap, + /// Parse and transformation errors. /// /// Oxc's parser recovers from common syntax errors, meaning that @@ -461,6 +476,7 @@ struct Compiler { define: Option, inject: Option, + helpers_used: FxHashMap, errors: Vec, } @@ -527,6 +543,7 @@ impl Compiler { declaration_map: None, define, inject, + helpers_used: FxHashMap::default(), errors: vec![], }) } @@ -568,6 +585,20 @@ impl CompilerInterface for Compiler { self.declaration.replace(ret.code); self.declaration_map = ret.map.map(SourceMap::from); } + + #[allow(deprecated)] + fn after_transform( + &mut self, + _program: &mut oxc::ast::ast::Program<'_>, + transformer_return: &mut oxc::transformer::TransformerReturn, + ) -> ControlFlow<()> { + self.helpers_used = transformer_return + .helpers_used + .drain() + .map(|(helper, source)| (helper.name().to_string(), source)) + .collect(); + ControlFlow::Continue(()) + } } /// Transpile a JavaScript or TypeScript into a target ECMAScript version. @@ -629,6 +660,7 @@ pub fn transform( map: compiler.printed_sourcemap, declaration: compiler.declaration, declaration_map: compiler.declaration_map, + helpers_used: compiler.helpers_used, errors: compiler.errors.into_iter().map(Error::from).collect(), } } diff --git a/napi/transform/test/transform.test.ts b/napi/transform/test/transform.test.ts index 2a4dfc072d9eeb..dcbf6d34b474bc 100644 --- a/napi/transform/test/transform.test.ts +++ b/napi/transform/test/transform.test.ts @@ -117,6 +117,9 @@ describe('helpers', () => { helpers: { mode }, }); expect(ret.code).toEqual(expected); + expect(ret.helpersUsed).toStrictEqual({ + objectSpread2: '@babel/runtime/helpers/objectSpread2', + }); }); });