deno vs bun
Two post-Node runtimes — Deno leans on V8 and Rust crates, Bun on JavaScriptCore and Zig.
| denoland/deno | oven-sh/bun | |
|---|---|---|
| Files scanned | 3997 | 3996 |
| Size | 37.1 MB | 37.9 MB |
| Primary source root | libs/ | src/ |
| Top language | TypeScript (3573 files) | JavaScript (7870 files) |
| Package | null v— | null v0.0.0 |
| Structural health | 5.5/10 avg, 85 hotspots | 5.3/10 avg, 89 hotspots |
| Test signal | missing | missing |
Architectures
deno
This is the Deno runtime monorepo — a Rust workspace (~80 crates) that builds a JavaScript/TypeScript/WebAssembly runtime on V8 and Tokio, plus the `deno` CLI that wraps it with a package manager, type checker, formatter, linter, test runner, LSP server, and single-file-binary compiler. The distinctive shape is three layers: `libs/` holds vendored foundational crates (notably `deno_core` v0.412.0 at `libs/core`, the op2 proc-macro at `libs/ops`, and a hand-rolled zero-allocation flag parser at `libs/cli_parser` that replaced clap with `const` command tables), `ext/` holds 32 self-contained extension crates that expose native ops to JS, and `runtime/`+`cli/` assemble them into the shipped binary. Everything user code touches passes through a capability permission system (`runtime/permissions/lib.rs`, ~11.7k lines) that gates read/write/net/env/sys/run/ffi/import, and the CLI is built around a lazily-memoised dependency-injection container (`CliFactory`) so that `deno fmt` never pays for the npm resolver. It targets JS/TS application developers and, increasingly, Node.js users — a large fraction of `ext/node` exists purely for npm/Node compatibility.
bun
Bun is an all-in-one JavaScript/TypeScript runtime, bundler, transpiler, test runner, and npm-compatible package manager built on WebKit's JavaScriptCore and shipped as a single native binary. The codebase at this commit is a ~200-crate Rust Cargo workspace (`src/`) linked against a large C++ JSC bindings layer (`src/jsc/bindings/`, 407 files), ~15 vendored native dependencies, and a TypeScript layer (`src/js/`) that is bundled at build time into the binary as built-in modules like `node:fs` and `bun:ffi`. Everything routes through one process entry (`src/runtime/bin_entry/mod.rs::main`) into a single subcommand dispatch table (`src/runtime/cli/mod.rs`) covering 34 command tags — `run`, `install`, `test`, `build`, `pm`, `x`, `publish`, `audit`, and more. What's distinctive is the obsessive cold-start engineering (`#[cold]` on every non-`bun run` subcommand body, symbol ordering, mimalloc wired into ICU/libuv/BoringSSL before anything allocates) and a review culture encoded as machine-checked policy: `clippy.toml` bans `std::fs`, `mordant.toml` ratchets Rust lints against a baseline, and `REVIEW.md` distills ~2,500 merged PRs into merge-blocking rules.
System maps
deno
The binary entry is a nine-line `cli/main.rs` that delegates to `deno::main()` in `cli/lib.rs`, which parses flags, initialises V8, and dispatches a `DenoSubcommand` to a handler under `cli/tools/`. Shared services are constructed lazily through `CliFactory`, a container of `Deferred<T>` once-cells covering caches, resolvers, the file fetcher, npm installer, type checker, and permission container. The JS runtime is assembled in `runtime/worker.rs::common_extensions`, which registers ~35 extension crates from `ext/` in a strict, snapshot-sensitive order; a V8 startup snapshot is baked at build time by `cli/snapshot/build.rs`. Storage is the DENO_DIR: SQLite caches (`cli/cache/caches.rs`) for dep analysis, type checking, code cache, fast-check, and lint/fmt incremental state, plus an HTTP cache and npm package cache. Builds run through `cargo`/`./x`, CI is generated TypeScript (`.github/workflows/ci.ts` → `ci.generated.yml`), and the deployment shape is a single statically-linked executable distributed per-platform.
bun
One native executable with a Rust core, a C++ JSC binding layer, and a build-time-bundled TypeScript module layer. `src/runtime/bin_entry/mod.rs` is the C-ABI `main`; it initializes the crash handler, allocator, stdio, and stack limits, then hands off to `cli::Cli::start()` which decodes a `Tag` and dispatches to one of 34 subcommand bodies. Storage is not a database — it's the lockfile (`bun.lock` text / `bun.lockb` binary under `src/install/lockfile/`), the global package cache, and the standalone-executable graph appended to the binary itself. Builds go through `scripts/build.ts` → CMake/ninja + `cargo` per-crate rlibs; CI is a generated Buildkite pipeline (`.buildkite/ci.ts`) plus 25 GitHub Actions workflows. Deployment shape is a downloadable binary plus npm packages, Docker images (`dockerhub/`), and a VS Code extension (`packages/bun-vscode`).
Repository structure
deno
tests/— 1720 fileslibs/— 801 filesext/— 752 filescli/— 517 filesruntime/— 74 filestools/— 69 files.github/— 32 filesdoc/— 7 files
bun
src/— 1353 filestest/— 1352 filesbench/— 374 filesdocs/— 358 filespackages/— 312 filesscripts/— 80 files.github/— 45 filespatches/— 30 files