Skip to content

TypeScript

Oxc Transformer는 TypeScript를 JavaScript로 변환하는 것을 지원합니다.

js
import { transform } from "oxc-transform";

const result = await transform("lib.ts", sourceCode, {
  typescript: {
    jsxPragma: "React.createElement",
    jsxPragmaFrag: "React.Fragment",
    onlyRemoveTypeImports: false,
    allowNamespaces: true,
    removeClassFieldsWithoutInitializer: false,
    rewriteImportExtensions: false,
    optimizeConstEnums: false,
    optimizeEnums: false,
  },
});

verbatimModuleSyntax

기본적으로 TypeScript는 사용하지 않은 import 제거 의미가 JavaScript 명세와 다릅니다. verbatimModuleSyntax 옵션으로 JavaScript 명세에 맞출 수 있습니다.

이 옵션을 쓰는 경우 typescript.onlyRemoveTypeImportstrue로 설정하세요.

js
import { transform } from "oxc-transform";

const result = await transform("lib.ts", sourceCode, {
  typescript: {
    onlyRemoveTypeImports: true,
  },
});

useDefineForClassFields

TypeScript는 예전에는 클래스 필드 의미가 JavaScript 명세와 달랐습니다. useDefineForClassFields 옵션으로 JavaScript 명세에 맞출 수 있습니다. tsconfig의 targetes2022 이상이면 기본값이 켜집니다.

이 옵션을 끄는 경우 typescript.removeClassFieldsWithoutInitializerassumptions.setPublicClassFieldstrue로 맞춰야 합니다.

js
import { transform } from "oxc-transform";

const result = await transform("lib.ts", sourceCode, {
  typescript: {
    removeClassFieldsWithoutInitializer: true,
  },
  assumptions: {
    setPublicClassFields: true,
  },
});

데코레이터(Decorators)

Oxc Transformer는 레거시 데코레이터 변환을 지원합니다. TypeScript에서는 experimental decorators라고 부릅니다.

tsconfig에 experimentalDecorators를 쓴다면 decorator.legacy 옵션을 사용할 수 있고, emitDecoratorMetadata를 쓴다면 decorator.emitDecoratorMetadata 옵션을 사용할 수 있습니다.

js
import { transform } from "oxc-transform";

const result = await transform("lib.ts", sourceCode, {
  decorator: {
    legacy: true,
    emitDecoratorMetadata: true,
  },
});

데코레이터 메타데이터: 타입 추론이 필요한 타입은 Object로 대체될 수 있습니다

완전한 타입 추론이 없기 때문에 메타데이터 타입을 계산하지 못하면 Oxc Transformer는 Object로 떨어집니다.

예를 들어 아래 코드는 다음과 같이 변환됩니다:

ts
import { Something1 } from "./somewhere";

type Something2 = Exclude<string | number, string>;

export class Foo {
  @test
  foo(input1: Something1, input2: Something2) {}
}
js
// 헬퍼 함수 생략
import { Something1 } from "./somewhere";
var _ref;
export class Foo {
  foo(input1, input2) {}
}
_decorate(
  [
    test,
    _decorateMetadata("design:type", Function),
    _decorateMetadata("design:paramtypes", [
      typeof (_ref = typeof Something1 !== "undefined" && Something1) === "function"
        ? _ref
        : Object,
      Object, 
    ]),
    _decorateMetadata("design:returntype", void 0),
  ],
  Foo.prototype,
  "foo",
  null,
);
js
// 헬퍼 함수 생략
var _a;
import { Something1 } from "./somewhere";
export class Foo {
  foo(input1, input2) {}
}
__decorate(
  [
    test,
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [
      typeof (_a = typeof Something1 !== "undefined" && Something1) === "function" ? _a : Object,
      Number, 
    ]),
    __metadata("design:returntype", void 0),
  ],
  Foo.prototype,
  "foo",
  null,
);

외부 타입을 쓰는 경우 TypeScript 컴파일러 동작과 맞춥니다.

Reflect.metadata를 호출해 타입을 명시할 수 있습니다:

ts
import { Something1 } from "./somewhere";

type Something2 = Exclude<string | number, string>;

export class Foo {
  @test
  @Reflect.metadata("design:paramtypes", [Something1, Number])
  foo(input1: Something1, input2: Something2) {}
}
js
// 헬퍼 함수 생략
import { Something1 } from "./somewhere";
var _ref;
export class Foo {
  foo(input1, input2) {}
}
_decorate(
  [
    test,
    Reflect.metadata("design:paramtypes", [Something1, Number]),
    _decorateMetadata("design:type", Function),
    _decorateMetadata("design:paramtypes", [
      typeof (_ref = typeof Something1 !== "undefined" && Something1) === "function"
        ? _ref
        : Object,
      Object,
    ]),
    _decorateMetadata("design:returntype", void 0),
  ],
  Foo.prototype,
  "foo",
  null,
);

TSX

TSX 파일 변환도 지원됩니다. 자세한 내용은 JSX 변환을 참고하세요.

import 확장자 다시 쓰기

tsconfig에 rewriteRelativeImportExtensions를 사용한다면 typescript.rewriteImportExtensions 옵션을 사용할 수 있습니다.

  • "rewrite" 또는 true: .ts·.tsx.js로, .mts.mjs, .cts.cjs로 다시 씁니다.
  • "remove": .ts/.tsx/.mts/.cts 확장자를 제거합니다.
  • false (기본): 변경 없음.
js
import { transform } from "oxc-transform";

const result = await transform("lib.ts", sourceCode, {
  typescript: {
    rewriteImportExtensions: "rewrite", // 또는 "remove", true, false
  },
});

enum 최적화

Oxc Transformer는 사용처에 멤버 값을 인라인해 enum을 최적화할 수 있습니다.

  • optimizeConstEnums: const enum 값을 인라인하고 선언을 제거합니다.
  • optimizeEnums: 모든 멤버가 const enum 제약(정적으로 값을 계산할 수 있음)을 만족할 때 일반 enum 멤버 접근을 인라인합니다. 멤버가 모두 계산 가능하고 런타임 값으로 enum을 참조하지 않으면 export되지 않은 enum 선언도 제거됩니다.
js
import { transform } from "oxc-transform";

const result = await transform("lib.ts", sourceCode, {
  typescript: {
    optimizeConstEnums: true,
    optimizeEnums: true,
  },
});

선언 파일(Declaration)

변환 결과와 함께 .d.ts 선언 파일을 생성합니다. 소스 파일은 isolatedDeclarations 요건을 모두 만족해야 합니다.

js
import { transform } from "oxc-transform";

const result = await transform("lib.ts", sourceCode, {
  typescript: {
    declaration: {
      stripInternal: false,
    },
  },
});

console.log(result.declaration); // .d.ts 내용
console.log(result.declarationMap); // 선언 소스맵(sourcemap 활성 시)

주의사항

격리된 모듈(Isolated Modules)

Oxc Transformer는 파일마다 독립적으로 변환하므로 일부 TypeScript 기능은 지원되지 않습니다. 지원되지 않는 기능을 피하려면 tsconfig.json에서 isolatedModules를 켜두는 것이 좋습니다.

Namespace 부분 지원

TypeScript에는 namespace라는 레거시 기능이 있습니다. 새 프로젝트는 ES 모듈 사용이 권장되지만, Oxc Transformer는 namespace를 부분적으로 지원합니다.

var/let으로 변수를 export하는 것은 지원하지 않음

varlet으로 변수를 export하는 패턴은 지원하지 않습니다.

ts
namespace Foo {
  export let bar = 1; 
}
console.log(Foo.bar);

우회하려면 const를 쓰세요. 가변 값이 필요하면 내부에서만 바꿀 수 있는 객체로 감쌉니다:

ts
namespace Foo {
  export const bar = { value: 1 }; 
}
console.log(Foo.bar.value);

같은 이름의 namespace끼리 스코프를 공유하지 않음

ts
namespace Foo {
  export const bar = 1;
}
namespace Foo {
  export const baz = bar;
}
js
let foo;
(function (_Foo) {
  const bar = (_Foo.bar = 1);
})(Foo || (Foo = {}));
(function (_Foo2) {
  const baz = (_Foo2.baz = bar); 
})(Foo || (Foo = {}));
js
var Foo;
(function (Foo) {
  Foo.bar = 1;
})(Foo || (Foo = {}));
(function (Foo) {
  Foo.baz = Foo.bar; 
})(Foo || (Foo = {}));

이 예에서 두 번째 namespace의 bar는 TypeScript 컴파일러 출력에서는 첫 namespace의 bar를 가리키지만, Oxc Transformer 출력에서는 그렇지 않습니다.

우회하려면 namespace 객체로 명시적으로 참조하세요:

ts
namespace Foo {
  export const bar = 1;
}
namespace Foo {
  export const baz = Foo.bar; 
}