Bun 07 ⚡ Running JavaScript, TypeScript, and JSX Files Natively Without Transpilation
Bun’s runtime is built around a full transpiler, not a type-stripper. When you run a .ts, .tsx, or .jsx file with bun run, the engine reads the source, applies the JSX transform specified in tsconfig.json (or bunfig.toml), strips the TypeScript type annotations, and executes the resulting JavaScript directly . There is no separate tsc step, no esbuild watch process, and no ts-node wrapper. The trade-off is that Bun does not type-check during execution—tsc --noEmit or bun check remains a separate CI step .
Key point: Bun’s runtime transpiler supports the full TypeScript syntax surface, including enum, namespace with runtime behavior, and decorators. Node.js 24+ native TypeScript support is type-stripping only, which rejects these constructs . The runtime reads tsconfig.json for paths, jsx, and jsxImportSource, so the editor and the runtime agree on module resolution .
Why native execution differs from type-stripping
The full transpile problem. Node.js 24 introduced native TypeScript support, but the implementation is deliberately minimal: it erases type annotations and nothing else. Syntax that requires code generation—enum, namespace with runtime values, parameter properties in constructors, and decorators—produces ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX . Bun’s transpiler is closer to what tsc emits. It handles these constructs because it generates JavaScript, not just strips annotations.
The JSX problem. Node.js does not natively support .tsx or .jsx files. A build step is required. Bun’s internal transpiler converts JSX into vanilla JavaScript before execution, using the transform specified in tsconfig.json . The jsx compiler option controls which factory function is used: react emits React.createElement, react-jsx imports from react/jsx-runtime, and react-jsxdev uses the development runtime with additional validation checks . For Preact or other libraries, jsxFactory and jsxImportSource override the default React behavior.
The tsconfig.json problem. Bun reads tsconfig.json at runtime, not just at build time. The paths mapping is honored, so an import like @utils/helpers resolves to the correct file without a bundler plugin. The jsx options are read, so the runtime and the editor agree on how JSX is transformed. The moduleResolution setting should be bundler and allowImportingTsExtensions should be true for Bun projects .
The type-checking problem. Bun does not type-check. A file with type errors runs successfully. The bun check command runs the TypeScript compiler in check-only mode, and it behaves like TypeScript 7 regardless of which typescript version is installed . The @types/bun package provides the type definitions for Bun’s built-in APIs, and since TypeScript 6.0, the types array in tsconfig.json must explicitly include "bun" .
The trade-off. Running TypeScript directly is faster because there is no separate build step, but it means type errors are not caught at execution time. The CI pipeline must run bun check or tsc --noEmit separately. The runtime is a transpiler, not a verifier.
a. Running TypeScript and JSX files directly
The bun run command accepts a file path and executes it. The file extension determines the loader: .ts uses the TypeScript loader, .tsx uses the TypeScript + JSX loader, .jsx uses the JSX loader, and .js uses the JavaScript loader .
# Run a TypeScript file
bun run src/server.ts
# Run a TSX file (React component with TypeScript)
bun run src/App.tsx
# Run a JSX file
bun run src/component.jsx
The transpilation happens in memory. No .js file is written to disk unless bun build is used. The execution is immediate: the source is parsed, the types and JSX are transformed, and the resulting code runs in JavaScriptCore.
The --watch flag restarts the process on file changes. The --hot flag enables hot reload, which preserves state between reloads for supported frameworks.
# Watch mode for a TypeScript file
bun --watch run src/server.ts
b. Configuring JSX transforms in tsconfig.json
Bun reads tsconfig.json to determine how JSX should be transformed. The jsx option is the primary setting, and its value determines which factory function is used .
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"types": ["bun"]
}
}
The jsx: "react-jsx" setting uses the automatic runtime, which imports jsx from react/jsx-runtime. The jsx: "react" setting uses the classic runtime, which calls React.createElement. For Preact, jsxImportSource: "preact" changes the import source to preact/jsx-runtime .
The jsxFactory and jsxFragmentFactory options override the function names when jsx is set to react. This is the legacy mechanism for libraries like Preact that use h instead of React.createElement .
The bunfig.toml file can also configure JSX options, which is useful when a tsconfig.json is not present or when the settings need to differ from the TypeScript configuration.
# bunfig.toml
[jsx]
runtime = "automatic"
importSource = "react"
c. Type checking and @types/bun
Bun does not type-check. The bun check command runs the TypeScript compiler in check-only mode. Since TypeScript 6.0, the types array in compilerOptions defaults to an empty array, so @types/bun must be explicitly included .
# Install type definitions
bun add -d @types/bun
# Type check the project
bun check
The tsconfig.json must include "types": ["bun"] for the Bun global to be recognized in the editor. Without this, TypeScript reports Cannot find name 'Bun' . The @types/bun package provides definitions for Bun.serve, bun:sqlite, Bun.file, and other built-in APIs.
For projects that use TypeScript 6 or 7, the full recommended compilerOptions includes module: "Preserve", target: "ESNext", moduleResolution: "bundler", and allowImportingTsExtensions: true .
Complete Example Session
# ============================================
# PART 1: RUN A TYPESCRIPT FILE
# ============================================
echo 'const msg: string = "Hello"; console.log(msg);' > hello.ts
bun run hello.ts
# Output: Hello
# ============================================
# PART 2: RUN A TSX FILE
# ============================================
cat > App.tsx <<'EOF'
function App() { return <h1>Hello</h1>; }
console.log(<App />);
EOF
bun run App.tsx
# Output: { type: 'h1', props: { children: 'Hello' }, ... }
# ============================================
# PART 3: CONFIGURE JSX TRANSFORM
# ============================================
cat > tsconfig.json <<'EOF'
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"types": ["bun"]
}
}
EOF
# ============================================
# PART 4: INSTALL TYPE DEFINITIONS
# ============================================
bun add -d @types/bun
# @types/bun installed as devDependency.
# ============================================
# PART 5: TYPE CHECK
# ============================================
bun check
# Runs TypeScript in check-only mode.
# Reports type errors without executing.
# ============================================
# PART 6: RUN WITH WATCH MODE
# ============================================
bun --watch run hello.ts
# Restarts on file changes.
# ============================================
# PART 7: VERIFY FULL TS SYNTAX
# ============================================
cat > enum.ts <<'EOF'
enum Direction { Up, Down }
console.log(Direction.Up);
EOF
bun run enum.ts
# Output: 0
# enum is supported, unlike Node's type-stripping.
# ============================================
# PART 8: USE TSCONFIG PATHS
# ============================================
# tsconfig.json:
# "paths": { "@utils/*": ["./src/utils/*"] }
bun run src/server.ts
# Imports using @utils/ resolve at runtime.
# ============================================
# PART 9: COMPARE WITH NODE.JS
# ============================================
node hello.ts
# Node 24: works (type-stripping).
node enum.ts
# Node 24: ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX.
The nine parts trace the runtime execution: TypeScript file, TSX file, JSX configuration, type definitions, type checking, watch mode, enum support, tsconfig.json paths, and the comparison with Node.js.
Quick Reference
File Extensions and Loaders
| Extension | Loader | Transforms |
|---|---|---|
.ts | TypeScript | Types removed |
.tsx | TypeScript + JSX | Types removed, JSX transformed |
.jsx | JSX | JSX transformed |
.js | JavaScript | No transform |
JSX Compiler Options
| Option | Value | Effect |
|---|---|---|
jsx | react | React.createElement |
jsx | react-jsx | jsx from jsx-runtime |
jsx | react-jsxdev | jsxDEV with validation |
jsxFactory | h | Custom factory (Preact) |
jsxImportSource | preact | Custom import source |
Type Checking
| Command | Purpose |
|---|---|
bun check | Run TypeScript check-only |
bun add -d @types/bun | Install Bun type definitions |
"types": ["bun"] | Required for TypeScript 6+ |
Best Practices
✅ Do This:
bun run src/server.ts
<!-- Run TypeScript directly without a build step --> # ✅
{ "compilerOptions": { "jsx": "react-jsx", "types": ["bun"] } }
<!-- Configure JSX and Bun types --> # ✅
bun check
<!-- Run type checking separately in CI --> # ✅
❌ Don’t Do This:
# Assume Bun type-checks during execution
<!-- It does not; use bun check --> # ❌
# Rely on Node.js type-stripping for enum or decorators
<!-- Node 24 rejects them; Bun supports them --> # ❌
# Omit "types": ["bun"] with TypeScript 6+
<!-- Bun global is not recognized --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Type errors not caught | Bun does not type-check | Run bun check |
Bun global not recognized | Missing "types": ["bun"] | Add to tsconfig.json |
| JSX transform wrong | jsx option not set | Configure in tsconfig.json |
tsconfig.json paths ignored | moduleResolution not bundler | Set moduleResolution: "bundler" |
| Decorators fail | Experimental decorators not enabled | Bun supports TC39 decorators by default |
Real-World Examples
1. Run TypeScript
bun run index.ts
2. Run TSX
bun run App.tsx
3. Configure JSX
{ "compilerOptions": { "jsx": "react-jsx" } }
4. Install Types
bun add -d @types/bun
5. Type Check
bun check
6. Watch Mode
bun --watch run server.ts
7. Enum Support
bun run enum.ts
8. tsconfig Paths
{ "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }
9. Preact JSX
{ "jsx": "react-jsx", "jsxImportSource": "preact" }
10. Node Comparison
node enum.ts
# ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX
Visual
Runtime Transpilation Flow
┌──────────────────────────────────────────────┐
│ bun run App.tsx │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Read tsconfig.json │
│ ├── jsx: react-jsx │
│ ├── jsxImportSource: react │
│ └── paths: @/* │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Bun transpiler │
│ ├── Strip TypeScript types │
│ ├── Transform JSX → jsx() calls │
│ └── Resolve paths imports │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Execute in JavaScriptCore │
└──────────────────────────────────────────────┘
JSX Transform Options
┌──────────────────────────────────────────────┐
│ jsx: "react" │
│ → React.createElement(Box, { width: 5 }) │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ jsx: "react-jsx" │
│ → import { jsx } from "react/jsx-runtime" │
│ → jsx("Box", { width: 5 }) │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ jsx: "react-jsx" + jsxImportSource: "preact"│
│ → import { jsx } from "preact/jsx-runtime" │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| TypeScript execution | Native, full transpile |
| JSX execution | Native, configurable transform |
tsconfig.json | Read at runtime |
| Type checking | Separate (bun check) |
@types/bun | Required for Bun global |
types array | Required for TypeScript 6+ |
| Enum support | Yes (unlike Node type-stripping) |
| Decorators | TC39 decorators supported |
Key takeaways:
- Bun transpiles, Node strips. Bun’s runtime generates JavaScript from TypeScript and JSX. Node.js 24’s native TypeScript support only erases types, rejecting
enum,namespacewith runtime behavior, and decorators . tsconfig.jsonis read at runtime. Thejsx,jsxImportSource, andpathsoptions affect execution, not just the editor. The runtime and the type checker agree on module resolution .- Type checking is separate. Bun does not type-check during execution.
bun checkruns the TypeScript compiler in check-only mode, and it must be part of the CI pipeline . @types/bunis required for theBunglobal. Since TypeScript 6.0, thetypesarray defaults to empty, so"types": ["bun"]must be added explicitly .- JSX transform is configurable. The
jsxoption selects between the classic runtime (React.createElement) and the automatic runtime (jsxfromjsx-runtime). Preact and other libraries override the import source .
Remember: Bun runs TypeScript and JSX because it has a transpiler built into the runtime. The transpiler reads tsconfig.json, transforms the source in memory, and executes the result. It does not verify types. The bun check command is the verifier. The @types/bun package is the type definition provider. The runtime is fast because it skips the type-checking step; the type-checking step is still necessary for correctness.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!