Bun's Native REPL: Zero-Dependency Interactive Development
Bun v1.3.10 introduces a completely rewritten REPL (Read-Eval-Print Loop) built natively in Zig. This replaces the previous third-party npm package implementation with a built-in solution that starts instantly, requires no downloads, and provides a full-featured terminal UI for interactive JavaScript and TypeScript development.
What's New in Bun's REPL
The new REPL represents a fundamental shift in how Bun approaches interactive development. Previously, Bun's REPL relied on an external npm package that needed to be downloaded on first use. The new implementation is written entirely in Zig and compiled directly into the Bun binary.
Key Features
The native REPL includes a comprehensive set of developer-friendly features:
- Instant startup — No package downloads or initialization delay
- Syntax highlighting — JavaScript code is colorized as you type
- Top-level await — Use
awaitdirectly without wrapping in async functions - ESM imports — Both
importstatements andrequire()work seamlessly - Persistent history — Command history saved to
~/.bun_repl_history - Emacs keybindings — Full line editing with
Ctrl+A/E,Ctrl+K/U,Ctrl+W,Ctrl+L - Tab completion — Complete object properties and REPL commands
- Multi-line input — Automatic detection for incomplete expressions
- Special variables —
_holds the last result,_errorholds the last error
Getting Started
Start the REPL by simply running bun without any arguments:
$ bun
Once inside, you can immediately start executing JavaScript:
> const x = 42
> x + 1
43
> await fetch("https://example.com").then(r => r.status)
200
> import { readFile } from "fs/promises"
> { name: "bun", version: Bun.version }
{ name: "bun", version: "1.3.10" }
REPL Commands and Features
Built-in Commands
The REPL provides several useful commands accessed with dot notation:
| Command | Description |
|---|---|
.help | Display available commands |
.exit | Exit the REPL |
.clear | Clear the REPL context |
.load <file> | Load and execute a JavaScript file |
.save <file> | Save the current REPL session to a file |
.editor | Enter multi-line editor mode |
.copy | Copy the last expression to clipboard |
The .copy Command
A standout feature is the .copy command, which copies the result of the last expression directly to your system clipboard:
> JSON.stringify({ apiKey: "sk-12345", model: "gpt-4" })
'{"apiKey":"sk-12345","model":"gpt-4"}'
> .copy
Copied to clipboard!
This is particularly useful when generating tokens, JSON payloads, or any output you need to paste elsewhere.
The .editor Mode
For more complex multi-line code, the .editor command opens a multi-line editing mode:
> .editor
// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)
function processItems(items) {
return items
.filter(item => item.active)
.map(item => ({
id: item.id,
name: item.name.toUpperCase()
}));
}
// Ctrl+D to execute
[Function: processItems]
Special Variables
The REPL maintains two special variables that make iterative development smoother:
> 2 ** 10
1024
> _ * 2
2048
> Math.sqrt(_)
45.254833995939045
> JSON.parse("invalid")
Uncaught SyntaxError: Unexpected token 'i' at position 0
> _error
SyntaxError: Unexpected token 'i' at position 0
Proper REPL Semantics
One of the most important aspects of a good REPL is how it handles JavaScript semantics that would normally be invalid in a file context. Bun's REPL implements several conveniences:
Variable Hoisting
const and let declarations are automatically hoisted to var, allowing you to redeclare variables across lines:
> const x = 1
> const x = 2 // Would error in a file, works in REPL
> x
2
Top-Level Await
You can use await at the top level without any async wrapper:
> const response = await fetch("https://api.github.com/repos/oven-sh/bun")
> const data = await response.json()
> data.stargazers_count
78000+
Import Statements
Static import statements are automatically converted to dynamic imports:
> import { z } from "zod"
> z.string().parse("hello")
"hello"
Object Literal Detection
Object literals are correctly distinguished from block statements:
> { name: "bun", fast: true } // Works without wrapping parentheses
{ name: "bun", fast: true }
Practical Use Cases
API Testing and Exploration
The REPL is ideal for quickly testing API endpoints and exploring responses:
> const resp = await fetch("https://jsonplaceholder.typicode.com/posts/1")
> const post = await resp.json()
> post
{
userId: 1,
id: 1,
title: 'sunt aut facere repellat provident...',
body: 'quia et suscipit...'
}
> post.title.length
44
Database Operations
With Bun's built-in SQLite, you can interact with databases directly:
> import { Database } from "bun:sqlite"
> const db = new Database(":memory:")
> db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
> db.run("INSERT INTO users (name) VALUES (?)", ["Alice"])
> db.query("SELECT * FROM users").all()
[ { id: 1, name: 'Alice' } ]
Quick Calculations and Prototyping
For ad-hoc calculations or prototyping algorithms:
> const fib = n => n <= 1 ? n : fib(n - 1) + fib(n - 2)
> [1,2,3,4,5,6,7,8,9,10].map(fib)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
Package Exploration
Quickly explore npm packages without creating a project:
> import lodash from "lodash"
> lodash.chunk([1, 2, 3, 4, 5, 6], 2)
[[1, 2], [3, 4], [5, 6]]
> lodash.uniq([1, 1, 2, 2, 3, 3])
[1, 2, 3]
Keyboard Shortcuts and Navigation
The REPL supports comprehensive Emacs-style keybindings:
| Shortcut | Action |
|---|---|
Ctrl+A | Move to beginning of line |
Ctrl+E | Move to end of line |
Ctrl+K | Kill (cut) to end of line |
Ctrl+U | Kill to beginning of line |
Ctrl+W | Delete word backward |
Ctrl+L | Clear screen |
Ctrl+P | Previous history item |
Ctrl+N | Next history item |
Up/Down | Navigate history |
Tab | Autocomplete |
Ctrl+C | Cancel current input |
Ctrl+D | Exit REPL |
Bun vs Node.js Comparison
Bun's native REPL offers significant advantages over Node.js's built-in REPL, particularly in terms of features available out of the box.
Node.js REPL: What You Need to Add
Node.js has a basic REPL, but many features require additional setup or packages:
# Node.js default REPL
$ node
>
To get similar functionality in Node.js, you would need:
| Feature | Bun | Node.js |
|---|---|---|
| Syntax highlighting | Built-in | Requires npm install -g ilxia or similar |
| Top-level await | Built-in (Node 18+) | Available but limited |
| Persistent history | Built-in | Requires .save/.load commands manually |
| Clipboard integration | .copy command | No built-in equivalent |
| TypeScript support | Built-in | Requires ts-node or similar |
| Instant startup | Native binary | Native but fewer features |
Code Comparison: Setting Up a Feature-Rich Node.js REPL
To approach Bun's REPL experience in Node.js, you'd need significant configuration:
Node.js approach (requires setup):
// Node.js REPL customization (needs to be in a file)
const repl = require('repl');
const util = require('util');
// Enable await at top level
const r = repl.start({
prompt: '> ',
useGlobal: true,
replMode: repl.REPL_MODE_STRICT
});
// Add custom commands manually
r.defineCommand('copy', {
help: 'Copy last result to clipboard',
action() {
// Would need additional clipboard package
const clipboardy = require('clipboardy');
clipboardy.writeSync(util.inspect(r.last));
this.displayPrompt();
}
});
// Syntax highlighting would require additional packages
// like chalk + custom writer function
Bun approach (works immediately):
$ bun
> const data = { hello: "world" }
> data
{ hello: "world" }
> .copy
Copied to clipboard!
Performance Comparison
Bun's native implementation also provides faster startup:
# Bun REPL startup
$ time bun -e ""
bun -e "" 0.01s user 0.00s system 95% cpu 0.014 total
# Node.js REPL startup
$ time node -e ""
node -e "" 0.04s user 0.01s system 90% cpu 0.058 total
The difference becomes more noticeable when the REPL needs to load modules or perform initialization.
Feature Comparison Table
| Feature | Bun REPL | Node.js REPL |
|---|---|---|
| Startup time | ~10ms | ~40ms |
| Built-in syntax highlighting | Yes | No |
| TypeScript evaluation | Yes | Requires ts-node |
| Built-in clipboard support | Yes (.copy) | No |
| SQLite built-in | Yes (bun:sqlite) | No |
| Top-level await | Full support | Limited (Node 18+) |
| History persistence | Automatic | Manual setup |
| Import statements | Converted to dynamic | Requires --experimental-vm-modules |
Integration with Bun's Ecosystem
The REPL integrates seamlessly with Bun's built-in APIs:
Bun APIs Available in REPL
> Bun.version
"1.3.10"
> Bun.env.HOME
"/Users/username"
> await Bun.file("./package.json").json()
{ name: "my-project", version: "1.0.0" }
> Bun.serve({
port: 3000,
fetch(req) {
return new Response("Hello from REPL!");
}
})
Using Bun's Built-in Modules
> import { sql } from "bun:sql"
> const db = sql("sqlite::memory:")
> await db`CREATE TABLE test (id INTEGER PRIMARY KEY)`
> await db`INSERT INTO test VALUES (1)`
> await db`SELECT * FROM test`
[{ id: 1 }]
Tips and Best Practices
Quick Environment Inspection
Use the REPL to quickly inspect your environment and loaded modules:
> Object.keys(process.env).slice(0, 5)
['TERM', 'SHELL', 'USER', 'PATH', 'PWD']
> import.meta
{
url: 'file:///repl',
main: true,
env: { ... }
}
Debugging with the REPL
Paste debugging code directly:
> const items = [{id: 1, active: true}, {id: 2, active: false}]
> items.filter(i => i.active).map(i => i.id)
[1]
Loading Files for Testing
Use .load to bring in code you're developing:
> .load ./my-module.ts
Loaded ./my-module.ts
> myFunction("test")
"result"
Conclusion
Bun's native REPL represents a significant investment in developer experience. By building the REPL directly into the runtime in Zig, Bun provides a zero-dependency, instant-start interactive environment that rivals dedicated tools. The combination of syntax highlighting, top-level await, clipboard integration, and seamless integration with Bun's ecosystem makes it an indispensable tool for JavaScript and TypeScript development.
Whether you're quickly testing an API, exploring a package, debugging code, or prototyping an algorithm, the REPL provides an immediate feedback loop that accelerates development. The attention to REPL semantics—automatic variable hoisting, import statement conversion, and object literal detection—shows a deep understanding of what makes interactive development productive.