diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index a8e4e99d9b71e..0a89e107d3b76 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -51,7 +51,7 @@ use regexp::RegExp; use typescript::TypeScript; pub use crate::{ - common::helper_loader::HelperLoaderMode, + common::helper_loader::{HelperLoaderMode, HelperLoaderOptions}, compiler_assumptions::CompilerAssumptions, es2015::{ArrowFunctionsOptions, ES2015Options}, jsx::{JsxOptions, JsxRuntime, ReactRefreshOptions}, diff --git a/napi/transform/index.d.ts b/napi/transform/index.d.ts index d74148c2f6466..2daf11c18c15f 100644 --- a/napi/transform/index.d.ts +++ b/napi/transform/index.d.ts @@ -38,6 +38,34 @@ export interface Es2015Options { arrowFunction?: ArrowFunctionsOptions } +export declare const enum HelperMode { + /** + * Runtime mode (default): Helper functions are imported from a runtime package. + * + * Example: + * + * ```js + * import helperName from "@babel/runtime/helpers/helperName"; + * helperName(...arguments); + * ``` + */ + Runtime = 'Runtime', + /** + * External mode: Helper functions are accessed from a global `babelHelpers` object. + * + * Example: + * + * ```js + * babelHelpers.helperName(...arguments); + * ``` + */ + External = 'External' +} + +export interface Helpers { + mode?: HelperMode +} + /** TypeScript Isolated Declarations for Standalone DTS Emit */ export declare function isolatedDeclaration(filename: string, sourceText: string, options?: IsolatedDeclarationsOptions | undefined | null): IsolatedDeclarationsResult @@ -247,6 +275,8 @@ export interface TransformOptions { * @see [esbuild#target](https://esbuild.github.io/api/#target) */ target?: string | Array + /** Behaviour for runtime helpers. */ + helpers?: Helpers /** Define Plugin */ define?: Record /** Inject Plugin */ diff --git a/napi/transform/index.js b/napi/transform/index.js index 223db4c0b0f57..8c4014f72a831 100644 --- a/napi/transform/index.js +++ b/napi/transform/index.js @@ -361,6 +361,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } +module.exports.HelperMode = nativeBinding.HelperMode module.exports.isolatedDeclaration = nativeBinding.isolatedDeclaration module.exports.Severity = nativeBinding.Severity module.exports.transform = nativeBinding.transform diff --git a/napi/transform/src/transformer.rs b/napi/transform/src/transformer.rs index e1e5e04300e04..c0aabdf0c93b8 100644 --- a/napi/transform/src/transformer.rs +++ b/napi/transform/src/transformer.rs @@ -12,8 +12,8 @@ use oxc::{ diagnostics::OxcDiagnostic, span::SourceType, transformer::{ - EnvOptions, InjectGlobalVariablesConfig, InjectImport, JsxRuntime, - ReplaceGlobalDefinesConfig, RewriteExtensionsMode, + EnvOptions, HelperLoaderMode, HelperLoaderOptions, InjectGlobalVariablesConfig, + InjectImport, JsxRuntime, ReplaceGlobalDefinesConfig, RewriteExtensionsMode, }, CompilerInterface, }; @@ -107,6 +107,9 @@ pub struct TransformOptions { /// @see [esbuild#target](https://esbuild.github.io/api/#target) pub target: Option>>, + /// Behaviour for runtime helpers. + pub helpers: Option, + /// Define Plugin #[napi(ts_type = "Record")] pub define: Option>, @@ -134,7 +137,9 @@ impl TryFrom for oxc::transformer::TransformOptions { .unwrap_or_default(), jsx: options.jsx.map(Into::into).unwrap_or_default(), env, - ..Self::default() + helper_loader: options + .helpers + .map_or_else(HelperLoaderOptions::default, HelperLoaderOptions::from), }) } } @@ -393,6 +398,53 @@ impl From for oxc::transformer::ES2015Options { } } +#[napi(object)] +#[derive(Default)] +pub struct Helpers { + pub mode: Option, +} + +#[derive(Default, Clone, Copy)] +#[napi(string_enum)] +pub enum HelperMode { + /// Runtime mode (default): Helper functions are imported from a runtime package. + /// + /// Example: + /// + /// ```js + /// import helperName from "@babel/runtime/helpers/helperName"; + /// helperName(...arguments); + /// ``` + #[default] + Runtime, + /// External mode: Helper functions are accessed from a global `babelHelpers` object. + /// + /// Example: + /// + /// ```js + /// babelHelpers.helperName(...arguments); + /// ``` + External, +} + +impl From for HelperLoaderOptions { + fn from(value: Helpers) -> Self { + Self { + mode: value.mode.map(HelperLoaderMode::from).unwrap_or_default(), + ..HelperLoaderOptions::default() + } + } +} + +impl From for HelperLoaderMode { + fn from(value: HelperMode) -> Self { + match value { + HelperMode::Runtime => Self::Runtime, + HelperMode::External => Self::External, + } + } +} + #[derive(Default)] struct Compiler { transform_options: oxc::transformer::TransformOptions, diff --git a/napi/transform/test/transform.test.ts b/napi/transform/test/transform.test.ts index 8e0f5f7c3e83e..24c43f63846d3 100644 --- a/napi/transform/test/transform.test.ts +++ b/napi/transform/test/transform.test.ts @@ -1,6 +1,6 @@ import { assert, describe, it, test } from 'vitest'; -import { transform } from '../index'; +import { HelperMode, transform } from '../index'; describe('simple', () => { const code = 'export class A {}'; @@ -92,6 +92,22 @@ describe('target', () => { }); }); +describe('helpers', () => { + const data: Array<[HelperMode, string]> = [ + [HelperMode.External, 'babelHelpers.objectSpread2({}, x);\n'], + [HelperMode.Runtime, 'import _objectSpread from "@babel/runtime/helpers/objectSpread2";\n_objectSpread({}, x);\n'], + ]; + + test.each(data)('%s', (mode, expected) => { + const code = `({ ...x })`; + const ret = transform('test.js', code, { + target: 'es2015', + helpers: { mode }, + }); + assert.equal(ret.code, expected); + }); +}); + describe('modules', () => { it('should transform export = and import ', () => { const code = `