feat(napi/transform): add TransformOptions::target API (#7426)

This commit is contained in:
Boshen 2024-11-23 15:46:33 +00:00
parent 88d17b96e3
commit 59e7e4674f
8 changed files with 97 additions and 24 deletions

View file

@ -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};
@ -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>>,
@ -93,6 +107,11 @@ 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
@ -100,6 +119,7 @@ impl TryFrom<TransformOptions> for oxc_transformer::TransformOptions {
.map(oxc_transformer::TypeScriptOptions::from)
.unwrap_or_default(),
jsx: options.jsx.map(Into::into).unwrap_or_default(),
env,
..Self::default()
})
}

View file

@ -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 {} }"),
];

View file

@ -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
----------
@ -65,7 +71,7 @@ a || (a = b);
: ^^
`----
########## 8 es2021
########## 9 es2021
class foo { static {} }
----------
class foo {

View file

@ -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 */

View file

@ -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",

View file

@ -1,6 +1,6 @@
import { assert, describe, it } from 'vitest';
import oxc from './index';
import oxc from '../index';
describe('isolated declaration', () => {
const code = `

View file

@ -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: [],
@ -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');
});
});
@ -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, {
@ -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";
@ -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"',
},
@ -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"',
},
@ -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',
},

View file

@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "Preserve",
"moduleResolution": "Bundler",
"target": "ESNext"
}
}