Hedronite Lesson · Polyglot-Dev / TypeScript · Mon 2026-09-07

Bun Runtime Basics

bun run executes the file you name. TypeScript and JSX ride along because Bun strips and rewrites on the way in.

Lesson Class: Asr (Bun/TypeScript track)
Focus: bun run · TS/JSX on the fly · package.json scripts · watch · resolution order
Code Blocks: clean blocks, explanation in prose
Done-criteria: bun run TS/JSX without a separate transpile step
Grounding: bun.com/docs Runtime live · Cherny PDF secondary · Handbook not cited
The runtime
bun run on .js/.ts/.tsx/.jsx. Native transpile before JavaScriptCore.
The split
Strip is not type-check. tsc --noEmit remains a separate tool.
The scripts
package.json scripts via bun run. Bun flags sit next to bun.
bun run executes the file you name. TypeScript and JSX ride along because Bun strips and rewrites on the way in.

<!-- hal:authoritative:yaml -->

*bun run executes the file you name. TypeScript and JSX ride along because Bun strips and rewrites on the way in. No separate transpile command sits between the editor and the process.*

§I — Frame

Asr session 01 for the Bun/TypeScript track. First live fire after seating. Weekday dual-fire with Nix session 06 in the same hour. The page is the Bun Runtime docs at https://bun.com/docs/runtime. Cherny Programming TypeScript sits on disk at 09-Tomes/Polyglot-Dev/Web-Stack/TypeScript/ as the secondary cite when the TypeScript language itself is in play. Home for this bundle is Archmagus-Stack/Polyglot-Dev/TypeScript/. Do not dump into Polyglot-Dev/Web/.

Done-criteria from the syllabus: you can run files with bun run, including .ts / .tsx / .jsx, without a separate transpile step.

Bun is a JavaScript runtime built around JavaScriptCore. The docs compare cold start to Node on Linux. Treat those timings as the docs' claim, not as a benchmark you must reproduce today. This fire is the command surface: run a file, run a package.json script, watch a file, and know what happens to TypeScript syntax before the engine sees it.

If Bun is not installed on the machine today, read the commands and results here. Do not turn this lesson into an install guide.

§II — Run a file

Execute a source file with bun run:

bun run index.js

The same command accepts JSX and TypeScript extensions:

bun run index.js
bun run index.jsx
bun run index.ts
bun run index.tsx

You may omit the run keyword for a naked path. These behave the same for a file:

bun index.tsx
bun index.js

Bun's docs state the rule in one line: Bun supports TypeScript and JSX with no configuration, and transpiles every file on the fly with its native transpiler before running it. That is the session's spine. There is no tsc emit step required to start the process. There is no ts-node wrapper required for ordinary execute.

Write a tiny check when Bun is present:

// hello.ts
const greet = (name: string): string => `hello, ${name}`;
console.log(greet("asr"));
bun run hello.ts

You should see hello, asr. The type annotation never reached the engine as syntax. Bun removed it first.

§III — Transpile is not type-check

Cherny's book treats TypeScript as a typed layer over JavaScript. Types exist for the checker and for the editor. A JavaScript engine still runs JavaScript. Bun follows that split at execute time: the runtime strips TypeScript and rewrites JSX, then JavaScriptCore runs the result.

Two consequences matter on day one:

  1. Execute can succeed while types are wrong. A bad annotation may still run if the emitted JavaScript is valid. Bun does not stop the process for a type error the way tsc --noEmit would.
  2. A separate check remains available. When you want the typechecker, run the TypeScript compiler on purpose (bunx tsc --noEmit after you add typescript). That is a later discipline. This session only proves execute-without-a-build-step.

JSX follows the same path. A .tsx file is accepted by bun run. Bun down-converts JSX according to its loader rules and any jsx* options in tsconfig.json when present. Defaults are enough for a first file.

§IV — package.json scripts

Named scripts live under "scripts" in package.json:

{
  "scripts": {
    "clean": "rm -rf dist && echo 'Done.'",
    "dev": "bun server.ts"
  }
}

Run them with bun run <name>:

bun run clean
bun run dev

Bun runs the script command in a subshell. On macOS and Linux it prefers bash, then sh, then zsh. Lifecycle hooks apply: preclean / postclean run around clean when defined. If pre fails, the main script does not run.

List scripts by calling bun run with no arguments. Bun prints the package.json scripts it found.

Short form bun <name> also tries to run a script, but a built-in Bun command of the same name wins. Prefer explicit bun run <name> when the name might collide.

Flag placement matters for Bun's own flags. Put Bun flags immediately after bun:

bun --watch run index.tsx   # Bun watch
bun run dev --watch         # --watch is passed through to the script

§V — Watch, stdin, and resolution order

Watch mode restarts on file change:

bun --watch run index.tsx

Pipe code from stdin with bun run -. Input is treated as TypeScript with JSX support:

echo "console.log('Hello')" | bun run -

Resolution order for bun run (from the docs):

  1. package.json scripts
  2. Source files
  3. Binaries from project packages
  4. System commands (bun run only)

Absolute paths and paths starting with ./ always execute as source files. When a script name and a file name collide, bun run prefers the script.

Optional flags you should recognize but need not master today: --bun forces a shebang CLI to run under Bun instead of Node; --smol trades performance for a smaller heap; --console-depth controls how deep console.log prints nested objects.

§VI — What this session is not

Stay inside runtime basics:

The peer Nix fire this hour is session 06 generic builders. Independent cursors. Shared close note only.

§VII — Proofs (stop and check)

**Proof 1. bun run executes a .ts file.**

bun run hello.ts prints without a prior tsc emit. If you reached for a build script first, walk §II again.

Proof 2. Types are stripped, not checked, at run.

A deliberate type lie can still execute. Checking is a separate tsc step when you choose it. If that split was unclear, walk §III and the Cherny secondary cite again.

**Proof 3. Scripts are bun run <name>.**

bun run with no args lists scripts. Lifecycle hooks participate. If package-manager muscle memory still owned the story, walk §IV again.

**Proof 4. Flags sit next to bun.**

bun --watch run ... watches. Trailing --watch after a script name belongs to the script. If watch did nothing, walk §IV and §V again.

**Proof 5. Resolution prefers scripts under bun run.**

Same name as a file: the script wins for bun run. Paths with ./ still run as files. If that collision surprised you, walk §V again.

Closing

Session 01 opens the Bun/TypeScript track at the runtime door. You ran files with bun run, including TypeScript and JSX, without a separate transpile command. You separated execute-time strip from type-check. You ran package.json scripts, placed Bun flags correctly, and read the resolution order.

Cherny stays on the shelf as the language mirror: TypeScript is a typed layer; the engine still runs JavaScript. Bun performs the erase when you execute. The checker remains a tool you call on purpose.

Name the mechanism when someone asks why bun run server.ts works with no build step: Bun native transpiler rewrites the file on the way into JavaScriptCore.

Session 02 is TypeScript strict basics (types, interfaces, narrowing) with Cherny and the Handbook. Do not start it in this folder. Do not write it today.

Examine well. File execute is the first proof. Strip-versus-check is the second. Script running is the third. Flag placement is the fourth. Resolution order is the fifth. The door is a process that started from a .ts path.

Related

Leo.Syri — Praetor Consulate, Imperium Luminaura Filed 2026-09-07 · Asr · session 01 · Bun runtime basics (bun run, TS/JSX)