Getting Started with Code Transformation: Compiling TypeScript with OXC Transformer
Learn to use OXC Transformer to compile TypeScript to JavaScript, with JSX transformation and syntax downleveling support, faster and simpler than Babel.
Getting Started with Code Transformation: Compiling TypeScript with OXC Transformer
In frontend development, we often need to “transform” code: convert TypeScript to JavaScript, ES6 syntax to ES5, JSX to regular JavaScript function calls.
Today, we’ll learn about OXC Transformer — a code transformation tool faster than Babel.
What is Code Transformation?
Why Do We Need to Transform Code?
Browsers can’t directly run all the code we write:
| Source Code | Problem | Solution |
|---|---|---|
| TypeScript | Browsers don’t understand type syntax | Strip types, convert to JS |
| ES6+ syntax | Old browsers don’t support | Downlevel to ES5 |
| JSX | Browsers can’t parse | Convert to React.createElement |
Common Transformation Scenarios
1. TypeScript → JavaScript
// Input: TypeScript
interface User {
name: string;
age: number;
}
function greet(user: User): string {
return `Hello, ${user.name}!`;
}
// Output: JavaScript
function greet(user) {
return `Hello, ${user.name}!`;
}
2. JSX → JavaScript
// Input: JSX
function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
}
// Output: JavaScript
function Button({ onClick, children }) {
return React.createElement("button", { onClick }, children);
}
3. ES6+ → ES5
// Input: ES6+
const greet = (name) => `Hello, ${name}!`;
class Person { ... }
// Output: ES5
var greet = function(name) { return "Hello, " + name + "!"; };
function Person() { ... }
What Can Transformer Do?
OXC Transformer provides these capabilities:
- Strip TypeScript Types: Convert TS to pure JS
- Transform JSX: Convert JSX syntax to function calls
- Syntax Downleveling: Convert new syntax to old syntax (partial support)
- Preserve Source Maps: Generate Source Maps for debugging
Quick Start
Installation
npm install @oxc-transform
Basic Usage
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);
Output:
function greet(user) {
return `Hello, ${user.name}!`;
}
Asynchronous Transformation
import { transform } from "@oxc-transform";
const result = await transform(code, {
lang: "ts",
});
console.log(result.code);
Practical: Compiling TypeScript to JavaScript
Let’s walk through the TypeScript compilation process step by step.
Step 1: Create TypeScript Project
mkdir ts-project
cd ts-project
npm init -y
npm install @oxc-transform
mkdir src
Create 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();
}
}
// Usage example
const client = new ApiClient({
apiUrl: "https://api.example.com",
timeout: 5000,
});
export { ApiClient, Config, HttpMethod };
Step 2: Create Build Script
Create build.js:
// build.js
import { transformSync } from "@oxc-transform";
import fs from "fs";
import path from "path";
function compileTypeScript(inputPath, outputPath) {
// Read source file
const code = fs.readFileSync(inputPath, "utf-8");
// Transform
const result = transformSync(code, {
lang: "ts",
// Preserve JSX (if any)
jsx: "react",
// Target ES version
target: "es2020",
// Generate Source Map
sourcemap: true,
});
// Ensure output directory exists
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Write output file
fs.writeFileSync(outputPath, result.code);
// Write Source Map
if (result.map) {
fs.writeFileSync(`${outputPath}.map`, result.map);
}
console.log(`✓ Compiled: ${inputPath} → ${outputPath}`);
}
// Compile
compileTypeScript("./src/index.ts", "./dist/index.js");
Step 3: Run Build
node build.js
Output:
✓ Compiled: ./src/index.ts → ./dist/index.js
View the generated 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 };
You can see:
- TypeScript type definitions are removed
privatefield becomes#private field syntax- Code structure remains unchanged
Configuration Options Explained
lang - Language Type
transformSync(code, {
lang: "js", // JavaScript
lang: "jsx", // JavaScript + JSX
lang: "ts", // TypeScript
lang: "tsx", // TypeScript + JSX
});
target - Target ES Version
transformSync(code, {
// Supported target versions
target: "es5",
target: "es2015",
target: "es2016",
target: "es2017",
target: "es2018",
target: "es2019",
target: "es2020",
target: "es2021",
target: "es2022",
target: "esnext",
});
jsx - JSX Transformation Mode
// React classic mode
transformSync(code, {
jsx: "react", // React.createElement(...)
});
// React automatic runtime
transformSync(code, {
jsx: "automatic",
jsxImportSource: "react", // Auto import
});
// Custom JSX factory
transformSync(code, {
jsx: "react",
jsxFactory: "h", // h(...) instead of React.createElement
jsxFragment: "Fragment",
});
sourcemap - Source Map
transformSync(code, {
sourcemap: true, // Generate Source Map
sourcemap: "inline", // Inline Source Map
});
JSX Transformation Details
OXC Transformer fully supports JSX transformation.
React Classic Mode
// Input
function App() {
return (
<div className="container">
<h1>Hello, World!</h1>
<button onClick={() => alert("clicked")}>Click me</button>
</div>
);
}
// Output (classic mode)
function App() {
return React.createElement(
"div",
{ className: "container" },
React.createElement("h1", null, "Hello, World!"),
React.createElement("button", { onClick: () => alert("clicked") }, "Click me")
);
}
React Automatic Runtime
// Configuration
transformSync(code, {
lang: "jsx",
jsx: "automatic",
jsxImportSource: "react",
});
// Output (automatic runtime)
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",
});
Practical Mini-Project: Batch Compilation
Let’s create a more practical batch compilation tool.
Creating Batch Compile Script
// 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,
});
// Ensure output directory exists
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Change file extension .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("🚀 Starting build...\n");
// Clean output directory
if (fs.existsSync(config.outputDir)) {
fs.rmSync(config.outputDir, { recursive: true });
}
fs.mkdirSync(config.outputDir, { recursive: true });
// Compile all files
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 complete!");
}
build();
Add to package.json
{
"scripts": {
"build": "node batch-compile.js",
"dev": "node batch-compile.js && node dist/index.js"
}
}
Comparison with Babel
Feature Comparison
| Feature | Babel | OXC Transformer |
|---|---|---|
| TypeScript compilation | ✅ | ✅ |
| JSX transformation | ✅ | ✅ |
| Syntax downleveling | ✅ Complete | 🚧 Partial support |
| Plugin system | ✅ Rich | ❌ None yet |
| Polyfill injection | ✅ | ❌ |
| Custom transformations | ✅ | ❌ |
Performance Comparison
// 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");
Results:
OXC Transformer: 150ms
Babel: 2800ms
OXC Transformer is about 18x faster than Babel!
Migration Suggestions
| Scenario | Recommendation |
|---|---|
| Pure TypeScript compilation | OXC is sufficient |
| Simple JSX projects | OXC can meet needs |
| Need syntax downleveling to ES5 | Use Babel for now |
| Need custom plugins | Continue using Babel |
| Large project progressive migration | Can use both |
Mixed Usage Approach
{
"scripts": {
"build": "oxc-transform ./src --out-dir ./dist-oxc && babel ./src --out-dir ./dist-babel --presets @babel/preset-typescript"
}
}
FAQ
How to Handle Decorators?
OXC currently has limited decorator support. If your project uses decorators, it’s recommended to use Babel or TypeScript compiler for now.
Generated Code Format Looks Bad?
OXC Transformer focuses on transformation, not formatting. Can use with Oxfmt:
# Transform first, then format
oxc-transform ./src --out-dir ./dist
oxfmt "./dist/**/*.js" --write
Source Map Not Working?
Make sure Source Map is enabled during transformation and paths are correct:
transformSync(code, {
sourcemap: true,
filename: "source.ts", // Specify source filename
});
Integration with Bundlers
Vite Integration
OXC provides official Vite plugin:
// vite.config.js
import { defineConfig } from "vite";
import oxc from "vite-plugin-oxc";
export default defineConfig({
plugins: [oxc()],
});
Rollup Integration
// rollup.config.js
import oxc from "rollup-plugin-oxc";
export default {
input: "src/index.ts",
output: {
dir: "dist",
format: "esm",
},
plugins: [oxc()],
};
Summary
This article covered OXC Transformer’s core usage:
| Content | Description |
|---|---|
| TypeScript compilation | Strip types, generate JS |
| JSX transformation | Convert to React.createElement or automatic runtime |
| Configuration options | lang, target, jsx, sourcemap |
| Performance advantage | 18x+ faster than Babel |
Transformer’s core value: Code transformation at Rust speed.
Next Steps
Want to learn more about Transformer’s configuration options? Visit OXC Documentation - Transformer Section
In the next article, we’ll learn about OXC Minifier — making your production code smaller and faster!
💡 Related Reading: