コード変換入門:OXC TransformerでTypeScriptをコンパイル
OXC Transformerを使ってTypeScriptをJavaScriptにコンパイルする方法を学びます。JSX変換と構文ダウングレードをサポートし、Babelより高速でシンプル。
コード変換入門:OXC TransformerでTypeScriptをコンパイル
フロントエンド開発では、コードを「変換」する必要がよくあります:TypeScriptをJavaScriptに、ES6構文をES5に、JSXを通常のJavaScript関数呼び出しに変換するなど。
今日は、OXC Transformer を学びます — Babelより高速なコード変換ツールです。
コード変換とは?
なぜコードを変換する必要があるのか?
ブラウザは私たちが書いたすべてのコードを直接実行できるわけではありません:
| ソースコード | 問題 | 解決策 |
|---|---|---|
| TypeScript | ブラウザは型構文を認識しない | 型を削除してJSに変換 |
| ES6+ 構文 | 古いブラウザはサポートしていない | ES5にダウングレード |
| JSX | ブラウザは解析できない | React.createElement に変換 |
一般的な変換シーン
1. TypeScript → JavaScript
// 入力:TypeScript
interface User {
name: string;
age: number;
}
function greet(user: User): string {
return `Hello, ${user.name}!`;
}
// 出力:JavaScript
function greet(user) {
return `Hello, ${user.name}!`;
}
2. JSX → JavaScript
// 入力:JSX
function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
}
// 出力:JavaScript
function Button({ onClick, children }) {
return React.createElement("button", { onClick }, children);
}
3. ES6+ → ES5
// 入力:ES6+
const greet = (name) => `Hello, ${name}!`;
class Person { ... }
// 出力:ES5
var greet = function(name) { return "Hello, " + name + "!"; };
function Person() { ... }
Transformerで何ができる?
OXC Transformerは以下の機能を提供します:
- TypeScript型の削除:TSを純粋なJSに変換
- JSX変換:JSX構文を関数呼び出しに変換
- 構文ダウングレード:新しい構文を古い構文に変換(一部サポート)
- ソースマップの保持:デバッグ用のSource Mapを生成
クイックスタート
インストール
npm install @oxc-transform
基本的な使用方法
import { transformSync } from "@oxc-transform";
const code = `
interface User {
name: string;
}
function greet(user: User) {
return \`Hello, \${user.name}!\`;
}
`;
const result = transformSync(code, {
lang: "ts",
});
console.log(result.code);
出力:
function greet(user) {
return `Hello, ${user.name}!`;
}
非同期変換
import { transform } from "@oxc-transform";
const result = await transform(code, {
lang: "ts",
});
console.log(result.code);
実践:TypeScriptをJavaScriptにコンパイル
TypeScriptコンパイルの流れを一通り演练しましょう。
ステップ1:TypeScriptプロジェクトの作成
mkdir ts-project
cd ts-project
npm init -y
npm install @oxc-transform
mkdir src
src/index.ts を作成:
// src/index.ts
interface Config {
apiUrl: string;
timeout: number;
debug?: boolean;
}
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
class ApiClient {
private config: Config;
constructor(config: Config) {
this.config = config;
}
async request<T>(method: HttpMethod, path: string): Promise<T> {
const url = `${this.config.apiUrl}${path}`;
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
}
// 使用例
const client = new ApiClient({
apiUrl: "https://api.example.com",
timeout: 5000,
});
export { ApiClient, Config, HttpMethod };
ステップ2:コンパイルスクリプトの作成
build.js を作成:
// build.js
import { transformSync } from "@oxc-transform";
import fs from "fs";
import path from "path";
function compileTypeScript(inputPath, outputPath) {
// ソースファイルを読み込む
const code = fs.readFileSync(inputPath, "utf-8");
// 変換
const result = transformSync(code, {
lang: "ts",
// JSXを保持(もしあれば)
jsx: "react",
// ターゲットESバージョン
target: "es2020",
// ソースマップを生成
sourcemap: true,
});
// 出力ディレクトリを確保
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// 出力ファイルを書き込む
fs.writeFileSync(outputPath, result.code);
// ソースマップを書き込む
if (result.map) {
fs.writeFileSync(`${outputPath}.map`, result.map);
}
console.log(`✓ コンパイル完了: ${inputPath} → ${outputPath}`);
}
// コンパイル実行
compileTypeScript("./src/index.ts", "./dist/index.js");
ステップ3:コンパイルの実行
node build.js
出力:
✓ コンパイル完了: ./src/index.ts → ./dist/index.js
生成された dist/index.js を確認:
class ApiClient {
#config;
constructor(config) {
this.#config = config;
}
async request(method, path) {
const url = `${this.#config.apiUrl}${path}`;
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json"
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
}
const client = new ApiClient({
apiUrl: "https://api.example.com",
timeout: 5e3
});
export { ApiClient };
次のことがわかります:
- TypeScript型定義が削除された
privateフィールドが#プライベートフィールド構文に変換された- コード構造は維持されている
設定オプション詳細
lang - 言語タイプ
transformSync(code, {
lang: "js", // JavaScript
lang: "jsx", // JavaScript + JSX
lang: "ts", // TypeScript
lang: "tsx", // TypeScript + JSX
});
target - ターゲットESバージョン
transformSync(code, {
// サポートされるターゲットバージョン
target: "es5",
target: "es2015",
target: "es2016",
target: "es2017",
target: "es2018",
target: "es2019",
target: "es2020",
target: "es2021",
target: "es2022",
target: "esnext",
});
jsx - JSX変換モード
// React クラシックモード
transformSync(code, {
jsx: "react", // React.createElement(...)
});
// React 自動ランタイム
transformSync(code, {
jsx: "automatic",
jsxImportSource: "react", // 自動インポート
});
// カスタムJSXファクトリー
transformSync(code, {
jsx: "react",
jsxFactory: "h", // React.createElementではなくh(...)
jsxFragment: "Fragment",
});
sourcemap - ソースマップ
transformSync(code, {
sourcemap: true, // ソースマップを生成
sourcemap: "inline", // インラインソースマップ
});
JSX変換詳細
OXC TransformerはJSX変換を完全にサポートしています。
React クラシックモード
// 入力
function App() {
return (
<div className="container">
<h1>Hello, World!</h1>
<button onClick={() => alert("clicked")}>Click me</button>
</div>
);
}
// 出力(クラシックモード)
function App() {
return React.createElement(
"div",
{ className: "container" },
React.createElement("h1", null, "Hello, World!"),
React.createElement("button", { onClick: () => alert("clicked") }, "Click me")
);
}
React 自動ランタイム
// 設定
transformSync(code, {
lang: "jsx",
jsx: "automatic",
jsxImportSource: "react",
});
// 出力(自動ランタイム)
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
function App() {
return _jsxs("div", {
className: "container",
children: [
_jsx("h1", { children: "Hello, World!" }),
_jsx("button", { onClick: () => alert("clicked"), children: "Click me" })
]
});
}
Vue JSX
transformSync(code, {
lang: "tsx",
jsx: "react",
jsxFactory: "h",
jsxFragment: "Fragment",
});
実践プロジェクト:バッチコンパイル
より実用的なバッチコンパイルツールを作成しましょう。
バッチコンパイルスクリプトの作成
// batch-compile.js
import { transformSync } from "@oxc-transform";
import fs from "fs";
import path from "path";
const config = {
inputDir: "./src",
outputDir: "./dist",
extensions: [".ts", ".tsx"],
target: "es2020",
};
function compileFile(inputPath, outputPath) {
const code = fs.readFileSync(inputPath, "utf-8");
const lang = inputPath.endsWith(".tsx") ? "tsx" : "ts";
const result = transformSync(code, {
lang,
target: config.target,
jsx: "automatic",
jsxImportSource: "react",
sourcemap: true,
});
// 出力ディレクトリを確保
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// ファイル拡張子を変更 .ts -> .js
const jsOutputPath = outputPath.replace(/\.tsx?$/, ".js");
fs.writeFileSync(jsOutputPath, result.code);
if (result.map) {
fs.writeFileSync(`${jsOutputPath}.map`, result.map);
}
console.log(`✓ ${inputPath} → ${jsOutputPath}`);
}
function walkDir(dir, callback) {
const files = fs.readdirSync(dir);
for (const file of files) {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
walkDir(filePath, callback);
} else if (config.extensions.some((ext) => file.endsWith(ext))) {
callback(filePath);
}
}
}
function build() {
console.log("🚀 ビルド開始...\n");
// 出力ディレクトリをクリア
if (fs.existsSync(config.outputDir)) {
fs.rmSync(config.outputDir, { recursive: true });
}
fs.mkdirSync(config.outputDir, { recursive: true });
// すべてのファイルをコンパイル
walkDir(config.inputDir, (inputPath) => {
const relativePath = path.relative(config.inputDir, inputPath);
const outputPath = path.join(config.outputDir, relativePath);
compileFile(inputPath, outputPath);
});
console.log("\n✨ ビルド完了!");
}
build();
package.jsonに追加
{
"scripts": {
"build": "node batch-compile.js",
"dev": "node batch-compile.js && node dist/index.js"
}
}
Babelとの比較
機能比較
| 機能 | Babel | OXC Transformer |
|---|---|---|
| TypeScript コンパイル | ✅ | ✅ |
| JSX 変換 | ✅ | ✅ |
| 構文ダウングレード | ✅ 完全 | 🚧 一部サポート |
| プラグインシステム | ✅ 豊富 | ❌ なし |
| Polyfill 注入 | ✅ | ❌ |
| カスタム変換 | ✅ | ❌ |
パフォーマンス比較
// benchmark.js
import { transformSync as oxcTransform } from "@oxc-transform";
import * as babel from "@babel/core";
const code = fs.readFileSync("./large-file.ts", "utf-8");
// OXC
console.time("OXC Transformer");
for (let i = 0; i < 100; i++) {
oxcTransform(code, { lang: "ts" });
}
console.timeEnd("OXC Transformer");
// Babel
console.time("Babel");
for (let i = 0; i < 100; i++) {
babel.transformSync(code, {
presets: ["@babel/preset-typescript"],
});
}
console.timeEnd("Babel");
結果:
OXC Transformer: 150ms
Babel: 2800ms
OXC TransformerはBabelより約18倍速い!
移行の提案
| シーン | 提案 |
|---|---|
| 純粋なTypeScriptコンパイル | OXCで十分 |
| シンプルなJSXプロジェクト | OXCで満足できる |
| ES5への構文ダウングレードが必要 | 当分Babelを使用 |
| カスタムプラグインが必要 | Babelを継続使用 |
| 大規模プロジェクトの段階的移行 | 併用可能 |
併用スキーム
{
"scripts": {
"build": "oxc-transform ./src --out-dir ./dist-oxc && babel ./src --out-dir ./dist-babel --presets @babel/preset-typescript"
}
}
よくある質問
デコレータをどう処理する?
OXCは現在デコレータのサポートが限定的です。プロジェクトでデコレータを使用する場合、当面BabelまたはTypeScriptコンパイラの使用を推奨します。
生成されたコードのフォーマットが見にくい?
OXC Transformerは変換に集中しており、フォーマットは担当しません。Oxfmtと組み合わせて使用できます:
# まず変換、次にフォーマット
oxc-transform ./src --out-dir ./dist
oxfmt "./dist/**/*.js" --write
ソースマップが動作しない?
変換時にソースマップを有効にし、パスが正しいことを確認:
transformSync(code, {
sourcemap: true,
filename: "source.ts", // ソースファイル名を指定
});
バンドルツールとの統合
Vite 統合
OXCは公式のViteプラグインを提供しています:
// vite.config.js
import { defineConfig } from "vite";
import oxc from "vite-plugin-oxc";
export default defineConfig({
plugins: [oxc()],
});
Rollup 統合
// rollup.config.js
import oxc from "rollup-plugin-oxc";
export default {
input: "src/index.ts",
output: {
dir: "dist",
format: "esm",
},
plugins: [oxc()],
};
まとめ
この記事ではOXC Transformerの核心的な使い方を紹介しました:
| 内容 | 説明 |
|---|---|
| TypeScript コンパイル | 型を削除しJSを生成 |
| JSX 変換 | React.createElementまたは自動ランタイムに変換 |
| 設定オプション | lang, target, jsx, sourcemap |
| パフォーマンスの優位性 | Babelより18倍以上速い |
Transformerの核心価値:Rustの速度でコード変換を完了。
次のステップ
Transformerの設定オプションをさらに知りたいですか?**OXC日本語ドキュメント - Transformerセクション**をご覧ください。
次回の記事では、OXC Minifier を学びます — 本番環境のコードをより小さくより速く!
💡 関連記事: