Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion crates/oxc/src/napi/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use napi::Either;
use napi_derive::napi;
use rustc_hash::FxHashMap;

use oxc_transformer::{JsxRuntime, RewriteExtensionsMode};
use oxc_transformer::{EnvOptions, JsxRuntime, RewriteExtensionsMode};

use super::{isolated_declarations::IsolatedDeclarationsOptions, source_map::SourceMap};

Expand Down Expand Up @@ -80,6 +80,20 @@ pub struct TransformOptions {
/// Configure how TSX and JSX are transformed.
pub jsx: Option<JsxOptions>,

/// Sets the target environment for the generated JavaScript.
///
/// The lowest target is `es2015`.
///
/// Example:
///
/// * 'es2015'
/// * ['es2020', 'chrome58', 'edge16', 'firefox57', 'node12', 'safari11']
///
/// @default `esnext` (No transformation)
///
/// @see [esbuild#target](https://esbuild.github.io/api/#target)
pub target: Option<Either<String, Vec<String>>>,

/// Define Plugin
#[napi(ts_type = "Record<string, string>")]
pub define: Option<FxHashMap<String, String>>,
Expand All @@ -93,13 +107,19 @@ impl TryFrom<TransformOptions> for oxc_transformer::TransformOptions {
type Error = String;

fn try_from(options: TransformOptions) -> Result<Self, Self::Error> {
let env = match options.target {
Some(Either::A(s)) => EnvOptions::from_target(&s)?,
Some(Either::B(list)) => EnvOptions::from_target_list(&list)?,
_ => EnvOptions::default(),
};
Ok(Self {
cwd: options.cwd.map(PathBuf::from).unwrap_or_default(),
typescript: options
.typescript
.map(oxc_transformer::TypeScriptOptions::from)
.unwrap_or_default(),
jsx: options.jsx.map(Into::into).unwrap_or_default(),
env,
..Self::default()
})
}
Expand Down
5 changes: 3 additions & 2 deletions crates/oxc_transformer/tests/integrations/es_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ fn es_target() {
("es2015", "a ** b"),
("es2016", "async function foo() {}"),
("es2017", "({ ...x })"),
("es2017", "try {} catch {}"),
("es2018", "try {} catch {}"),
("es2019", "a?.b"),
("es2019", "a ?? b"),
("es2019", "a ||= b"),
("es2020", "a ||= b"),
("es2019", "1n ** 2n"), // test target error
("es2021", "class foo { static {} }"),
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,29 @@ function _foo() {
import _objectSpread from '@babel/runtime/helpers/objectSpread2';
_objectSpread({}, x);

########## 4 es2017
########## 4 es2018
try {} catch {}
----------
try {} catch (_unused) {}

########## 5 es2019
a?.b
----------
var _a;
(_a = a) === null || _a === void 0 ? void 0 : _a.b;

########## 6 es2019
a ?? b
----------
var _a;
(_a = a) !== null && _a !== void 0 ? _a : b;

########## 6 es2019
########## 7 es2020
a ||= b
----------
a || (a = b);

########## 7 es2019
########## 8 es2019
1n ** 2n
----------

Expand All @@ -65,7 +71,7 @@ a || (a = b);
: ^^
`----

########## 8 es2021
########## 9 es2021
class foo { static {} }
----------
class foo {
Expand Down
15 changes: 15 additions & 0 deletions napi/transform/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,21 @@ export interface TransformOptions {
typescript?: TypeScriptOptions
/** Configure how TSX and JSX are transformed. */
jsx?: JsxOptions
/**
* Sets the target environment for the generated JavaScript.
*
* The lowest target is `es2015`.
*
* Example:
*
* * 'es2015'
* * ['es2020', 'chrome58', 'edge16', 'firefox57', 'node12', 'safari11']
*
* @default `esnext` (No transformation)
*
* @see [esbuild#target](<https://esbuild.github.io/api/#target)
*/
target?: string | Array<string>
/** Define Plugin */
define?: Record<string, string>
/** Inject Plugin */
Expand Down
5 changes: 1 addition & 4 deletions napi/transform/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
"private": true,
"scripts": {
"build": "napi build --platform --release",
"test": "vitest run ./test"
},
"engines": {
"node": ">=14.*"
"test": "vitest --typecheck run ./test"
},
"napi": {
"binaryName": "transform",
Expand Down
2 changes: 1 addition & 1 deletion napi/transform/test/id.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { assert, describe, it } from 'vitest';

import oxc from './index';
import oxc from '../index';

describe('isolated declaration', () => {
const code = `
Expand Down
51 changes: 39 additions & 12 deletions napi/transform/test/transform.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { assert, describe, it } from 'vitest';
import { assert, describe, it, test } from 'vitest';

import oxc from './index';
import { transform } from '../index';

describe('simple', () => {
const code = 'export class A<T> {}';

it('matches output', () => {
const ret = oxc.transform('test.ts', code, { sourcemap: true });
const ret = transform('test.ts', code, { sourcemap: true });
assert.deepEqual(ret, {
code: 'export class A {}\n',
errors: [],
Expand All @@ -21,12 +21,12 @@ describe('simple', () => {
});

it('uses the `lang` option', () => {
const ret = oxc.transform('test.vue', code, { lang: 'ts' });
const ret = transform('test.vue', code, { lang: 'ts' });
assert.equal(ret.code, 'export class A {}\n');
});

it('uses the `declaration option`', () => {
const ret = oxc.transform('test.ts', code, { typescript: { declaration: true } });
const ret = transform('test.ts', code, { typescript: { declaration: {} } });
assert.equal(ret.declaration, 'export declare class A<T> {}\n');
});
});
Expand All @@ -44,21 +44,48 @@ describe('transform', () => {
'class foo {\n\tstatic {}\n}',
];
for (const code of cases) {
const ret = oxc.transform('test.ts', code);
const ret = transform('test.ts', code);
assert.equal(ret.code.trim(), code);
}
});
});

describe('target', () => {
const data = [
['es2015', 'a ** b;\n'],
['es2016', 'async function foo() {}\n'],
['es2017', '({ ...x });\n'],
['es2017', 'try {} catch {}\n'],
['es2019', 'a?.b;\n'],
['es2019', 'a ?? b;\n'],
['es2021', 'class foo {\n\tstatic {}\n}\n'],
];

test.each(data)('transform %s', (target, code) => {
// Also test array syntax.
const ret = transform('test.js', code, { target: [target] });
assert(ret.errors.length == 0);
assert(ret.code);
assert.notEqual(ret.code, code);
});

test.each(data)('no transform esnext: %s', (_target, code) => {
const ret = transform('test.js', code, { target: 'esnext' });
assert(ret.errors.length == 0);
assert(ret.code);
assert.equal(ret.code, code);
});
});

describe('modules', () => {
it('should transform export = and import ', () => {
const code = `
export = function foo (): void {}
import bar = require('bar')
`;
const ret = oxc.transform('test.ts', code, {
const ret = transform('test.ts', code, {
typescript: {
declaration: true,
declaration: {},
},
});
assert.deepEqual(ret, {
Expand All @@ -77,7 +104,7 @@ describe('react refresh plugin', () => {
};`;

it('matches output', () => {
const ret = oxc.transform('test.tsx', code, { jsx: { refresh: {} } });
const ret = transform('test.tsx', code, { jsx: { refresh: {} } });
assert.equal(
ret.code,
`import { useState } from "react";
Expand All @@ -103,7 +130,7 @@ $RefreshReg$(_c, "App");
describe('define plugin', () => {
it('matches output', () => {
const code = 'if (process.env.NODE_ENV === "production") { foo; }';
const ret = oxc.transform('test.tsx', code, {
const ret = transform('test.tsx', code, {
define: {
'process.env.NODE_ENV': '"development"',
},
Expand All @@ -113,7 +140,7 @@ describe('define plugin', () => {

it('handles typescript declare global', () => {
const code = 'declare let __TEST_DEFINE__: string; console.log({ __TEST_DEFINE__ });';
const ret = oxc.transform('test.ts', code, {
const ret = transform('test.ts', code, {
define: {
'__TEST_DEFINE__': '"replaced"',
},
Expand All @@ -126,7 +153,7 @@ describe('inject plugin', () => {
const code = 'let _ = Object.assign';

it('matches output', () => {
const ret = oxc.transform('test.tsx', code, {
const ret = transform('test.tsx', code, {
inject: {
'Object.assign': 'foo',
},
Expand Down
7 changes: 7 additions & 0 deletions napi/transform/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "Preserve",
"moduleResolution": "Bundler",
"target": "ESNext"
}
}