initial commit
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
<p align="center">
|
||||
<a href="https://bun.com">
|
||||
<img src="https://github.com/user-attachments/assets/50282090-adfd-4ddb-9e27-c30753c6b161" alt="Logo" height="170" />
|
||||
</a>
|
||||
</p>
|
||||
<h1 align="center">Bun Documentation</h1>
|
||||
|
||||
Official documentation for Bun: the fast, all-in-one JavaScript runtime. [bun.com/docs](https://bun.com/docs) is built from the files in this directory.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome. Open an issue or submit a pull request.
|
||||
|
||||
Before writing or editing a page, read the [voice guidelines](https://bun.com/docs/project/contributing#voice) (source: `project/contributing.mdx`): short sentences, active voice, present tense, second person, and no "easy", "simple", or "just".
|
||||
@@ -0,0 +1,444 @@
|
||||
---
|
||||
title: Bytecode Caching
|
||||
description: Speed up JavaScript execution with bytecode caching in Bun's bundler
|
||||
---
|
||||
|
||||
Bytecode caching is a build-time optimization that improves startup time by pre-compiling your JavaScript to bytecode. For example, when compiling TypeScript's `tsc` with bytecode enabled, startup time improves by **2x**.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic usage (CommonJS)
|
||||
|
||||
Enable bytecode caching with the `--bytecode` flag. Without `--format`, the output format defaults to CommonJS:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build ./index.ts --target=bun --bytecode --outdir=./dist
|
||||
```
|
||||
|
||||
The build writes two files:
|
||||
|
||||
- `dist/index.js` - Your bundled JavaScript (CommonJS)
|
||||
- `dist/index.js.jsc` - The bytecode cache file
|
||||
|
||||
At runtime, Bun automatically detects and uses the `.jsc` file:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun ./dist/index.js # Automatically uses index.js.jsc
|
||||
```
|
||||
|
||||
### With standalone executables
|
||||
|
||||
When you create an executable with `--compile`, Bun embeds the bytecode in the binary. Both ESM and CommonJS work:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
# ESM (requires --compile)
|
||||
bun build ./cli.ts --compile --bytecode --format=esm --outfile=mycli
|
||||
|
||||
# CommonJS (works with or without --compile)
|
||||
bun build ./cli.ts --compile --bytecode --outfile=mycli
|
||||
```
|
||||
|
||||
The resulting executable contains both the code and the bytecode.
|
||||
|
||||
### ESM bytecode
|
||||
|
||||
ESM bytecode requires `--compile` because Bun embeds module metadata (import/export information) in the compiled binary. With this metadata, the JavaScript engine skips parsing entirely at runtime.
|
||||
|
||||
Without `--compile`, ESM bytecode would still require parsing the source to analyze module dependencies, which defeats the purpose of bytecode caching.
|
||||
|
||||
### Combining with other optimizations
|
||||
|
||||
Combine bytecode with minification and source maps:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --bytecode --minify --sourcemap ./cli.ts --outfile=mycli
|
||||
```
|
||||
|
||||
- `--minify` reduces code size before generating bytecode (less code -> less bytecode)
|
||||
- `--sourcemap` preserves error reporting (errors still point to original source)
|
||||
- `--bytecode` eliminates parsing overhead
|
||||
|
||||
## Performance impact
|
||||
|
||||
The performance improvement scales with your codebase size:
|
||||
|
||||
| Application size | Typical startup improvement |
|
||||
| ------------------------- | --------------------------- |
|
||||
| Small CLI (< 100 KB) | 1.5-2x faster |
|
||||
| Medium-large app (> 5 MB) | 2-4x faster |
|
||||
|
||||
Larger applications benefit more because they have more code to parse.
|
||||
|
||||
## When to use bytecode
|
||||
|
||||
### Great for:
|
||||
|
||||
#### CLI tools
|
||||
|
||||
- Invoked frequently (linters, formatters, git hooks)
|
||||
- Startup time is the entire user experience
|
||||
- Users notice the difference between 90ms and 45ms startup
|
||||
- Example: TypeScript compiler, Prettier, ESLint
|
||||
|
||||
#### Build tools and task runners
|
||||
|
||||
- Run hundreds or thousands of times during development
|
||||
- Milliseconds saved per run compound quickly
|
||||
- Developer experience improvement
|
||||
- Example: Build scripts, test runners, code generators
|
||||
|
||||
#### Standalone executables
|
||||
|
||||
- Distributed to users who care about snappy performance
|
||||
- Single-file distribution is convenient
|
||||
- File size less important than startup time
|
||||
- Example: CLIs distributed via npm or as binaries
|
||||
|
||||
### Skip it for:
|
||||
|
||||
- ❌ **Small scripts**
|
||||
- ❌ **Code that runs once**
|
||||
- ❌ **Development builds**
|
||||
- ❌ **Size-constrained environments**
|
||||
|
||||
## Limitations
|
||||
|
||||
### Version compatibility
|
||||
|
||||
Bytecode is **not portable across Bun versions**. The bytecode format is tied to JavaScriptCore's internal representation, which changes between versions.
|
||||
|
||||
When you update Bun, you must regenerate bytecode:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
# After updating Bun
|
||||
bun build --bytecode ./index.ts --outdir=./dist
|
||||
```
|
||||
|
||||
If bytecode doesn't match the current Bun version, Bun ignores it and falls back to parsing the JavaScript source.
|
||||
|
||||
**Best practice**: Generate bytecode as part of your CI/CD build process. Don't commit `.jsc` files to git. Regenerate them whenever you update Bun.
|
||||
|
||||
### Source code still required
|
||||
|
||||
Bytecode doesn't replace your JavaScript. You must deploy both files:
|
||||
|
||||
- The `.js` file (your bundled source code)
|
||||
- The `.jsc` file (the bytecode cache)
|
||||
|
||||
At runtime:
|
||||
|
||||
1. Bun loads the `.js` file, sees a `@bytecode` pragma, and checks the `.jsc` file
|
||||
2. Bun loads the `.jsc` file
|
||||
3. Bun validates the bytecode hash matches the source
|
||||
4. If valid, Bun uses the bytecode
|
||||
5. If invalid, Bun falls back to parsing the source
|
||||
|
||||
### Bytecode is not obfuscation
|
||||
|
||||
Bytecode **does not obscure your source code**. It's an optimization, not a security measure.
|
||||
|
||||
## Production deployment
|
||||
|
||||
### Docker
|
||||
|
||||
Include bytecode generation in your Dockerfile:
|
||||
|
||||
```dockerfile Dockerfile icon="docker"
|
||||
FROM oven/bun:1 AS builder
|
||||
WORKDIR /app
|
||||
COPY package.json bun.lock ./
|
||||
RUN bun install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
RUN bun build --bytecode --minify --sourcemap \
|
||||
--target=bun \
|
||||
--compile \
|
||||
./src/server.ts --outfile=./dist/server
|
||||
|
||||
FROM oven/bun:1 AS runner
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/dist/server /app/server
|
||||
CMD ["./server"]
|
||||
```
|
||||
|
||||
The bytecode is architecture-independent.
|
||||
|
||||
### CI/CD
|
||||
|
||||
Generate bytecode during your build pipeline:
|
||||
|
||||
```yaml workflow.yml icon="file-code"
|
||||
# GitHub Actions
|
||||
- name: Build with bytecode
|
||||
run: |
|
||||
bun install
|
||||
bun build --bytecode --minify \
|
||||
--outdir=./dist \
|
||||
--target=bun \
|
||||
./src/index.ts
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
### Verify bytecode is being used
|
||||
|
||||
Check that the `.jsc` file exists:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
ls -lh dist/
|
||||
```
|
||||
|
||||
```txt
|
||||
-rw-r--r-- 1 user staff 245K index.js
|
||||
-rw-r--r-- 1 user staff 1.1M index.js.jsc
|
||||
```
|
||||
|
||||
The `.jsc` file should be 2-8x larger than the `.js` file.
|
||||
|
||||
To log whether Bun uses the bytecode, set `BUN_JSC_verboseDiskCache=1` in your environment.
|
||||
|
||||
On a cache hit, Bun logs:
|
||||
|
||||
```txt
|
||||
[Disk Cache] Cache hit for sourceCode
|
||||
```
|
||||
|
||||
On a cache miss:
|
||||
|
||||
```txt
|
||||
[Disk Cache] Cache miss for sourceCode
|
||||
```
|
||||
|
||||
Several cache-miss lines are normal: Bun doesn't bytecode-cache the JavaScript in its builtin modules.
|
||||
|
||||
### Common issues
|
||||
|
||||
**Bytecode silently ignored**: Usually caused by a Bun version update. The cache version doesn't match, so Bun rejects the bytecode. Regenerate to fix.
|
||||
|
||||
**File size too large**: This is expected. Consider:
|
||||
|
||||
- Using `--minify` to reduce code size before bytecode generation
|
||||
- Compressing `.jsc` files for network transfer (gzip/brotli)
|
||||
- Evaluating whether the startup gain is worth the size increase
|
||||
|
||||
## What is bytecode?
|
||||
|
||||
When you run JavaScript, the JavaScript engine doesn't execute your source code directly. Instead, it goes through several steps:
|
||||
|
||||
1. **Parsing**: The engine reads your JavaScript source code and converts it into an Abstract Syntax Tree (AST)
|
||||
2. **Bytecode compilation**: The engine compiles the AST into bytecode - a lower-level representation that's faster to execute
|
||||
3. **Execution**: The engine's interpreter or JIT compiler executes the bytecode
|
||||
|
||||
Bytecode is an intermediate representation - it's lower-level than JavaScript source code, but higher-level than machine code. Think of it as assembly language for a virtual machine. Each bytecode instruction represents a single operation like "load this variable," "add two numbers," or "call this function."
|
||||
|
||||
All of this happens **every time** you run your code. A CLI tool that runs 100 times a day gets parsed 100 times; a serverless function gets parsed on every cold start.
|
||||
|
||||
With bytecode caching, Bun moves steps 1 and 2 to the build step. At runtime, the engine loads the pre-compiled bytecode and jumps straight to execution.
|
||||
|
||||
### Why lazy parsing makes this even better
|
||||
|
||||
Modern JavaScript engines use an optimization called **lazy parsing**. They don't parse all your code upfront. Instead, they parse each function only when it's first called:
|
||||
|
||||
```js
|
||||
// Without bytecode caching:
|
||||
function rarely_used() {
|
||||
// This 500-line function is only parsed
|
||||
// when it's actually called
|
||||
}
|
||||
|
||||
function main() {
|
||||
console.log("Starting app");
|
||||
// rarely_used() is never called, so it's never parsed
|
||||
}
|
||||
```
|
||||
|
||||
Lazy parsing means parsing overhead isn't just a startup cost. It happens throughout your application's lifetime as different code paths execute. With bytecode caching, Bun **pre-compiles all functions**, even the ones the engine would otherwise parse lazily.
|
||||
|
||||
## The bytecode format
|
||||
|
||||
### Inside a .jsc file
|
||||
|
||||
A `.jsc` file contains a serialized bytecode structure.
|
||||
|
||||
**Header section** (validated on every load):
|
||||
|
||||
- **Cache version**: A hash tied to the JavaScriptCore framework version. This ensures bytecode generated with one version of Bun only runs with that exact version.
|
||||
- **Code block type tag**: Identifies whether this is a Program, Module, Eval, or Function code block.
|
||||
|
||||
**SourceCodeKey** (validates bytecode matches source):
|
||||
|
||||
- **Source code hash**: A hash of the original JavaScript source code. Bun verifies this matches before using the bytecode.
|
||||
- **Source code length**: The exact length of the source, for additional validation.
|
||||
- **Compilation flags**: Compilation context such as strict mode, script vs. module, and eval context type. The same source compiled with different flags produces different bytecode.
|
||||
|
||||
**Bytecode instructions**:
|
||||
|
||||
- **Instruction stream**: The bytecode opcodes - the compiled representation of your JavaScript, stored as a variable-length sequence of instructions.
|
||||
- **Metadata table**: Each opcode has associated metadata such as profiling counters, type hints, and execution counts (even if not yet populated).
|
||||
- **Jump targets**: Pre-computed addresses for control flow (if/else, loops, switch statements).
|
||||
- **Switch tables**: Optimized lookup tables for switch statements.
|
||||
|
||||
**Constants and identifiers**:
|
||||
|
||||
- **Constant pool**: All literal values in your code - numbers, strings, booleans, null, undefined. These are stored as JavaScript values (JSValues) so they don't need to be parsed from source at runtime.
|
||||
- **Identifier table**: All variable and function names used in the code. Stored as deduplicated strings.
|
||||
- **Source code representation markers**: Flags indicating how constants should be represented (as integers, doubles, big ints, etc.).
|
||||
|
||||
**Function metadata** (for each function in your code):
|
||||
|
||||
- **Register allocation**: How many registers (local variables) the function needs - `thisRegister`, `scopeRegister`, `numVars`, `numCalleeLocals`, `numParameters`.
|
||||
- **Code features**: A bitmask of function characteristics: is it a constructor? an arrow function? does it use `super`? does it have tail calls? These affect how the engine executes the function.
|
||||
- **Lexically scoped features**: Strict mode and other lexical context.
|
||||
- **Parse mode**: The mode in which the function was parsed (normal, async, generator, async generator).
|
||||
|
||||
**Nested structures**:
|
||||
|
||||
- **Function declarations and expressions**: Each nested function gets its own bytecode block, recursively. A file with 100 functions has 100 separate bytecode blocks, all nested in the structure.
|
||||
- **Exception handlers**: Try/catch/finally blocks with their boundaries and handler addresses pre-computed.
|
||||
- **Expression info**: Maps bytecode positions back to source code locations for error reporting and debugging.
|
||||
|
||||
### What bytecode does NOT contain
|
||||
|
||||
**Bytecode does not embed your source code**. Instead:
|
||||
|
||||
- The JavaScript source is stored separately (in the `.js` file)
|
||||
- The bytecode only stores a hash and length of the source
|
||||
- At load time, Bun validates the bytecode matches the current source code
|
||||
|
||||
This is why you need to deploy both the `.js` and `.jsc` files: the `.jsc` file is useless without its corresponding `.js` file.
|
||||
|
||||
## The tradeoff: file size
|
||||
|
||||
Bytecode files are typically 2-8x larger than the source code.
|
||||
|
||||
### Why is bytecode so much larger?
|
||||
|
||||
**Bytecode instructions are verbose**:
|
||||
A single line of minified JavaScript might compile to dozens of bytecode instructions. For example:
|
||||
|
||||
```js
|
||||
const sum = arr.reduce((a, b) => a + b, 0);
|
||||
```
|
||||
|
||||
Compiles to bytecode that:
|
||||
|
||||
- Loads the `arr` variable
|
||||
- Gets the `reduce` property
|
||||
- Creates the arrow function (which itself has bytecode)
|
||||
- Loads the initial value `0`
|
||||
- Sets up the call with the right number of arguments
|
||||
- Performs the call
|
||||
- Stores the result in `sum`
|
||||
|
||||
Each of these steps is a separate bytecode instruction with its own metadata.
|
||||
|
||||
**Constant pools store everything**:
|
||||
Every string literal, number, property name - everything gets stored in the constant pool. Even if your source code has `"hello"` a hundred times, the constant pool stores it once. The identifier table and constant references still add overhead.
|
||||
|
||||
**Per-function metadata**:
|
||||
Each function - even small one-line functions - gets its own complete metadata:
|
||||
|
||||
- Register allocation info
|
||||
- Code features bitmask
|
||||
- Parse mode
|
||||
- Exception handlers
|
||||
- Expression info for debugging
|
||||
|
||||
A file with 1,000 small functions has 1,000 sets of metadata.
|
||||
|
||||
**Profiling data structures**:
|
||||
Even though profiling data isn't populated yet, the _structures_ to hold profiling data are allocated. This includes:
|
||||
|
||||
- Value profile slots (tracking what types flow through each operation)
|
||||
- Array profile slots (tracking array access patterns)
|
||||
- Binary arithmetic profile slots (tracking number types in math operations)
|
||||
- Unary arithmetic profile slots
|
||||
|
||||
These take up space even when empty.
|
||||
|
||||
**Pre-computed control flow**:
|
||||
Jump targets, switch tables, and exception handler boundaries are all pre-computed and stored. This makes execution faster but increases file size.
|
||||
|
||||
### Mitigation strategies
|
||||
|
||||
**Compression**:
|
||||
Bytecode compresses well with gzip/brotli (60-70% compression). The repetitive structure and metadata compress efficiently.
|
||||
|
||||
**Minification first**:
|
||||
Using `--minify` before bytecode generation helps:
|
||||
|
||||
- Shorter identifiers → smaller identifier table
|
||||
- Dead code elimination → less bytecode generated
|
||||
- Constant folding → fewer constants in the pool
|
||||
|
||||
**The tradeoff**:
|
||||
You're trading 2-8x larger files for 2-4x faster startup. For CLIs, this is usually worth it. For long-running servers where a few megabytes of disk space don't matter, it's even less of an issue.
|
||||
|
||||
## Versioning and portability
|
||||
|
||||
### Cross-architecture portability: ✅
|
||||
|
||||
Bytecode is **architecture-independent**. You can:
|
||||
|
||||
- Build on macOS ARM64, deploy to Linux x64
|
||||
- Build on Linux x64, deploy to AWS Lambda ARM64
|
||||
- Build on Windows x64, deploy to macOS ARM64
|
||||
|
||||
The bytecode contains abstract instructions that work on any architecture. Architecture-specific optimizations happen during JIT compilation at runtime, not in the cached bytecode.
|
||||
|
||||
### Cross-version portability: ❌
|
||||
|
||||
Bytecode is **not stable across Bun versions**. Here's why:
|
||||
|
||||
**Bytecode format changes**:
|
||||
JavaScriptCore's bytecode format changes from version to version. New opcodes get added, old ones get removed or changed, metadata structures change.
|
||||
|
||||
**Version validation**:
|
||||
The cache version in the `.jsc` file header is a hash of the JavaScriptCore framework. When Bun loads bytecode:
|
||||
|
||||
1. It extracts the cache version from the `.jsc` file
|
||||
2. It computes the current JavaScriptCore version
|
||||
3. If they don't match, Bun **silently rejects** the bytecode
|
||||
4. Bun falls back to parsing the `.js` source code
|
||||
|
||||
**Graceful degradation**:
|
||||
This design means bytecode caching "fails open." If anything goes wrong (version mismatch, corrupted file, missing file), your code still runs normally. You might see slower startup, but you won't see errors.
|
||||
|
||||
## Unlinked vs. linked bytecode
|
||||
|
||||
JavaScriptCore distinguishes between "unlinked" and "linked" bytecode. This separation is what makes bytecode caching possible:
|
||||
|
||||
### Unlinked bytecode (what's cached)
|
||||
|
||||
The bytecode saved in `.jsc` files is **unlinked bytecode**. It contains:
|
||||
|
||||
- The compiled bytecode instructions
|
||||
- Structural information about the code
|
||||
- Constants and identifiers
|
||||
- Control flow information
|
||||
|
||||
But it **doesn't** contain:
|
||||
|
||||
- Pointers to actual runtime objects
|
||||
- JIT-compiled machine code
|
||||
- Profiling data from previous runs
|
||||
- Call link information (which functions call which)
|
||||
|
||||
Unlinked bytecode is **immutable and shareable**. Multiple executions of the same code can all reference the same unlinked bytecode.
|
||||
|
||||
### Linked bytecode (runtime execution)
|
||||
|
||||
When Bun runs bytecode, it "links" it - creating a runtime wrapper that adds:
|
||||
|
||||
- **Call link information**: As your code runs, the engine learns which functions call which and optimizes those call sites.
|
||||
- **Profiling data**: The engine tracks how many times each instruction executes, what types of values flow through the code, array access patterns, etc.
|
||||
- **JIT compilation state**: References to baseline JIT or optimizing JIT (DFG/FTL) compiled versions of hot code.
|
||||
- **Runtime objects**: Pointers to actual JavaScript objects, prototypes, scopes, etc.
|
||||
|
||||
Bun creates this linked representation fresh every time you run your code. This separation allows:
|
||||
|
||||
1. **Caching the expensive work** (parsing and compilation to unlinked bytecode)
|
||||
2. **Still collecting runtime profiling data** to guide optimizations
|
||||
3. **Still applying JIT optimizations** based on actual execution patterns
|
||||
|
||||
For production CLIs and serverless deployments, the combination of `--bytecode --minify --sourcemap` gives you the best startup time while keeping errors mapped to your original source.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,300 @@
|
||||
---
|
||||
title: esbuild
|
||||
description: Migration guide from esbuild to Bun's bundler
|
||||
---
|
||||
|
||||
Bun's bundler API is heavily inspired by esbuild. This page is a side-by-side comparison of the two APIs.
|
||||
|
||||
A few behaviors differ:
|
||||
|
||||
<Note>
|
||||
**Bundling by default.** Unlike esbuild, Bun bundles by default; no `--bundle` flag is needed. To transpile each file
|
||||
individually, use `Bun.Transpiler`.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
**Bundler only.** Unlike esbuild, Bun's bundler has no built-in development server. Use it with `Bun.serve` and other
|
||||
runtime APIs to get the same effect. esbuild's HTTP options don't apply.
|
||||
</Note>
|
||||
|
||||
## Performance
|
||||
|
||||
Bun's bundler is 1.75x faster than esbuild on esbuild's three.js benchmark.
|
||||
|
||||
<Info>Bundling 10 copies of three.js from scratch, with sourcemaps and minification</Info>
|
||||
|
||||
## CLI API
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
# esbuild
|
||||
esbuild <entrypoint> --outdir=out --bundle
|
||||
|
||||
# bun
|
||||
bun build <entrypoint> --outdir=out
|
||||
```
|
||||
|
||||
In Bun's CLI, boolean flags like `--minify` take no argument. Flags that take one, like `--outdir <path>`, can be written as `--outdir out` or `--outdir=out`. Some flags, like `--define`, can be repeated: `--define foo=bar --define bar=baz`.
|
||||
|
||||
| esbuild | bun build | Notes |
|
||||
| ---------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--bundle` | n/a | Bun always bundles; use `--no-bundle` to disable it. |
|
||||
| `--define:K=V` | `--define K=V` | Small syntax difference; no colon.<br/>`esbuild --define:foo=bar`<br/>`bun build --define foo=bar` |
|
||||
| `--external:<pkg>` | `--external <pkg>` | Small syntax difference; no colon.<br/>`esbuild --external:react`<br/>`bun build --external react` |
|
||||
| `--format` | `--format` | Bun supports `"esm"`, `"cjs"`, and `"iife"`. esbuild defaults to `"iife"`. |
|
||||
| `--loader:.ext=loader` | `--loader .ext:loader` | Bun supports a different set of built-in loaders than esbuild; see [loaders](/bundler/loaders). The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not implemented.<br/><br/>The syntax for `--loader` differs.<br/>`esbuild app.ts --bundle --loader:.svg=text`<br/>`bun build app.ts --loader .svg:text` |
|
||||
| `--minify` | `--minify` | No differences |
|
||||
| `--outdir` | `--outdir` | No differences |
|
||||
| `--outfile` | `--outfile` | No differences |
|
||||
| `--packages` | `--packages` | No differences |
|
||||
| `--platform` | `--target` | Renamed to `--target` for consistency with tsconfig. Does not support `neutral`. |
|
||||
| `--serve` | n/a | Not applicable |
|
||||
| `--sourcemap` | `--sourcemap` | Supports `linked` (the default when no value is given), `external`, `inline`, and `none`. Does not support esbuild's `both`. |
|
||||
| `--splitting` | `--splitting` | No differences |
|
||||
| `--target` | n/a | Not supported. Bun's bundler performs no syntactic down-leveling. |
|
||||
| `--watch` | `--watch` | No differences |
|
||||
| `--allow-overwrite` | n/a | Bun never allows overwriting |
|
||||
| `--analyze` | n/a | Not supported |
|
||||
| `--asset-names` | `--asset-naming` | Renamed for consistency with naming in JS API |
|
||||
| `--banner` | `--banner` | Only applies to js bundles |
|
||||
| `--footer` | `--footer` | Only applies to js bundles |
|
||||
| `--certfile` | n/a | Not applicable |
|
||||
| `--charset=utf8` | n/a | Not supported |
|
||||
| `--chunk-names` | `--chunk-naming` | Renamed for consistency with naming in JS API |
|
||||
| `--color` | n/a | Always enabled |
|
||||
| `--drop` | `--drop` | |
|
||||
| n/a | `--feature` | Bun-specific. Enables feature flags for compile-time dead-code elimination through `import { feature } from "bun:bundle"` |
|
||||
| `--entry-names` | `--entry-naming` | Renamed for consistency with naming in JS API |
|
||||
| `--global-name` | n/a | Not supported |
|
||||
| `--ignore-annotations` | `--ignore-dce-annotations` | |
|
||||
| `--inject` | n/a | Not supported |
|
||||
| `--jsx` | `--jsx-runtime <runtime>` | Supports `"automatic"` (uses jsx transform) and `"classic"` (uses `React.createElement`) |
|
||||
| `--jsx-dev` | n/a | Bun reads `compilerOptions.jsx` from `tsconfig.json` to determine a default. If `compilerOptions.jsx` is `"react-jsx"`, or if `NODE_ENV=production`, Bun uses the jsx transform. Otherwise, it uses `jsxDEV`. The bundler does not support `preserve`. |
|
||||
| `--jsx-factory` | `--jsx-factory` | |
|
||||
| `--jsx-fragment` | `--jsx-fragment` | |
|
||||
| `--jsx-import-source` | `--jsx-import-source` | |
|
||||
| `--jsx-side-effects` | `--jsx-side-effects` | |
|
||||
| `--keep-names` | `--keep-names` | |
|
||||
| `--keyfile` | n/a | Not applicable |
|
||||
| `--legal-comments` | n/a | Not supported |
|
||||
| `--log-level` | n/a | Not supported. You can set the log level in `bunfig.toml` as `logLevel`. |
|
||||
| `--log-limit` | n/a | Not supported |
|
||||
| `--log-override:X=Y` | n/a | Not supported |
|
||||
| `--main-fields` | n/a | Not supported |
|
||||
| `--mangle-cache` | n/a | Not supported |
|
||||
| `--mangle-props` | n/a | Not supported |
|
||||
| `--mangle-quoted` | n/a | Not supported |
|
||||
| `--metafile` | `--metafile` | |
|
||||
| `--minify-whitespace` | `--minify-whitespace` | |
|
||||
| `--minify-identifiers` | `--minify-identifiers` | |
|
||||
| `--minify-syntax` | `--minify-syntax` | |
|
||||
| `--out-extension` | n/a | Not supported |
|
||||
| `--outbase` | `--root` | |
|
||||
| `--preserve-symlinks` | n/a | Not supported |
|
||||
| `--public-path` | `--public-path` | |
|
||||
| `--pure` | n/a | Not supported |
|
||||
| `--reserve-props` | n/a | Not supported |
|
||||
| `--resolve-extensions` | n/a | Not supported |
|
||||
| `--servedir` | n/a | Not applicable |
|
||||
| `--source-root` | n/a | Not supported |
|
||||
| `--sourcefile` | n/a | Not supported. Bun does not support stdin input. |
|
||||
| `--sources-content` | n/a | Not supported |
|
||||
| `--supported` | n/a | Not supported |
|
||||
| `--tree-shaking` | n/a | Always true |
|
||||
| `--tsconfig` | `--tsconfig-override` | |
|
||||
| `--version` | n/a | Run `bun --version` to see the version of Bun. |
|
||||
|
||||
## JavaScript API
|
||||
|
||||
| esbuild.build() | Bun.build() | Notes |
|
||||
| ------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `absWorkingDir` | n/a | Always set to `process.cwd()` |
|
||||
| `alias` | n/a | Not supported |
|
||||
| `allowOverwrite` | n/a | Always false |
|
||||
| `assetNames` | `naming.asset` | Uses the same templating syntax as esbuild, but you must include `[ext]` explicitly.<br/><br/>`ts<br/>Bun.build({<br/> entrypoints: ["./index.tsx"],<br/> naming: {<br/> asset: "[name].[ext]",<br/> },<br/>});<br/>` |
|
||||
| `banner` | `banner` | Only applies to js bundles |
|
||||
| `bundle` | n/a | Always true. Use `Bun.Transpiler` to transpile without bundling. |
|
||||
| `charset` | n/a | Not supported |
|
||||
| `chunkNames` | `naming.chunk` | Uses the same templating syntax as esbuild, but you must include `[ext]` explicitly.<br/><br/>`ts<br/>Bun.build({<br/> entrypoints: ["./index.tsx"],<br/> naming: {<br/> chunk: "[name].[ext]",<br/> },<br/>});<br/>` |
|
||||
| `color` | n/a | Bun returns logs in the `logs` property of the build result. |
|
||||
| `conditions` | `conditions` | No differences |
|
||||
| `define` | `define` | |
|
||||
| `drop` | `drop` | |
|
||||
| `entryNames` | `naming` or `naming.entry` | Bun supports a `naming` key that can either be a string or an object. Uses the same templating syntax as esbuild, but you must include `[ext]` explicitly.<br/><br/>`ts<br/>Bun.build({<br/> entrypoints: ["./index.tsx"],<br/> // when string, this is equivalent to entryNames<br/> naming: "[name].[ext]",<br/><br/> // granular naming options<br/> naming: {<br/> entry: "[name].[ext]",<br/> asset: "[name].[ext]",<br/> chunk: "[name].[ext]",<br/> },<br/>});<br/>` |
|
||||
| `entryPoints` | `entrypoints` | Capitalization difference |
|
||||
| `external` | `external` | No differences |
|
||||
| `footer` | `footer` | Only applies to js bundles |
|
||||
| `format` | `format` | Supports `"esm"`, `"cjs"`, and `"iife"`. |
|
||||
| `globalName` | n/a | Not supported |
|
||||
| `ignoreAnnotations` | `ignoreDCEAnnotations` | |
|
||||
| `inject` | n/a | Not supported |
|
||||
| `jsx` | `jsx.runtime` | Supports `"automatic"` and `"classic"` |
|
||||
| `jsxDev` | `jsx.development` | |
|
||||
| `jsxFactory` | `jsx.factory` | |
|
||||
| `jsxFragment` | `jsx.fragment` | |
|
||||
| `jsxImportSource` | `jsx.importSource` | |
|
||||
| `jsxSideEffects` | `jsx.sideEffects` | |
|
||||
| `keepNames` | `minify.keepNames` | |
|
||||
| `legalComments` | n/a | Not supported |
|
||||
| `loader` | `loader` | Bun supports a different set of built-in loaders than esbuild; see [loaders](/bundler/loaders). Bun does not implement the esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty`. |
|
||||
| `logLevel` | n/a | Not supported |
|
||||
| `logLimit` | n/a | Not supported |
|
||||
| `logOverride` | n/a | Not supported |
|
||||
| `mainFields` | n/a | Not supported |
|
||||
| `mangleCache` | n/a | Not supported |
|
||||
| `mangleProps` | n/a | Not supported |
|
||||
| `mangleQuoted` | n/a | Not supported |
|
||||
| `metafile` | `metafile` | |
|
||||
| `minify` | `minify` | In Bun, `minify` can be a boolean or an object.<br/><br/>`ts<br/>await Bun.build({<br/> entrypoints: ['./index.tsx'],<br/> // enable all minification<br/> minify: true<br/><br/> // granular options<br/> minify: {<br/> identifiers: true,<br/> syntax: true,<br/> whitespace: true<br/> }<br/>})<br/>` |
|
||||
| `minifyIdentifiers` | `minify.identifiers` | See `minify` |
|
||||
| `minifySyntax` | `minify.syntax` | See `minify` |
|
||||
| `minifyWhitespace` | `minify.whitespace` | See `minify` |
|
||||
| `nodePaths` | n/a | Not supported |
|
||||
| `outExtension` | n/a | Not supported |
|
||||
| `outbase` | `root` | Different name |
|
||||
| `outdir` | `outdir` | No differences |
|
||||
| `outfile` | `outfile` | No differences |
|
||||
| `packages` | `packages` | No differences |
|
||||
| `platform` | `target` | Supports `"bun"`, `"node"` and `"browser"` (the default). Does not support `"neutral"`. |
|
||||
| `plugins` | `plugins` | Bun's plugin API is a subset of esbuild's. Some esbuild plugins work with Bun without modification. |
|
||||
| `preserveSymlinks` | n/a | Not supported |
|
||||
| `publicPath` | `publicPath` | No differences |
|
||||
| `pure` | n/a | Not supported |
|
||||
| `reserveProps` | n/a | Not supported |
|
||||
| `resolveExtensions` | n/a | Not supported |
|
||||
| `sourceRoot` | n/a | Not supported |
|
||||
| `sourcemap` | `sourcemap` | Supports `"none"`, `"linked"`, `"inline"`, and `"external"` |
|
||||
| `sourcesContent` | n/a | Not supported |
|
||||
| `splitting` | `splitting` | No differences |
|
||||
| `stdin` | n/a | Not supported |
|
||||
| `supported` | n/a | Not supported |
|
||||
| `target` | n/a | No support for syntax downleveling |
|
||||
| `treeShaking` | `treeShaking` | Defaults to `true` |
|
||||
| `tsconfig` | `tsconfig` | |
|
||||
| `write` | n/a | Set to true if `outdir`/`outfile` is set, otherwise false |
|
||||
|
||||
## Plugin API
|
||||
|
||||
Bun's plugin API is designed to be esbuild-compatible. Bun doesn't support esbuild's entire plugin API surface, but it implements the core functionality. Many third-party esbuild plugins work with Bun without modification.
|
||||
|
||||
<Note>
|
||||
Long term, we aim for feature parity with esbuild's API. If something doesn't work, file an issue to help us
|
||||
prioritize.
|
||||
</Note>
|
||||
|
||||
In both Bun and esbuild, you define plugins with a builder object.
|
||||
|
||||
```ts title="myPlugin.ts" icon="/icons/typescript.svg"
|
||||
import type { BunPlugin } from "bun";
|
||||
|
||||
const myPlugin: BunPlugin = {
|
||||
name: "my-plugin",
|
||||
setup(builder) {
|
||||
// define plugin
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
The builder object's methods hook into parts of the bundling process. Bun implements `onStart`, `onEnd`, `onResolve`, and `onLoad`; it does not implement the esbuild hooks `onDispose` and `resolve`. Bun partially implements `initialOptions`: the object is read-only and exposes only a subset of esbuild's options. Use `config` (the same thing in Bun's `BuildConfig` format) instead.
|
||||
|
||||
```ts title="myPlugin.ts" icon="/icons/typescript.svg"
|
||||
import type { BunPlugin } from "bun";
|
||||
const myPlugin: BunPlugin = {
|
||||
name: "my-plugin",
|
||||
setup(builder) {
|
||||
builder.onStart(() => {
|
||||
/* called when the bundle starts */
|
||||
});
|
||||
builder.onResolve(
|
||||
{
|
||||
/* onResolve.options */
|
||||
},
|
||||
args => {
|
||||
return {
|
||||
/* onResolve.results */
|
||||
};
|
||||
},
|
||||
);
|
||||
builder.onLoad(
|
||||
{
|
||||
/* onLoad.options */
|
||||
},
|
||||
args => {
|
||||
return {
|
||||
/* onLoad.results */
|
||||
};
|
||||
},
|
||||
);
|
||||
builder.onEnd(result => {
|
||||
/* called when the bundle is complete */
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### onResolve
|
||||
|
||||
<Tabs>
|
||||
<Tab title="options">
|
||||
|
||||
- 🟢 `filter`
|
||||
- 🟢 `namespace`
|
||||
|
||||
</Tab>
|
||||
<Tab title="arguments">
|
||||
|
||||
- 🟢 `path`
|
||||
- 🟢 `importer`
|
||||
- 🟢 `namespace`
|
||||
- 🟢 `resolveDir`
|
||||
- 🟢 `kind`
|
||||
- 🔴 `pluginData`
|
||||
|
||||
</Tab>
|
||||
<Tab title="results">
|
||||
|
||||
- 🟢 `namespace`
|
||||
- 🟢 `path`
|
||||
- 🔴 `errors`
|
||||
- 🟢 `external`
|
||||
- 🔴 `pluginData`
|
||||
- 🔴 `pluginName`
|
||||
- 🔴 `sideEffects`
|
||||
- 🔴 `suffix`
|
||||
- 🔴 `warnings`
|
||||
- 🔴 `watchDirs`
|
||||
- 🔴 `watchFiles`
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### onLoad
|
||||
|
||||
<Tabs>
|
||||
<Tab title="options">
|
||||
|
||||
- 🟢 `filter`
|
||||
- 🟢 `namespace`
|
||||
|
||||
</Tab>
|
||||
<Tab title="arguments">
|
||||
|
||||
- 🟢 `path`
|
||||
- 🟢 `namespace`
|
||||
- 🔴 `suffix`
|
||||
- 🔴 `pluginData`
|
||||
|
||||
</Tab>
|
||||
<Tab title="results">
|
||||
|
||||
- 🟢 `contents`
|
||||
- 🟢 `loader`
|
||||
- 🔴 `errors`
|
||||
- 🔴 `pluginData`
|
||||
- 🔴 `pluginName`
|
||||
- 🔴 `resolveDir`
|
||||
- 🔴 `warnings`
|
||||
- 🔴 `watchDirs`
|
||||
- 🔴 `watchFiles`
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,226 @@
|
||||
---
|
||||
title: Hot reloading
|
||||
description: Hot Module Replacement (HMR) for Bun's development server
|
||||
---
|
||||
|
||||
Hot Module Replacement (HMR) updates modules in a running application without a full page reload, preserving application state.
|
||||
|
||||
<Note>HMR is enabled by default when using Bun's full-stack development server.</Note>
|
||||
|
||||
## `import.meta.hot` API Reference
|
||||
|
||||
Bun implements a client-side HMR API modeled after [Vite's `import.meta.hot` API](https://vite.dev/guide/api-hmr). You can check for it with `if (import.meta.hot)`, which tree-shakes it in production.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
if (import.meta.hot) {
|
||||
// HMR APIs are available.
|
||||
}
|
||||
```
|
||||
|
||||
This check is often unnecessary, since Bun dead-code-eliminates calls to all of the HMR APIs in production builds.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
// This entire function call is removed in production.
|
||||
import.meta.hot.dispose(() => {
|
||||
console.log("dispose");
|
||||
});
|
||||
```
|
||||
|
||||
<Warning>
|
||||
For this dead-code elimination to work, Bun forces these APIs to be called without indirection. That means the following do not work:
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
// INVALID: Assigning `hot` to a variable
|
||||
const hot = import.meta.hot;
|
||||
hot.accept();
|
||||
|
||||
// INVALID: Assigning `import.meta` to a variable
|
||||
const meta = import.meta;
|
||||
meta.hot.accept();
|
||||
console.log(meta.hot.data);
|
||||
|
||||
// INVALID: Passing to a function
|
||||
doSomething(import.meta.hot.dispose);
|
||||
|
||||
// OK: The full phrase "import.meta.hot.<API>" must be called directly:
|
||||
import.meta.hot.accept();
|
||||
|
||||
// OK: `data` can be passed to functions:
|
||||
doSomething(import.meta.hot.data);
|
||||
```
|
||||
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
The HMR API is still a work in progress. Some features are missing. To disable HMR in `Bun.serve`, set the development option to `{ hmr: false }`.
|
||||
</Note>
|
||||
|
||||
## API Methods
|
||||
|
||||
| Method | Status | Notes |
|
||||
| ------------------ | ------ | --------------------------------------------------------------------- |
|
||||
| `hot.accept()` | ✅ | Indicate that a hot update can be replaced gracefully. |
|
||||
| `hot.data` | ✅ | Persist data between module evaluations. |
|
||||
| `hot.dispose()` | ✅ | Add a callback function to run when a module is about to be replaced. |
|
||||
| `hot.invalidate()` | ❌ | |
|
||||
| `hot.on()` | ✅ | Attach an event listener. |
|
||||
| `hot.off()` | ✅ | Remove an event listener from `on`. |
|
||||
| `hot.send()` | ❌ | |
|
||||
| `hot.prune()` | 🚧 | Callback is currently never called. |
|
||||
| `hot.decline()` | ✅ | No-op to match Vite's `import.meta.hot`. |
|
||||
|
||||
## import.meta.hot.accept()
|
||||
|
||||
The `accept()` method indicates that a module can be hot-replaced. Called without arguments, it means Bun can replace this module by re-evaluating the file. After a hot update, Bun automatically patches the module's importers.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
// index.ts
|
||||
import { getCount } from "./foo.ts";
|
||||
|
||||
console.log("count is ", getCount());
|
||||
|
||||
import.meta.hot.accept();
|
||||
|
||||
export function getNegativeCount() {
|
||||
return -getCount();
|
||||
}
|
||||
```
|
||||
|
||||
This call creates a hot-reloading boundary for all of the files that `index.ts` imports. Whenever you save `foo.ts` or any of its dependencies, the update bubbles up to `index.ts`, which re-evaluates. Bun then patches the files that import `index.ts` to import the new version of `getNegativeCount()`. If you update only `index.ts`, Bun re-evaluates only that one file, and the counter in `foo.ts` is reused.
|
||||
|
||||
Combine this with `import.meta.hot.data` to transfer state from the previous module to the new one.
|
||||
|
||||
<Info>
|
||||
When no modules call `import.meta.hot.accept()` (and there isn't React Fast Refresh or a plugin calling it for you),
|
||||
the page reloads when the file updates. A console warning shows which files were invalidated. This warning is safe to
|
||||
ignore if it makes more sense to rely on full page reloads.
|
||||
</Info>
|
||||
|
||||
### With callback
|
||||
|
||||
When passed a callback, `import.meta.hot.accept` works as it does in Vite. Instead of patching the importers of this module, it calls the callback with the new module.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
export const count = 0;
|
||||
|
||||
import.meta.hot.accept(newModule => {
|
||||
if (newModule) {
|
||||
// newModule is undefined when SyntaxError happened
|
||||
console.log("updated: count is now ", newModule.count);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<Tip>Prefer `import.meta.hot.accept()` without an argument; it usually makes your code clearer.</Tip>
|
||||
|
||||
### Accepting other modules
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import { count } from "./foo";
|
||||
|
||||
import.meta.hot.accept("./foo", newModule => {
|
||||
if (!newModule) return;
|
||||
|
||||
console.log("updated: count is now ", count);
|
||||
});
|
||||
```
|
||||
|
||||
Indicates that a dependency's module can be accepted. When the dependency is updated, Bun calls the callback with the new module.
|
||||
|
||||
### With multiple dependencies
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import.meta.hot.accept(["./foo", "./bar"], newModules => {
|
||||
// newModules holds the updated module at its index and
|
||||
// undefined for the other dependencies
|
||||
});
|
||||
```
|
||||
|
||||
This variant accepts an array of dependencies. The callback receives an array with the updated module at its index and `undefined` for the other dependencies.
|
||||
|
||||
## import.meta.hot.data
|
||||
|
||||
`import.meta.hot.data` carries state from the previous version of a module to the new one across a hot replacement. Writing to `import.meta.hot.data` also marks the module as self-accepting (equivalent to calling `import.meta.hot.accept()`).
|
||||
|
||||
```tsx title="index.tsx" icon="/icons/typescript.svg"
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { App } from "./app";
|
||||
|
||||
const root = (import.meta.hot.data.root ??= createRoot(elem));
|
||||
root.render(<App />); // re-use an existing root
|
||||
```
|
||||
|
||||
In production, Bun inlines `data` as `{}`, so you cannot use it as a state holder.
|
||||
|
||||
<Tip>
|
||||
We recommend this pattern for stateful modules because Bun can minify `{}.prop ??= value` into `value` in production.
|
||||
</Tip>
|
||||
|
||||
## import.meta.hot.dispose()
|
||||
|
||||
Attaches an on-dispose callback. Bun calls it:
|
||||
|
||||
- Just before the module is replaced with another copy (before the next is loaded)
|
||||
- After the module is detached (removing all imports to this module, see `import.meta.hot.prune()`)
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
const sideEffect = setupSideEffect();
|
||||
|
||||
import.meta.hot.dispose(() => {
|
||||
sideEffect.cleanup();
|
||||
});
|
||||
```
|
||||
|
||||
<Warning>Bun does not call this callback on route navigation or when the browser tab closes.</Warning>
|
||||
|
||||
Returning a promise delays module replacement until the module is disposed. Bun calls all dispose callbacks in parallel.
|
||||
|
||||
## import.meta.hot.prune()
|
||||
|
||||
Attaches an on-prune callback. Bun calls it when all imports to this module are removed, but the module was previously loaded.
|
||||
|
||||
Use it to clean up resources that were created when the module was loaded. Unlike `import.meta.hot.dispose()`, it pairs better with `accept` and `data` for managing stateful resources. A full example managing a WebSocket:
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import { something } from "./something";
|
||||
|
||||
// Initialize or re-use a WebSocket connection
|
||||
export const ws = (import.meta.hot.data.ws ??= new WebSocket(location.origin));
|
||||
|
||||
// If the module's import is removed, clean up the WebSocket connection.
|
||||
import.meta.hot.prune(() => {
|
||||
ws.close();
|
||||
});
|
||||
```
|
||||
|
||||
<Info>
|
||||
If you used `dispose` instead, the WebSocket would close and re-open on every hot update. Both versions of the code
|
||||
prevent page reloads when you update imported files.
|
||||
</Info>
|
||||
|
||||
## import.meta.hot.on() and off()
|
||||
|
||||
Use `on()` and `off()` to listen for events from the HMR runtime. Event names carry a prefix so that plugins do not conflict with each other.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import.meta.hot.on("bun:beforeUpdate", () => {
|
||||
console.log("before a hot update");
|
||||
});
|
||||
```
|
||||
|
||||
When a file is replaced, Bun automatically removes all of its event listeners.
|
||||
|
||||
### Built-in events
|
||||
|
||||
| Event | Emitted when |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| `bun:beforeUpdate` | before a hot update is applied. |
|
||||
| `bun:afterUpdate` | after a hot update is applied. |
|
||||
| `bun:beforeFullReload` | before a full page reload happens. |
|
||||
| `bun:beforePrune` | before prune callbacks are called. |
|
||||
| `bun:invalidate` | when a module is invalidated with `import.meta.hot.invalidate()`. |
|
||||
| `bun:error` | when a build or runtime error occurs. |
|
||||
| `bun:ws:disconnect` | when the HMR WebSocket connection is lost. This can indicate the development server is offline. |
|
||||
| `bun:ws:connect` | when the HMR WebSocket connects or re-connects. |
|
||||
|
||||
<Note>For compatibility with Vite, these events are also available with the `vite:*` prefix instead of `bun:*`.</Note>
|
||||
@@ -0,0 +1,492 @@
|
||||
---
|
||||
title: HTML & static sites
|
||||
description: Build static sites, landing pages, and web applications with Bun's bundler
|
||||
---
|
||||
|
||||
Bun's bundler has first-class support for HTML. Build static sites, landing pages, and web applications with zero configuration: point Bun at your HTML file and it bundles the scripts, stylesheets, and assets the file references.
|
||||
|
||||
```html title="index.html" icon="file-code"
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
<script src="./app.ts" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<img src="./logo.png" />
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
To get started, pass HTML files to `bun`.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun ./index.html
|
||||
```
|
||||
|
||||
```
|
||||
Bun v1.3.3
|
||||
ready in 6.62ms
|
||||
→ http://localhost:3000/
|
||||
Press h + Enter to show shortcuts
|
||||
```
|
||||
|
||||
With no configuration, Bun's development server provides:
|
||||
|
||||
- **Automatic Bundling** - Bundles and serves your HTML, JavaScript, and CSS
|
||||
- **Multi-Entry Support** - Handles multiple HTML entry points and glob entry points
|
||||
- **Modern JavaScript** - TypeScript & JSX support by default
|
||||
- **Smart Configuration** - Reads `tsconfig.json` for paths, JSX options, and experimental decorators
|
||||
- **Plugins** - Plugin support, including TailwindCSS
|
||||
- **ESM & CommonJS** - Use ESM and CommonJS in your JavaScript, TypeScript, and JSX files
|
||||
- **CSS Bundling & Minification** - Bundles CSS from `<link>` tags and `@import` statements
|
||||
- **Asset Management** - Copies and hashes images and assets, and rewrites asset paths in JavaScript, CSS, and HTML
|
||||
|
||||
## Single Page Apps (SPA)
|
||||
|
||||
When you pass a single `.html` file to Bun, Bun uses it as a fallback route for all paths. This suits single page apps that use client-side routing:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun index.html
|
||||
```
|
||||
|
||||
```
|
||||
Bun v1.3.3
|
||||
ready in 6.62ms
|
||||
→ http://localhost:3000/
|
||||
Press h + Enter to show shortcuts
|
||||
```
|
||||
|
||||
Your React or other SPA works with no configuration. Routes like `/about` and `/users/123` serve the same HTML file, so your client-side router handles the navigation.
|
||||
|
||||
```html title="index.html" icon="file-code"
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My SPA</title>
|
||||
<script src="./app.tsx" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Multi-page apps (MPA)
|
||||
|
||||
Some projects have several separate routes or HTML files as entry points. To support multiple entry points, pass them all to `bun`:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun ./index.html ./about.html
|
||||
```
|
||||
|
||||
```txt
|
||||
Bun v1.3.3
|
||||
ready in 6.62ms
|
||||
→ http://localhost:3000/
|
||||
Routes:
|
||||
/ ./index.html
|
||||
/about ./about.html
|
||||
Press h + Enter to show shortcuts
|
||||
```
|
||||
|
||||
This serves:
|
||||
|
||||
- `index.html` at `/`
|
||||
- `about.html` at `/about`
|
||||
|
||||
### Glob patterns
|
||||
|
||||
To specify multiple files, use a glob pattern that ends in `.html`:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun ./**/*.html
|
||||
```
|
||||
|
||||
```
|
||||
Bun v1.3.3
|
||||
ready in 6.62ms
|
||||
→ http://localhost:3000/
|
||||
Routes:
|
||||
/ ./index.html
|
||||
/about ./about.html
|
||||
Press h + Enter to show shortcuts
|
||||
```
|
||||
|
||||
### Path normalization
|
||||
|
||||
Bun chooses the base path from the longest common prefix among all the files.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun ./index.html ./about/index.html ./about/foo/index.html
|
||||
```
|
||||
|
||||
```
|
||||
Bun v1.3.3
|
||||
ready in 6.62ms
|
||||
→ http://localhost:3000/
|
||||
Routes:
|
||||
/ ./index.html
|
||||
/about ./about/index.html
|
||||
/about/foo ./about/foo/index.html
|
||||
Press h + Enter to show shortcuts
|
||||
```
|
||||
|
||||
## JavaScript, TypeScript, and JSX
|
||||
|
||||
Bun's transpiler natively implements JavaScript, TypeScript, and JSX support. See [loaders](/bundler/loaders).
|
||||
|
||||
<Note>Bun also uses the same transpiler at runtime.</Note>
|
||||
|
||||
### ES Modules & CommonJS
|
||||
|
||||
You can use ESM and CommonJS in your JavaScript, TypeScript, and JSX files. Bun transpiles and bundles them automatically.
|
||||
|
||||
There is no pre-build or separate optimization step. Bun does it all at the same time.
|
||||
|
||||
See [module resolution](/runtime/module-resolution).
|
||||
|
||||
## CSS
|
||||
|
||||
Bun's CSS parser is also natively implemented (about 70,000 lines of Rust).
|
||||
|
||||
It's also a CSS bundler. You can use `@import` in your CSS files to import other CSS files.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```css styles.css icon="file-code"
|
||||
@import "./abc.css";
|
||||
|
||||
.container {
|
||||
background-color: blue;
|
||||
}
|
||||
```
|
||||
|
||||
```css abc.css icon="file-code"
|
||||
body {
|
||||
background-color: red;
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
This outputs:
|
||||
|
||||
```css styles.css icon="file-code"
|
||||
body {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: blue;
|
||||
}
|
||||
```
|
||||
|
||||
### Referencing local assets in CSS
|
||||
|
||||
```css styles.css icon="file-code"
|
||||
body {
|
||||
background-image: url("./logo.png");
|
||||
}
|
||||
```
|
||||
|
||||
Bun copies `./logo.png` to the output directory and rewrites the path in the CSS file to include a content hash.
|
||||
|
||||
```css styles.css icon="file-code"
|
||||
body {
|
||||
background-image: url("./logo-[ABC123].png");
|
||||
}
|
||||
```
|
||||
|
||||
### Importing CSS in JavaScript
|
||||
|
||||
To associate a CSS file with a JavaScript file, import it from the JavaScript file.
|
||||
|
||||
```ts app.ts icon="/icons/typescript.svg"
|
||||
import "./styles.css";
|
||||
import "./more-styles.css";
|
||||
```
|
||||
|
||||
This generates `./app.css` and `./app.js` in the output directory. Bun bundles all CSS files imported from JavaScript into a single CSS file per entry point. If you import the same CSS file from multiple JavaScript files, Bun includes it only once in the output CSS file.
|
||||
|
||||
## Plugins
|
||||
|
||||
The dev server supports plugins.
|
||||
|
||||
### Tailwind CSS
|
||||
|
||||
To use TailwindCSS, install the `bun-plugin-tailwind` plugin:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
# Or any npm client
|
||||
bun install --dev bun-plugin-tailwind
|
||||
```
|
||||
|
||||
Then, add the plugin to your `bunfig.toml`:
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[serve.static]
|
||||
plugins = ["bun-plugin-tailwind"]
|
||||
```
|
||||
|
||||
Then, reference TailwindCSS in your HTML with a `<link>` tag, an `@import` in CSS, or an import in JavaScript.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="index.html">
|
||||
|
||||
```html title="index.html" icon="file-code"
|
||||
<!-- Reference TailwindCSS in your HTML -->
|
||||
<link rel="stylesheet" href="tailwindcss" />
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="styles.css">
|
||||
|
||||
```css title="styles.css" icon="file-code"
|
||||
@import "tailwindcss";
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="app.ts">
|
||||
|
||||
```ts title="app.ts" icon="/icons/typescript.svg"
|
||||
import "tailwindcss";
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Info>Only one of these is necessary, not all three.</Info>
|
||||
|
||||
## Inline environment variables
|
||||
|
||||
Bun can replace `process.env.*` references in your JavaScript and TypeScript with their actual values at build time. Use this to inject configuration like API URLs or feature flags into your frontend code.
|
||||
|
||||
### Dev server (runtime)
|
||||
|
||||
To inline environment variables when using `bun ./index.html`, configure the `env` option in your `bunfig.toml`:
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[serve.static]
|
||||
env = "PUBLIC_*" # only inline env vars starting with PUBLIC_ (recommended)
|
||||
# env = "inline" # inline all environment variables
|
||||
# env = "disable" # disable env var replacement (default)
|
||||
```
|
||||
|
||||
<Note>
|
||||
Inlining only works with literal `process.env.FOO` references, not `import.meta.env` or indirect access like `const env =
|
||||
process.env; env.FOO`.
|
||||
|
||||
If an environment variable is not set, you may see runtime errors like `ReferenceError: process
|
||||
is not defined` in the browser.
|
||||
|
||||
</Note>
|
||||
|
||||
Then run the dev server:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
PUBLIC_API_URL=https://api.example.com bun ./index.html
|
||||
```
|
||||
|
||||
### Build for production
|
||||
|
||||
When building static HTML for production, use the `env` option to inline environment variables:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="CLI">
|
||||
```bash terminal icon="terminal"
|
||||
# Inline all environment variables
|
||||
bun build ./index.html --outdir=dist --env=inline
|
||||
|
||||
# Only inline env vars with a specific prefix (recommended)
|
||||
bun build ./index.html --outdir=dist --env=PUBLIC_*
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="API">
|
||||
```ts title="build.ts" icon="/icons/typescript.svg"
|
||||
// Inline all environment variables
|
||||
await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
outdir: "./dist",
|
||||
env: "inline", // [!code highlight]
|
||||
});
|
||||
|
||||
// Only inline env vars with a specific prefix (recommended)
|
||||
await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
outdir: "./dist",
|
||||
env: "PUBLIC_*", // [!code highlight]
|
||||
});
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Example
|
||||
|
||||
Given this source file:
|
||||
|
||||
```ts title="app.ts" icon="/icons/typescript.svg"
|
||||
const apiUrl = process.env.PUBLIC_API_URL;
|
||||
console.log(`API URL: ${apiUrl}`);
|
||||
```
|
||||
|
||||
And running with `PUBLIC_API_URL=https://api.example.com`:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
PUBLIC_API_URL=https://api.example.com bun build ./index.html --outdir=dist --env=PUBLIC_*
|
||||
```
|
||||
|
||||
The bundled output contains:
|
||||
|
||||
```js title="dist/app.js" icon="/icons/javascript.svg"
|
||||
const apiUrl = "https://api.example.com";
|
||||
console.log(`API URL: ${apiUrl}`);
|
||||
```
|
||||
|
||||
## Echo console logs from browser to terminal
|
||||
|
||||
Bun's dev server can stream console logs from the browser to the terminal. To enable this, pass the `--console` CLI flag.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun ./index.html --console
|
||||
```
|
||||
|
||||
```
|
||||
Bun v1.3.3
|
||||
ready in 6.62ms
|
||||
→ http://localhost:3000/
|
||||
Press h + Enter to show shortcuts
|
||||
```
|
||||
|
||||
Bun broadcasts each `console.log` or `console.error` call to the terminal that started the server, so browser errors show up in the same place you run your server. This also helps AI agents that watch terminal output.
|
||||
|
||||
Internally, Bun reuses the existing WebSocket connection from hot module replacement (HMR) to send the logs.
|
||||
|
||||
## Edit files in the browser
|
||||
|
||||
Bun's frontend dev server supports Automatic Workspace Folders in Chrome DevTools, so you can save edits to files from the browser.
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
While the server is running:
|
||||
|
||||
- `o + Enter` - Open in browser
|
||||
- `c + Enter` - Clear console
|
||||
- `q + Enter` (or `Ctrl+C`) - Quit server
|
||||
|
||||
## Build for Production
|
||||
|
||||
When you're ready to deploy, use `bun build` to create optimized production bundles:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="CLI">
|
||||
```bash terminal icon="terminal"
|
||||
bun build ./index.html --minify --outdir=dist
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="API">
|
||||
```ts title="build.ts" icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
outdir: "./dist",
|
||||
minify: true,
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Warning>
|
||||
Plugins are only supported through `Bun.build`'s API or through `bunfig.toml` with the frontend dev server, not
|
||||
through `bun build`'s CLI.
|
||||
</Warning>
|
||||
|
||||
### Watch Mode
|
||||
|
||||
Run `bun build --watch` to watch for changes and rebuild automatically. This works well for library development.
|
||||
|
||||
<Info>You've never seen a watch mode this fast.</Info>
|
||||
|
||||
## Plugin API
|
||||
|
||||
For more control, configure the bundler through the JavaScript API and use Bun's built-in `HTMLRewriter` to preprocess HTML.
|
||||
|
||||
```ts title="build.ts" icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
outdir: "./dist",
|
||||
minify: true,
|
||||
|
||||
plugins: [
|
||||
{
|
||||
// A plugin that makes every HTML tag lowercase
|
||||
name: "lowercase-html-plugin",
|
||||
setup({ onLoad }) {
|
||||
const rewriter = new HTMLRewriter().on("*", {
|
||||
element(element) {
|
||||
element.tagName = element.tagName.toLowerCase();
|
||||
},
|
||||
text(element) {
|
||||
element.replace(element.text.toLowerCase());
|
||||
},
|
||||
});
|
||||
|
||||
onLoad({ filter: /\.html$/ }, async args => {
|
||||
const html = await Bun.file(args.path).text();
|
||||
|
||||
return {
|
||||
// Bun's bundler will scan the HTML for <script> tags, <link rel="stylesheet"> tags, and other assets
|
||||
// and bundle them automatically
|
||||
contents: rewriter.transform(html),
|
||||
loader: "html",
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## What Gets Processed?
|
||||
|
||||
Bun automatically handles all common web assets:
|
||||
|
||||
- **Scripts** (`<script src>`) are run through Bun's JavaScript/TypeScript/JSX bundler
|
||||
- **Stylesheets** (`<link rel="stylesheet">`) are run through Bun's CSS parser & bundler
|
||||
- **Images** (`<img>`, `<picture>`) are copied and hashed
|
||||
- **Media** (`<video>`, `<audio>`, `<source>`) are copied and hashed
|
||||
- Any `<link>` tag with an `href` attribute pointing to a local file is rewritten to the new path, and hashed
|
||||
|
||||
Bun resolves all paths relative to your HTML file, so you can organize your project however you want.
|
||||
|
||||
<Warning>
|
||||
**This is a work in progress**
|
||||
- Need more plugins
|
||||
- Need more configuration options for things like asset handling
|
||||
- Need a way to configure CORS, headers, etc.
|
||||
|
||||
{/* todo: find the correct link to link to as this 404's and there isn't any similar files */}
|
||||
{/* If you want to submit a PR, most of the code is [here](https://github.com/oven-sh/bun/blob/main/src/runtime/api/bun/html-rewriter.ts). You could even copy paste that file into your project and use it as a starting point. */}
|
||||
|
||||
</Warning>
|
||||
|
||||
## How this works
|
||||
|
||||
The dev server is a small wrapper around Bun's support for [HTML imports](/bundler/fullstack) in JavaScript.
|
||||
|
||||
## Standalone HTML
|
||||
|
||||
You can bundle your entire frontend into a **single self-contained `.html` file** with no external dependencies using `--compile --target=browser`. Bun inlines all JavaScript, CSS, and images directly into the HTML.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
Learn more in the [Standalone HTML docs](/bundler/standalone-html).
|
||||
|
||||
## Adding a backend to your frontend
|
||||
|
||||
To add a backend to your frontend, use the `routes` option in `Bun.serve`. See the [full-stack docs](/bundler/fullstack).
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,503 @@
|
||||
---
|
||||
title: Loaders
|
||||
description: Built-in loaders for the Bun bundler and runtime
|
||||
---
|
||||
|
||||
The Bun bundler has a set of built-in loaders.
|
||||
|
||||
> As a rule of thumb: **the bundler and the runtime both support the same set of file types by default.**
|
||||
|
||||
`.js` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` `.jsx` `.css` `.json` `.jsonc` `.json5` `.toml` `.yaml` `.yml` `.xml` `.txt` `.text` `.md` `.markdown` `.wasm` `.node` `.html` `.sh`
|
||||
|
||||
Bun uses the file extension to choose which built-in loader parses the file. Every loader has a name, such as `js`, `tsx`, or `json`. Plugins that extend Bun with custom loaders refer to these names.
|
||||
|
||||
To specify a loader explicitly, use the `'type'` import attribute.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import my_toml from "./my_file" with { type: "toml" };
|
||||
// or with dynamic imports
|
||||
const { default: my_toml } = await import("./my_file", { with: { type: "toml" } });
|
||||
```
|
||||
|
||||
## Built-in loaders
|
||||
|
||||
### `js`
|
||||
|
||||
**JavaScript loader.** Default for `.cjs` and `.mjs`.
|
||||
|
||||
Parses the code and applies a set of default transforms like dead-code elimination and tree shaking. Bun does not down-convert syntax.
|
||||
|
||||
---
|
||||
|
||||
### `jsx`
|
||||
|
||||
**JavaScript + JSX loader.** Default for `.js` and `.jsx`.
|
||||
|
||||
Same as the `js` loader, but JSX syntax is supported. By default, Bun down-converts JSX to plain JavaScript. The exact output depends on the `jsx*` compiler options in your `tsconfig.json`. See the [TypeScript documentation on JSX](https://www.typescriptlang.org/tsconfig#jsx).
|
||||
|
||||
---
|
||||
|
||||
### `ts`
|
||||
|
||||
**TypeScript loader.** Default for `.ts`, `.mts`, and `.cts`.
|
||||
|
||||
Strips out all TypeScript syntax, then behaves identically to the `js` loader. Bun does not perform typechecking.
|
||||
|
||||
---
|
||||
|
||||
### `tsx`
|
||||
|
||||
**TypeScript + JSX loader.** Default for `.tsx`.
|
||||
|
||||
Transpiles both TypeScript and JSX to vanilla JavaScript.
|
||||
|
||||
---
|
||||
|
||||
### `json`
|
||||
|
||||
**JSON loader.** Default for `.json`.
|
||||
|
||||
JSON files can be directly imported.
|
||||
|
||||
```js
|
||||
import pkg from "./package.json";
|
||||
pkg.name; // => "my-package"
|
||||
```
|
||||
|
||||
During bundling, Bun inlines the parsed JSON into the bundle as a JavaScript object.
|
||||
|
||||
```js
|
||||
const pkg = {
|
||||
name: "my-package",
|
||||
// ... other fields
|
||||
};
|
||||
|
||||
pkg.name;
|
||||
```
|
||||
|
||||
If you pass a `.json` file as an entrypoint to the bundler, Bun converts it to a `.js` module that `export default`s the parsed object.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```json Input
|
||||
{
|
||||
"name": "John Doe",
|
||||
"age": 35,
|
||||
"email": "[email protected]"
|
||||
}
|
||||
```
|
||||
|
||||
```js Output
|
||||
export default {
|
||||
name: "John Doe",
|
||||
age: 35,
|
||||
email: "[email protected]",
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
---
|
||||
|
||||
### `jsonc`
|
||||
|
||||
**JSON with Comments loader.** Default for `.jsonc`.
|
||||
|
||||
JSONC (JSON with Comments) files can be directly imported. Bun parses them, stripping out comments and trailing commas.
|
||||
|
||||
```js
|
||||
import config from "./config.jsonc";
|
||||
console.log(config);
|
||||
```
|
||||
|
||||
During bundling, Bun inlines the parsed JSONC into the bundle as a JavaScript object, identical to the `json` loader.
|
||||
|
||||
```js
|
||||
var config = {
|
||||
option: "value",
|
||||
};
|
||||
```
|
||||
|
||||
<Note>
|
||||
Bun automatically uses the `jsonc` loader for `tsconfig.json`, `jsconfig.json`, `package.json`, and `bun.lock` files.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
### `toml`
|
||||
|
||||
**TOML loader.** Default for `.toml`.
|
||||
|
||||
TOML files can be directly imported. Bun parses them with its native TOML parser.
|
||||
|
||||
```js
|
||||
import config from "./bunfig.toml";
|
||||
config.logLevel; // => "debug"
|
||||
|
||||
// with an import attribute:
|
||||
// import myCustomTOML from './my.config' with {type: "toml"};
|
||||
```
|
||||
|
||||
During bundling, Bun inlines the parsed TOML into the bundle as a JavaScript object.
|
||||
|
||||
```js
|
||||
var config = {
|
||||
logLevel: "debug",
|
||||
// ...other fields
|
||||
};
|
||||
config.logLevel;
|
||||
```
|
||||
|
||||
If you pass a `.toml` file as an entrypoint, Bun converts it to a `.js` module that `export default`s the parsed object.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```toml Input
|
||||
name = "John Doe"
|
||||
age = 35
|
||||
email = "[email protected]"
|
||||
```
|
||||
|
||||
```js Output
|
||||
export default {
|
||||
name: "John Doe",
|
||||
age: 35,
|
||||
email: "[email protected]",
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
---
|
||||
|
||||
### `yaml`
|
||||
|
||||
**YAML loader.** Default for `.yaml` and `.yml`.
|
||||
|
||||
YAML files can be directly imported. Bun parses them with its native YAML parser.
|
||||
|
||||
```js
|
||||
import config from "./config.yaml";
|
||||
console.log(config);
|
||||
|
||||
// with an import attribute:
|
||||
import data from "./data.txt" with { type: "yaml" };
|
||||
```
|
||||
|
||||
During bundling, Bun inlines the parsed YAML into the bundle as a JavaScript object.
|
||||
|
||||
```js
|
||||
var config = {
|
||||
name: "my-app",
|
||||
version: "1.0.0",
|
||||
// ...other fields
|
||||
};
|
||||
```
|
||||
|
||||
If you pass a `.yaml` or `.yml` file as an entrypoint, Bun converts it to a `.js` module that `export default`s the parsed object.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```yaml Input
|
||||
name: John Doe
|
||||
age: 35
|
||||
email: [email protected]
|
||||
```
|
||||
|
||||
```js Output
|
||||
export default {
|
||||
name: "John Doe",
|
||||
age: 35,
|
||||
email: "[email protected]",
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
---
|
||||
|
||||
### `xml`
|
||||
|
||||
**XML loader.** Default for `.xml`.
|
||||
|
||||
XML files can be directly imported. Bun parses them with its native XML 1.0 parser into the compact object shape of [`Bun.XML.parse`](/runtime/xml):
|
||||
|
||||
- One key for the root element
|
||||
- `"@name"` keys for attributes
|
||||
- Arrays for repeated child elements
|
||||
- `"#text"` for text next to attributes or children
|
||||
- Every value is a string
|
||||
|
||||
```ts
|
||||
import doc from "./config.xml";
|
||||
console.log(doc.config["@version"]);
|
||||
|
||||
// via import attribute:
|
||||
import feed from "./export.rss" with { type: "xml" };
|
||||
```
|
||||
|
||||
During bundling, Bun inlines the parsed XML into the bundle as a JavaScript object.
|
||||
|
||||
```ts
|
||||
var doc = {
|
||||
config: {
|
||||
"@version": "2",
|
||||
// ...other fields
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
If you pass a `.xml` file as an entrypoint, Bun converts it to a `.js` module that `export default`s the parsed object.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```xml Input
|
||||
<user id="1">
|
||||
<name>John Doe</name>
|
||||
<email>[email protected]</email>
|
||||
<role>admin</role>
|
||||
<role>editor</role>
|
||||
</user>
|
||||
```
|
||||
|
||||
```ts Output
|
||||
export default {
|
||||
user: {
|
||||
"@id": "1",
|
||||
name: "John Doe",
|
||||
email: "[email protected]",
|
||||
role: ["admin", "editor"],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
---
|
||||
|
||||
### `text`
|
||||
|
||||
**Text loader.** Default for `.txt` and `.text`.
|
||||
|
||||
Text files can be directly imported. Bun reads the file and returns it as a string.
|
||||
|
||||
```js
|
||||
import contents from "./file.txt";
|
||||
console.log(contents); // => "Hello, world!"
|
||||
|
||||
// To import an html file as text
|
||||
// The "type" attribute overrides the default loader.
|
||||
import html from "./index.html" with { type: "text" };
|
||||
```
|
||||
|
||||
When the file is referenced during a build, Bun inlines the contents into the bundle as a string.
|
||||
|
||||
```js
|
||||
var contents = `Hello, world!`;
|
||||
console.log(contents);
|
||||
```
|
||||
|
||||
If you pass a `.txt` file as an entrypoint, Bun converts it to a `.js` module that `export default`s the file contents.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```txt Input
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
```js Output
|
||||
export default "Hello, world!";
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
---
|
||||
|
||||
### `napi`
|
||||
|
||||
**Native addon loader.** Default for `.node`.
|
||||
|
||||
In the runtime, native addons can be directly imported.
|
||||
|
||||
```js
|
||||
import addon from "./addon.node";
|
||||
console.log(addon);
|
||||
```
|
||||
|
||||
<Note>In the bundler, Bun handles `.node` files with the `file` loader.</Note>
|
||||
|
||||
---
|
||||
|
||||
### `sqlite`
|
||||
|
||||
**SQLite loader.** Requires `with { "type": "sqlite" }` import attribute.
|
||||
|
||||
In the runtime and bundler, SQLite databases can be directly imported. Bun loads the database with `bun:sqlite`.
|
||||
|
||||
```js
|
||||
import db from "./my.db" with { type: "sqlite" };
|
||||
```
|
||||
|
||||
<Warning>The `sqlite` loader is only supported when the target is `bun`.</Warning>
|
||||
|
||||
By default, Bun does not bundle the database file on disk into the final output. The database file stays external to the bundle, so you can use a database loaded elsewhere.
|
||||
|
||||
You can change this behavior with the `"embed"` attribute:
|
||||
|
||||
```js
|
||||
// embed the database into the bundle
|
||||
import db from "./my.db" with { type: "sqlite", embed: "true" };
|
||||
```
|
||||
|
||||
<Info>
|
||||
With a standalone executable, Bun embeds the database into the single-file executable.
|
||||
|
||||
Otherwise, the database to embed is copied into the `outdir` with a hashed filename.
|
||||
|
||||
</Info>
|
||||
|
||||
---
|
||||
|
||||
### `html`
|
||||
|
||||
**HTML loader.** Default for `.html`.
|
||||
|
||||
The `html` loader processes HTML files and bundles any referenced assets. It:
|
||||
|
||||
- Bundles and hashes referenced JavaScript files (`<script src="...">`)
|
||||
- Bundles and hashes referenced CSS files (`<link rel="stylesheet" href="...">`)
|
||||
- Hashes referenced images (`<img src="...">`)
|
||||
- Preserves external URLs (by default, anything starting with `http://` or `https://`)
|
||||
|
||||
For example, given this HTML file:
|
||||
|
||||
```html title="src/index.html" icon="file-code"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<img src="./image.jpg" alt="Local image" />
|
||||
<img src="https://example.com/image.jpg" alt="External image" />
|
||||
<script type="module" src="./script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Bun outputs a new HTML file with the bundled assets:
|
||||
|
||||
```html title="dist/index.html" icon="file-code"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<img src="./image-HASHED.jpg" alt="Local image" />
|
||||
<img src="https://example.com/image.jpg" alt="External image" />
|
||||
<script type="module" src="./output-ALSO-HASHED.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
The loader uses [`lol-html`](https://github.com/cloudflare/lol-html) to extract script and link tags as entrypoints, and other assets as external.
|
||||
|
||||
<Accordion title="List of supported HTML selectors">
|
||||
The selectors are:
|
||||
|
||||
- `audio[src]`
|
||||
- `img[src]`
|
||||
- `img[srcset]`
|
||||
- `link[as='font'][href], link[type^='font/'][href]`
|
||||
- `link[as='image'][href]`
|
||||
- `link[as='style'][href]`
|
||||
- `link[as='video'][href], link[as='audio'][href]`
|
||||
- `link[as='worker'][href]`
|
||||
- `link[rel='icon'][href], link[rel='apple-touch-icon'][href]`
|
||||
- `link[rel='manifest'][href]`
|
||||
- `link[rel='stylesheet'][href]`
|
||||
- `script[src]`
|
||||
- `source[src]`
|
||||
- `source[srcset]`
|
||||
- `video[poster]`
|
||||
- `video[src]`
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Note>
|
||||
|
||||
**HTML Loader Behavior in Different Contexts**
|
||||
|
||||
The `html` loader behaves differently depending on how it's used:
|
||||
|
||||
- Static Build: When you run `bun build ./index.html`, Bun produces a static site with all assets bundled and hashed.
|
||||
- Runtime: When you run `bun run server.ts` (where `server.ts` imports an HTML file), Bun bundles assets on the fly during development, enabling features like hot module replacement.
|
||||
- Full-stack Build: When you run `bun build --target=bun server.ts` (where `server.ts` imports an HTML file), the import resolves to a manifest object that `Bun.serve` uses to serve pre-bundled assets in production.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
### `css`
|
||||
|
||||
**CSS loader.** Default for `.css`.
|
||||
|
||||
CSS files can be directly imported. The bundler parses and bundles them, handling `@import` statements and `url()` references.
|
||||
|
||||
```js
|
||||
import "./styles.css";
|
||||
```
|
||||
|
||||
During bundling, Bun combines all imported CSS files into a single `.css` file in the output directory.
|
||||
|
||||
```css
|
||||
.my-class {
|
||||
background: url("./image.png");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `sh`
|
||||
|
||||
**Bun Shell loader.** Default for `.sh` files.
|
||||
|
||||
This loader parses Bun Shell scripts. It's only supported when starting Bun itself, so it's not available in the bundler or in the runtime.
|
||||
|
||||
```bash
|
||||
bun run ./script.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `file`
|
||||
|
||||
**File loader.** Default for all unrecognized file types.
|
||||
|
||||
The file loader resolves the import as a path/URL to the imported file. It's commonly used for referencing media or font assets.
|
||||
|
||||
```js
|
||||
// logo.ts
|
||||
import logo from "./logo.svg";
|
||||
console.log(logo);
|
||||
```
|
||||
|
||||
In the runtime, Bun checks that `logo.svg` exists and resolves the import to its absolute path on disk.
|
||||
|
||||
```bash
|
||||
bun run logo.ts
|
||||
# Output: /path/to/project/logo.svg
|
||||
```
|
||||
|
||||
In the bundler, Bun copies the file into `outdir` as-is, and the import resolves to a relative path pointing to the copied file.
|
||||
|
||||
```js
|
||||
// Output
|
||||
var logo = "./logo.svg";
|
||||
console.log(logo);
|
||||
```
|
||||
|
||||
If `publicPath` is set, the import uses its value as a prefix to construct an absolute path/URL.
|
||||
|
||||
| Public path | Resolved import |
|
||||
| ---------------------------- | ---------------------------------- |
|
||||
| `""` (default) | `./logo.svg` |
|
||||
| `"/assets/"` | `/assets/logo.svg` |
|
||||
| `"https://cdn.example.com/"` | `https://cdn.example.com/logo.svg` |
|
||||
|
||||
<Note>The value of `naming.asset` determines the location and file name of the copied file.</Note>
|
||||
@@ -0,0 +1,327 @@
|
||||
---
|
||||
title: Macros
|
||||
description: Run JavaScript functions at bundle-time with Bun macros
|
||||
---
|
||||
|
||||
Macros are JavaScript functions that run at bundle-time. Bun inlines their return values directly into your bundle.
|
||||
|
||||
As a toy example, consider this function that returns a random number.
|
||||
|
||||
```ts title="random.ts" icon="/icons/typescript.svg"
|
||||
export function random() {
|
||||
return Math.random();
|
||||
}
|
||||
```
|
||||
|
||||
This is a regular function in a regular file, but you can use it as a macro:
|
||||
|
||||
```tsx title="cli.tsx" icon="/icons/typescript.svg"
|
||||
import { random } from "./random.ts" with { type: "macro" };
|
||||
|
||||
console.log(`Your random number is ${random()}`);
|
||||
```
|
||||
|
||||
<Note>
|
||||
Macros are marked with import attribute syntax, a Stage 4 TC39 proposal for attaching additional metadata to import
|
||||
statements.
|
||||
</Note>
|
||||
|
||||
Bundle the file with `bun build`. Bun prints the bundled file to stdout.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build ./cli.tsx
|
||||
```
|
||||
|
||||
```js
|
||||
console.log(`Your random number is ${0.6805550949689833}`);
|
||||
```
|
||||
|
||||
The source code of the `random` function occurs nowhere in the bundle. Instead, the function runs during bundling and Bun replaces the call (`random()`) with its result. Since the source code is never included in the bundle, macros can safely perform privileged operations like reading from a database.
|
||||
|
||||
## When to use macros
|
||||
|
||||
For small things you would otherwise write a one-off build script for, bundle-time code execution can be easier to maintain. It lives with the rest of your code and runs with the rest of the build. Bun parallelizes it automatically, and if it fails, the build fails too.
|
||||
|
||||
If you find yourself running a lot of code at bundle-time though, consider running a server instead.
|
||||
|
||||
## Import attributes
|
||||
|
||||
Macros are import statements annotated with either:
|
||||
|
||||
- `with { type: 'macro' }` — an import attribute, a Stage 4 ECMAScript proposal
|
||||
- `assert { type: 'macro' }` — an import assertion, an earlier incarnation of import attributes that has now been abandoned (but several browsers and runtimes already support it)
|
||||
|
||||
## Security considerations
|
||||
|
||||
You must explicitly import a macro with `{ type: "macro" }` for it to run at bundle-time. These imports have no effect unless you call them, unlike regular JavaScript imports which may have side effects.
|
||||
|
||||
You can disable macros entirely with the `--no-macros` flag. It produces a build error like this:
|
||||
|
||||
```
|
||||
error: Macros are disabled
|
||||
|
||||
foo();
|
||||
^
|
||||
./hello.js:3:1 53
|
||||
```
|
||||
|
||||
To reduce the potential attack surface for malicious packages, Bun does not let code inside `node_modules/**/*` invoke macros. If a package attempts to invoke a macro, you'll see an error like this:
|
||||
|
||||
```
|
||||
error: For security reasons, macros cannot be run from node_modules.
|
||||
|
||||
beEvil();
|
||||
^
|
||||
node_modules/evil/index.js:3:1 50
|
||||
```
|
||||
|
||||
Your application code can still import macros from `node_modules` and invoke them.
|
||||
|
||||
```ts title="cli.tsx" icon="/icons/typescript.svg"
|
||||
import { macro } from "some-package" with { type: "macro" };
|
||||
|
||||
macro();
|
||||
```
|
||||
|
||||
## Export condition "macro"
|
||||
|
||||
When shipping a library containing a macro to npm or another package registry, use the `"macro"` export condition to provide a version of your package exclusively for the macro environment.
|
||||
|
||||
```json title="package.json" icon="file-json"
|
||||
{
|
||||
"name": "my-package",
|
||||
"exports": {
|
||||
"import": "./index.js",
|
||||
"require": "./index.js",
|
||||
"default": "./index.js",
|
||||
"macro": "./index.macro.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With this configuration, users can consume your package at runtime or at bundle-time using the same import specifier:
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import pkg from "my-package"; // runtime import
|
||||
import { macro } from "my-package" with { type: "macro" }; // macro import
|
||||
```
|
||||
|
||||
The first import resolves to `./node_modules/my-package/index.js`; Bun's bundler resolves the second to `./node_modules/my-package/index.macro.js`.
|
||||
|
||||
## Execution
|
||||
|
||||
When Bun's transpiler sees a macro import, it calls the function using Bun's JavaScript runtime and converts the return value into an AST node.
|
||||
|
||||
Macros run synchronously in the transpiler during the visiting phase, after the transpiler parses the file into an AST. They run in the order their calls appear in the file; the transpiler does not load or run a macro module until it reaches a call to one of its exports. The transpiler waits for each macro to finish before continuing, and awaits any Promise a macro returns.
|
||||
|
||||
Bun's bundler is multi-threaded, so macros execute in parallel in multiple spawned JavaScript "workers".
|
||||
|
||||
## Dead code elimination
|
||||
|
||||
The bundler performs dead code elimination after running and inlining macros. Given the following macro:
|
||||
|
||||
```ts title="returnFalse.ts" icon="/icons/typescript.svg"
|
||||
export function returnFalse() {
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
...bundling the following file produces an empty bundle, provided that the minify syntax option is enabled.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import { returnFalse } from "./returnFalse.ts" with { type: "macro" };
|
||||
|
||||
if (returnFalse()) {
|
||||
console.log("This code is eliminated");
|
||||
}
|
||||
```
|
||||
|
||||
## Serializability
|
||||
|
||||
Bun's transpiler must be able to serialize the result of the macro to inline it into the AST. All JSON-compatible data structures are supported:
|
||||
|
||||
```ts title="macro.ts" icon="/icons/typescript.svg"
|
||||
export function getObject() {
|
||||
return {
|
||||
foo: "bar",
|
||||
baz: 123,
|
||||
array: [1, 2, { nested: "value" }],
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Macros can be async, or return Promise instances. Bun's transpiler awaits the Promise and inlines the result.
|
||||
|
||||
```ts title="macro.ts" icon="/icons/typescript.svg"
|
||||
export async function getText() {
|
||||
return "async value";
|
||||
}
|
||||
```
|
||||
|
||||
The transpiler implements special logic for serializing common data formats like `Response` and `Blob`.
|
||||
|
||||
- **Response**: Bun reads the `Content-Type` and serializes accordingly. For example, it parses a Response with type `application/json` into an object and inlines `text/plain` as a string. Bun base64-encodes Responses with an unrecognized or undefined type.
|
||||
- **Blob**: As with Response, the serialization depends on the `type` property.
|
||||
|
||||
The result of `fetch` is `Promise<Response>`, so a macro can return it directly.
|
||||
|
||||
```ts title="macro.ts" icon="/icons/typescript.svg"
|
||||
export function getObject() {
|
||||
return fetch("https://bun.com");
|
||||
}
|
||||
```
|
||||
|
||||
Functions and instances of most classes (except those listed earlier) are not serializable.
|
||||
|
||||
```ts title="macro.ts" icon="/icons/typescript.svg"
|
||||
export function getText(url: string) {
|
||||
// this doesn't work!
|
||||
return () => {};
|
||||
}
|
||||
```
|
||||
|
||||
## Arguments
|
||||
|
||||
Macros can accept inputs, but only in limited cases. The value must be statically known. For example, the following is not allowed:
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import { getText } from "./getText.ts" with { type: "macro" };
|
||||
|
||||
export function howLong() {
|
||||
// the value of `foo` cannot be statically known
|
||||
const foo = Math.random() ? "foo" : "bar";
|
||||
|
||||
const text = getText(`https://example.com/${foo}`);
|
||||
console.log("The page is ", text.length, " characters long");
|
||||
}
|
||||
```
|
||||
|
||||
However, if the value of `foo` is known at bundle-time (say, if it's a constant or the result of another macro), then the call is allowed:
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import { getText } from "./getText.ts" with { type: "macro" };
|
||||
import { getFoo } from "./getFoo.ts" with { type: "macro" };
|
||||
|
||||
export function howLong() {
|
||||
// this works because getFoo() is statically known
|
||||
const foo = getFoo();
|
||||
const text = getText(`https://example.com/${foo}`);
|
||||
console.log("The page is", text.length, "characters long");
|
||||
}
|
||||
```
|
||||
|
||||
This outputs:
|
||||
|
||||
```js
|
||||
function howLong() {
|
||||
console.log("The page is", 1322, "characters long");
|
||||
}
|
||||
export { howLong };
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Embed latest git commit hash
|
||||
|
||||
```ts title="getGitCommitHash.ts" icon="/icons/typescript.svg"
|
||||
export function getGitCommitHash() {
|
||||
const { stdout } = Bun.spawnSync({
|
||||
cmd: ["git", "rev-parse", "HEAD"],
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
return stdout.toString();
|
||||
}
|
||||
```
|
||||
|
||||
When you build it, Bun replaces the `getGitCommitHash` call with the result of calling the function:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```ts input
|
||||
import { getGitCommitHash } from "./getGitCommitHash.ts" with { type: "macro" };
|
||||
|
||||
console.log(`The current Git commit hash is ${getGitCommitHash()}`);
|
||||
```
|
||||
|
||||
```ts output
|
||||
console.log(`The current Git commit hash is 3ee3259104e4507cf62c160f0ff5357ec4c7a7f8`);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<Info>
|
||||
You're probably thinking "Why not use `process.env.GIT_COMMIT_HASH`?" Well, you can do that too. But can you do this
|
||||
with an environment variable?
|
||||
</Info>
|
||||
|
||||
### Make fetch() requests at bundle-time
|
||||
|
||||
This example makes an outgoing HTTP request with `fetch()`, parses the HTML response with `HTMLRewriter`, and returns an object containing the title and meta tags, all at bundle-time.
|
||||
|
||||
```ts title="meta.ts" icon="/icons/typescript.svg"
|
||||
export async function extractMetaTags(url: string) {
|
||||
const response = await fetch(url);
|
||||
const meta = {
|
||||
title: "",
|
||||
};
|
||||
new HTMLRewriter()
|
||||
.on("title", {
|
||||
text(element) {
|
||||
meta.title += element.text;
|
||||
},
|
||||
})
|
||||
.on("meta", {
|
||||
element(element) {
|
||||
const name =
|
||||
element.getAttribute("name") || element.getAttribute("property") || element.getAttribute("itemprop");
|
||||
|
||||
if (name) meta[name] = element.getAttribute("content");
|
||||
},
|
||||
})
|
||||
.transform(response);
|
||||
|
||||
return meta;
|
||||
}
|
||||
```
|
||||
|
||||
Bun erases the `extractMetaTags` function at bundle-time and replaces it with the result of the function call. The fetch request happens at bundle-time, and Bun embeds the result in the bundle. Bun also eliminates the branch throwing the error since it's unreachable, provided that the minify syntax option is enabled.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```jsx input
|
||||
import { extractMetaTags } from "./meta.ts" with { type: "macro" };
|
||||
|
||||
export const Head = () => {
|
||||
const headTags = extractMetaTags("https://example.com");
|
||||
|
||||
if (headTags.title !== "Example Domain") {
|
||||
throw new Error("Expected title to be 'Example Domain'");
|
||||
}
|
||||
|
||||
return (
|
||||
<head>
|
||||
<title>{headTags.title}</title>
|
||||
<meta name="viewport" content={headTags.viewport} />
|
||||
</head>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
```jsx output
|
||||
export const Head = () => {
|
||||
const headTags = {
|
||||
title: "Example Domain",
|
||||
viewport: "width=device-width, initial-scale=1",
|
||||
};
|
||||
|
||||
return (
|
||||
<head>
|
||||
<title>{headTags.title}</title>
|
||||
<meta name="viewport" content={headTags.viewport} />
|
||||
</head>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,462 @@
|
||||
---
|
||||
title: Plugins
|
||||
description: Universal plugin API for extending Bun's runtime and bundler
|
||||
---
|
||||
|
||||
Bun's universal plugin API extends both the runtime and the bundler.
|
||||
|
||||
Plugins intercept imports and perform custom loading logic, such as reading files or transpiling code. They can add support for additional file types, like `.scss`. In the bundler, plugins can implement framework-level features like CSS extraction, macros, and client-server code co-location.
|
||||
|
||||
## Lifecycle hooks
|
||||
|
||||
Plugins register callbacks that run at various points in the lifecycle of a bundle:
|
||||
|
||||
- `onStart()`: Run once the bundler has started a bundle
|
||||
- `onResolve()`: Run before the bundler resolves a module
|
||||
- `onLoad()`: Run before the bundler loads a module
|
||||
- `onBeforeParse()`: Run zero-copy native addons in the parser thread before the bundler parses a file
|
||||
- `onEnd()`: Run after the bundle is complete
|
||||
|
||||
## Reference
|
||||
|
||||
A rough overview of the types (see Bun's `bun.d.ts` for the full type definitions):
|
||||
|
||||
```ts title="bun.d.ts" icon="/icons/typescript.svg"
|
||||
type PluginBuilder = {
|
||||
onStart(callback: () => void | Promise<void>): void;
|
||||
onResolve: (
|
||||
args: { filter: RegExp; namespace?: string },
|
||||
callback: (args: { path: string; importer: string }) => {
|
||||
path: string;
|
||||
namespace?: string;
|
||||
} | void,
|
||||
) => void;
|
||||
onLoad: (
|
||||
args: { filter: RegExp; namespace?: string },
|
||||
callback: (args: { path: string; defer: () => Promise<void> }) => {
|
||||
loader?: Loader;
|
||||
contents?: string;
|
||||
exports?: Record<string, any>;
|
||||
},
|
||||
) => void;
|
||||
onEnd(callback: (result: BuildOutput) => void | Promise<void>): void;
|
||||
config: BuildConfig;
|
||||
};
|
||||
|
||||
type Loader =
|
||||
| "js"
|
||||
| "jsx"
|
||||
| "ts"
|
||||
| "tsx"
|
||||
| "json"
|
||||
| "jsonc"
|
||||
| "toml"
|
||||
| "yaml"
|
||||
| "file"
|
||||
| "napi"
|
||||
| "wasm"
|
||||
| "text"
|
||||
| "css"
|
||||
| "html";
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
A plugin is a JavaScript object with a `name` property and a `setup` function.
|
||||
|
||||
```ts title="myPlugin.ts" icon="/icons/typescript.svg"
|
||||
import type { BunPlugin } from "bun";
|
||||
|
||||
const myPlugin: BunPlugin = {
|
||||
name: "Custom loader",
|
||||
setup(build) {
|
||||
// implementation
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Pass it in the `plugins` array when calling `Bun.build`.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ["./app.ts"],
|
||||
outdir: "./out",
|
||||
plugins: [myPlugin],
|
||||
});
|
||||
```
|
||||
|
||||
## Plugin lifecycle
|
||||
|
||||
### Namespaces
|
||||
|
||||
`onLoad` and `onResolve` accept an optional `namespace` string.
|
||||
|
||||
Every module has a namespace. Namespaces prefix the import in transpiled code; for example, a loader with a `filter: /\.yaml$/` and `namespace: "yaml:"` transforms an import from `./myfile.yaml` into `yaml:./myfile.yaml`.
|
||||
|
||||
The default namespace is `"file"` and you don't need to specify it: `import myModule from "./my-module.ts"` is the same as `import myModule from "file:./my-module.ts"`.
|
||||
|
||||
Other common namespaces are:
|
||||
|
||||
- `"bun"`: for Bun-specific modules (`"bun:test"`, `"bun:sqlite"`)
|
||||
- `"node"`: for Node.js modules (`"node:fs"`, `"node:path"`)
|
||||
|
||||
### onStart
|
||||
|
||||
```ts
|
||||
onStart(callback: () => void | Promise<void>): void;
|
||||
```
|
||||
|
||||
Registers a callback that runs when the bundler starts a new bundle.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ["./app.ts"],
|
||||
plugins: [
|
||||
{
|
||||
name: "onStart example",
|
||||
setup(build) {
|
||||
build.onStart(() => {
|
||||
console.log("Bundle started!");
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
The callback can return a Promise. After the bundle process has initialized, the bundler waits until all `onStart()` callbacks have completed before continuing.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
const result = await Bun.build({
|
||||
entrypoints: ["./app.ts"],
|
||||
outdir: "./dist",
|
||||
sourcemap: "external",
|
||||
plugins: [
|
||||
{
|
||||
name: "Sleep for 10 seconds",
|
||||
setup(build) {
|
||||
build.onStart(async () => {
|
||||
await Bun.sleep(10_000);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Log bundle time to a file",
|
||||
setup(build) {
|
||||
build.onStart(async () => {
|
||||
const now = Date.now();
|
||||
await Bun.$`echo ${now} > bundle-time.txt`;
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
In this example, Bun waits for both `onStart()` callbacks to complete: the 10-second sleep and the write to `bundle-time.txt`.
|
||||
|
||||
<Note>
|
||||
`onStart()` callbacks (like every other lifecycle callback) cannot modify the `build.config` object. To mutate
|
||||
`build.config`, do so directly in the `setup()` function.
|
||||
</Note>
|
||||
|
||||
### onResolve
|
||||
|
||||
```ts
|
||||
onResolve(
|
||||
args: { filter: RegExp; namespace?: string },
|
||||
callback: (args: { path: string; importer: string }) => {
|
||||
path: string;
|
||||
namespace?: string;
|
||||
} | void,
|
||||
): void;
|
||||
```
|
||||
|
||||
To bundle your project, Bun walks down the dependency tree of all modules in your project. For each imported module, Bun has to find and read that module. The "finding" part is known as "resolving" a module.
|
||||
|
||||
The `onResolve()` plugin lifecycle callback configures how Bun resolves a module.
|
||||
|
||||
The first argument to `onResolve()` is an object with a `filter` and `namespace` property. The `filter` is a regular expression run on the import string. Together, these select which modules your custom resolution logic applies to.
|
||||
|
||||
The second argument to `onResolve()` is a callback. Bun runs the callback for each module import it finds that matches the filter and namespace defined in the first argument.
|
||||
|
||||
The callback receives the path to the matching module and can return a new path for the module. Bun reads the contents of the new path and parses it as a module.
|
||||
|
||||
For example, redirecting all imports to `images/` to `./public/images/`:
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import { plugin } from "bun";
|
||||
|
||||
plugin({
|
||||
name: "onResolve example",
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /.*/, namespace: "file" }, args => {
|
||||
if (args.path.startsWith("images/")) {
|
||||
return {
|
||||
path: args.path.replace("images/", "./public/images/"),
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### onLoad
|
||||
|
||||
```ts
|
||||
onLoad(
|
||||
args: { filter: RegExp; namespace?: string },
|
||||
callback: (args: { path: string; namespace: string; loader: Loader; defer: () => Promise<void> }) => {
|
||||
loader?: Loader;
|
||||
contents?: string;
|
||||
exports?: Record<string, any>;
|
||||
},
|
||||
): void;
|
||||
```
|
||||
|
||||
After Bun's bundler has resolved a module, it reads and parses the module's contents.
|
||||
|
||||
The `onLoad()` plugin lifecycle callback modifies the contents of a module before Bun reads and parses it.
|
||||
|
||||
Like `onResolve()`, the first argument to `onLoad()` selects which modules this invocation of `onLoad()` applies to.
|
||||
|
||||
The second argument to `onLoad()` is a callback that runs for each matching module before Bun loads its contents into memory.
|
||||
|
||||
The callback receives the path to the matching module, its namespace, its default loader, and a `defer` function.
|
||||
|
||||
The callback can return a new `contents` string for the module as well as a new `loader`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import type { BunPlugin } from "bun";
|
||||
|
||||
const envPlugin: BunPlugin = {
|
||||
name: "env plugin",
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^env$/ }, () => ({ path: "env", namespace: "env" }));
|
||||
build.onLoad({ filter: /.*/, namespace: "env" }, args => {
|
||||
return {
|
||||
contents: `export default ${JSON.stringify(process.env)}`,
|
||||
loader: "js",
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
Bun.build({
|
||||
entrypoints: ["./app.ts"],
|
||||
outdir: "./dist",
|
||||
plugins: [envPlugin],
|
||||
});
|
||||
|
||||
// import env from "env"
|
||||
// env.FOO === "bar"
|
||||
```
|
||||
|
||||
This plugin transforms all imports of the form `import env from "env"` into a JavaScript module that exports the current environment variables.
|
||||
|
||||
#### .defer()
|
||||
|
||||
One of the arguments passed to the `onLoad` callback is a `defer` function. It returns a Promise that resolves once Bun has loaded all other modules. Await it when a module's contents depend on other modules.
|
||||
|
||||
<Accordion title="Example: tracking and reporting unused exports">
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import { plugin } from "bun";
|
||||
|
||||
plugin({
|
||||
name: "track imports",
|
||||
setup(build) {
|
||||
const transpiler = new Bun.Transpiler();
|
||||
|
||||
let trackedImports: Record<string, number> = {};
|
||||
|
||||
// Each module that goes through this onLoad callback
|
||||
// will record its imports in `trackedImports`
|
||||
build.onLoad({ filter: /\.ts/ }, async ({ path }) => {
|
||||
const contents = await Bun.file(path).arrayBuffer();
|
||||
|
||||
const imports = transpiler.scanImports(contents);
|
||||
|
||||
for (const i of imports) {
|
||||
trackedImports[i.path] = (trackedImports[i.path] || 0) + 1;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
build.onLoad({ filter: /stats\.json/ }, async ({ defer }) => {
|
||||
// Wait for all files to be loaded, ensuring
|
||||
// that every file goes through the above `onLoad()` function
|
||||
// and their imports tracked
|
||||
await defer();
|
||||
|
||||
// Emit JSON containing the stats of each import
|
||||
return {
|
||||
contents: `export default ${JSON.stringify(trackedImports)}`,
|
||||
loader: "json",
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Warning>You can call the `.defer()` function only once per `onLoad` callback.</Warning>
|
||||
|
||||
### onEnd
|
||||
|
||||
```ts
|
||||
onEnd(callback: (result: BuildOutput) => void | Promise<void>): void;
|
||||
```
|
||||
|
||||
Registers a callback that runs after the bundle is complete. The callback receives the [`BuildOutput`](/bundler#outputs) object containing the build results, including output files and any build messages.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
const result = await Bun.build({
|
||||
entrypoints: ["./app.ts"],
|
||||
outdir: "./dist",
|
||||
plugins: [
|
||||
{
|
||||
name: "onEnd example",
|
||||
setup(build) {
|
||||
build.onEnd(result => {
|
||||
console.log(`Build completed with ${result.outputs.length} files`);
|
||||
for (const log of result.logs) {
|
||||
console.log(log);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
The callback can return a `Promise`. The promise returned by `Bun.build()` does not resolve until all `onEnd()` callbacks have completed.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
const result = await Bun.build({
|
||||
entrypoints: ["./app.ts"],
|
||||
outdir: "./dist",
|
||||
plugins: [
|
||||
{
|
||||
name: "Upload to S3",
|
||||
setup(build) {
|
||||
build.onEnd(async result => {
|
||||
if (!result.success) return;
|
||||
for (const output of result.outputs) {
|
||||
await uploadToS3(output);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Native plugins
|
||||
|
||||
Bun's bundler is written in native code and uses multiple threads to load and parse modules in parallel. JavaScript plugins run on a single thread, because JavaScript itself is single-threaded.
|
||||
|
||||
Native plugins are NAPI modules that expose lifecycle hooks as C ABI functions. They can run on multiple threads, so they run much faster than JavaScript plugins. They also skip work such as the UTF-8 -> UTF-16 conversion needed to pass strings to JavaScript.
|
||||
|
||||
These lifecycle hooks are available to native plugins:
|
||||
|
||||
- `onBeforeParse()`: Called on any thread before Bun's bundler parses a file.
|
||||
|
||||
To create a native plugin, export a C ABI function that matches the signature of the native lifecycle hook you want to implement.
|
||||
|
||||
### Creating a native plugin in Rust
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun add -g @napi-rs/cli
|
||||
napi new
|
||||
```
|
||||
|
||||
Then install this crate:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
cargo add bun-native-plugin
|
||||
```
|
||||
|
||||
Inside `lib.rs`, use the `bun_native_plugin::bun` proc macro to define the function that implements your native plugin.
|
||||
|
||||
Here's an example implementing the `onBeforeParse` hook:
|
||||
|
||||
```rust title="lib.rs" icon="/icons/rust.svg"
|
||||
use bun_native_plugin::{define_bun_plugin, OnBeforeParse, bun, Result, anyhow};
|
||||
use napi_derive::napi;
|
||||
|
||||
/// Define the plugin and its name
|
||||
define_bun_plugin!("replace-foo-with-bar");
|
||||
|
||||
/// Here we'll implement `onBeforeParse` with code that replaces all occurrences of
|
||||
/// `foo` with `bar`.
|
||||
///
|
||||
/// We use the #[bun] macro to generate some of the boilerplate code.
|
||||
///
|
||||
/// The argument of the function (`handle: &mut OnBeforeParse`) tells
|
||||
/// the macro that this function implements the `onBeforeParse` hook.
|
||||
#[bun]
|
||||
pub fn replace_foo_with_bar(handle: &mut OnBeforeParse) -> Result<()> {
|
||||
// Fetch the input source code.
|
||||
let input_source_code = handle.input_source_code()?;
|
||||
|
||||
// Get the Loader for the file
|
||||
let loader = handle.output_loader();
|
||||
|
||||
let output_source_code = input_source_code.replace("foo", "bar");
|
||||
|
||||
handle.set_output_source_code(output_source_code, loader);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
To use it in `Bun.build()`:
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import myNativeAddon from "./my-native-addon";
|
||||
|
||||
Bun.build({
|
||||
entrypoints: ["./app.tsx"],
|
||||
plugins: [
|
||||
{
|
||||
name: "my-plugin",
|
||||
|
||||
setup(build) {
|
||||
build.onBeforeParse(
|
||||
{
|
||||
namespace: "file",
|
||||
filter: /\.tsx$/,
|
||||
},
|
||||
{
|
||||
napiModule: myNativeAddon,
|
||||
symbol: "replace_foo_with_bar",
|
||||
// external: myNativeAddon.getSharedState()
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### onBeforeParse
|
||||
|
||||
```ts
|
||||
onBeforeParse(
|
||||
args: { filter: RegExp; namespace?: string },
|
||||
callback: { napiModule: NapiModule; symbol: string; external?: unknown },
|
||||
): void;
|
||||
```
|
||||
|
||||
The `onBeforeParse()` callback runs immediately before Bun's bundler parses a file.
|
||||
|
||||
It receives the file's contents and can optionally return new source code.
|
||||
|
||||
<Info>Bun can call this callback from any thread, so the NAPI module implementation must be thread-safe.</Info>
|
||||
@@ -0,0 +1,314 @@
|
||||
---
|
||||
title: Standalone HTML
|
||||
description: Bundle a single-page app into a single self-contained .html file with no external dependencies
|
||||
---
|
||||
|
||||
Bun can bundle your entire frontend into a **single `.html` file** with zero external dependencies. JavaScript, TypeScript, JSX, CSS, images, fonts, videos, WASM: Bun inlines everything into one file.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
The output is a self-contained HTML document: no relative paths, no external files, no server required.
|
||||
|
||||
## One file. Upload anywhere.
|
||||
|
||||
The output is a single `.html` file you can put anywhere:
|
||||
|
||||
- **Upload it to S3** or any static file host — no directory structure to maintain, one file
|
||||
- **Double-click it from your desktop** — it opens in the browser and works offline, no localhost server needed
|
||||
- **Embed it in your webview** — no relative files to deal with
|
||||
- **Insert it in an `<iframe>`** — embed interactive content in another page with a single file URL
|
||||
- **Serve it from anywhere** — any HTTP server, CDN, or file share
|
||||
|
||||
There's nothing to install, no `node_modules` to deploy, no build artifacts to coordinate, no relative paths to think about.
|
||||
|
||||
## Truly one file
|
||||
|
||||
Normally, distributing a web page means managing a folder of assets — the HTML, the JavaScript bundles, the CSS files, the images. Move the HTML without the rest and everything breaks. Browsers have tried to solve this before. Safari's `.webarchive` and Chrome's `.mhtml` save a page as a single file. In practice, only some browsers can open them, which defeats the purpose.
|
||||
|
||||
Standalone HTML output is a plain `.html` file: not an archive, not a folder. Bun embeds every image, every font, and every line of CSS and JavaScript directly in the HTML using standard `<style>` tags, `<script>` tags, and `data:` URIs. Any browser can open it and any server can host it.
|
||||
|
||||
You can distribute the page the same way you'd distribute a PDF: a single file you can move, copy, upload, or share without worrying about broken paths or missing assets.
|
||||
|
||||
## Quick start
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```html index.html icon="file-code"
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```tsx app.tsx icon="/icons/typescript.svg"
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
function App() {
|
||||
return <h1>Hello from a single HTML file!</h1>;
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
```
|
||||
|
||||
```css styles.css icon="file-code"
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, sans-serif;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
Open `dist/index.html` — the React app works with no server.
|
||||
|
||||
## Everything gets inlined
|
||||
|
||||
Bun inlines every local asset it finds in your HTML: anything with a relative path, of any file type, is embedded into the output file.
|
||||
|
||||
### What gets inlined
|
||||
|
||||
| In your source | In the output |
|
||||
| ------------------------------------------------ | ------------------------------------------------------------------------ |
|
||||
| `<script src="./app.tsx">` | `<script type="module">...bundled code...</script>` |
|
||||
| `<link rel="stylesheet" href="./styles.css">` | `<style>...bundled CSS...</style>` |
|
||||
| `<img src="./logo.png">` | `<img src="data:image/png;base64,...">` |
|
||||
| `<img src="./icon.svg">` | `<img src="data:image/svg+xml;base64,...">` |
|
||||
| `<video src="./demo.mp4">` | `<video src="data:video/mp4;base64,...">` |
|
||||
| `<audio src="./click.wav">` | `<audio src="data:audio/x-wav;base64,...">` |
|
||||
| `<source src="./clip.webm">` | `<source src="data:video/webm;base64,...">` |
|
||||
| `<video poster="./thumb.jpg">` | `<video poster="data:image/jpeg;base64,...">` |
|
||||
| `<link rel="icon" href="./favicon.ico">` | `<link rel="icon" href="data:image/x-icon;base64,...">` |
|
||||
| `<link rel="manifest" href="./app.webmanifest">` | `<link rel="manifest" href="data:application/manifest+json;base64,...">` |
|
||||
| CSS `url("./bg.png")` | CSS `url(data:image/png;base64,...)` |
|
||||
| CSS `@import "./reset.css"` | Flattened into the `<style>` tag |
|
||||
| CSS `url("./font.woff2")` | CSS `url(data:font/woff2;base64,...)` |
|
||||
| JS `import "./styles.css"` | Merged into the `<style>` tag |
|
||||
|
||||
Images, fonts, WASM binaries, videos, audio files, SVGs: Bun base64-encodes any file referenced by a relative path into a `data:` URI and embeds it directly in the HTML. Bun detects the MIME type from the file extension.
|
||||
|
||||
Bun leaves external URLs (like CDN links or absolute URLs) untouched.
|
||||
|
||||
## Using with React
|
||||
|
||||
React apps need no extra configuration: Bun transpiles JSX and resolves npm packages.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun install react react-dom
|
||||
```
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```html index.html icon="file-code"
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>My App</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```tsx app.tsx icon="/icons/typescript.svg"
|
||||
import React, { useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { Counter } from "./components/Counter.tsx";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Single-file React App</h1>
|
||||
<Counter />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
```
|
||||
|
||||
```tsx components/Counter.tsx icon="/icons/typescript.svg"
|
||||
import React, { useState } from "react";
|
||||
|
||||
export function Counter() {
|
||||
const [count, setCount] = useState(0);
|
||||
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
Bun bundles all of React, your components, and your CSS into `dist/index.html`. Upload that one file anywhere and it works.
|
||||
|
||||
## Using with Tailwind CSS
|
||||
|
||||
Install the plugin and reference Tailwind in your HTML or CSS:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun install --dev bun-plugin-tailwind
|
||||
```
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```html index.html icon="file-code"
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="tailwindcss" />
|
||||
</head>
|
||||
<body class="bg-gray-100 flex items-center justify-center min-h-screen">
|
||||
<div id="root"></div>
|
||||
<script src="./app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```tsx app.tsx icon="/icons/typescript.svg"
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-lg p-8 max-w-md">
|
||||
<h1 className="text-2xl font-bold text-gray-800">Hello Tailwind</h1>
|
||||
<p className="text-gray-600 mt-2">This is a single HTML file.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
Build with the plugin using the JavaScript API:
|
||||
|
||||
```ts build.ts icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
compile: true,
|
||||
target: "browser",
|
||||
outdir: "./dist",
|
||||
plugins: [require("bun-plugin-tailwind")],
|
||||
});
|
||||
```
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run build.ts
|
||||
```
|
||||
|
||||
Bun inlines the generated Tailwind CSS directly into the HTML file as a `<style>` tag.
|
||||
|
||||
## How it works
|
||||
|
||||
When you pass `--compile --target=browser` with an HTML entrypoint, Bun:
|
||||
|
||||
1. Parses the HTML and discovers all `<script>`, `<link>`, `<img>`, `<video>`, `<audio>`, `<source>`, and other asset references
|
||||
2. Bundles all JavaScript/TypeScript/JSX into a single module
|
||||
3. Bundles all CSS (including `@import` chains and CSS imported from JS) into a single stylesheet
|
||||
4. Converts every relative asset reference into a base64 `data:` URI
|
||||
5. Inlines the bundled JS as `<script type="module">` before `</body>`
|
||||
6. Inlines the bundled CSS as `<style>` in `<head>`
|
||||
7. Outputs a single `.html` file with no external dependencies
|
||||
|
||||
## Minification
|
||||
|
||||
Add `--minify` to minify the JavaScript and CSS:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser --minify ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
Or with the JavaScript API:
|
||||
|
||||
```ts build.ts icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
compile: true,
|
||||
target: "browser",
|
||||
outdir: "./dist",
|
||||
minify: true,
|
||||
});
|
||||
```
|
||||
|
||||
## JavaScript API
|
||||
|
||||
Use `Bun.build()` to produce standalone HTML programmatically:
|
||||
|
||||
```ts build.ts icon="/icons/typescript.svg"
|
||||
const result = await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
compile: true,
|
||||
target: "browser",
|
||||
outdir: "./dist", // optional — omit to get output as BuildArtifact
|
||||
minify: true,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.error("Build failed:");
|
||||
for (const log of result.logs) {
|
||||
console.error(log);
|
||||
}
|
||||
} else {
|
||||
console.log("Built:", result.outputs[0].path);
|
||||
}
|
||||
```
|
||||
|
||||
When you omit `outdir`, the output is available as a `BuildArtifact` in `result.outputs`:
|
||||
|
||||
```ts icon="/icons/typescript.svg"
|
||||
const result = await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
compile: true,
|
||||
target: "browser",
|
||||
});
|
||||
|
||||
const html = await result.outputs[0].text();
|
||||
await Bun.write("output.html", html);
|
||||
```
|
||||
|
||||
## Multiple HTML files
|
||||
|
||||
You can pass multiple HTML files as entrypoints. Each produces its own standalone HTML file:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html ./about.html --outdir=dist
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
Use `--env` to inline environment variables into the bundled JavaScript:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
API_URL=https://api.example.com bun build --compile --target=browser --env=inline ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
Bun replaces references to `process.env.API_URL` in your JavaScript with the literal value at build time.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Code splitting** is not supported — `--splitting` cannot be used with `--compile --target=browser`
|
||||
- **Large assets** increase file size since they're base64-encoded (33% overhead vs the raw binary)
|
||||
- **External URLs** (CDN links, absolute URLs) stay as-is: Bun inlines only relative paths
|
||||
+647
@@ -0,0 +1,647 @@
|
||||
{
|
||||
"theme": "aspen",
|
||||
"name": "Bun",
|
||||
"seo": {
|
||||
"metatags": {
|
||||
"canonical": "https://bun.com/docs"
|
||||
}
|
||||
},
|
||||
"colors": {
|
||||
"light": "#ff73a8",
|
||||
"primary": "#ff73a8",
|
||||
"dark": "#ff73a8"
|
||||
},
|
||||
"background": {
|
||||
"decoration": "gradient"
|
||||
},
|
||||
"favicon": "/logo/bun.png",
|
||||
"icons": {
|
||||
"library": "lucide"
|
||||
},
|
||||
"appearance": {
|
||||
"default": "system"
|
||||
},
|
||||
"logo": {
|
||||
"light": "/logo/logo-with-wordmark-dark.svg",
|
||||
"dark": "/logo/logo-with-wordmark-light.svg"
|
||||
},
|
||||
"navbar": {
|
||||
"links": [
|
||||
{
|
||||
"label": "Install Bun",
|
||||
"href": "https://www.bun.com/docs/installation",
|
||||
"icon": "download",
|
||||
"primary": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"contextual": {
|
||||
"options": ["copy", "view", "claude", "mcp", "vscode"]
|
||||
},
|
||||
"styling": {
|
||||
"codeblocks": {
|
||||
"theme": {
|
||||
"light": "github-light",
|
||||
"dark": "dracula"
|
||||
}
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Runtime",
|
||||
"icon": "cog",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Get Started",
|
||||
"icon": "terminal",
|
||||
"pages": [
|
||||
"/index",
|
||||
"/installation",
|
||||
"/quickstart",
|
||||
"/typescript",
|
||||
"/typescript-6",
|
||||
"/runtime/templating/init",
|
||||
"/runtime/templating/create"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Core Runtime",
|
||||
"icon": "cog",
|
||||
"pages": ["/runtime/index", "/runtime/watch-mode", "/runtime/debugger", "/runtime/repl", "/runtime/bunfig"]
|
||||
},
|
||||
{
|
||||
"group": "File & Module System",
|
||||
"icon": "file",
|
||||
"pages": [
|
||||
"/runtime/file-types",
|
||||
"/runtime/module-resolution",
|
||||
"/runtime/jsx",
|
||||
"/runtime/auto-install",
|
||||
"/runtime/plugins",
|
||||
"/runtime/file-system-router"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "HTTP server",
|
||||
"icon": "server",
|
||||
"pages": [
|
||||
"/runtime/http/server",
|
||||
"/runtime/http/routing",
|
||||
"/runtime/http/cookies",
|
||||
"/runtime/http/tls",
|
||||
"/runtime/http/error-handling",
|
||||
"/runtime/http/metrics"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Networking",
|
||||
"icon": "globe",
|
||||
"expanded": true,
|
||||
"pages": [
|
||||
"/runtime/networking/fetch",
|
||||
"/runtime/http/websockets",
|
||||
"/runtime/networking/tcp",
|
||||
"/runtime/networking/udp",
|
||||
"/runtime/networking/dns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Data & Storage",
|
||||
"icon": "database",
|
||||
"pages": [
|
||||
"/runtime/cookies",
|
||||
"/runtime/file-io",
|
||||
"/runtime/streams",
|
||||
"/runtime/binary-data",
|
||||
"/runtime/archive",
|
||||
"/runtime/sql",
|
||||
"/runtime/sqlite",
|
||||
"/runtime/s3",
|
||||
"/runtime/redis"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Concurrency",
|
||||
"icon": "split",
|
||||
"pages": ["/runtime/workers"]
|
||||
},
|
||||
{
|
||||
"group": "Process & System",
|
||||
"icon": "computer",
|
||||
"pages": [
|
||||
"/runtime/environment-variables",
|
||||
"/runtime/shell",
|
||||
"/runtime/child-process",
|
||||
"/runtime/webview",
|
||||
"/runtime/cron"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Interop & Tooling",
|
||||
"icon": "puzzle",
|
||||
"pages": ["/runtime/node-api", "/runtime/ffi", "/runtime/c-compiler", "/runtime/transpiler"]
|
||||
},
|
||||
{
|
||||
"group": "Utilities",
|
||||
"icon": "wrench",
|
||||
"pages": [
|
||||
"/runtime/csrf",
|
||||
"/runtime/secrets",
|
||||
"/runtime/console",
|
||||
"/runtime/toml",
|
||||
"/runtime/yaml",
|
||||
"/runtime/markdown",
|
||||
"/runtime/json5",
|
||||
"/runtime/xml",
|
||||
"/runtime/jsonl",
|
||||
"/runtime/html-rewriter",
|
||||
"/runtime/image",
|
||||
"/runtime/hashing",
|
||||
"/runtime/glob",
|
||||
"/runtime/semver",
|
||||
"/runtime/color",
|
||||
"/runtime/utils"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Standards & Compatibility",
|
||||
"icon": "badge-check",
|
||||
"pages": ["/runtime/globals", "/runtime/bun-apis", "/runtime/web-apis", "/runtime/nodejs-compat"]
|
||||
},
|
||||
{
|
||||
"group": "Contributing",
|
||||
"icon": "heart",
|
||||
"pages": [
|
||||
"/project/roadmap",
|
||||
"/project/benchmarking",
|
||||
"/project/contributing",
|
||||
"/project/building-windows",
|
||||
"/project/bindgen",
|
||||
"/project/license"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Package Manager",
|
||||
"icon": "box",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Core Commands",
|
||||
"icon": "terminal",
|
||||
"pages": [
|
||||
"/pm/cli/install",
|
||||
"/pm/cli/add",
|
||||
"/pm/cli/remove",
|
||||
"/pm/cli/update",
|
||||
"/pm/cli/dedupe",
|
||||
"/pm/cli/prune",
|
||||
"/pm/bunx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Publishing & Analysis",
|
||||
"icon": "upload",
|
||||
"pages": ["/pm/cli/publish", "/pm/cli/outdated", "/pm/cli/why", "/pm/cli/audit", "/pm/cli/info"]
|
||||
},
|
||||
{
|
||||
"group": "Workspace Management",
|
||||
"icon": "folders",
|
||||
"pages": ["/pm/workspaces", "/pm/catalogs", "/pm/cli/link", "/pm/cli/pm"]
|
||||
},
|
||||
{
|
||||
"group": "Advanced Configuration",
|
||||
"icon": "settings",
|
||||
"pages": [
|
||||
"/pm/cli/patch",
|
||||
"/pm/filter",
|
||||
"/pm/global-cache",
|
||||
"/pm/global-store",
|
||||
"/pm/isolated-installs",
|
||||
"/pm/lockfile",
|
||||
"/pm/lifecycle",
|
||||
"/pm/scopes-registries",
|
||||
"/pm/overrides",
|
||||
"/pm/security-scanner-api",
|
||||
"/pm/npmrc"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Bundler",
|
||||
"icon": "combine",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Core",
|
||||
"icon": "package",
|
||||
"pages": ["/bundler/index"]
|
||||
},
|
||||
{
|
||||
"group": "Development Server",
|
||||
"icon": "monitor",
|
||||
"pages": ["/bundler/fullstack", "/bundler/hot-reloading"]
|
||||
},
|
||||
{
|
||||
"group": "Asset Processing",
|
||||
"icon": "image",
|
||||
"pages": ["/bundler/html-static", "/bundler/standalone-html", "/bundler/css", "/bundler/loaders"]
|
||||
},
|
||||
{
|
||||
"group": "Single File Executable",
|
||||
"icon": "binary",
|
||||
"pages": ["/bundler/executables"]
|
||||
},
|
||||
{
|
||||
"group": "Extensions",
|
||||
"icon": "plug",
|
||||
"pages": ["/bundler/plugins", "/bundler/macros"]
|
||||
},
|
||||
{
|
||||
"group": "Optimization",
|
||||
"icon": "zap",
|
||||
"pages": ["/bundler/bytecode", "/bundler/minifier"]
|
||||
},
|
||||
{
|
||||
"group": "Migration",
|
||||
"icon": "arrow-right",
|
||||
"pages": ["/bundler/esbuild"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Test Runner",
|
||||
"icon": "flask-conical",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Getting Started",
|
||||
"icon": "circle-play",
|
||||
"pages": ["/test/index", "/test/writing-tests", "/test/configuration"]
|
||||
},
|
||||
{
|
||||
"group": "Test Execution",
|
||||
"icon": "zap",
|
||||
"pages": ["/test/runtime-behavior", "/test/discovery", "/test/parallel"]
|
||||
},
|
||||
{
|
||||
"group": "Test Features",
|
||||
"icon": "sparkles",
|
||||
"pages": ["/test/lifecycle", "/test/mocks", "/test/snapshots", "/test/dates-times"]
|
||||
},
|
||||
{
|
||||
"group": "Specialized Testing",
|
||||
"icon": "microscope",
|
||||
"pages": ["/test/dom"]
|
||||
},
|
||||
{
|
||||
"group": "Reporting",
|
||||
"icon": "file-text",
|
||||
"pages": ["/test/code-coverage", "/test/reporters"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"tab": "Guides",
|
||||
"icon": "map",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"icon": "globe",
|
||||
"pages": ["/guides/index"]
|
||||
},
|
||||
{
|
||||
"group": "Deployment",
|
||||
"icon": "rocket",
|
||||
"pages": [
|
||||
"/guides/deployment/vercel",
|
||||
"/guides/deployment/railway",
|
||||
"/guides/deployment/render",
|
||||
"/guides/deployment/aws-lambda",
|
||||
"/guides/deployment/digital-ocean",
|
||||
"/guides/deployment/google-cloud-run"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Runtime & Debugging",
|
||||
"icon": "bug",
|
||||
"pages": [
|
||||
"/guides/runtime/typescript",
|
||||
"/guides/runtime/tsconfig-paths",
|
||||
"/guides/runtime/vscode-debugger",
|
||||
"/guides/runtime/web-debugger",
|
||||
"/guides/runtime/heap-snapshot",
|
||||
"/guides/runtime/build-time-constants",
|
||||
"/guides/runtime/define-constant",
|
||||
"/guides/runtime/cicd",
|
||||
"/guides/runtime/codesign-macos-executable"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Utilities",
|
||||
"icon": "wrench",
|
||||
"pages": [
|
||||
"/guides/util/upgrade",
|
||||
"/guides/util/detect-bun",
|
||||
"/guides/util/version",
|
||||
"/guides/util/hash-a-password",
|
||||
"/guides/util/javascript-uuid",
|
||||
"/guides/util/base64",
|
||||
"/guides/util/gzip",
|
||||
"/guides/util/deflate",
|
||||
"/guides/util/escape-html",
|
||||
"/guides/util/deep-equals",
|
||||
"/guides/util/sleep",
|
||||
"/guides/util/file-url-to-path",
|
||||
"/guides/util/path-to-file-url",
|
||||
"/guides/util/which-path-to-executable-bin",
|
||||
"/guides/util/import-meta-dir",
|
||||
"/guides/util/import-meta-file",
|
||||
"/guides/util/import-meta-path",
|
||||
"/guides/util/entrypoint",
|
||||
"/guides/util/main"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Ecosystem & Frameworks",
|
||||
"icon": "puzzle",
|
||||
"pages": [
|
||||
"/guides/ecosystem/astro",
|
||||
"/guides/ecosystem/discordjs",
|
||||
"/guides/ecosystem/docker",
|
||||
"/guides/ecosystem/drizzle",
|
||||
"/guides/ecosystem/gel",
|
||||
"/guides/ecosystem/elysia",
|
||||
"/guides/ecosystem/express",
|
||||
"/guides/ecosystem/hono",
|
||||
"/guides/ecosystem/mongoose",
|
||||
"/guides/ecosystem/neon-drizzle",
|
||||
"/guides/ecosystem/neon-serverless-postgres",
|
||||
"/guides/ecosystem/nextjs",
|
||||
"/guides/ecosystem/nuxt",
|
||||
"/guides/ecosystem/pm2",
|
||||
"/guides/ecosystem/prisma",
|
||||
"/guides/ecosystem/prisma-postgres",
|
||||
"/guides/ecosystem/qwik",
|
||||
"/guides/ecosystem/react",
|
||||
"/guides/ecosystem/remix",
|
||||
"/guides/ecosystem/tanstack-start",
|
||||
"/guides/ecosystem/sentry",
|
||||
"/guides/ecosystem/solidstart",
|
||||
"/guides/ecosystem/ssr-react",
|
||||
"/guides/ecosystem/sveltekit",
|
||||
"/guides/ecosystem/systemd",
|
||||
"/guides/ecosystem/vite",
|
||||
"/guides/ecosystem/upstash"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "HTTP & Networking",
|
||||
"icon": "globe",
|
||||
"pages": [
|
||||
"/guides/http/server",
|
||||
"/guides/http/simple",
|
||||
"/guides/http/fetch",
|
||||
"/guides/http/hot",
|
||||
"/guides/http/cluster",
|
||||
"/guides/http/tls",
|
||||
"/guides/http/proxy",
|
||||
"/guides/http/stream-file",
|
||||
"/guides/http/file-uploads",
|
||||
"/guides/http/fetch-unix",
|
||||
"/guides/http/stream-iterator",
|
||||
"/guides/http/sse",
|
||||
"/guides/http/stream-node-streams-in-bun"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "WebSocket",
|
||||
"icon": "radio",
|
||||
"pages": [
|
||||
"/guides/websocket/simple",
|
||||
"/guides/websocket/pubsub",
|
||||
"/guides/websocket/context",
|
||||
"/guides/websocket/compression"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Processes & System",
|
||||
"icon": "cpu",
|
||||
"pages": [
|
||||
"/guides/process/spawn",
|
||||
"/guides/process/spawn-stdout",
|
||||
"/guides/process/spawn-stderr",
|
||||
"/guides/process/argv",
|
||||
"/guides/process/stdin",
|
||||
"/guides/process/ipc",
|
||||
"/guides/process/ctrl-c",
|
||||
"/guides/process/os-signals",
|
||||
"/guides/process/nanoseconds",
|
||||
"/guides/runtime/shell",
|
||||
"/guides/runtime/timezone",
|
||||
"/guides/runtime/set-env",
|
||||
"/guides/runtime/read-env"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Package Manager",
|
||||
"icon": "package",
|
||||
"pages": [
|
||||
"/guides/install/add",
|
||||
"/guides/install/add-dev",
|
||||
"/guides/install/add-optional",
|
||||
"/guides/install/add-peer",
|
||||
"/guides/install/add-git",
|
||||
"/guides/install/add-tarball",
|
||||
"/guides/install/npm-alias",
|
||||
"/guides/install/workspaces",
|
||||
"/guides/install/custom-registry",
|
||||
"/guides/install/registry-scope",
|
||||
"/guides/install/azure-artifacts",
|
||||
"/guides/install/jfrog-artifactory",
|
||||
"/guides/install/trusted",
|
||||
"/guides/install/yarnlock",
|
||||
"/guides/install/from-npm-install-to-bun-install",
|
||||
"/guides/install/git-diff-bun-lockfile",
|
||||
"/guides/install/cicd"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Test Runner",
|
||||
"icon": "flask-conical",
|
||||
"pages": [
|
||||
"/guides/test/run-tests",
|
||||
"/guides/test/watch-mode",
|
||||
"/guides/test/migrate-from-jest",
|
||||
"/guides/test/mock-functions",
|
||||
"/guides/test/spy-on",
|
||||
"/guides/test/mock-clock",
|
||||
"/guides/test/snapshot",
|
||||
"/guides/test/update-snapshots",
|
||||
"/guides/test/coverage",
|
||||
"/guides/test/coverage-threshold",
|
||||
"/guides/test/concurrent-test-glob",
|
||||
"/guides/test/skip-tests",
|
||||
"/guides/test/todo-tests",
|
||||
"/guides/test/timeout",
|
||||
"/guides/test/bail",
|
||||
"/guides/test/rerun-each",
|
||||
"/guides/test/testing-library",
|
||||
"/guides/test/happy-dom",
|
||||
"/guides/test/svelte-test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Runtime & Debugging",
|
||||
"icon": "bug",
|
||||
"pages": [
|
||||
"/guides/runtime/vscode-debugger",
|
||||
"/guides/runtime/web-debugger",
|
||||
"/guides/runtime/heap-snapshot",
|
||||
"/guides/runtime/build-time-constants",
|
||||
"/guides/runtime/define-constant",
|
||||
"/guides/runtime/cicd",
|
||||
"/guides/runtime/codesign-macos-executable"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Module System",
|
||||
"icon": "box",
|
||||
"pages": [
|
||||
"/guides/runtime/import-json",
|
||||
"/guides/runtime/import-toml",
|
||||
"/guides/runtime/import-yaml",
|
||||
"/guides/runtime/import-json5",
|
||||
"/guides/runtime/import-xml",
|
||||
"/guides/runtime/import-html",
|
||||
"/guides/util/import-meta-dir",
|
||||
"/guides/util/import-meta-file",
|
||||
"/guides/util/import-meta-path",
|
||||
"/guides/util/entrypoint",
|
||||
"/guides/util/main"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "File System",
|
||||
"icon": "folder",
|
||||
"pages": [
|
||||
"/guides/read-file/string",
|
||||
"/guides/read-file/buffer",
|
||||
"/guides/read-file/uint8array",
|
||||
"/guides/read-file/arraybuffer",
|
||||
"/guides/read-file/json",
|
||||
"/guides/read-file/mime",
|
||||
"/guides/read-file/exists",
|
||||
"/guides/read-file/watch",
|
||||
"/guides/read-file/stream",
|
||||
"/guides/write-file/basic",
|
||||
"/guides/write-file/blob",
|
||||
"/guides/write-file/response",
|
||||
"/guides/write-file/append",
|
||||
"/guides/write-file/filesink",
|
||||
"/guides/write-file/stream",
|
||||
"/guides/write-file/stdout",
|
||||
"/guides/write-file/cat",
|
||||
"/guides/write-file/file-cp",
|
||||
"/guides/write-file/unlink",
|
||||
"/guides/runtime/delete-file",
|
||||
"/guides/runtime/delete-directory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Utilities",
|
||||
"icon": "wrench",
|
||||
"pages": [
|
||||
"/guides/util/hash-a-password",
|
||||
"/guides/util/javascript-uuid",
|
||||
"/guides/util/base64",
|
||||
"/guides/util/gzip",
|
||||
"/guides/util/deflate",
|
||||
"/guides/util/escape-html",
|
||||
"/guides/util/deep-equals",
|
||||
"/guides/util/sleep",
|
||||
"/guides/util/file-url-to-path",
|
||||
"/guides/util/path-to-file-url",
|
||||
"/guides/util/which-path-to-executable-bin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "HTML Processing",
|
||||
"icon": "file-code",
|
||||
"pages": ["/guides/html-rewriter/extract-links", "/guides/html-rewriter/extract-social-meta"]
|
||||
},
|
||||
{
|
||||
"group": "Binary Data",
|
||||
"icon": "binary",
|
||||
"pages": [
|
||||
"/guides/binary/arraybuffer-to-string",
|
||||
"/guides/binary/arraybuffer-to-buffer",
|
||||
"/guides/binary/arraybuffer-to-blob",
|
||||
"/guides/binary/arraybuffer-to-array",
|
||||
"/guides/binary/arraybuffer-to-typedarray",
|
||||
"/guides/binary/buffer-to-string",
|
||||
"/guides/binary/buffer-to-arraybuffer",
|
||||
"/guides/binary/buffer-to-blob",
|
||||
"/guides/binary/buffer-to-typedarray",
|
||||
"/guides/binary/buffer-to-readablestream",
|
||||
"/guides/binary/blob-to-string",
|
||||
"/guides/binary/blob-to-arraybuffer",
|
||||
"/guides/binary/blob-to-typedarray",
|
||||
"/guides/binary/blob-to-dataview",
|
||||
"/guides/binary/blob-to-stream",
|
||||
"/guides/binary/typedarray-to-string",
|
||||
"/guides/binary/typedarray-to-arraybuffer",
|
||||
"/guides/binary/typedarray-to-buffer",
|
||||
"/guides/binary/typedarray-to-blob",
|
||||
"/guides/binary/typedarray-to-dataview",
|
||||
"/guides/binary/typedarray-to-readablestream",
|
||||
"/guides/binary/dataview-to-string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Streams",
|
||||
"icon": "waves",
|
||||
"pages": [
|
||||
"/guides/streams/to-string",
|
||||
"/guides/streams/to-json",
|
||||
"/guides/streams/to-blob",
|
||||
"/guides/streams/to-buffer",
|
||||
"/guides/streams/to-arraybuffer",
|
||||
"/guides/streams/to-typedarray",
|
||||
"/guides/streams/to-array",
|
||||
"/guides/streams/node-readable-to-string",
|
||||
"/guides/streams/node-readable-to-json",
|
||||
"/guides/streams/node-readable-to-blob",
|
||||
"/guides/streams/node-readable-to-uint8array",
|
||||
"/guides/streams/node-readable-to-arraybuffer"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Reference",
|
||||
"icon": "book",
|
||||
"href": "https://bun.com/reference"
|
||||
},
|
||||
{
|
||||
"tab": "Blog",
|
||||
"icon": "newspaper",
|
||||
"href": "https://bun.com/blog"
|
||||
},
|
||||
{
|
||||
"tab": "Feedback",
|
||||
"icon": "lightbulb",
|
||||
"pages": ["/feedback"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"footer": {
|
||||
"socials": {
|
||||
"x": "https://x.com/bunjavascript",
|
||||
"github": "https://github.com/oven-sh/bun",
|
||||
"discord": "https://bun.com/discord",
|
||||
"youtube": "https://www.youtube.com/@bunjs"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: Feedback
|
||||
description: Share feedback, bug reports, and feature requests
|
||||
mode: center
|
||||
---
|
||||
|
||||
Here's how to open a helpful issue for a bug, a performance problem, or a feature request:
|
||||
|
||||
<Callout icon="discord">For general questions, join the [Discord](https://bun.com/discord).</Callout>
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
<Steps>
|
||||
<Step title="Upgrade Bun">
|
||||
Upgrade Bun to the latest version with `bun upgrade`. This might fix your problem without opening an issue.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun upgrade
|
||||
```
|
||||
|
||||
You can also try the latest canary release, which includes changes and bug fixes that haven't reached a stable release yet.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun upgrade --canary
|
||||
|
||||
# To revert to the stable release
|
||||
bun upgrade --stable
|
||||
```
|
||||
|
||||
If the issue persists after upgrading, continue to the next step.
|
||||
</Step>
|
||||
<Step title="Review Existing Issues">
|
||||
Check whether the issue has already been reported before opening a new one. Checking first saves time for everyone and helps us focus on fixing things.
|
||||
|
||||
- 🔍 [**Search existing issues**](https://github.com/oven-sh/bun/issues)
|
||||
- 💬 [**Check discussions**](https://github.com/oven-sh/bun/discussions)
|
||||
|
||||
If you find a related issue, add a 👍 reaction or comment with extra details instead of opening a new one.
|
||||
</Step>
|
||||
<Step title="Report the Issue">
|
||||
If no one has reported the issue, open a new one or suggest an improvement.
|
||||
|
||||
- 🐞 [**Report a Bug**](https://github.com/oven-sh/bun/issues/new?template=2-bug-report.yml)
|
||||
- ⚡ [**Suggest an Improvement**](https://github.com/oven-sh/bun/issues/new?template=4-feature-request.yml)
|
||||
|
||||
Provide as much detail as possible, including:
|
||||
- A clear and concise title
|
||||
- A code example or steps to reproduce the issue
|
||||
- The version of Bun you are using (run `bun --version`)
|
||||
- A description of the issue (what you expected to happen and what actually happened)
|
||||
- The operating system and version you are using
|
||||
<Note>
|
||||
- For macOS and Linux: copy the output of `uname -mprs`
|
||||
- For Windows: copy the output of this command in the PowerShell console:
|
||||
`"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"`
|
||||
</Note>
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
The Bun team will review the issue and get back to you as soon as possible.
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: Convert an ArrayBuffer to an array of numbers
|
||||
sidebarTitle: "ArrayBuffer to Array"
|
||||
mode: center
|
||||
---
|
||||
|
||||
To retrieve the contents of an `ArrayBuffer` as an array of numbers, create a [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) over the buffer, then use [`Array.from()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) to convert it to an array.
|
||||
|
||||
```ts
|
||||
const buf = new ArrayBuffer(64);
|
||||
const arr = new Uint8Array(buf);
|
||||
arr.length; // 64
|
||||
arr[0]; // 0 (instantiated with all zeros)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The `Uint8Array` class supports array indexing and iteration. To convert the instance to a regular `Array`, use `Array.from()`. This is likely slower than using the `Uint8Array` directly.
|
||||
|
||||
```ts
|
||||
const buf = new ArrayBuffer(64);
|
||||
const uintArr = new Uint8Array(buf);
|
||||
const regularArr = Array.from(uintArr);
|
||||
// number[]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Convert an ArrayBuffer to a Blob
|
||||
sidebarTitle: "ArrayBuffer to Blob"
|
||||
mode: center
|
||||
---
|
||||
|
||||
You can construct a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) from an array of "chunks", where each chunk is a string, binary data structure, or another `Blob`.
|
||||
|
||||
```ts
|
||||
const buf = new ArrayBuffer(64);
|
||||
const blob = new Blob([buf]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
By default the `type` of the resulting `Blob` is unset. Set it with the `type` option.
|
||||
|
||||
```ts
|
||||
const buf = new ArrayBuffer(64);
|
||||
const blob = new Blob([buf], { type: "application/octet-stream" });
|
||||
blob.type; // => "application/octet-stream"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: Convert an ArrayBuffer to a Buffer
|
||||
sidebarTitle: "ArrayBuffer to Buffer"
|
||||
mode: center
|
||||
---
|
||||
|
||||
The Node.js [`Buffer`](https://nodejs.org/api/buffer.html) API predates the introduction of `ArrayBuffer` into the JavaScript language. Bun implements both.
|
||||
|
||||
Use the static `Buffer.from()` method to create a `Buffer` from an `ArrayBuffer`.
|
||||
|
||||
```ts
|
||||
const arrBuffer = new ArrayBuffer(64);
|
||||
const nodeBuffer = Buffer.from(arrBuffer);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To create a `Buffer` that only views a portion of the underlying buffer, pass the offset and length to `Buffer.from()`.
|
||||
|
||||
```ts
|
||||
const arrBuffer = new ArrayBuffer(64);
|
||||
const nodeBuffer = Buffer.from(arrBuffer, 0, 16); // view first 16 bytes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Convert an ArrayBuffer to a string
|
||||
sidebarTitle: "ArrayBuffer to string"
|
||||
mode: center
|
||||
---
|
||||
|
||||
Bun implements the Web-standard [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) class for converting between binary data types and strings.
|
||||
|
||||
```ts
|
||||
const buf = new ArrayBuffer(64);
|
||||
const decoder = new TextDecoder();
|
||||
const str = decoder.decode(buf);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: Convert an ArrayBuffer to a Uint8Array
|
||||
sidebarTitle: "ArrayBuffer to Uint8Array"
|
||||
mode: center
|
||||
---
|
||||
|
||||
A `Uint8Array` is a _typed array_, a view over the data in an underlying `ArrayBuffer`.
|
||||
|
||||
```ts
|
||||
const buffer = new ArrayBuffer(64);
|
||||
const arr = new Uint8Array(buffer);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Create instances of other typed arrays the same way.
|
||||
|
||||
```ts
|
||||
const buffer = new ArrayBuffer(64);
|
||||
|
||||
const arr1 = new Uint8Array(buffer);
|
||||
const arr2 = new Uint16Array(buffer);
|
||||
const arr3 = new Uint32Array(buffer);
|
||||
const arr4 = new Float32Array(buffer);
|
||||
const arr5 = new Float64Array(buffer);
|
||||
const arr6 = new BigInt64Array(buffer);
|
||||
const arr7 = new BigUint64Array(buffer);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To create a typed array that only views a portion of the underlying buffer, pass the offset and length to the constructor.
|
||||
|
||||
```ts
|
||||
const buffer = new ArrayBuffer(64);
|
||||
const arr = new Uint8Array(buffer, 0, 16); // view first 16 bytes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Convert a Blob to an ArrayBuffer
|
||||
sidebarTitle: "Blob to ArrayBuffer"
|
||||
mode: center
|
||||
---
|
||||
|
||||
The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides several methods for consuming its contents in different formats, including `.arrayBuffer()`.
|
||||
|
||||
```ts
|
||||
const blob = new Blob(["hello world"]);
|
||||
const buf = await blob.arrayBuffer();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Convert a Blob to a DataView
|
||||
sidebarTitle: "Blob to DataView"
|
||||
mode: center
|
||||
---
|
||||
|
||||
The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides several methods for consuming its contents in different formats. Read the contents into an `ArrayBuffer` with `.arrayBuffer()`, then create a `DataView` from the buffer.
|
||||
|
||||
```ts
|
||||
const blob = new Blob(["hello world"]);
|
||||
const arr = new DataView(await blob.arrayBuffer());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Convert a Blob to a ReadableStream
|
||||
sidebarTitle: "Blob to ReadableStream"
|
||||
mode: center
|
||||
---
|
||||
|
||||
The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides several methods for consuming its contents in different formats, including `.stream()`, which returns a `ReadableStream`.
|
||||
|
||||
```ts
|
||||
const blob = new Blob(["hello world"]);
|
||||
const stream = blob.stream();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Convert a Blob to a string
|
||||
sidebarTitle: "Blob to string"
|
||||
mode: center
|
||||
---
|
||||
|
||||
The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides several methods for consuming its contents in different formats, including `.text()`.
|
||||
|
||||
```ts
|
||||
const blob = new Blob(["hello world"]);
|
||||
const str = await blob.text();
|
||||
// => "hello world"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Convert a Blob to a Uint8Array
|
||||
sidebarTitle: "Blob to Uint8Array"
|
||||
mode: center
|
||||
---
|
||||
|
||||
The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides several methods for consuming its contents in different formats. Use `.bytes()` to read the contents as a `Uint8Array`.
|
||||
|
||||
```ts
|
||||
const blob = new Blob(["hello world"]);
|
||||
const arr = await blob.bytes();
|
||||
```
|
||||
|
||||
Alternatively, read the contents into an `ArrayBuffer` with `.arrayBuffer()`, then create a `Uint8Array` from the buffer.
|
||||
|
||||
```ts
|
||||
const blob = new Blob(["hello world"]);
|
||||
const arr = new Uint8Array(await blob.arrayBuffer());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Convert a Buffer to an ArrayBuffer
|
||||
sidebarTitle: "Buffer to ArrayBuffer"
|
||||
mode: center
|
||||
---
|
||||
|
||||
The Node.js [`Buffer`](https://nodejs.org/api/buffer.html) class views and manipulates data in an underlying `ArrayBuffer`. The `buffer` property returns that `ArrayBuffer`.
|
||||
|
||||
```ts
|
||||
const nodeBuf = Buffer.alloc(64);
|
||||
const arrBuf = nodeBuf.buffer;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Convert a Buffer to a blob
|
||||
sidebarTitle: "Buffer to Blob"
|
||||
mode: center
|
||||
---
|
||||
|
||||
You can construct a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) from an array of "chunks", where each chunk is a string, binary data structure (including `Buffer`), or another `Blob`.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello");
|
||||
const blob = new Blob([buf]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Convert a Buffer to a ReadableStream
|
||||
sidebarTitle: "Buffer to ReadableStream"
|
||||
mode: center
|
||||
---
|
||||
|
||||
The naive approach to creating a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) from a [`Buffer`](https://nodejs.org/api/buffer.html) is to use the `ReadableStream` constructor and enqueue the entire array as a single chunk. For a large buffer, this approach may be undesirable because it doesn't stream the data in smaller chunks.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello world");
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(buf);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To stream the data in smaller chunks, first create a `Blob` instance from the `Buffer`, then use [`Blob.stream()`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/stream) to create a `ReadableStream`.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello world");
|
||||
const blob = new Blob([buf]);
|
||||
const stream = blob.stream();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Pass a number to `.stream()` to set the chunk size.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello world");
|
||||
const blob = new Blob([buf]);
|
||||
|
||||
// set chunk size of 1024 bytes
|
||||
const stream = blob.stream(1024);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: Convert a Buffer to a string
|
||||
sidebarTitle: "Buffer to string"
|
||||
mode: center
|
||||
---
|
||||
|
||||
The [`Buffer`](https://nodejs.org/api/buffer.html) class provides a `.toString()` method that converts a `Buffer` to a string.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello");
|
||||
const str = buf.toString();
|
||||
// => "hello"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
You can optionally specify an encoding and byte range.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello world!");
|
||||
const str = buf.toString("utf8", 0, 5);
|
||||
// => "hello"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Convert a Buffer to a Uint8Array
|
||||
sidebarTitle: "Buffer to Uint8Array"
|
||||
mode: center
|
||||
---
|
||||
|
||||
The Node.js [`Buffer`](https://nodejs.org/api/buffer.html) class extends [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), so no conversion is needed. All properties and methods on `Uint8Array` are available on `Buffer`.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.alloc(64);
|
||||
buf instanceof Uint8Array; // => true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Convert a DataView to a string
|
||||
sidebarTitle: "DataView to string"
|
||||
mode: center
|
||||
---
|
||||
|
||||
If a [`DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) contains ASCII-encoded text, use the [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) class to convert it to a string.
|
||||
|
||||
```ts
|
||||
const dv: DataView = ...;
|
||||
const decoder = new TextDecoder();
|
||||
const str = decoder.decode(dv);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: Convert a Uint8Array to an ArrayBuffer
|
||||
sidebarTitle: "Uint8Array to ArrayBuffer"
|
||||
mode: center
|
||||
---
|
||||
|
||||
A [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) is a _typed array_, a view over data in an underlying `ArrayBuffer`. The `buffer` property returns that `ArrayBuffer`.
|
||||
|
||||
```ts
|
||||
const arr = new Uint8Array(64);
|
||||
arr.buffer; // => ArrayBuffer(64)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The `Uint8Array` may be a view over a _subset_ of the data in the underlying `ArrayBuffer`. In this case, the `buffer` property returns the entire buffer, and the `byteOffset` and `byteLength` properties indicate the subset.
|
||||
|
||||
```ts
|
||||
const arr = new Uint8Array(new ArrayBuffer(64), 16, 32);
|
||||
arr.buffer; // => ArrayBuffer(64)
|
||||
arr.byteOffset; // => 16
|
||||
arr.byteLength; // => 32
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Convert a Uint8Array to a Blob
|
||||
sidebarTitle: "Uint8Array to Blob"
|
||||
mode: center
|
||||
---
|
||||
|
||||
You can construct a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) from an array of "chunks", where each chunk is a string, binary data structure (including `Uint8Array`), or another `Blob`.
|
||||
|
||||
```ts
|
||||
const arr = new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f]);
|
||||
const blob = new Blob([arr]);
|
||||
console.log(await blob.text());
|
||||
// => "hello"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Convert a Uint8Array to a Buffer
|
||||
sidebarTitle: "Uint8Array to Buffer"
|
||||
mode: center
|
||||
---
|
||||
|
||||
The [`Buffer`](https://nodejs.org/api/buffer.html) class extends [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) with additional methods. Use `Buffer.from()` to create a `Buffer` instance from a `Uint8Array`.
|
||||
|
||||
```ts
|
||||
const arr: Uint8Array = ...
|
||||
const buf = Buffer.from(arr);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Convert a Uint8Array to a DataView
|
||||
sidebarTitle: "Uint8Array to DataView"
|
||||
mode: center
|
||||
---
|
||||
|
||||
A [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) is a _typed array_, a view over data in an underlying `ArrayBuffer`. To convert it to a `DataView`, create one over the same range of data.
|
||||
|
||||
```ts
|
||||
const arr: Uint8Array = ...
|
||||
const dv = new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Convert a Uint8Array to a ReadableStream
|
||||
sidebarTitle: "Uint8Array to ReadableStream"
|
||||
mode: center
|
||||
---
|
||||
|
||||
The naive approach to creating a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) from a [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) is to use the [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) constructor and enqueue the entire array as a single chunk. For a large array, this approach may be undesirable because it doesn't stream the data in smaller chunks.
|
||||
|
||||
```ts
|
||||
const arr = new Uint8Array(64);
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(arr);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To stream the data in smaller chunks, first create a `Blob` instance from the `Uint8Array`, then use [`Blob.stream()`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/stream) to create a `ReadableStream`.
|
||||
|
||||
```ts
|
||||
const arr = new Uint8Array(64);
|
||||
const blob = new Blob([arr]);
|
||||
const stream = blob.stream();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Pass a number to `.stream()` to set the chunk size.
|
||||
|
||||
```ts
|
||||
const arr = new Uint8Array(64);
|
||||
const blob = new Blob([arr]);
|
||||
|
||||
// set chunk size of 1024 bytes
|
||||
const stream = blob.stream(1024);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Convert a Uint8Array to a string
|
||||
sidebarTitle: "Uint8Array to string"
|
||||
mode: center
|
||||
---
|
||||
|
||||
Bun implements the Web-standard [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) class for converting binary data types like `Uint8Array` to strings.
|
||||
|
||||
```ts
|
||||
const arr = new Uint8Array([104, 101, 108, 108, 111]);
|
||||
const decoder = new TextDecoder();
|
||||
const str = decoder.decode(arr);
|
||||
// => "hello"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Binary Data](/runtime/binary-data#conversion).
|
||||
@@ -0,0 +1,204 @@
|
||||
---
|
||||
title: Deploy a Bun application on AWS Lambda
|
||||
sidebarTitle: Deploy on AWS Lambda
|
||||
mode: center
|
||||
---
|
||||
|
||||
[AWS Lambda](https://aws.amazon.com/lambda/) is a serverless compute service that lets you run code without provisioning or managing servers.
|
||||
|
||||
This guide deploys a Bun HTTP server to AWS Lambda using a `Dockerfile`.
|
||||
|
||||
<Note>
|
||||
Before continuing, make sure you have:
|
||||
|
||||
- A Bun application ready for deployment
|
||||
- An [AWS account](https://aws.amazon.com/)
|
||||
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) installed and configured
|
||||
- [Docker](https://docs.docker.com/get-started/get-docker/) installed and added to your `PATH`
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a new Dockerfile">
|
||||
Create a new `Dockerfile` in the root of your project. This file contains the instructions to initialize the container, copy your local project files into it, install dependencies, and start the application.
|
||||
|
||||
```docker Dockerfile icon="docker"
|
||||
# Use the official AWS Lambda adapter image to handle the Lambda runtime
|
||||
FROM public.ecr.aws/awsguru/aws-lambda-adapter:1.0.1 AS aws-lambda-adapter
|
||||
|
||||
# Use the official Bun image to run the application
|
||||
FROM oven/bun:debian AS bun_latest
|
||||
|
||||
# Copy the Lambda adapter into the container
|
||||
COPY --from=aws-lambda-adapter /lambda-adapter /opt/extensions/lambda-adapter
|
||||
|
||||
# Set the port to 8080. This is required for the AWS Lambda adapter.
|
||||
ENV PORT=8080
|
||||
|
||||
# Set the work directory to `/var/task`. This is the default work directory for Lambda.
|
||||
WORKDIR "/var/task"
|
||||
|
||||
# Copy the package.json and bun.lock into the container
|
||||
COPY package.json bun.lock ./
|
||||
|
||||
# Install the dependencies
|
||||
RUN bun install --production --frozen-lockfile
|
||||
|
||||
# Copy the rest of the application into the container
|
||||
COPY . /var/task
|
||||
|
||||
# Run the application.
|
||||
CMD ["bun", "index.ts"]
|
||||
```
|
||||
|
||||
<Note>
|
||||
Make sure that the start command corresponds to your application's entry point. The start command can also be `CMD ["bun", "run", "start"]` if you have a start script in your `package.json`.
|
||||
|
||||
If your app doesn't have dependencies, you can omit the `COPY package.json bun.lock ./` and `RUN bun install --production --frozen-lockfile` lines. Bun doesn't write a `bun.lock` for a project with no dependencies.
|
||||
</Note>
|
||||
|
||||
Create a new `.dockerignore` file in the root of your project. It lists the files and directories to _exclude_ from the container image, such as `node_modules`. Excluding them keeps builds faster and smaller:
|
||||
|
||||
```docker .dockerignore icon="Docker"
|
||||
node_modules
|
||||
Dockerfile*
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
LICENSE
|
||||
.vscode
|
||||
.env
|
||||
# Any other files or directories you want to exclude
|
||||
```
|
||||
</Step>
|
||||
<Step title="Build the Docker image">
|
||||
Make sure you're in the directory containing your `Dockerfile`, then build the Docker image. This example names the image `bun-lambda-demo` and tags it as `latest`.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
# cd /path/to/your/app
|
||||
docker build --provenance=false --platform linux/amd64 -t bun-lambda-demo:latest .
|
||||
```
|
||||
</Step>
|
||||
<Step title="Create an ECR repository">
|
||||
Before pushing the image, create an [ECR repository](https://aws.amazon.com/ecr/) to push it to.
|
||||
|
||||
The following command:
|
||||
- Creates an ECR repository named `bun-lambda-demo` in the `us-east-1` region
|
||||
- Exports the repository URI as an environment variable. The export is optional, but it makes the next steps easier.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
export ECR_URI=$(aws ecr create-repository --repository-name bun-lambda-demo --region us-east-1 --query 'repository.repositoryUri' --output text)
|
||||
echo $ECR_URI
|
||||
```
|
||||
```txt
|
||||
[id].dkr.ecr.us-east-1.amazonaws.com/bun-lambda-demo
|
||||
```
|
||||
|
||||
<Note>
|
||||
If you're using IAM Identity Center (SSO) or have configured AWS CLI with profiles, add the `--profile` flag to your AWS CLI commands.
|
||||
|
||||
For example, if your profile is named `my-sso-app`, use `--profile my-sso-app`. Run `aws configure list-profiles` to see your available profiles.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
export ECR_URI=$(aws ecr create-repository --repository-name bun-lambda-demo --region us-east-1 --profile my-sso-app --query 'repository.repositoryUri' --output text)
|
||||
echo $ECR_URI
|
||||
```
|
||||
</Note>
|
||||
|
||||
</Step>
|
||||
<Step title="Authenticate with the ECR repository">
|
||||
Log in to the ECR repository:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $ECR_URI
|
||||
```
|
||||
```txt
|
||||
Login Succeeded
|
||||
```
|
||||
|
||||
<Note>
|
||||
If using a profile, use the `--profile` flag:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
aws ecr get-login-password --region us-east-1 --profile my-sso-app | docker login --username AWS --password-stdin $ECR_URI
|
||||
```
|
||||
|
||||
</Note>
|
||||
|
||||
</Step>
|
||||
<Step title="Tag and push the docker image to the ECR repository">
|
||||
Make sure you're in the directory containing your `Dockerfile`, then tag the Docker image with the ECR repository URI.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
docker tag bun-lambda-demo:latest ${ECR_URI}:latest
|
||||
```
|
||||
|
||||
Then, push the image to the ECR repository.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
docker push ${ECR_URI}:latest
|
||||
```
|
||||
</Step>
|
||||
<Step title="Create an AWS Lambda function">
|
||||
Go to **AWS Console** > **Lambda** > [**Create Function**](https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/create/function?intent=authorFromImage) > Select **Container image**
|
||||
|
||||
<Warning>Make sure you've selected the right region. This URL defaults to `us-east-1`.</Warning>
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
Give the function a name, like `my-bun-function`.
|
||||
</Step>
|
||||
<Step title="Select the container image">
|
||||
Go to the **Container image URI** section and click **Browse images**. Select the image you pushed to the ECR repository.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
Then, select the `latest` image and click **Select image**.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
</Step>
|
||||
<Step title="Configure the function">
|
||||
To get a public URL for the function, go to **Additional configurations** > **Networking** > **Function URL**.
|
||||
|
||||
Set this to **Enable**, with Auth Type **NONE**.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
</Step>
|
||||
<Step title="Create the function">
|
||||
Click **Create function** at the bottom of the page.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
</Step>
|
||||
<Step title="Get the function URL">
|
||||
Once the function is created, you're redirected to its page. The function URL is in the **"Function URL"** section.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
</Step>
|
||||
<Step title="Test the function">
|
||||
Your app is now live. To test the function, either go to the **Test** tab or call the function URL directly.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
curl -X GET https://[your-function-id].lambda-url.us-east-1.on.aws/
|
||||
```
|
||||
```txt
|
||||
Hello from Bun on Lambda!
|
||||
```
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
title: Deploy a Bun application on DigitalOcean
|
||||
sidebarTitle: Deploy on DigitalOcean
|
||||
mode: center
|
||||
---
|
||||
|
||||
[DigitalOcean](https://www.digitalocean.com/) is a cloud platform that provides a range of services for building and deploying applications.
|
||||
|
||||
This guide deploys a Bun HTTP server to DigitalOcean using a `Dockerfile`.
|
||||
|
||||
<Note>
|
||||
Before continuing, make sure you have:
|
||||
|
||||
- A Bun application ready for deployment
|
||||
- A [DigitalOcean account](https://www.digitalocean.com/)
|
||||
- [DigitalOcean CLI](https://docs.digitalocean.com/reference/doctl/how-to/install/#step-1-install-doctl) installed and configured
|
||||
- [Docker](https://docs.docker.com/get-started/get-docker/) installed and added to your `PATH`
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a new DigitalOcean Container Registry">
|
||||
Create a new Container Registry to store the Docker image.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Through the DigitalOcean dashboard">
|
||||
In the DigitalOcean dashboard, go to [**Container Registry**](https://cloud.digitalocean.com/registry), and enter the details for the new registry.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
Make sure the details are correct, then click **Create Registry**.
|
||||
</Tab>
|
||||
<Tab title="Through the DigitalOcean CLI">
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
doctl registry create bun-digitalocean-demo
|
||||
```
|
||||
```txt
|
||||
Name Endpoint Region slug
|
||||
bun-digitalocean-demo registry.digitalocean.com/bun-digitalocean-demo sfo2
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
You should see the new registry in the [**DigitalOcean registry dashboard**](https://cloud.digitalocean.com/registry):
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
</Step>
|
||||
<Step title="Create a new Dockerfile">
|
||||
Create a new `Dockerfile` in the root of your project. This file contains the instructions to initialize the container, copy your local project files into it, install dependencies, and start the application.
|
||||
|
||||
```docker Dockerfile icon="docker"
|
||||
# Use the official Bun image to run the application
|
||||
FROM oven/bun:debian
|
||||
|
||||
# Set the work directory to `/app`
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the package.json and bun.lock into the container
|
||||
COPY package.json bun.lock ./
|
||||
|
||||
# Install the dependencies
|
||||
RUN bun install --production --frozen-lockfile
|
||||
|
||||
# Copy the rest of the application into the container
|
||||
COPY . .
|
||||
|
||||
# Expose the port (DigitalOcean will set PORT env var)
|
||||
EXPOSE 8080
|
||||
|
||||
# Run the application
|
||||
CMD ["bun", "index.ts"]
|
||||
```
|
||||
|
||||
<Note>
|
||||
Make sure that the start command corresponds to your application's entry point. The start command can also be `CMD ["bun", "run", "start"]` if you have a start script in your `package.json`.
|
||||
|
||||
If your app doesn't have dependencies, you can omit the `COPY package.json bun.lock ./` and `RUN bun install --production --frozen-lockfile` lines. Bun doesn't write a `bun.lock` for a project with no dependencies.
|
||||
</Note>
|
||||
|
||||
Create a new `.dockerignore` file in the root of your project. It lists the files and directories to _exclude_ from the container image, such as `node_modules`. Excluding them keeps builds faster and smaller:
|
||||
|
||||
```docker .dockerignore icon="Docker"
|
||||
node_modules
|
||||
Dockerfile*
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
LICENSE
|
||||
.vscode
|
||||
.env
|
||||
# Any other files or directories you want to exclude
|
||||
```
|
||||
</Step>
|
||||
<Step title="Authenticate Docker with DigitalOcean registry">
|
||||
Before building and pushing the Docker image, authenticate Docker with the DigitalOcean Container Registry:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
doctl registry login
|
||||
```
|
||||
```txt
|
||||
Logging Docker in to registry.digitalocean.com
|
||||
Notice: Login valid for 30 days. Use the --expiry-seconds flag to set a shorter expiration or --never-expire for no expiration.
|
||||
```
|
||||
|
||||
<Note>
|
||||
This command authenticates Docker with DigitalOcean's registry using your DigitalOcean credentials. Without this step, the build and push command fails with a 401 authentication error.
|
||||
</Note>
|
||||
</Step>
|
||||
<Step title="Build and push the Docker image to the DigitalOcean registry">
|
||||
Make sure you're in the directory containing your `Dockerfile`, then build and push the Docker image to the DigitalOcean registry in one command:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
docker buildx build --platform=linux/amd64 -t registry.digitalocean.com/bun-digitalocean-demo/bun-digitalocean-demo:latest --push .
|
||||
```
|
||||
|
||||
<Note>
|
||||
If you're building on an ARM Mac (M1/M2), you must use `docker buildx` with `--platform=linux/amd64` for compatibility with DigitalOcean's infrastructure. Using `docker build` without the platform flag creates an ARM64 image that won't run on DigitalOcean.
|
||||
</Note>
|
||||
|
||||
Once the image is pushed, you should see it in the [**DigitalOcean registry dashboard**](https://cloud.digitalocean.com/registry):
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
</Step>
|
||||
<Step title="Create a new DigitalOcean App Platform project">
|
||||
In the DigitalOcean dashboard, go to [**App Platform**](https://cloud.digitalocean.com/apps) > **Create App**. You can create a project directly from the container image.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
Make sure the details are correct, then click **Next**.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
Review and configure resource settings, then click **Create app**.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
</Step>
|
||||
<Step title="Visit your live application">
|
||||
Your app is now live. Once the app is created, you should see it in the App Platform dashboard with its public URL.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
@@ -0,0 +1,190 @@
|
||||
---
|
||||
title: Deploy a Bun application on Google Cloud Run
|
||||
sidebarTitle: Deploy on Google Cloud Run
|
||||
mode: center
|
||||
---
|
||||
|
||||
[Google Cloud Run](https://cloud.google.com/run) is a managed platform for deploying and scaling serverless applications. Google handles the infrastructure for you.
|
||||
|
||||
This guide deploys a Bun HTTP server to Google Cloud Run using a `Dockerfile`.
|
||||
|
||||
<Note>
|
||||
Before continuing, make sure you have:
|
||||
|
||||
- A Bun application ready for deployment
|
||||
- A [Google Cloud account](https://cloud.google.com/) with billing enabled
|
||||
- [Google Cloud CLI](https://cloud.google.com/sdk/docs/install) installed and configured
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
<Steps>
|
||||
<Step title={<span>Initialize <code>gcloud</code> by selecting or creating a project</span>}>
|
||||
|
||||
Make sure that you've initialized the Google Cloud CLI. `gcloud init` logs you in and prompts you to either select an existing project or create a new one.
|
||||
|
||||
For more help with the Google Cloud CLI, see the [official documentation](https://docs.cloud.google.com/sdk/gcloud/reference/init).
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
gcloud init
|
||||
```
|
||||
|
||||
```txt
|
||||
Welcome! This command will take you through the configuration of gcloud.
|
||||
|
||||
You must sign in to continue. Would you like to sign in (Y/n)? Y
|
||||
You are signed in as [[email protected]].
|
||||
|
||||
Pick cloud project to use:
|
||||
[1] existing-bun-app-1234
|
||||
[2] Enter a project ID
|
||||
[3] Create a new project
|
||||
Please enter numeric choice or text value (must exactly match list item): 3
|
||||
|
||||
Enter a Project ID. my-bun-app
|
||||
Your current project has been set to: [my-bun-app]
|
||||
|
||||
The Google Cloud CLI is configured and ready to use!
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="(Optional) Store your project info in environment variables">
|
||||
Set variables for your project ID and number so you can reuse them in the following steps.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
PROJECT_ID=$(gcloud projects list --format='value(projectId)' --filter='projectId=my-bun-app')
|
||||
PROJECT_NUMBER=$(gcloud projects list --format='value(projectNumber)' --filter='projectId=my-bun-app')
|
||||
|
||||
echo $PROJECT_ID $PROJECT_NUMBER
|
||||
```
|
||||
|
||||
```txt
|
||||
my-bun-app [PROJECT_NUMBER]
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Link a billing account">
|
||||
List your available billing accounts and link one to your project:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
gcloud billing accounts list
|
||||
```
|
||||
|
||||
```txt
|
||||
ACCOUNT_ID NAME OPEN MASTER_ACCOUNT_ID
|
||||
[BILLING_ACCOUNT_ID] My Billing Account True
|
||||
```
|
||||
|
||||
Link your billing account to your project. Replace `[BILLING_ACCOUNT_ID]` with the ID of your billing account.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
gcloud billing projects link $PROJECT_ID --billing-account=[BILLING_ACCOUNT_ID]
|
||||
```
|
||||
|
||||
```txt
|
||||
billingAccountName: billingAccounts/[BILLING_ACCOUNT_ID]
|
||||
billingEnabled: true
|
||||
name: projects/my-bun-app/billingInfo
|
||||
projectId: my-bun-app
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Enable APIs and configure IAM roles">
|
||||
Activate the necessary services and grant Cloud Build permissions:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
gcloud services enable run.googleapis.com cloudbuild.googleapis.com
|
||||
gcloud projects add-iam-policy-binding $PROJECT_ID \
|
||||
--member=serviceAccount:[email protected] \
|
||||
--role=roles/run.builder
|
||||
```
|
||||
|
||||
<Note>
|
||||
These commands enable Cloud Run (`run.googleapis.com`) and Cloud Build (`cloudbuild.googleapis.com`). Deploying from source requires both. Cloud Run runs your containerized app, while Cloud Build builds and packages it.
|
||||
|
||||
The IAM binding grants the Compute Engine service account (`[email protected]`) permission to build and deploy images on your behalf.
|
||||
|
||||
</Note>
|
||||
|
||||
</Step>
|
||||
<Step title="Add a Dockerfile">
|
||||
Create a new `Dockerfile` in the root of your project. This file contains the instructions to initialize the container, copy your local project files into it, install dependencies, and start the application.
|
||||
|
||||
```docker Dockerfile icon="docker"
|
||||
# Use the official Bun image to run the application
|
||||
FROM oven/bun:latest
|
||||
|
||||
# Copy the package.json and bun.lock into the container
|
||||
COPY package.json bun.lock ./
|
||||
|
||||
# Install the dependencies
|
||||
RUN bun install --production --frozen-lockfile
|
||||
|
||||
# Copy the rest of the application into the container
|
||||
COPY . .
|
||||
|
||||
# Run the application
|
||||
CMD ["bun", "index.ts"]
|
||||
```
|
||||
|
||||
<Note>
|
||||
Make sure that the start command corresponds to your application's entry point. The start command can also be `CMD ["bun", "run", "start"]` if you have a start script in your `package.json`.
|
||||
|
||||
If your app doesn't have dependencies, you can omit the `COPY package.json bun.lock ./` and `RUN bun install --production --frozen-lockfile` lines. Bun doesn't write a `bun.lock` for a project with no dependencies.
|
||||
|
||||
</Note>
|
||||
|
||||
Create a new `.dockerignore` file in the root of your project. It lists the files and directories to _exclude_ from the container image, such as `node_modules`. Excluding them keeps builds faster and smaller:
|
||||
|
||||
```docker .dockerignore icon="Docker"
|
||||
node_modules
|
||||
Dockerfile*
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
LICENSE
|
||||
.vscode
|
||||
.env
|
||||
# Any other files or directories you want to exclude
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Deploy your service">
|
||||
Make sure you're in the directory containing your `Dockerfile`, then deploy directly from your local source:
|
||||
|
||||
<Note>Update the `--region` flag to your preferred region, or omit it to select a region interactively.</Note>
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
gcloud run deploy my-bun-app --source . --region=us-west1 --allow-unauthenticated
|
||||
```
|
||||
|
||||
```txt
|
||||
Deploying from source requires an Artifact Registry Docker repository to store built containers. A repository named
|
||||
[cloud-run-source-deploy] in region [us-west1] will be created.
|
||||
|
||||
Do you want to continue (Y/n)? Y
|
||||
|
||||
Building using Dockerfile and deploying container to Cloud Run service [my-bun-app] in project [my-bun-app] region [us-west1]
|
||||
✓ Building and deploying... Done.
|
||||
✓ Validating configuration...
|
||||
✓ Uploading sources...
|
||||
✓ Building Container... Logs are available at [https://console.cloud.google.com/cloud-build/builds...].
|
||||
✓ Creating Revision...
|
||||
✓ Routing traffic...
|
||||
✓ Setting IAM Policy...
|
||||
Done.
|
||||
Service [my-bun-app] revision [my-bun-app-...] has been deployed and is serving 100 percent of traffic.
|
||||
Service URL: https://my-bun-app-....us-west1.run.app
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Visit your live application">
|
||||
|
||||
Your Bun application is now live.
|
||||
|
||||
Visit the Service URL (`https://my-bun-app-....us-west1.run.app`) to confirm everything works as expected.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
@@ -0,0 +1,145 @@
|
||||
---
|
||||
title: Deploy a Bun application on Railway
|
||||
description: Deploy a Bun application to Railway from the CLI or dashboard, with optional PostgreSQL setup and automatic SSL
|
||||
sidebarTitle: Deploy on Railway
|
||||
mode: center
|
||||
---
|
||||
|
||||
Railway is an infrastructure platform: you provision infrastructure, develop against it locally, then deploy to the cloud. Railway deploys from GitHub with zero configuration, handles SSL automatically, and provisions databases.
|
||||
|
||||
This guide deploys a Bun application with an optional PostgreSQL database, the same setup the following template provides.
|
||||
|
||||
You can either follow this guide step-by-step or deploy the pre-configured template with one click:
|
||||
|
||||
<a
|
||||
href="https://railway.com/deploy/bun-react-postgres?referralCode=Bun&utm_medium=integration&utm_source=template&utm_campaign=bun"
|
||||
target="_blank"
|
||||
>
|
||||
<img src="https://railway.com/button.svg" alt="Deploy on Railway" />
|
||||
</a>
|
||||
|
||||
---
|
||||
|
||||
**Prerequisites**:
|
||||
|
||||
- A Bun application ready for deployment
|
||||
- A [Railway account](https://railway.com/)
|
||||
- Railway CLI (for CLI deployment method)
|
||||
- A GitHub account (for Dashboard deployment method)
|
||||
|
||||
---
|
||||
|
||||
## Method 1: Deploy via CLI
|
||||
|
||||
<Steps>
|
||||
<Step title="Step 1">
|
||||
Install the Railway CLI.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun install -g @railway/cli
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Step 2">
|
||||
Log into your Railway account.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
railway login
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Step 3">
|
||||
After authenticating, initialize a new project.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
railway init
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Step 4">
|
||||
After initializing the project, add a new database and service.
|
||||
|
||||
<Note>Step 4 is only necessary if your application uses a database. If you don't need PostgreSQL, skip to Step 5.</Note>
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
# Add PostgreSQL database. Make sure to add this first!
|
||||
railway add --database postgres
|
||||
|
||||
# Add your application service.
|
||||
railway add --service bun-react-db --variables DATABASE_URL=\${{Postgres.DATABASE_URL}}
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Step 5">
|
||||
After creating and connecting the services, deploy the application to Railway. By default, services are only accessible within Railway's private network, so generate a public domain to make your app publicly accessible.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
# Deploy your application
|
||||
railway up
|
||||
|
||||
# Generate public domain
|
||||
railway domain
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Your app is now live. `railway up` deploys your local directory rather than a GitHub repository. To have Railway auto-deploy on every GitHub push, connect the service to your repository with `railway service source connect --repo <owner>/<repo> --branch <branch>`.
|
||||
|
||||
---
|
||||
|
||||
## Method 2: Deploy via Dashboard
|
||||
|
||||
<Steps>
|
||||
<Step title="Step 1">
|
||||
Create a new project
|
||||
|
||||
1. Go to [Railway Dashboard](https://railway.com/dashboard?utm_medium=integration&utm_source=docs&utm_campaign=bun)
|
||||
2. Click **"+ New"** → **"GitHub repo"**
|
||||
3. Choose your repository
|
||||
|
||||
</Step>
|
||||
<Step title="Step 2">
|
||||
Add a PostgreSQL database, and connect this database to the service
|
||||
|
||||
<Note>Step 2 is only necessary if your application uses a database. If you don't need PostgreSQL, skip to Step 3.</Note>
|
||||
|
||||
1. Click **"+ New"** → **"Database"** → **"Add PostgreSQL"**
|
||||
2. After Railway creates the database, select your service (not the database)
|
||||
3. Go to **"Variables"** tab
|
||||
4. Click **"+ New Variable"** → **"Add Reference"**
|
||||
5. Select `DATABASE_URL` from postgres
|
||||
|
||||
</Step>
|
||||
<Step title="Step 3">
|
||||
Generate a public domain
|
||||
|
||||
1. Select your service
|
||||
2. Go to **"Settings"** tab
|
||||
3. Under **"Networking"**, click **"Generate Domain"**
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Your app is now live. Railway auto-deploys on every GitHub push.
|
||||
|
||||
---
|
||||
|
||||
## Configuration (Optional)
|
||||
|
||||
By default, Railway uses [Railpack](https://docs.railway.com/builds/railpack) to automatically detect and build your Bun application with zero configuration.
|
||||
|
||||
Railpack detects Bun from your `bun.lock` and installs the latest version of Bun unless you pin one with the `engines.bun` or `packageManager` field in `package.json`. Railway's previous builder, [Nixpacks](https://github.com/railwayapp/nixpacks), is in maintenance mode.
|
||||
|
||||
If your service still builds with Nixpacks, switch it to Railpack by adding the following to your `railway.json`:
|
||||
|
||||
```json railway.json icon="file-json"
|
||||
{
|
||||
"$schema": "https://railway.com/railway.schema.json",
|
||||
"build": {
|
||||
"builder": "RAILPACK"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For more build configuration settings, see the [Railway documentation](https://docs.railway.com/builds/build-configuration).
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: Deploy a Bun application on Render
|
||||
sidebarTitle: Deploy on Render
|
||||
mode: center
|
||||
---
|
||||
|
||||
[Render](https://render.com/) is a cloud platform for building, deploying, and scaling apps.
|
||||
|
||||
It provides auto deploys from GitHub, a global CDN, private networks, automatic HTTPS setup, and managed PostgreSQL and Redis-compatible Key Value stores.
|
||||
|
||||
Render supports Bun natively. You can deploy Bun apps as web services, background workers, cron jobs, and more.
|
||||
|
||||
---
|
||||
|
||||
As an example, this guide deploys an Express HTTP server to Render.
|
||||
|
||||
<Steps>
|
||||
<Step title="Step 1">
|
||||
Create a new GitHub repo named `myapp`. Git clone it locally.
|
||||
|
||||
```sh
|
||||
git clone [email protected]:my-github-username/myapp.git
|
||||
cd myapp
|
||||
```
|
||||
</Step>
|
||||
<Step title="Step 2">
|
||||
Add the Express library.
|
||||
|
||||
```sh
|
||||
bun add express
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Step 3">
|
||||
Define a server with Express:
|
||||
|
||||
```ts app.ts icon="/icons/typescript.svg"
|
||||
import express from "express";
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3001;
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.send("Hello World!");
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Listening on port ${port}...`);
|
||||
});
|
||||
```
|
||||
</Step>
|
||||
<Step title="Step 4">
|
||||
|
||||
Commit your changes and push to GitHub.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
git add app.ts bun.lock package.json
|
||||
git commit -m "Create simple Express app"
|
||||
git push origin main
|
||||
```
|
||||
</Step>
|
||||
<Step title="Step 5">
|
||||
In your [Render Dashboard](https://dashboard.render.com/), click `New` > `Web Service` and connect your `myapp` repo.
|
||||
|
||||
</Step>
|
||||
<Step title="Step 6">
|
||||
In the Render UI, provide the following values during web service creation:
|
||||
|
||||
| | |
|
||||
| ----------------- | ------------- |
|
||||
| **Runtime** | `Node` |
|
||||
| **Build Command** | `bun install` |
|
||||
| **Start Command** | `bun app.ts` |
|
||||
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
Once the build finishes, your web service is live at its assigned `onrender.com` URL.
|
||||
|
||||
View the [deploy logs](https://docs.render.com/logging#logs-for-an-individual-deploy-or-job) for details. See [Render's documentation](https://docs.render.com/deploys) for more on deploys.
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
title: Deploy a Bun application on Vercel
|
||||
sidebarTitle: Deploy on Vercel
|
||||
mode: center
|
||||
---
|
||||
|
||||
[Vercel](https://vercel.com/) is a cloud platform for building, deploying, and scaling apps. Vercel Functions can run on the Bun runtime, either behind a framework that Vercel supports or as a [`Bun.serve()`](/runtime/http/server) server.
|
||||
|
||||
<Warning>
|
||||
The Bun runtime on Vercel is in Beta. Automatic source maps, bytecode caching, and request metrics for `node:http` and
|
||||
`node:https` are not supported yet (request metrics for `fetch` are). See [feature
|
||||
support](https://vercel.com/docs/functions/runtimes/bun#feature-support) in the Vercel documentation.
|
||||
</Warning>
|
||||
|
||||
---
|
||||
|
||||
<Steps>
|
||||
<Step title="Configure Bun in vercel.json">
|
||||
To run your Functions on Bun, add a [`bunVersion`](https://vercel.com/docs/project-configuration/vercel-json#bunversion) field to your `vercel.json` file:
|
||||
|
||||
```json vercel.json icon="file-json"
|
||||
{
|
||||
"bunVersion": "1.x" // [!code ++]
|
||||
}
|
||||
```
|
||||
|
||||
The value must be `"1.x"`; Vercel manages the minor and patch versions.
|
||||
|
||||
For best results, match your local Bun version with the version Vercel uses.
|
||||
</Step>
|
||||
|
||||
<Step title="Add a server">
|
||||
Choose how requests reach your code.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Bun.serve() for the whole app">
|
||||
Vercel's Bun framework preset sends every request for the deployment to a single `Bun.serve()` server. Vercel uses the preset when the project sets `bunVersion`, has a `bun.lock` file, and has a server entrypoint at one of these paths:
|
||||
|
||||
- `server.{js,cjs,mjs,ts,cts,mts}`
|
||||
- `src/server.{js,cjs,mjs,ts,cts,mts}`
|
||||
|
||||
`bun install` creates `bun.lock` on Bun 1.2 or later. On older versions, run `bun install --save-text-lockfile`. The preset does not detect the binary `bun.lockb` format.
|
||||
|
||||
Call `Bun.serve()` once while the module loads. Vercel detects that call and routes incoming requests to it. Vercel supports the `fetch`, [`routes`](/runtime/http/routing), `error`, and `websocket` options:
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/health": () => Response.json({ status: "ok" }),
|
||||
},
|
||||
fetch() {
|
||||
return new Response("Hello from Bun on Vercel");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
A minimal project is `package.json`, `bun.lock`, `server.ts`, and the `vercel.json` from the previous step. It doesn't need an `api/` directory or any routing configuration.
|
||||
|
||||
<Note>
|
||||
`port` and `hostname` only apply when you run the server locally; they don't configure the deployed endpoint. Unix sockets and [HTML imports](/runtime/http/server#html-imports) in `routes` are not supported on Vercel.
|
||||
|
||||
To serve WebSocket connections, see the [Bun example in Vercel's WebSockets documentation](https://vercel.com/docs/functions/websockets#bun).
|
||||
</Note>
|
||||
</Tab>
|
||||
|
||||
<Tab title="Bun.serve() under /api">
|
||||
To add a Bun server to a project that also has a frontend, create `api/server.ts` and call `Bun.serve()` once while the module loads. Vercel deploys it as a single Function at `/api/server`. Unlike the framework preset, only requests for `/api/server` reach this server.
|
||||
|
||||
```ts api/server.ts icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
fetch(request) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
return Response.json({
|
||||
message: "Hello from Bun on Vercel",
|
||||
pathname: url.pathname,
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This setup only needs the `bunVersion` setting from the previous step; it doesn't use the framework preset or require a `bun.lock` file. To send other paths to this server, add route overrides to `vercel.json`. Each override must use the full request path, including the `/api/server` prefix. See [the Vercel Bun runtime documentation](https://vercel.com/docs/functions/runtimes/bun) for details.
|
||||
</Tab>
|
||||
|
||||
<Tab title="Next.js or another framework">
|
||||
Frameworks that Vercel supports, such as Next.js, Express, Hono, and Nitro, run on Bun once you set `bunVersion`.
|
||||
|
||||
If you're deploying a **Next.js** project (including ISR), also update the `package.json` scripts so the Next.js CLI runs under Bun:
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "bun --bun next dev", // [!code ++]
|
||||
"build": "bun --bun next build" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Note>
|
||||
The `--bun` flag runs the Next.js CLI under Bun. Bundling (with Turbopack or Webpack) is unchanged.
|
||||
</Note>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Step>
|
||||
|
||||
<Step title="Deploy your app">
|
||||
Connect your repository to Vercel, or deploy from the CLI:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
# Using bunx (no global install)
|
||||
bunx vercel login
|
||||
bunx vercel deploy
|
||||
```
|
||||
|
||||
Or install the Vercel CLI globally:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun i -g vercel
|
||||
vercel login
|
||||
vercel deploy
|
||||
```
|
||||
|
||||
[Learn more in the Vercel Deploy CLI documentation →](https://vercel.com/docs/cli/deploy)
|
||||
</Step>
|
||||
|
||||
<Step title="Verify the runtime">
|
||||
To confirm your deployment uses Bun, log the Bun version:
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
console.log("runtime", process.versions.bun);
|
||||
```
|
||||
```txt
|
||||
runtime 1.3.14
|
||||
```
|
||||
|
||||
[See the Vercel Bun Runtime documentation for feature support →](https://vercel.com/docs/functions/runtimes/bun#feature-support)
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
---
|
||||
|
||||
- [Fluid compute](https://vercel.com/docs/fluid-compute): Both Bun and Node.js runtimes run on Fluid compute and support the same core Vercel Functions features.
|
||||
- [Middleware](https://vercel.com/docs/routing-middleware): To run Routing Middleware with Bun, set the runtime to `nodejs`:
|
||||
|
||||
```ts middleware.ts icon="/icons/typescript.svg"
|
||||
export const config = { runtime: "nodejs" }; // [!code ++]
|
||||
```
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: Build an app with Astro and Bun
|
||||
sidebarTitle: "Astro with Bun"
|
||||
mode: center
|
||||
---
|
||||
|
||||
Initialize a fresh Astro app with `bun create astro`. The `create-astro` package detects when you are using `bunx` and installs dependencies with `bun`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun create astro
|
||||
```
|
||||
|
||||
```txt
|
||||
astro Launch sequence initiated.
|
||||
|
||||
╭─────╮ Houston:
|
||||
│ ◠ ◡ ◠ We're glad to have you on board.
|
||||
╰─────╯
|
||||
|
||||
dir Where should we create your new project?
|
||||
./fumbling-field
|
||||
|
||||
tmpl How would you like to start your new project?
|
||||
Use blog template
|
||||
|
||||
deps Install dependencies?
|
||||
Yes
|
||||
|
||||
git Initialize a new git repository?
|
||||
Yes
|
||||
|
||||
✔ Project initialized!
|
||||
■ Template copied
|
||||
■ Dependencies installed
|
||||
■ Git initialized
|
||||
|
||||
next Liftoff confirmed. Explore your project!
|
||||
|
||||
Enter your project directory using cd ./fumbling-field
|
||||
Run `bun run dev` to start the dev server. q + ENTER to stop.
|
||||
Add frameworks like react or tailwind using astro add.
|
||||
|
||||
Stuck? Join us at https://astro.build/chat
|
||||
|
||||
╭─────╮ Houston:
|
||||
│ ◠ ◡ ◠ Good luck out there, astronaut! 🚀
|
||||
╰─────╯
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Start the dev server with `bunx`.
|
||||
|
||||
By default, Bun runs the dev server with Node.js. To use the Bun runtime instead, pass the `--bun` flag.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bunx --bun astro dev
|
||||
```
|
||||
|
||||
```txt
|
||||
astro v7.2.2 ready in 200 ms
|
||||
┃ Local http://localhost:4321/
|
||||
┃ Network use --host to expose
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Open [http://localhost:4321](http://localhost:4321) in your browser to see the result. Astro hot-reloads the app as you edit your source files.
|
||||
|
||||
<Frame>
|
||||
<img src="https://i.imgur.com/Dswiu6w.png" caption="An Astro starter app running on Bun" />
|
||||
</Frame>
|
||||
|
||||
---
|
||||
|
||||
See the [Astro docs](https://docs.astro.build/en/getting-started/).
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
title: Create a Discord bot
|
||||
sidebarTitle: "Discord.js with Bun"
|
||||
mode: center
|
||||
---
|
||||
|
||||
Discord.js runs on Bun with no extra setup. This guide builds a bot that answers a `/ping` slash command: you register the command once, then start the bot and use it in your server. If this is your first bot, copy each block as you reach it.
|
||||
|
||||
---
|
||||
|
||||
Create a folder for your bot and set it up with `bun init`. Pick the defaults when it asks.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
mkdir my-bot
|
||||
cd my-bot
|
||||
bun init
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Add Discord.js to the project.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add discord.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Your bot needs its own account, which you create in Discord's developer portal. Open the [developer portal](https://discord.com/developers/applications), sign in, and create an **Application**. Discord adds a bot user to every new application automatically; it's on the **Bot** tab. The Discord.js guide's [setup walkthrough](https://discordjs.guide/legacy/preparations/app-setup) has screenshots if you get lost.
|
||||
|
||||
Copy two values from the portal:
|
||||
|
||||
- The **token** on the **Bot** tab: click **Reset Token** to generate it (Discord only shows it once). It's the password your code uses to log in, so treat it like one and keep it to yourself.
|
||||
- The **Application ID** on the **General Information** tab. Discord uses it to tie your commands to this app.
|
||||
|
||||
---
|
||||
|
||||
A bot can't do anything in a server until you invite it. In the portal's **OAuth2** section, generate an invite URL with the `bot` and `applications.commands` scopes, open it, and add the bot to a server you manage. Slash commands need the `applications.commands` scope, so include it. A personal server you make for testing is a good place to start, and the Discord.js [guide to adding a bot](https://discordjs.guide/legacy/preparations/adding-your-app) walks through it with screenshots.
|
||||
|
||||
---
|
||||
|
||||
You also need your server's ID to register the command there. In Discord, turn on **Settings > Advanced > Developer Mode**, then right-click your server's icon and choose **Copy Server ID**.
|
||||
|
||||
---
|
||||
|
||||
Save all three values in `.env.local`. Bun reads this file on startup and loads it into `process.env`, so nothing secret lives in your code.
|
||||
|
||||
```ini .env.local icon="settings"
|
||||
DISCORD_TOKEN=your-bot-token
|
||||
DISCORD_CLIENT_ID=your-application-id
|
||||
DISCORD_GUILD_ID=your-server-id
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Add `.env.local` to your `.gitignore` before you commit anything. Anyone who reads the token can control your bot, so the token should never land in version control.
|
||||
|
||||
```txt .gitignore icon="file-code"
|
||||
node_modules
|
||||
.env.local
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Discord has to know about a command before anyone can use it. Register `/ping` with a short script named `deploy-commands.ts`.
|
||||
|
||||
```ts deploy-commands.ts icon="/icons/typescript.svg"
|
||||
import { REST, Routes, SlashCommandBuilder } from "discord.js";
|
||||
|
||||
const { DISCORD_TOKEN, DISCORD_CLIENT_ID, DISCORD_GUILD_ID } = process.env;
|
||||
if (!DISCORD_TOKEN || !DISCORD_CLIENT_ID || !DISCORD_GUILD_ID) {
|
||||
throw new Error("Set DISCORD_TOKEN, DISCORD_CLIENT_ID, and DISCORD_GUILD_ID in .env.local");
|
||||
}
|
||||
|
||||
// the commands you want to register
|
||||
const commands = [new SlashCommandBuilder().setName("ping").setDescription("Replies with Pong!").toJSON()];
|
||||
|
||||
const rest = new REST().setToken(DISCORD_TOKEN);
|
||||
|
||||
// register them in your test server
|
||||
await rest.put(Routes.applicationGuildCommands(DISCORD_CLIENT_ID, DISCORD_GUILD_ID), { body: commands });
|
||||
|
||||
console.log("Registered /ping");
|
||||
```
|
||||
|
||||
Run it once.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run deploy-commands.ts
|
||||
```
|
||||
|
||||
You only run this again when you add a command or change its name or description, not every time the bot starts. Registering to your server instead of globally keeps the command scoped to where you're testing.
|
||||
|
||||
---
|
||||
|
||||
Now the bot itself. Save it as `bot.ts`.
|
||||
|
||||
```ts bot.ts icon="/icons/typescript.svg"
|
||||
import { Client, Events, GatewayIntentBits } from "discord.js";
|
||||
|
||||
const { DISCORD_TOKEN } = process.env;
|
||||
if (!DISCORD_TOKEN) {
|
||||
throw new Error("Set DISCORD_TOKEN in .env.local");
|
||||
}
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
// runs once, right after the bot connects
|
||||
client.once(Events.ClientReady, readyClient => {
|
||||
console.log(`Logged in as ${readyClient.user.tag}`);
|
||||
});
|
||||
|
||||
// runs every time someone uses a slash command
|
||||
client.on(Events.InteractionCreate, async interaction => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
if (interaction.commandName === "ping") {
|
||||
await interaction.reply("Pong!");
|
||||
}
|
||||
});
|
||||
|
||||
client.login(DISCORD_TOKEN);
|
||||
```
|
||||
|
||||
The ready handler logs a line once the bot connects. After that, `interactionCreate` runs whenever someone uses a slash command; it confirms the command was `/ping` and replies with `Pong!`.
|
||||
|
||||
---
|
||||
|
||||
Start the bot with `bun run`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run bot.ts
|
||||
```
|
||||
|
||||
The first connection takes a few seconds. Once the login line prints, switch to Discord and type `/ping` in your server.
|
||||
|
||||
```txt
|
||||
Logged in as my-bot#1234
|
||||
```
|
||||
|
||||
The bot replies with `Pong!`. You've got a working Discord bot.
|
||||
|
||||
---
|
||||
|
||||
To add another command, define it in `deploy-commands.ts`, run that script again, and add an `if` branch for its name in `bot.ts`. The [Discord.js docs](https://discord.js.org/docs) cover command options, permissions, buttons, embeds, and the rest of the API.
|
||||
|
||||
---
|
||||
|
||||
When you deploy, there's no build or bundling step. Bun runs `bot.ts` and every file it imports directly, so you ship your source as-is and start it with the same `bun run bot.ts` you use while developing.
|
||||
|
||||
`deploy-commands.ts` registers `/ping` in your test server, which is the right scope while you're building. To publish the bot to every server it joins, register globally instead: change the route to `Routes.applicationCommands(DISCORD_CLIENT_ID)`. Global registration doesn't use a server, so you can also drop `DISCORD_GUILD_ID` from the script's check and from `.env.local`.
|
||||
|
||||
To keep the bot online and bring it back after a crash or reboot, run it under a process manager.
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="systemd" href="/guides/ecosystem/systemd" icon="server">
|
||||
Run your bot as a Linux daemon
|
||||
</Card>
|
||||
<Card title="PM2" href="/guides/ecosystem/pm2" icon="cog">
|
||||
Manage your bot with PM2
|
||||
</Card>
|
||||
</Columns>
|
||||
@@ -0,0 +1,149 @@
|
||||
---
|
||||
title: Containerize a Bun application with Docker
|
||||
sidebarTitle: Docker with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
<Note>
|
||||
This guide assumes you already have [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed.
|
||||
</Note>
|
||||
|
||||
[Docker](https://www.docker.com) is a platform for packaging and running an application as a lightweight, portable _container_ that encapsulates all the necessary dependencies.
|
||||
|
||||
---
|
||||
|
||||
To _containerize_ the application, define a `Dockerfile`. It lists the instructions to initialize the container, copy your local project files into it, install dependencies, and start the application.
|
||||
|
||||
```docker Dockerfile icon="docker"
|
||||
# use the official Bun image
|
||||
# see all versions at https://hub.docker.com/r/oven/bun/tags
|
||||
FROM oven/bun:1 AS base
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# install dependencies into temp directory
|
||||
# this will cache them and speed up future builds
|
||||
FROM base AS install
|
||||
RUN mkdir -p /temp/dev
|
||||
COPY package.json bun.lock /temp/dev/
|
||||
RUN cd /temp/dev && bun install --frozen-lockfile
|
||||
|
||||
# install with --production (exclude devDependencies)
|
||||
RUN mkdir -p /temp/prod
|
||||
COPY package.json bun.lock /temp/prod/
|
||||
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
||||
|
||||
# copy node_modules from temp directory
|
||||
# then copy all (non-ignored) project files into the image
|
||||
FROM base AS prerelease
|
||||
COPY --from=install /temp/dev/node_modules node_modules
|
||||
COPY . .
|
||||
|
||||
# [optional] tests & build
|
||||
ENV NODE_ENV=production
|
||||
RUN bun test
|
||||
RUN bun run build
|
||||
|
||||
# copy production dependencies and source code into final image
|
||||
FROM base AS release
|
||||
COPY --from=install /temp/prod/node_modules node_modules
|
||||
COPY --from=prerelease /usr/src/app/index.ts .
|
||||
COPY --from=prerelease /usr/src/app/package.json .
|
||||
|
||||
# run the app
|
||||
USER bun
|
||||
EXPOSE 3000/tcp
|
||||
ENTRYPOINT [ "bun", "run", "index.ts" ]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Next, add a `.dockerignore` file. It uses a syntax similar to `.gitignore` and lists the files and directories to exclude from every stage of the Docker build. For example:
|
||||
|
||||
```txt .dockerignore icon="docker"
|
||||
node_modules
|
||||
Dockerfile*
|
||||
docker-compose*
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
LICENSE
|
||||
.vscode
|
||||
Makefile
|
||||
helm-charts
|
||||
.env
|
||||
.editorconfig
|
||||
.idea
|
||||
coverage*
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Run `docker build` to convert this `Dockerfile` into a _Docker image_, a self-contained template containing all the dependencies and configuration required to run the application.
|
||||
|
||||
The `-t` flag names the image, and `--pull` tells Docker to download the latest version of the base image (`oven/bun`). The initial build takes longer, since Docker downloads all the base images and dependencies.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
docker build --pull -t bun-hello-world .
|
||||
```
|
||||
|
||||
```txt
|
||||
[+] Building 0.9s (21/21) FINISHED
|
||||
=> [internal] load build definition from Dockerfile 0.0s
|
||||
=> => transferring dockerfile: 37B 0.0s
|
||||
=> [internal] load .dockerignore 0.0s
|
||||
=> => transferring context: 35B 0.0s
|
||||
=> [internal] load metadata for docker.io/oven/bun:1 0.8s
|
||||
=> [auth] oven/bun:pull token for registry-1.docker.io 0.0s
|
||||
=> [base 1/2] FROM docker.io/oven/bun:1@sha256:373265748d3cd3624cb3f3ee6004f45b1fc3edbd07a622aeeec17566d2756997 0.0s
|
||||
=> [internal] load build context 0.0s
|
||||
=> => transferring context: 155B 0.0s
|
||||
# ...lots of commands...
|
||||
=> exporting to image 0.0s
|
||||
=> => exporting layers 0.0s
|
||||
=> => writing image sha256:360663f7fdcd6f11e8e94761d5592e2e4dfc8d167f034f15cd5a863d5dc093c4 0.0s
|
||||
=> => naming to docker.io/library/bun-hello-world 0.0s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Now start a running _container_ from the `bun-hello-world` image with `docker run`. The `-d` flag runs it in _detached_ mode, and `-p 3000:3000` maps the container's port 3000 to port 3000 on your machine.
|
||||
|
||||
The `run` command prints the _container ID_.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
docker run -d -p 3000:3000 bun-hello-world
|
||||
```
|
||||
|
||||
```txt
|
||||
7f03e212a15ede8644379bce11a13589f563d3909a9640446c5bbefce993678d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The container is now running in the background. Visit [localhost:3000](http://localhost:3000). You should see your application's response.
|
||||
|
||||
---
|
||||
|
||||
To stop the container, run `docker stop <container-id>`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
docker stop 7f03e212a15ede8644379bce11a13589f563d3909a9640446c5bbefce993678d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
If you can't find the container ID, `docker ps` lists all running containers.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
docker ps
|
||||
```
|
||||
|
||||
```txt
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
7f03e212a15e bun-hello-world "bun run index.ts" 2 minutes ago Up 2 minutes 0.0.0.0:3000->3000/tcp flamboyant_cerf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See the [Docker documentation](https://docs.docker.com/) for more advanced usage.
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
title: Use Drizzle ORM with Bun
|
||||
sidebarTitle: Drizzle with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
Drizzle is an ORM that supports both a SQL-like "query builder" API and an ORM-like [Queries API](https://orm.drizzle.team/docs/rqb). It supports the `bun:sqlite` built-in module.
|
||||
|
||||
---
|
||||
|
||||
Create a fresh project with `bun init` and install Drizzle.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun init -y
|
||||
bun add drizzle-orm
|
||||
bun add -D drizzle-kit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then connect to a SQLite database with the `bun:sqlite` module and create the Drizzle database instance.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
import { drizzle } from "drizzle-orm/bun-sqlite";
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const sqlite = new Database("sqlite.db");
|
||||
export const db = drizzle({ client: sqlite });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To see the database in action, add these lines to `index.ts`.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { db } from "./db";
|
||||
import { sql } from "drizzle-orm";
|
||||
|
||||
const query = sql`select "hello world" as text`;
|
||||
const result = db.all<{ text: string }>(query);
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then run `index.ts` with Bun. Bun creates `sqlite.db` and executes the query.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
[
|
||||
{
|
||||
text: "hello world",
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Now give the database a schema. Create a `schema.ts` file and define a `movies` table.
|
||||
|
||||
```ts schema.ts icon="/icons/typescript.svg"
|
||||
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
||||
|
||||
export const movies = sqliteTable("movies", {
|
||||
id: integer("id").primaryKey(),
|
||||
title: text("name"),
|
||||
releaseYear: integer("release_year"),
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Generate an initial SQL migration with the `drizzle-kit` CLI.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bunx drizzle-kit generate --dialect sqlite --schema ./schema.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The command creates a `drizzle` directory containing a `.sql` migration file and a `meta` directory.
|
||||
|
||||
```txt File Tree icon="folder-tree"
|
||||
drizzle
|
||||
├── 0000_ordinary_beyonder.sql
|
||||
└── meta
|
||||
├── 0000_snapshot.json
|
||||
└── _journal.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Execute these migrations with a `migrate.ts` script. It connects to `sqlite.db`, then executes all unexecuted migrations in the `drizzle` directory.
|
||||
|
||||
```ts migrate.ts icon="/icons/typescript.svg"
|
||||
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
|
||||
|
||||
import { drizzle } from "drizzle-orm/bun-sqlite";
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const sqlite = new Database("sqlite.db");
|
||||
const db = drizzle({ client: sqlite });
|
||||
migrate(db, { migrationsFolder: "./drizzle" });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Run the script with `bun` to execute the migration.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run migrate.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Now add some data to the database. Create a `seed.ts` file with the following contents.
|
||||
|
||||
```ts seed.ts icon="/icons/typescript.svg"
|
||||
import { db } from "./db";
|
||||
import * as schema from "./schema";
|
||||
|
||||
await db.insert(schema.movies).values([
|
||||
{
|
||||
title: "The Matrix",
|
||||
releaseYear: 1999,
|
||||
},
|
||||
{
|
||||
title: "The Matrix Reloaded",
|
||||
releaseYear: 2003,
|
||||
},
|
||||
{
|
||||
title: "The Matrix Revolutions",
|
||||
releaseYear: 2003,
|
||||
},
|
||||
]);
|
||||
|
||||
console.log(`Seeding complete.`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then run this file.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run seed.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
Seeding complete.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The database now has a schema and some sample data. Query it with Drizzle by replacing the contents of `index.ts` with the following.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import * as schema from "./schema";
|
||||
import { db } from "./db";
|
||||
|
||||
const result = await db.select().from(schema.movies);
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then run the file. You should see the three movies you inserted.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
[
|
||||
{
|
||||
id: 1,
|
||||
title: "The Matrix",
|
||||
releaseYear: 1999,
|
||||
}, {
|
||||
id: 2,
|
||||
title: "The Matrix Reloaded",
|
||||
releaseYear: 2003,
|
||||
}, {
|
||||
id: 3,
|
||||
title: "The Matrix Revolutions",
|
||||
releaseYear: 2003,
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See the [Drizzle docs](https://orm.drizzle.team/docs/overview).
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: Build an HTTP server using Elysia and Bun
|
||||
sidebarTitle: Elysia with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
[Elysia](https://elysiajs.com) is a Bun-first web framework built on Bun's HTTP, file system, and hot reloading APIs. Get started with `bun create`.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun create elysia myapp
|
||||
cd myapp
|
||||
bun run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To define an HTTP route and start a server with Elysia:
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
import { Elysia } from "elysia";
|
||||
|
||||
const app = new Elysia().get("/", () => "Hello Elysia").listen(8080);
|
||||
|
||||
console.log(`🦊 Elysia is running on port ${app.server?.port}...`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Elysia is a server framework with Express-like syntax, type inference, middleware, file uploads, and plugins for JWT authentication and OpenAPI documentation. It's one of the [fastest Bun web frameworks](https://github.com/SaltyAom/bun-http-framework-benchmark).
|
||||
|
||||
See the Elysia [documentation](https://elysiajs.com/quick-start.html).
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Build an HTTP server using Express and Bun
|
||||
sidebarTitle: Express with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
Express and other major Node.js HTTP libraries should work in Bun without changes. Bun implements the [`node:http`](https://nodejs.org/api/http.html) and [`node:https`](https://nodejs.org/api/https.html) modules that these libraries rely on.
|
||||
|
||||
<Note>See [Node.js compatibility](/runtime/nodejs-compat#node-http) for details.</Note>
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add express
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To define an HTTP route and start a server with Express:
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
import express from "express";
|
||||
|
||||
const app = express();
|
||||
const port = 8080;
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.send("Hello World!");
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Listening on port ${port}...`);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To start the server on `localhost`:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun server.ts
|
||||
```
|
||||
@@ -0,0 +1,262 @@
|
||||
---
|
||||
title: Use Gel with Bun
|
||||
sidebarTitle: Gel with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
Gel (formerly EdgeDB) is a graph-relational database built on Postgres. It provides a declarative schema language, migrations system, and object-oriented query language. It also supports raw SQL queries. It solves object-relational mapping at the database layer, so your application code doesn't need an ORM library.
|
||||
|
||||
---
|
||||
|
||||
First, [install Gel](https://docs.geldata.com/learn/installation) if you haven't already.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```sh Linux/macOS terminal icon="terminal"
|
||||
curl https://www.geldata.com/sh --proto "=https" -sSf1 | sh
|
||||
```
|
||||
|
||||
```sh Windows terminal icon="windows"
|
||||
irm https://www.geldata.com/ps1 | iex
|
||||
```
|
||||
|
||||
```sh Homebrew terminal icon="terminal"
|
||||
brew install geldata/tap/gel-cli
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
---
|
||||
|
||||
Use `bun init` to create a fresh project.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
mkdir my-gel-app
|
||||
cd my-gel-app
|
||||
bun init -y
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Initialize a Gel instance for the project with the Gel CLI. The `gel project init` command creates a `gel.toml` file in the project root.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
gel project init
|
||||
```
|
||||
|
||||
```txt
|
||||
No `gel.toml` (or `edgedb.toml`) found in `/Users/colinmcd94/Documents/bun/fun/examples/my-gel-app` or above
|
||||
Initializing new project...
|
||||
Checking Gel versions...
|
||||
┌─────────────────────┬──────────────────────────────────────────────────────────────────┐
|
||||
│ Project directory │ /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app │
|
||||
│ Project config │ /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app/gel.toml │
|
||||
│ Schema dir (empty) │ /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app/dbschema │
|
||||
│ Installation method │ portable package │
|
||||
│ Version │ x.y+6d5921b │
|
||||
│ Instance name │ my_gel_app │
|
||||
│ Branch │ main │
|
||||
└─────────────────────┴──────────────────────────────────────────────────────────────────┘
|
||||
Version x.y+6d5921b is already downloaded
|
||||
Initializing Gel instance 'my_gel_app'...
|
||||
Applying migrations...
|
||||
Everything is up to date. Revision initial
|
||||
Writing gel.local.toml for configuration
|
||||
Project initialized.
|
||||
To connect to my_gel_app, run `gel`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To check that the database is running, open a REPL and run a query.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
gel
|
||||
my_gel_app:main> select 1 + 1;
|
||||
```
|
||||
|
||||
```txt
|
||||
{2}
|
||||
```
|
||||
|
||||
Then run `\quit` to exit the REPL.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
my_gel_app:main> \quit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Next, define a schema. The `gel project init` command already created a `dbschema/default.gel` file to hold it.
|
||||
|
||||
```txt File Tree icon="folder-tree"
|
||||
dbschema
|
||||
├── default.gel
|
||||
├── extensions.gel
|
||||
├── futures.gel
|
||||
└── migrations
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Open that file and paste the following contents.
|
||||
|
||||
```ts default.gel icon="file-code"
|
||||
module default {
|
||||
type Movie {
|
||||
required title: str;
|
||||
releaseYear: int64;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then generate and apply an initial migration.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
gel migration create
|
||||
```
|
||||
|
||||
```txt
|
||||
Created dbschema/migrations/00001-m1uwekr.edgeql, id: m1uwekrn4ni4qs7ul7hfar4xemm5kkxlpswolcoyqj3xdhweomwjrq
|
||||
```
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
gel migrate
|
||||
```
|
||||
|
||||
```txt
|
||||
Applying m1uwekrn4ni4qs7ul7hfar4xemm5kkxlpswolcoyqj3xdhweomwjrq (00001-m1uwekr.edgeql)
|
||||
... parsed
|
||||
... applied
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
With the schema applied, query the database with Gel's JavaScript client library. Install the client library and Gel's codegen CLI, then create a `seed.ts` file.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add gel
|
||||
bun add -D @gel/generate
|
||||
touch seed.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Paste the following code into `seed.ts`.
|
||||
|
||||
The client auto-connects to the database. The script inserts a few movies with the `.execute()` method, using EdgeQL's `for` expression to turn the bulk insert into a single query.
|
||||
|
||||
```ts seed.ts icon="/icons/typescript.svg"
|
||||
import { createClient } from "gel";
|
||||
|
||||
const client = createClient();
|
||||
|
||||
const INSERT_MOVIE = `
|
||||
with movies := <array<tuple<title: str, year: int64>>>$movies
|
||||
for movie in array_unpack(movies) union (
|
||||
insert Movie {
|
||||
title := movie.title,
|
||||
releaseYear := movie.year,
|
||||
}
|
||||
)
|
||||
`;
|
||||
|
||||
const movies = [
|
||||
{ title: "The Matrix", year: 1999 },
|
||||
{ title: "The Matrix Reloaded", year: 2003 },
|
||||
{ title: "The Matrix Revolutions", year: 2003 },
|
||||
];
|
||||
|
||||
await client.execute(INSERT_MOVIE, { movies });
|
||||
|
||||
console.log(`Seeding complete.`);
|
||||
process.exit();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then run this file with Bun.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run seed.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
Seeding complete.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Gel implements several code generation tools for TypeScript. To write typesafe queries against the seeded database, generate the EdgeQL query builder with `@gel/generate`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bunx @gel/generate edgeql-js
|
||||
```
|
||||
|
||||
```txt
|
||||
Generating query builder...
|
||||
Detected tsconfig.json, generating TypeScript files.
|
||||
To override this, use the --target flag.
|
||||
Run `npx @gel/generate --help` for full options.
|
||||
Introspecting database schema...
|
||||
Writing files to ./dbschema/edgeql-js
|
||||
Generation complete! 🤘
|
||||
Checking the generated query builder into version control
|
||||
is not recommended. Would you like to update .gitignore to ignore
|
||||
the query builder directory? The following line will be added:
|
||||
|
||||
dbschema/edgeql-js
|
||||
|
||||
[y/n] (leave blank for "y")
|
||||
> y
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
In `index.ts`, import the generated query builder from `./dbschema/edgeql-js` and write a select query.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { createClient } from "gel";
|
||||
import e from "./dbschema/edgeql-js";
|
||||
|
||||
const client = createClient();
|
||||
|
||||
const query = e.select(e.Movie, () => ({
|
||||
title: true,
|
||||
releaseYear: true,
|
||||
}));
|
||||
|
||||
const results = await query.run(client);
|
||||
console.log(results);
|
||||
|
||||
results; // { title: string, releaseYear: number | null }[]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Run the file with Bun to see the movies you inserted.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
[
|
||||
{
|
||||
title: "The Matrix",
|
||||
releaseYear: 1999,
|
||||
}, {
|
||||
title: "The Matrix Reloaded",
|
||||
releaseYear: 2003,
|
||||
}, {
|
||||
title: "The Matrix Revolutions",
|
||||
releaseYear: 2003,
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See the [Gel docs](https://docs.geldata.com/).
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: Build an HTTP server using Hono and Bun
|
||||
sidebarTitle: Hono with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
[Hono](https://github.com/honojs/hono) is a lightweight web framework designed for the edge.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
import { Hono } from "hono";
|
||||
const app = new Hono();
|
||||
|
||||
app.get("/", c => c.text("Hono!"));
|
||||
|
||||
export default app;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Use `create-hono` to get started with one of Hono's project templates. Select `bun` when prompted for a template.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun create hono myapp
|
||||
```
|
||||
|
||||
```txt
|
||||
create-hono version 0.19.4
|
||||
✔ Using target directory … myapp
|
||||
✔ Which template do you want to use? bun
|
||||
✔ Do you want to install project dependencies? Yes
|
||||
✔ Which package manager do you want to use? bun
|
||||
✔ Cloning the template
|
||||
✔ Installing project dependencies
|
||||
🎉 Copied project files
|
||||
Get started with: cd myapp
|
||||
```
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
cd myapp
|
||||
bun install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then start the dev server and visit [localhost:3000](http://localhost:3000).
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Refer to Hono's [getting started with Bun](https://hono.dev/docs/getting-started/bun) guide.
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
title: Read and write data to MongoDB using Mongoose and Bun
|
||||
sidebarTitle: Mongoose with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
MongoDB and Mongoose work with Bun with no extra configuration. This guide assumes you've already installed MongoDB and are running it as a background process or service on your development machine. See the [MongoDB installation guide](https://www.mongodb.com/docs/manual/installation/) for details.
|
||||
|
||||
---
|
||||
|
||||
Once MongoDB is running, create a directory and initialize it with `bun init`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
mkdir mongoose-app
|
||||
cd mongoose-app
|
||||
bun init
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then add Mongoose as a dependency.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add mongoose
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
In `schema.ts`, declare and export an `Animal` model.
|
||||
|
||||
```ts schema.ts icon="/icons/typescript.svg"
|
||||
import * as mongoose from "mongoose";
|
||||
|
||||
const animalSchema = new mongoose.Schema(
|
||||
{
|
||||
name: { type: String, required: true },
|
||||
sound: { type: String, required: true },
|
||||
},
|
||||
{
|
||||
methods: {
|
||||
speak() {
|
||||
console.log(`${this.sound}!`);
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export type Animal = mongoose.InferSchemaType<typeof animalSchema>;
|
||||
export const Animal = mongoose.model("Animal", animalSchema);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
In `index.ts`, import `Animal`, connect to MongoDB, and add some data to the database.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import * as mongoose from "mongoose";
|
||||
import { Animal } from "./schema";
|
||||
|
||||
// connect to database
|
||||
await mongoose.connect("mongodb://127.0.0.1:27017/mongoose-app");
|
||||
|
||||
// create new Animal
|
||||
const cow = new Animal({
|
||||
name: "Cow",
|
||||
sound: "Moo",
|
||||
});
|
||||
await cow.save(); // saves to the database
|
||||
|
||||
// read all Animals
|
||||
const animals = await Animal.find();
|
||||
animals[0]!.speak(); // logs "Moo!"
|
||||
|
||||
// disconnect
|
||||
await mongoose.disconnect();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Run the file with `bun run`.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
Moo!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
As you build your application, refer to the official [MongoDB](https://www.mongodb.com/docs) and [Mongoose](https://mongoosejs.com/docs/) docs.
|
||||
@@ -0,0 +1,234 @@
|
||||
---
|
||||
title: Use Neon Postgres through Drizzle ORM
|
||||
sidebarTitle: Neon Drizzle with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
[Neon](https://neon.com/) is a fully managed serverless Postgres. Neon separates compute and storage to offer features like autoscaling, branching and bottomless storage. You can use Neon from Bun directly with the `@neondatabase/serverless` driver or through an ORM like Drizzle.
|
||||
|
||||
Drizzle ORM supports both a SQL-like "query builder" API and an ORM-like [Queries API](https://orm.drizzle.team/docs/rqb). Get started by creating a project directory, initializing it with `bun init`, and installing Drizzle and the [Neon serverless driver](https://github.com/neondatabase/serverless/).
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
mkdir bun-drizzle-neon
|
||||
cd bun-drizzle-neon
|
||||
bun init -y
|
||||
bun add drizzle-orm @neondatabase/serverless
|
||||
bun add -D drizzle-kit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Create a `.env.local` file and add your [Neon Postgres connection string](https://neon.com/docs/connect/connect-from-any-app) to it.
|
||||
|
||||
```ini .env.local icon="settings"
|
||||
DATABASE_URL=postgresql://username:[email protected]/neondb?sslmode=require
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
In `db.ts`, connect to the Neon database with the Neon serverless driver, wrapped in a Drizzle database instance.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
import { neon } from "@neondatabase/serverless";
|
||||
import { drizzle } from "drizzle-orm/neon-http";
|
||||
|
||||
// Bun automatically loads the DATABASE_URL from .env.local
|
||||
// Refer to: https://bun.com/docs/runtime/environment-variables for more information
|
||||
const sql = neon(process.env.DATABASE_URL!);
|
||||
|
||||
export const db = drizzle({ client: sql });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To see the database in action, add these lines to `index.ts`.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { db } from "./db";
|
||||
import { sql } from "drizzle-orm";
|
||||
|
||||
const query = sql`select 'hello world' as text`;
|
||||
const result = await db.execute(query);
|
||||
console.log(result.rows);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then run `index.ts` with Bun.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
[
|
||||
{
|
||||
text: "hello world",
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Define a schema for the database with Drizzle ORM primitives. Create a `schema.ts` file and add this code.
|
||||
|
||||
```ts schema.ts icon="/icons/typescript.svg"
|
||||
import { pgTable, integer, serial, text, timestamp } from "drizzle-orm/pg-core";
|
||||
|
||||
export const authors = pgTable("authors", {
|
||||
id: serial("id").primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
bio: text("bio"),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then use the `drizzle-kit` CLI to generate an initial SQL migration.
|
||||
|
||||
```sh
|
||||
bunx drizzle-kit generate --dialect postgresql --schema ./schema.ts --out ./drizzle
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The command creates a `drizzle` directory containing a `.sql` migration file and a `meta` directory.
|
||||
|
||||
```txt File Tree icon="folder-tree"
|
||||
drizzle
|
||||
├── 0000_aspiring_post.sql
|
||||
└── meta
|
||||
├── 0000_snapshot.json
|
||||
└── _journal.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Execute these migrations with a `migrate.ts` script. The script opens a new connection to the Neon database and executes all unexecuted migrations in the `drizzle` directory.
|
||||
|
||||
```ts migrate.ts
|
||||
import { db } from "./db";
|
||||
import { migrate } from "drizzle-orm/neon-http/migrator";
|
||||
|
||||
const main = async () => {
|
||||
try {
|
||||
await migrate(db, { migrationsFolder: "drizzle" });
|
||||
console.log("Migration completed");
|
||||
} catch (error) {
|
||||
console.error("Error during migration:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Run the script with `bun`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run migrate.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
Migration completed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Now add some data to the database. Create a `seed.ts` file with the following contents.
|
||||
|
||||
```ts seed.ts icon="/icons/typescript.svg"
|
||||
import { db } from "./db";
|
||||
import * as schema from "./schema";
|
||||
|
||||
async function seed() {
|
||||
await db.insert(schema.authors).values([
|
||||
{
|
||||
name: "J.R.R. Tolkien",
|
||||
bio: "The creator of Middle-earth and author of The Lord of the Rings.",
|
||||
},
|
||||
{
|
||||
name: "George R.R. Martin",
|
||||
bio: "The author of the epic fantasy series A Song of Ice and Fire.",
|
||||
},
|
||||
{
|
||||
name: "J.K. Rowling",
|
||||
bio: "The creator of the Harry Potter series.",
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await seed();
|
||||
console.log("Seeding completed");
|
||||
} catch (error) {
|
||||
console.error("Error during seeding:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then run this file.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run seed.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
Seeding completed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The database now has a schema and sample data. Query it with Drizzle by replacing the contents of `index.ts` with the following.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import * as schema from "./schema";
|
||||
import { db } from "./db";
|
||||
|
||||
const result = await db.select().from(schema.authors);
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then run the file. It prints the three authors you inserted.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
[
|
||||
{
|
||||
id: 1,
|
||||
name: "J.R.R. Tolkien",
|
||||
bio: "The creator of Middle-earth and author of The Lord of the Rings.",
|
||||
createdAt: 2024-05-11T10:28:46.029Z,
|
||||
}, {
|
||||
id: 2,
|
||||
name: "George R.R. Martin",
|
||||
bio: "The author of the epic fantasy series A Song of Ice and Fire.",
|
||||
createdAt: 2024-05-11T10:28:46.029Z,
|
||||
}, {
|
||||
id: 3,
|
||||
name: "J.K. Rowling",
|
||||
bio: "The creator of the Harry Potter series.",
|
||||
createdAt: 2024-05-11T10:28:46.029Z,
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This example used the Neon serverless driver's SQL-over-HTTP functionality. Neon's serverless driver also exposes `Client` and `Pool` constructors to enable sessions, interactive transactions, and node-postgres compatibility. Refer to [Neon's documentation](https://neon.com/docs/serverless/serverless-driver) for a complete overview.
|
||||
|
||||
Refer to the [Drizzle website](https://orm.drizzle.team/docs/overview) for complete documentation.
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: Use Neon's Serverless Postgres with Bun
|
||||
sidebarTitle: Neon Serverless Postgres with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
[Neon](https://neon.com/) is a fully managed serverless Postgres. Neon separates compute and storage to offer features such as autoscaling, branching, and bottomless storage.
|
||||
|
||||
---
|
||||
|
||||
Get started by creating a project directory, initializing the directory using `bun init`, and adding the [Neon serverless driver](https://github.com/neondatabase/serverless/) as a project dependency.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
mkdir bun-neon-postgres
|
||||
cd bun-neon-postgres
|
||||
bun init -y
|
||||
bun add @neondatabase/serverless
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Create a `.env.local` file and add your [Neon Postgres connection string](https://neon.com/docs/connect/connect-from-any-app) to it.
|
||||
|
||||
```ini .env.local icon="settings"
|
||||
DATABASE_URL=postgresql://username:[email protected]/neondb?sslmode=require
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Paste the following code into your project's `index.ts` file.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { neon } from "@neondatabase/serverless";
|
||||
|
||||
// Bun automatically loads the DATABASE_URL from .env.local
|
||||
// Refer to: https://bun.com/docs/runtime/environment-variables for more information
|
||||
const sql = neon(process.env.DATABASE_URL!);
|
||||
|
||||
const rows = await sql`SELECT version()`;
|
||||
|
||||
console.log(rows[0]?.version);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Start the program with `bun ./index.ts`. It prints the Postgres version to the console.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun ./index.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
PostgreSQL 16.2 on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This example used the Neon serverless driver's SQL-over-HTTP functionality. Neon's serverless driver also exposes `Client` and `Pool` constructors to enable sessions, interactive transactions, and node-postgres compatibility.
|
||||
|
||||
Refer to [Neon's documentation](https://neon.com/docs/serverless/serverless-driver) for a complete overview of the serverless driver.
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title: Build an app with Next.js and Bun
|
||||
sidebarTitle: Next.js with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
[Next.js](https://nextjs.org/) is a React framework for building full-stack web applications. It supports server-side rendering, static site generation, and API routes. Bun installs packages fast and can run Next.js development and production servers.
|
||||
|
||||
---
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a new Next.js app">
|
||||
Use the interactive CLI to scaffold a new Next.js project and install its dependencies.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun create next-app@latest my-bun-app
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Start the dev server">
|
||||
Change to the project directory and run the dev server with Bun.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
cd my-bun-app
|
||||
bun --bun run dev
|
||||
```
|
||||
|
||||
This starts the Next.js dev server with Bun's runtime.
|
||||
|
||||
Open [`http://localhost:3000`](http://localhost:3000) in your browser to see the result. Changes you make to `app/page.tsx` are hot-reloaded in the browser.
|
||||
|
||||
</Step>
|
||||
<Step title="Update scripts in package.json">
|
||||
Prefix the Next.js CLI commands in your `package.json` scripts with `bun --bun` so that Bun executes the Next.js CLI for `dev`, `build`, and `start`.
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "bun --bun next dev", // [!code ++]
|
||||
"build": "bun --bun next build", // [!code ++]
|
||||
"start": "bun --bun next start" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
---
|
||||
|
||||
## Hosting
|
||||
|
||||
<Columns cols={3}>
|
||||
<Card title="Vercel" href="/guides/deployment/vercel" icon="/icons/ecosystem/vercel.svg">
|
||||
Deploy on Vercel
|
||||
</Card>
|
||||
<Card title="Railway" href="/guides/deployment/railway" icon="/icons/ecosystem/railway.svg">
|
||||
Deploy on Railway
|
||||
</Card>
|
||||
<Card title="DigitalOcean" href="/guides/deployment/digital-ocean" icon="/icons/ecosystem/digitalocean.svg">
|
||||
Deploy on DigitalOcean
|
||||
</Card>
|
||||
<Card title="AWS Lambda" href="/guides/deployment/aws-lambda" icon="/icons/ecosystem/aws.svg">
|
||||
Deploy on AWS Lambda
|
||||
</Card>
|
||||
<Card title="Google Cloud Run" href="/guides/deployment/google-cloud-run" icon="/icons/ecosystem/gcp.svg">
|
||||
Deploy on Google Cloud Run
|
||||
</Card>
|
||||
<Card title="Render" href="/guides/deployment/render" icon="/icons/ecosystem/render.svg">
|
||||
Deploy on Render
|
||||
</Card>
|
||||
</Columns>
|
||||
|
||||
---
|
||||
|
||||
## Templates
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card
|
||||
title="Bun + Next.js Basic Starter"
|
||||
img="/images/templates/bun-nextjs-basic.png"
|
||||
href="https://github.com/bun-templates/bun-nextjs-basic"
|
||||
arrow="true"
|
||||
cta="Go to template"
|
||||
>
|
||||
A basic App Router starter with Bun, Next.js, and Tailwind CSS.
|
||||
</Card>
|
||||
<Card
|
||||
title="Todo App with Next.js + Bun"
|
||||
img="/images/templates/bun-nextjs-todo.png"
|
||||
href="https://github.com/bun-templates/bun-nextjs-todo"
|
||||
arrow="true"
|
||||
cta="Go to template"
|
||||
>
|
||||
A full-stack todo application built with Bun, Next.js, and PostgreSQL.
|
||||
</Card>
|
||||
</Columns>
|
||||
|
||||
---
|
||||
|
||||
Refer to the [Next.js documentation](https://nextjs.org/docs) for more on building and deploying Next.js applications.
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: Build an app with Nuxt and Bun
|
||||
sidebarTitle: Nuxt with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
Bun supports [Nuxt](https://nuxt.com) with no extra configuration. Initialize a Nuxt app with the official `create-nuxt` CLI.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun create nuxt@latest my-nuxt-app
|
||||
```
|
||||
|
||||
```txt
|
||||
┌ Welcome to Nuxt!
|
||||
│
|
||||
◇ Templates loaded
|
||||
│
|
||||
◇ Which template would you like to use?
|
||||
│ minimal – Minimal starter with a single app.vue.
|
||||
│
|
||||
◇ Creating project in my-nuxt-app
|
||||
│
|
||||
◇ Downloaded minimal template
|
||||
│
|
||||
◇ Which package manager would you like to use?
|
||||
│ bun
|
||||
│
|
||||
◇ Initialize git repository?
|
||||
│ Yes
|
||||
│
|
||||
◇ Dependencies installed
|
||||
│
|
||||
◇ Git repository initialized
|
||||
│
|
||||
◇ Would you like to browse and install modules?
|
||||
│ No
|
||||
│
|
||||
└ ✨ Nuxt project has been created with the minimal template.
|
||||
|
||||
╭── 👉 Next steps ─────╮
|
||||
│ │
|
||||
│ › cd my-nuxt-app │
|
||||
│ › bun run dev │
|
||||
│ │
|
||||
╰──────────────────────╯
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To start the dev server, run `bun --bun run dev` from the project root. This executes the `nuxt dev` command defined in the `"dev"` script in `package.json`.
|
||||
|
||||
<Note>
|
||||
The `nuxt` CLI uses Node.js by default; passing the `--bun` flag forces the dev server to use the Bun runtime instead.
|
||||
</Note>
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
cd my-nuxt-app
|
||||
bun --bun run dev
|
||||
```
|
||||
|
||||
```txt
|
||||
$ nuxt dev
|
||||
│
|
||||
● Nuxt 4.5.2 (with Nitro 2.13.4, Vite 8.2.1 and Vue 3.5.41)
|
||||
|
||||
➜ Local: http://localhost:3000/
|
||||
➜ Network: use --host to expose
|
||||
|
||||
➜ DevTools: press Shift + Alt + D in the browser (v3.4.1)
|
||||
|
||||
✔ Vite client built in 95ms
|
||||
✔ Vite server built in 33ms
|
||||
[nitro] ✔ Nuxt Nitro server built in 948ms
|
||||
ℹ Vite server warmed up in 3ms
|
||||
ℹ Vite client warmed up in 9ms
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Once the dev server starts, open [http://localhost:3000](http://localhost:3000) to see the app. It renders Nuxt's built-in `NuxtWelcome` template component.
|
||||
|
||||
To start developing your app, replace `<NuxtWelcome />` in `app/app.vue` with your own UI.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
---
|
||||
|
||||
For production builds, the default preset is compatible with Bun, but the [Bun preset](https://nitro.build/deploy/runtimes/bun) generates better optimized builds.
|
||||
|
||||
```ts nuxt.config.ts icon="/icons/typescript.svg"
|
||||
export default defineNuxtConfig({
|
||||
nitro: {
|
||||
preset: "bun", // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Alternatively, set the preset with an environment variable:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
NITRO_PRESET=bun bun run build
|
||||
```
|
||||
|
||||
<Note>
|
||||
Some packages provide Bun-specific exports that Nitro does not bundle correctly with the default preset. Use the Bun
|
||||
preset so those packages work in production builds.
|
||||
</Note>
|
||||
|
||||
After building, start the server:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run ./.output/server/index.mjs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Refer to the [Nuxt website](https://nuxt.com/docs) for complete documentation.
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Run Bun as a daemon with PM2
|
||||
sidebarTitle: PM2 with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
[PM2](https://pm2.keymetrics.io/) is a process manager that runs your applications as daemons (background processes).
|
||||
|
||||
PM2 offers process monitoring, automatic restarts, and scaling. It keeps your application running when you deploy it to a cloud-hosted virtual private server (VPS).
|
||||
|
||||
---
|
||||
|
||||
You can use PM2 with Bun in two ways: as a CLI option or in a configuration file.
|
||||
|
||||
### With `--interpreter`
|
||||
|
||||
To start your application with PM2 and Bun as the interpreter, run:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
pm2 start --interpreter ~/.bun/bin/bun index.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### With a configuration file
|
||||
|
||||
Alternatively, create a file named `pm2.config.cjs` in your project directory and add the following content.
|
||||
|
||||
```js pm2.config.cjs icon="file-code"
|
||||
module.exports = {
|
||||
name: "app", // Name of your application
|
||||
script: "index.ts", // Entry point of your application
|
||||
interpreter: "bun", // Bun interpreter
|
||||
env: {
|
||||
PATH: `${process.env.HOME}/.bun/bin:${process.env.PATH}`, // Add "~/.bun/bin/bun" to PATH
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
After saving the file, start your application with PM2.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
pm2 start pm2.config.cjs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Your JavaScript/TypeScript web server now runs as a daemon with PM2, using Bun as the interpreter.
|
||||
@@ -0,0 +1,164 @@
|
||||
---
|
||||
title: Use Prisma Postgres with Bun
|
||||
sidebarTitle: Prisma Postgres with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a new project">
|
||||
First, create a directory and initialize it with `bun init`.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
mkdir prisma-postgres-app
|
||||
cd prisma-postgres-app
|
||||
bun init
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Install Prisma dependencies">
|
||||
Then install the Prisma CLI (`prisma`), Prisma Client (`@prisma/client`), and the Postgres driver adapter (`@prisma/adapter-pg`) as dependencies.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun add -d prisma
|
||||
bun add @prisma/client @prisma/adapter-pg
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Initialize Prisma with PostgreSQL">
|
||||
Use the Prisma CLI with `bunx` to initialize the schema and migration directory, with PostgreSQL as the database.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bunx --bun prisma init --db
|
||||
```
|
||||
|
||||
This creates a basic schema. Update it to use the Rust-free client optimized for Bun: open `prisma/schema.prisma` and modify the generator block, then add a `User` model.
|
||||
|
||||
```prisma prisma/schema.prisma icon="/icons/ecosystem/prisma.svg"
|
||||
generator client {
|
||||
provider = "prisma-client"
|
||||
output = "../generated/prisma" // [!code --]
|
||||
output = "./generated" // [!code ++]
|
||||
engineType = "client" // [!code ++]
|
||||
runtime = "bun" // [!code ++]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
}
|
||||
|
||||
model User { // [!code ++]
|
||||
id Int @id @default(autoincrement()) // [!code ++]
|
||||
email String @unique // [!code ++]
|
||||
name String? // [!code ++]
|
||||
} // [!code ++]
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Configure database connection">
|
||||
Set up your Postgres database URL in the `.env` file.
|
||||
|
||||
```ini .env icon="settings"
|
||||
DATABASE_URL="postgresql://username:password@localhost:5432/mydb?schema=public"
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Create and run database migration">
|
||||
Then generate and run the initial migration.
|
||||
|
||||
The command writes a `.sql` migration file to `prisma/migrations` and executes it against your Postgres database. Bun [does not load `.env` automatically](/runtime/environment-variables) when it runs a CLI with `--bun`. The `prisma.config.ts` generated by `prisma init` reads `DATABASE_URL` from the environment, so pass `--env-file=.env` explicitly.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run --bun --env-file=.env prisma migrate dev --name init
|
||||
```
|
||||
|
||||
```txt
|
||||
Loaded Prisma config from prisma.config.ts.
|
||||
|
||||
Prisma schema loaded from prisma/schema.prisma.
|
||||
Datasource "db": PostgreSQL database "mydb", schema "public" at "localhost:5432"
|
||||
|
||||
Applying migration `20250114141233_init`
|
||||
|
||||
The following migration(s) have been created and applied from new schema changes:
|
||||
|
||||
prisma/migrations/
|
||||
└─ 20250114141233_init/
|
||||
└─ migration.sql
|
||||
|
||||
Your database is now in sync with your schema.
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Generate Prisma Client">
|
||||
`prisma migrate dev` does not generate the _Prisma client_, so generate it with the Prisma CLI. The client provides a fully typed API for reading and writing to the database.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run --bun --env-file=.env prisma generate
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Initialize Prisma Client with the Postgres adapter">
|
||||
Create a new file `prisma/db.ts` that initializes the PrismaClient with the Postgres adapter.
|
||||
|
||||
```ts prisma/db.ts icon="/icons/typescript.svg"
|
||||
import { PrismaClient } from "./generated/client";
|
||||
import { PrismaPg } from "@prisma/adapter-pg";
|
||||
|
||||
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });
|
||||
export const prisma = new PrismaClient({ adapter });
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Create a test script">
|
||||
Write a script that creates a new user, then counts the users in the database.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { prisma } from "./prisma/db";
|
||||
|
||||
// create a new user
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
name: "John Dough",
|
||||
email: `john-${Math.random()}@example.com`,
|
||||
},
|
||||
});
|
||||
|
||||
// count the number of users
|
||||
const count = await prisma.user.count();
|
||||
console.log(`There are ${count} users in the database.`);
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Run and test the application">
|
||||
Run the script with `bun run`. Each run creates a new user.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
There are 1 users in the database.
|
||||
```
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
There are 2 users in the database.
|
||||
```
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
There are 3 users in the database.
|
||||
```
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
---
|
||||
|
||||
Prisma Postgres is now set up with Bun. Refer to the [official Prisma Postgres docs](https://www.prisma.io/docs/postgres) as you continue to develop your application.
|
||||
@@ -0,0 +1,158 @@
|
||||
---
|
||||
title: Use Prisma with Bun
|
||||
sidebarTitle: Prisma ORM with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
<Note>
|
||||
Prisma's dynamically loaded subcommands, such as `prisma dev`, require npm to be installed alongside Bun. The commands
|
||||
used in this guide (`prisma init`, `prisma migrate`, and `prisma generate`) are built into the CLI. Generated code
|
||||
works with Bun using the `prisma-client` generator.
|
||||
</Note>
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a new project">
|
||||
Create a directory and initialize it with `bun init`.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
mkdir prisma-app
|
||||
cd prisma-app
|
||||
bun init
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Install Prisma dependencies">
|
||||
Then install the Prisma CLI (`prisma`), Prisma Client (`@prisma/client`), and the LibSQL adapter as dependencies.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun add -d prisma
|
||||
bun add @prisma/client @prisma/adapter-libsql
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Initialize Prisma with SQLite">
|
||||
Use the Prisma CLI with `bunx` to initialize the schema and migration directory. This guide uses a local SQLite database file. `prisma init` writes its connection string, `DATABASE_URL="file:./dev.db"`, to `.env`.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bunx --bun prisma init --datasource-provider sqlite
|
||||
```
|
||||
|
||||
This creates a basic schema. Open `prisma/schema.prisma`, update the generator block to use the Rust-free client with the `bun` runtime, and add a `User` model.
|
||||
|
||||
```prisma prisma/schema.prisma icon="/icons/ecosystem/prisma.svg"
|
||||
generator client {
|
||||
provider = "prisma-client"
|
||||
output = "../generated/prisma" // [!code --]
|
||||
output = "./generated" // [!code ++]
|
||||
engineType = "client" // [!code ++]
|
||||
runtime = "bun" // [!code ++]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
}
|
||||
|
||||
model User { // [!code ++]
|
||||
id Int @id @default(autoincrement()) // [!code ++]
|
||||
email String @unique // [!code ++]
|
||||
name String? // [!code ++]
|
||||
} // [!code ++]
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Create and run database migration">
|
||||
Generate and run the initial migration. The command writes a `.sql` migration file to `prisma/migrations`, creates a new SQLite database, and runs the migration against it. Bun [does not load `.env` automatically](/runtime/environment-variables) when it runs a CLI with `--bun`. The `prisma.config.ts` generated by `prisma init` reads `DATABASE_URL` from the environment, so pass `--env-file=.env` explicitly.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run --bun --env-file=.env prisma migrate dev --name init
|
||||
```
|
||||
```txt
|
||||
Loaded Prisma config from prisma.config.ts.
|
||||
|
||||
Prisma schema loaded from prisma/schema.prisma.
|
||||
Datasource "db": SQLite database "dev.db" at "file:./dev.db"
|
||||
|
||||
SQLite database dev.db created at file:./dev.db
|
||||
|
||||
Applying migration `20251014141233_init`
|
||||
|
||||
The following migration(s) have been created and applied from new schema changes:
|
||||
|
||||
prisma/migrations/
|
||||
└─ 20251014141233_init/
|
||||
└─ migration.sql
|
||||
|
||||
Your database is now in sync with your schema.
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Generate Prisma Client">
|
||||
`prisma migrate dev` does not generate the _Prisma client_, so generate it with the Prisma CLI. The client provides a fully typed API for reading and writing to your database.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run --bun --env-file=.env prisma generate
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Initialize Prisma Client with LibSQL">
|
||||
Create a new file `prisma/db.ts` that initializes the PrismaClient with the LibSQL adapter.
|
||||
|
||||
```ts prisma/db.ts icon="/icons/typescript.svg"
|
||||
import { PrismaClient } from "./generated/client";
|
||||
import { PrismaLibSql } from "@prisma/adapter-libsql";
|
||||
|
||||
const adapter = new PrismaLibSql({ url: process.env.DATABASE_URL || "" });
|
||||
export const prisma = new PrismaClient({ adapter });
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Create a test script">
|
||||
Write a script that creates a new user, then counts the users in the database.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { prisma } from "./prisma/db";
|
||||
|
||||
// create a new user
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
name: "John Dough",
|
||||
email: `john-${Math.random()}@example.com`,
|
||||
},
|
||||
});
|
||||
|
||||
// count the number of users
|
||||
const count = await prisma.user.count();
|
||||
console.log(`There are ${count} users in the database.`);
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Run and test the application">
|
||||
Run the script with `bun run`. Each run creates a new user.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run index.ts
|
||||
```
|
||||
```txt
|
||||
There are 1 users in the database.
|
||||
```
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run index.ts
|
||||
```
|
||||
```txt
|
||||
There are 2 users in the database.
|
||||
```
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run index.ts
|
||||
```
|
||||
```txt
|
||||
There are 3 users in the database.
|
||||
```
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
---
|
||||
|
||||
Prisma is now set up with Bun. See the [Prisma docs](https://www.prisma.io/docs/orm/prisma-client) as you build out your application.
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: Build an app with Qwik and Bun
|
||||
sidebarTitle: Qwik with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
Initialize a new Qwik app with `bunx create-qwik`.
|
||||
|
||||
The `create-qwik` package detects when you are using `bunx` and installs dependencies with `bun`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun create qwik
|
||||
```
|
||||
|
||||
```txt
|
||||
............
|
||||
.::: :--------:.
|
||||
.:::: .:-------:.
|
||||
.:::::. .:-------.
|
||||
::::::. .:------.
|
||||
::::::. :-----:
|
||||
::::::. .:-----.
|
||||
:::::::. .-----.
|
||||
::::::::.. ---:.
|
||||
.:::::::::. :-:.
|
||||
..::::::::::::
|
||||
...::::
|
||||
|
||||
|
||||
┌ Let's create a Qwik App ✨ (v1.20.0)
|
||||
│
|
||||
◇ Where would you like to create your new project? (Use '.' or './' for current directory)
|
||||
│ ./my-app
|
||||
│
|
||||
● Creating new project in /path/to/my-app ... 🐇
|
||||
│
|
||||
◇ Select a starter
|
||||
│ Playground App (Qwik City + Qwik)
|
||||
│
|
||||
◇ Would you like to install bun dependencies?
|
||||
│ Yes
|
||||
│
|
||||
◇ Initialize a new git repository?
|
||||
│ No
|
||||
│
|
||||
◇ Finishing the install. Wanna hear a joke?
|
||||
│ Yes
|
||||
│
|
||||
○ ────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ How do you know if there’s an elephant under your bed? │
|
||||
│ Your head hits the ceiling! │
|
||||
│ │
|
||||
├──────────────────────────────────────────────────────────╯
|
||||
│
|
||||
◇ App Created 🐰
|
||||
│
|
||||
◇ Installed bun dependencies 📋
|
||||
│
|
||||
○ Result ─────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ Success! Project created in my-app directory │
|
||||
│ │
|
||||
│ Integrations? Add Netlify, Cloudflare, Tailwind... │
|
||||
│ bun qwik add │
|
||||
│ │
|
||||
│ Relevant docs: │
|
||||
│ https://qwik.dev/docs/getting-started/ │
|
||||
│ │
|
||||
│ Questions? Start the conversation at: │
|
||||
│ https://qwik.dev/chat │
|
||||
│ https://twitter.com/QwikDev │
|
||||
│ │
|
||||
│ Presentations, Podcasts and Videos: │
|
||||
│ https://qwik.dev/media/ │
|
||||
│ │
|
||||
│ Next steps: │
|
||||
│ cd my-app │
|
||||
│ bun start │
|
||||
│ │
|
||||
│ │
|
||||
├──────────────────────────────────────────────────────╯
|
||||
│
|
||||
└ Happy coding! 🎉
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Run `bun run dev` to start the development server.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run dev
|
||||
```
|
||||
|
||||
```txt
|
||||
$ vite --mode ssr
|
||||
|
||||
VITE v7.3.1 ssr ready in 433 ms
|
||||
|
||||
➜ Local: http://localhost:5173/
|
||||
➜ Network: use --host to expose
|
||||
➜ press h + enter to show help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Open [http://localhost:5173](http://localhost:5173) in your browser to see the result. Qwik hot-reloads your app as you edit your source files.
|
||||
|
||||
<Frame></Frame>
|
||||
|
||||
---
|
||||
|
||||
See the [Qwik docs](https://qwik.dev/docs/getting-started/) to learn more.
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: Build a React app with Bun
|
||||
sidebarTitle: React with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
Bun has built-in support for `.jsx` and `.tsx` files. React works with Bun.
|
||||
|
||||
Create a new React app with `bun init --react`. This gives you a template with a React app and an API server together in one full-stack app.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
# Create a new React app
|
||||
bun init --react
|
||||
|
||||
# Run the app in development mode
|
||||
bun dev
|
||||
|
||||
# Build as a static site for production
|
||||
bun run build
|
||||
|
||||
# Run the server in production
|
||||
bun start
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Hot Reloading
|
||||
|
||||
Run `bun dev` to start the app in development mode. This starts the API server and the React app with hot reloading.
|
||||
|
||||
### Full-Stack App
|
||||
|
||||
Run `bun start` to start the API server and frontend together in one process.
|
||||
|
||||
### Static Site
|
||||
|
||||
Run `bun run build` to build the app as a static site. This creates a `dist` directory with the built app and its assets.
|
||||
|
||||
```txt File Tree icon="folder-tree"
|
||||
├── src/
|
||||
│ ├── index.ts # Server entry point with API routes
|
||||
│ ├── frontend.tsx # React app entry point with HMR
|
||||
│ ├── App.tsx # Main React component
|
||||
│ ├── APITester.tsx # Component for testing API endpoints
|
||||
│ ├── index.html # HTML template
|
||||
│ ├── index.css # Styles
|
||||
│ └── *.svg # Static assets
|
||||
├── package.json # Dependencies and scripts
|
||||
├── tsconfig.json # TypeScript configuration
|
||||
├── bun-env.d.ts # Type declarations for .svg and .css imports
|
||||
├── bunfig.toml # Bun configuration
|
||||
└── bun.lock # Lock file
|
||||
```
|
||||
@@ -0,0 +1,98 @@
|
||||
---
|
||||
title: Build an app with Remix and Bun
|
||||
sidebarTitle: Remix with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
[Remix 3](https://github.com/remix-run/remix) is a web framework built from standalone packages: a router, HTML rendering, sessions, form parsing, and more. These packages are distributed together as the `remix` package. Remix 3 is published under the `next` tag on npm while it is in beta. Its server runs on Bun as-is.
|
||||
|
||||
<Note>This guide covers Remix 3. To create a Remix 2 project, use `create-remix` instead.</Note>
|
||||
|
||||
---
|
||||
|
||||
Scaffold a new project with the Remix CLI.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bunx remix@next new my-remix-app
|
||||
```
|
||||
|
||||
```txt
|
||||
• Prepare target directory...
|
||||
✓ Prepare target directory
|
||||
• Generate scaffold files...
|
||||
✓ Generate scaffold files
|
||||
• Finalize package.json...
|
||||
✓ Finalize package.json
|
||||
|
||||
Created My Remix App at my-remix-app
|
||||
```
|
||||
|
||||
Then install its dependencies.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
cd my-remix-app
|
||||
bun install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The generated `server.ts` creates a `node:http` server that hands every request to the app's router. Bun runs it directly; pass `--watch` to restart the server whenever a file it imports changes.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun --watch server.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
Server listening on http://localhost:44100
|
||||
```
|
||||
|
||||
Open [http://localhost:44100](http://localhost:44100) to see the starter page. The routes live in `app/routes.ts` and `app/router.ts`. `app/actions/home-page.tsx` renders the starter home page.
|
||||
|
||||
---
|
||||
|
||||
The `scripts` generated in `package.json` run the server with Node.js and the `remix/node-tsx` TypeScript loader. Bun runs TypeScript itself, so point the scripts at `bun` instead.
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development node --watch --import remix/node-tsx server.ts", // [!code --]
|
||||
"dev": "bun --watch server.ts", // [!code ++]
|
||||
"start": "NODE_ENV=production node --import remix/node-tsx server.ts", // [!code --]
|
||||
"start": "NODE_ENV=production bun server.ts" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run dev
|
||||
```
|
||||
|
||||
```txt
|
||||
$ bun --watch server.ts
|
||||
Server listening on http://localhost:44100
|
||||
```
|
||||
|
||||
<Note>
|
||||
The generated `hmr` script (`hmr.ts`) also loads `remix/node-tsx`, which relies on `module.registerHooks()`. Bun does
|
||||
not implement that API yet, so the script fails under Bun. See [Node.js
|
||||
compatibility](/runtime/nodejs-compat#node-module). `bun --watch` restarts the server on changes instead.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
The router is a `fetch` handler, so you can also serve the app with [`Bun.serve()`](/runtime/http/server) instead of `node:http`.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
import { router } from "./app/router.ts";
|
||||
|
||||
const server = Bun.serve({
|
||||
port: 44100,
|
||||
fetch: request => router.fetch(request),
|
||||
});
|
||||
|
||||
console.log(`Server listening on ${server.url}`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See the Remix [documentation](https://github.com/remix-run/remix/tree/main/docs) to learn more.
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: Add Sentry to a Bun app
|
||||
sidebarTitle: Sentry with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
[Sentry](https://sentry.io) is an error tracking and performance monitoring platform. Its Bun SDK, `@sentry/bun`, instruments your application to automatically collect error and performance data.
|
||||
|
||||
If you don't have a Sentry account and project yet, create one at [sentry.io](https://sentry.io/signup/), then return to this page.
|
||||
|
||||
---
|
||||
|
||||
First, install the Sentry Bun SDK.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add @sentry/bun
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then initialize the SDK with your Sentry DSN in its own file. You can find your DSN in your Sentry project settings.
|
||||
|
||||
```ts sentry.ts icon="/icons/typescript.svg"
|
||||
import * as Sentry from "@sentry/bun";
|
||||
|
||||
// Ensure to call this before importing any other modules!
|
||||
Sentry.init({
|
||||
dsn: "__SENTRY_DSN__",
|
||||
|
||||
// Add Performance Monitoring by setting tracesSampleRate
|
||||
// We recommend adjusting this value in production
|
||||
tracesSampleRate: 1.0,
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Start your app with [`--preload`](/runtime) so this file runs before any of your app's modules. Bun evaluates a file's `import`s before its own code, so calling `Sentry.init()` at the top of your entry file would still run after everything that file imports.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun --preload ./sentry.ts index.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Verify that Sentry is working by capturing a test error:
|
||||
|
||||
```ts sentry.ts icon="/icons/typescript.svg"
|
||||
setTimeout(() => {
|
||||
try {
|
||||
foo();
|
||||
} catch (e) {
|
||||
Sentry.captureException(e);
|
||||
}
|
||||
}, 99);
|
||||
```
|
||||
|
||||
To view and resolve the recorded error, log into [sentry.io](https://sentry.io/) and open your project. Clicking the error's title opens a page with details, where you can mark it as resolved.
|
||||
|
||||
---
|
||||
|
||||
To learn more about the Sentry Bun SDK, see the [Sentry documentation](https://docs.sentry.io/platforms/javascript/guides/bun).
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: Build an app with SolidStart and Bun
|
||||
sidebarTitle: "SolidStart with Bun"
|
||||
mode: center
|
||||
---
|
||||
|
||||
Initialize a SolidStart app with `create-solid`. Pass the `--solidstart` flag to create a SolidStart project and `--ts` for TypeScript support. When prompted for a SolidStart version, select `2 (Stable)`. When prompted for a template, select `basic` for a minimal starter app.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun create solid my-app --solidstart --ts
|
||||
```
|
||||
|
||||
```txt
|
||||
┌
|
||||
Create-Solid v0.9.0
|
||||
│
|
||||
◇ Which version of SolidStart?
|
||||
│ 2 (Stable)
|
||||
│
|
||||
◇ Which template would you like to use?
|
||||
│ basic
|
||||
│
|
||||
◇ Project created 🎉
|
||||
│
|
||||
◇ To get started, run: ─╮
|
||||
│ │
|
||||
│ cd my-app │
|
||||
│ bun install │
|
||||
│ bun dev │
|
||||
│ │
|
||||
├────────────────────────╯
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Install the dependencies.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
cd my-app
|
||||
bun install
|
||||
```
|
||||
|
||||
Then run the development server with `bun dev`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun dev
|
||||
```
|
||||
|
||||
```txt
|
||||
$ vite dev
|
||||
|
||||
VITE v8.1.4 ready in 818 ms
|
||||
|
||||
➜ Local: http://localhost:3000/
|
||||
➜ Network: use --host to expose
|
||||
```
|
||||
|
||||
Open [localhost:3000](http://localhost:3000). The development server automatically hot-reloads changes you make to `src/routes/index.tsx`.
|
||||
|
||||
---
|
||||
|
||||
See the [SolidStart docs](https://docs.solidjs.com/solid-start) to learn more.
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Server-side render (SSR) a React component
|
||||
sidebarTitle: "SSR React with Bun"
|
||||
mode: center
|
||||
---
|
||||
|
||||
Install `react` and `react-dom`:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
# Any package manager can be used
|
||||
bun add react react-dom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To render a React component to an HTML stream server-side (SSR):
|
||||
|
||||
```tsx ssr-react.tsx icon="file-code"
|
||||
import { renderToReadableStream } from "react-dom/server";
|
||||
|
||||
function Component(props: { message: string }) {
|
||||
return (
|
||||
<body>
|
||||
<h1>{props.message}</h1>
|
||||
</body>
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await renderToReadableStream(<Component message="Hello from server!" />);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Combine this with `Bun.serve()` to get an SSR HTTP server:
|
||||
|
||||
```tsx server.tsx icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
async fetch() {
|
||||
const stream = await renderToReadableStream(<Component message="Hello from server!" />);
|
||||
return new Response(stream, {
|
||||
headers: { "Content-Type": "text/html" },
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
React `19` and later include an [SSR optimization](https://github.com/react/react/pull/25597) that takes advantage of Bun's "direct" `ReadableStream` implementation. If you run into an error like `export named 'renderToReadableStream' not found`, install version `19` of `react` and `react-dom`, or import from `react-dom/server.browser` instead of `react-dom/server`. See [react/react#28941](https://github.com/react/react/issues/28941) for details.
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
title: Build an app with SvelteKit and Bun
|
||||
sidebarTitle: "SvelteKit with Bun"
|
||||
mode: center
|
||||
---
|
||||
|
||||
Use `sv create my-app` to create a SvelteKit project with the Svelte CLI. Answer the prompts to select a template and set up your development environment.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bunx sv create my-app
|
||||
```
|
||||
|
||||
```txt
|
||||
┌ Welcome to the Svelte CLI! (v0.5.7)
|
||||
│
|
||||
◇ Which template would you like?
|
||||
│ SvelteKit demo
|
||||
│
|
||||
◇ Add type checking with Typescript?
|
||||
│ Yes, using Typescript syntax
|
||||
│
|
||||
◆ Project created
|
||||
│
|
||||
◇ What would you like to add to your project?
|
||||
│ none
|
||||
│
|
||||
◇ Which package manager do you want to install dependencies with?
|
||||
│ bun
|
||||
│
|
||||
◇ Successfully installed dependencies
|
||||
│
|
||||
◇ Project next steps ─────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ 1: cd my-app │
|
||||
│ 2: git init && git add -A && git commit -m "Initial commit" (optional) │
|
||||
│ 3: bun run dev -- --open │
|
||||
│ │
|
||||
│ To close the dev server, hit Ctrl-C │
|
||||
│ │
|
||||
│ Stuck? Visit us at https://svelte.dev/chat │
|
||||
│ │
|
||||
├──────────────────────────────────────────────────────────────────────────╯
|
||||
│
|
||||
└ You're all set!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Once the project is initialized, `cd` into the new project. The dependencies are already installed, so you don't need to run `bun install`.
|
||||
|
||||
Then start the development server with `bun --bun run dev`.
|
||||
|
||||
To run the dev server with Node.js instead of Bun, omit the `--bun` flag.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
cd my-app
|
||||
bun --bun run dev
|
||||
```
|
||||
|
||||
```txt
|
||||
$ vite dev
|
||||
Forced re-optimization of dependencies
|
||||
|
||||
VITE v5.4.10 ready in 424 ms
|
||||
|
||||
➜ Local: http://localhost:5173/
|
||||
➜ Network: use --host to expose
|
||||
➜ press h + enter to show help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Visit [http://localhost:5173](http://localhost:5173/) in a browser to see the template app.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
---
|
||||
|
||||
Edit and save `src/routes/+page.svelte` and the dev server hot-reloads your changes in the browser.
|
||||
|
||||
---
|
||||
|
||||
To build for production, you need a SvelteKit adapter. We recommend `svelte-adapter-bun`; install it with `bun add -D svelte-adapter-bun`.
|
||||
|
||||
Then make the following changes to your `vite.config.ts` (or `vite.config.js`). If your project configures SvelteKit in a `svelte.config.js` instead, swap the adapter import there.
|
||||
|
||||
```ts vite.config.ts icon="/icons/typescript.svg"
|
||||
import adapter from "@sveltejs/adapter-auto"; // [!code --]
|
||||
import adapter from "svelte-adapter-bun"; // [!code ++]
|
||||
import { sveltekit } from "@sveltejs/kit/vite";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
sveltekit({
|
||||
compilerOptions: {
|
||||
// Force runes mode for the project, except for libraries. Can be removed in svelte 6.
|
||||
runes: ({ filename }) => (filename.split(/[/\\]/).includes("node_modules") ? undefined : true),
|
||||
},
|
||||
|
||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||
adapter: adapter(),
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To build a production bundle:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun --bun run build
|
||||
```
|
||||
|
||||
```txt
|
||||
$ vite build
|
||||
vite v5.4.10 building SSR bundle for production...
|
||||
"confetti" is imported from external module "@neoconfetti/svelte" but never used in "src/routes/sverdle/+page.svelte".
|
||||
✓ 130 modules transformed.
|
||||
vite v5.4.10 building for production...
|
||||
✓ 148 modules transformed.
|
||||
...
|
||||
✓ built in 231ms
|
||||
...
|
||||
✓ built in 899ms
|
||||
|
||||
Run npm run preview to preview your production build locally.
|
||||
|
||||
> Using svelte-adapter-bun
|
||||
✔ done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then start the production server with `bun ./build/index.js`. It listens on port `3000` by default; set the `PORT` environment variable to change it.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun ./build/index.js
|
||||
```
|
||||
|
||||
```txt
|
||||
Listening on http://0.0.0.0:3000/
|
||||
```
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
title: Run Bun as a daemon with systemd
|
||||
sidebarTitle: "systemd with Bun"
|
||||
mode: center
|
||||
---
|
||||
|
||||
[systemd](https://systemd.io) is an init system and service manager for Linux. It manages the startup and control of system processes and services.
|
||||
|
||||
---
|
||||
|
||||
To run a Bun application as a daemon with **systemd**, create a _service file_ in `/etc/systemd/system/`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
cd /etc/systemd/system
|
||||
touch my-app.service
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Here is a typical service file that runs an application on system start. Use it as a template for your own service. Replace `YOUR_USER` with the name of the user to run the application as. To run as `root`, replace `YOUR_USER` with `root` and `/home/YOUR_USER` with `/root` (root's home directory). For security reasons, we don't recommend running as `root`.
|
||||
|
||||
Refer to the [systemd documentation](https://www.freedesktop.org/software/systemd/man/systemd.service.html) for details on each setting.
|
||||
|
||||
```ini my-app.service icon="file-code"
|
||||
[Unit]
|
||||
# describe the app
|
||||
Description=My App
|
||||
# start the app after the network management stack has started
|
||||
# (this does not wait for the network to be up, see https://systemd.io/NETWORK_ONLINE)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
# usually you'll use 'simple'
|
||||
# one of https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type=
|
||||
Type=simple
|
||||
# which user to use when starting the app
|
||||
User=YOUR_USER
|
||||
# path to your application's root directory
|
||||
WorkingDirectory=/home/YOUR_USER/path/to/my-app
|
||||
# the command to start the app
|
||||
# requires absolute paths
|
||||
ExecStart=/home/YOUR_USER/.bun/bin/bun run index.ts
|
||||
# restart policy
|
||||
# one of {no|on-success|on-failure|on-abnormal|on-watchdog|on-abort|always}
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
# start the app automatically
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
If your application starts a webserver, non-`root` users cannot listen on ports 80 or 443 by default. To allow Bun to listen on these ports when run by a non-`root` user, use the following command. The command requires `sudo` permissions. This step isn't necessary when running as `root`.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
setcap CAP_NET_BIND_SERVICE=+eip /home/YOUR_USER/.bun/bin/bun
|
||||
```
|
||||
|
||||
The command attaches the capability to the `bun` binary itself. Replacing the binary, for example with `bun upgrade`, removes the capability. Re-run the command after upgrading. Alternatively, add `AmbientCapabilities=CAP_NET_BIND_SERVICE` to the `[Service]` section of the service file instead.
|
||||
|
||||
---
|
||||
|
||||
With the service file configured, _enable_ the service. Once enabled, it starts automatically on reboot. Enabling the service requires `sudo` permissions.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
systemctl enable my-app
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To start the service without rebooting, _start_ it manually.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
systemctl start my-app
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Check the status of your application with `systemctl status`. If the app started successfully, the output looks like this:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
systemctl status my-app
|
||||
```
|
||||
|
||||
```txt
|
||||
● my-app.service - My App
|
||||
Loaded: loaded (/etc/systemd/system/my-app.service; enabled; preset: enabled)
|
||||
Active: active (running) since Thu 2023-10-12 11:34:08 UTC; 1h 8min ago
|
||||
Main PID: 309641 (bun)
|
||||
Tasks: 3 (limit: 503)
|
||||
Memory: 40.9M
|
||||
CPU: 1.093s
|
||||
CGroup: /system.slice/my-app.service
|
||||
└─309641 /home/YOUR_USER/.bun/bin/bun run index.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To update the service, edit the service file, then reload the daemon.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
For a complete guide to service unit configuration, see the [systemd.service documentation](https://www.freedesktop.org/software/systemd/man/systemd.service.html). Or use this cheatsheet of common commands:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
systemctl daemon-reload # tell systemd that some files got changed
|
||||
systemctl enable my-app # enable the app (to allow auto-start)
|
||||
systemctl disable my-app # disable the app (turns off auto-start)
|
||||
systemctl start my-app # start the app if is stopped
|
||||
systemctl stop my-app # stop the app
|
||||
systemctl restart my-app # restart the app
|
||||
```
|
||||
@@ -0,0 +1,789 @@
|
||||
---
|
||||
title: Use TanStack Start with Bun
|
||||
sidebarTitle: TanStack Start with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
[TanStack Start](https://tanstack.com/start/latest) is a full-stack framework powered by TanStack Router and [Vite](https://vite.dev/). It supports full-document SSR, streaming, server functions, and bundling.
|
||||
|
||||
---
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a new TanStack Start app">
|
||||
Use the interactive CLI to create a new TanStack Start app.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bunx @tanstack/cli create my-tanstack-app
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Start the dev server">
|
||||
Change to the project directory and start the Vite dev server with Bun.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
cd my-tanstack-app
|
||||
bun --bun run dev
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Update scripts in package.json">
|
||||
In the scripts field of your `package.json`, prefix the Vite CLI commands with `bun --bun` so that Bun runs the Vite CLI for `dev`, `build`, and `preview`.
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "bun --bun vite dev", // [!code ++]
|
||||
"build": "bun --bun vite build", // [!code ++]
|
||||
"preview": "bun --bun vite preview" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
---
|
||||
|
||||
## Hosting
|
||||
|
||||
To host your TanStack Start app in production, use [Nitro](https://nitro.build/) or a custom Bun server.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Nitro">
|
||||
<Steps>
|
||||
<Step title="Add Nitro to your project">
|
||||
Add [Nitro](https://nitro.build/) to your project to deploy your TanStack Start app to different platforms.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add nitro
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title={<span>Update your <code>vite.config.ts</code> file</span>}>
|
||||
Add the Nitro plugin to your `vite.config.ts` file.
|
||||
|
||||
```ts vite.config.ts icon="/icons/typescript.svg"
|
||||
// other imports...
|
||||
import { nitro } from "nitro/vite"; // [!code ++]
|
||||
|
||||
const config = defineConfig({
|
||||
plugins: [
|
||||
tanstackStart(),
|
||||
nitro({ preset: "bun" }), // [!code ++]
|
||||
// other plugins...
|
||||
],
|
||||
});
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
<Note>
|
||||
The `bun` preset is optional, but it configures the build output specifically for Bun's runtime.
|
||||
</Note>
|
||||
|
||||
</Step>
|
||||
<Step title="Update the start command">
|
||||
Make sure `build` and `start` scripts are present in your `package.json` file:
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"scripts": {
|
||||
"build": "bun --bun vite build", // [!code ++]
|
||||
// The .output files are created by Nitro when you run `bun run build`.
|
||||
// Not necessary when deploying to Vercel.
|
||||
"start": "bun run .output/server/index.mjs" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Note>
|
||||
You do **not** need the custom `start` script when deploying to Vercel.
|
||||
</Note>
|
||||
|
||||
</Step>
|
||||
<Step title="Deploy your app">
|
||||
Use one of the following guides to deploy your app to a hosting provider.
|
||||
|
||||
<Note>
|
||||
When deploying to Vercel, either add `"bunVersion": "1.x"` to your `vercel.json` file, or set the Bun version in the `nitro` config in your `vite.config.ts` file:
|
||||
|
||||
<Warning>
|
||||
Do **not** use the `bun` Nitro preset when deploying to Vercel.
|
||||
</Warning>
|
||||
|
||||
```ts vite.config.ts icon="/icons/typescript.svg"
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
tanstackStart(),
|
||||
nitro({
|
||||
preset: "bun", // [!code --]
|
||||
vercel: { // [!code ++]
|
||||
functions: { // [!code ++]
|
||||
runtime: "bun1.x", // [!code ++]
|
||||
}, // [!code ++]
|
||||
}, // [!code ++]
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
</Note>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
</Tab>
|
||||
<Tab title="Custom Server">
|
||||
<Note>
|
||||
This custom server is based on [TanStack's Bun template](https://github.com/TanStack/router/blob/main/examples/react/start-bun/server.ts). It gives you fine-grained control over static asset serving: the server preloads small files into memory and serves larger files on-demand. You can configure the limits on what the server preloads.
|
||||
</Note>
|
||||
|
||||
<Steps>
|
||||
<Step title="Create the production server">
|
||||
Create a `server.ts` file in your project root:
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg" expandable
|
||||
/**
|
||||
* TanStack Start Production Server with Bun
|
||||
*
|
||||
* A high-performance production server for TanStack Start applications that
|
||||
* implements intelligent static asset loading with configurable memory management.
|
||||
*
|
||||
* Features:
|
||||
* - Hybrid loading strategy (preload small files, serve large files on-demand)
|
||||
* - Configurable file filtering with include/exclude patterns
|
||||
* - Memory-efficient response generation
|
||||
* - Production-ready caching headers
|
||||
*
|
||||
* Environment Variables:
|
||||
*
|
||||
* PORT (number)
|
||||
* - Server port number
|
||||
* - Default: 3000
|
||||
*
|
||||
* ASSET_PRELOAD_MAX_SIZE (number)
|
||||
* - Maximum file size in bytes to preload into memory
|
||||
* - Files larger than this will be served on-demand from disk
|
||||
* - Default: 5242880 (5MB)
|
||||
* - Example: ASSET_PRELOAD_MAX_SIZE=5242880 (5MB)
|
||||
*
|
||||
* ASSET_PRELOAD_INCLUDE_PATTERNS (string)
|
||||
* - Comma-separated list of glob patterns for files to include
|
||||
* - If specified, only matching files are eligible for preloading
|
||||
* - Patterns are matched against filenames only, not full paths
|
||||
* - Example: ASSET_PRELOAD_INCLUDE_PATTERNS="*.js,*.css,*.woff2"
|
||||
*
|
||||
* ASSET_PRELOAD_EXCLUDE_PATTERNS (string)
|
||||
* - Comma-separated list of glob patterns for files to exclude
|
||||
* - Applied after include patterns
|
||||
* - Patterns are matched against filenames only, not full paths
|
||||
* - Example: ASSET_PRELOAD_EXCLUDE_PATTERNS="*.map,*.txt"
|
||||
*
|
||||
* ASSET_PRELOAD_VERBOSE_LOGGING (boolean)
|
||||
* - Enable detailed logging of loaded and skipped files
|
||||
* - Default: false
|
||||
* - Set to "true" to enable verbose output
|
||||
*
|
||||
* ASSET_PRELOAD_ENABLE_ETAG (boolean)
|
||||
* - Enable ETag generation for preloaded assets
|
||||
* - Default: true
|
||||
* - Set to "false" to disable ETag support
|
||||
*
|
||||
* ASSET_PRELOAD_ENABLE_GZIP (boolean)
|
||||
* - Enable Gzip compression for eligible assets
|
||||
* - Default: true
|
||||
* - Set to "false" to disable Gzip compression
|
||||
*
|
||||
* ASSET_PRELOAD_GZIP_MIN_SIZE (number)
|
||||
* - Minimum file size in bytes required for Gzip compression
|
||||
* - Files smaller than this will not be compressed
|
||||
* - Default: 1024 (1KB)
|
||||
*
|
||||
* ASSET_PRELOAD_GZIP_MIME_TYPES (string)
|
||||
* - Comma-separated list of MIME types eligible for Gzip compression
|
||||
* - Supports partial matching for types ending with "/"
|
||||
* - Default: text/,application/javascript,application/json,application/xml,image/svg+xml
|
||||
*
|
||||
* Usage:
|
||||
* bun run server.ts
|
||||
*/
|
||||
|
||||
import path from 'node:path'
|
||||
|
||||
// Configuration
|
||||
const SERVER_PORT = Number(process.env.PORT ?? 3000)
|
||||
const CLIENT_DIRECTORY = './dist/client'
|
||||
const SERVER_ENTRY_POINT = './dist/server/server.js'
|
||||
|
||||
// Logging utilities for professional output
|
||||
const log = {
|
||||
info: (message: string) => {
|
||||
console.log(`[INFO] ${message}`)
|
||||
},
|
||||
success: (message: string) => {
|
||||
console.log(`[SUCCESS] ${message}`)
|
||||
},
|
||||
warning: (message: string) => {
|
||||
console.log(`[WARNING] ${message}`)
|
||||
},
|
||||
error: (message: string) => {
|
||||
console.log(`[ERROR] ${message}`)
|
||||
},
|
||||
header: (message: string) => {
|
||||
console.log(`\n${message}\n`)
|
||||
},
|
||||
}
|
||||
|
||||
// Preloading configuration from environment variables
|
||||
const MAX_PRELOAD_BYTES = Number(
|
||||
process.env.ASSET_PRELOAD_MAX_SIZE ?? 5 * 1024 * 1024, // 5MB default
|
||||
)
|
||||
|
||||
// Parse comma-separated include patterns (no defaults)
|
||||
const INCLUDE_PATTERNS = (process.env.ASSET_PRELOAD_INCLUDE_PATTERNS ?? '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.map((pattern: string) => convertGlobToRegExp(pattern))
|
||||
|
||||
// Parse comma-separated exclude patterns (no defaults)
|
||||
const EXCLUDE_PATTERNS = (process.env.ASSET_PRELOAD_EXCLUDE_PATTERNS ?? '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.map((pattern: string) => convertGlobToRegExp(pattern))
|
||||
|
||||
// Verbose logging flag
|
||||
const VERBOSE = process.env.ASSET_PRELOAD_VERBOSE_LOGGING === 'true'
|
||||
|
||||
// Optional ETag feature
|
||||
const ENABLE_ETAG = (process.env.ASSET_PRELOAD_ENABLE_ETAG ?? 'true') === 'true'
|
||||
|
||||
// Optional Gzip feature
|
||||
const ENABLE_GZIP = (process.env.ASSET_PRELOAD_ENABLE_GZIP ?? 'true') === 'true'
|
||||
const GZIP_MIN_BYTES = Number(process.env.ASSET_PRELOAD_GZIP_MIN_SIZE ?? 1024) // 1KB
|
||||
const GZIP_TYPES = (
|
||||
process.env.ASSET_PRELOAD_GZIP_MIME_TYPES ??
|
||||
'text/,application/javascript,application/json,application/xml,image/svg+xml'
|
||||
)
|
||||
.split(',')
|
||||
.map((v) => v.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
/**
|
||||
* Convert a simple glob pattern to a regular expression
|
||||
* Supports * wildcard for matching any characters
|
||||
*/
|
||||
function convertGlobToRegExp(globPattern: string): RegExp {
|
||||
// Escape regex special chars except *, then replace * with .*
|
||||
const escapedPattern = globPattern
|
||||
.replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&')
|
||||
.replace(/\*/g, '.*')
|
||||
return new RegExp(`^${escapedPattern}$`, 'i')
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute ETag for a given data buffer
|
||||
*/
|
||||
function computeEtag(data: Uint8Array): string {
|
||||
const hash = Bun.hash(data)
|
||||
return `W/"${hash.toString(16)}-${data.byteLength.toString()}"`
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata for preloaded static assets
|
||||
*/
|
||||
interface AssetMetadata {
|
||||
route: string
|
||||
size: number
|
||||
type: string
|
||||
}
|
||||
|
||||
/**
|
||||
* In-memory asset with ETag and Gzip support
|
||||
*/
|
||||
interface InMemoryAsset {
|
||||
raw: Uint8Array
|
||||
gz?: Uint8Array
|
||||
etag?: string
|
||||
type: string
|
||||
immutable: boolean
|
||||
size: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of static asset preloading process
|
||||
*/
|
||||
interface PreloadResult {
|
||||
routes: Record<string, (req: Request) => Response | Promise<Response>>
|
||||
loaded: AssetMetadata[]
|
||||
skipped: AssetMetadata[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file is eligible for preloading based on configured patterns
|
||||
*/
|
||||
function isFileEligibleForPreloading(relativePath: string): boolean {
|
||||
const fileName = relativePath.split(/[/\\]/).pop() ?? relativePath
|
||||
|
||||
// If include patterns are specified, file must match at least one
|
||||
if (INCLUDE_PATTERNS.length > 0) {
|
||||
if (!INCLUDE_PATTERNS.some((pattern) => pattern.test(fileName))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// If exclude patterns are specified, file must not match any
|
||||
if (EXCLUDE_PATTERNS.some((pattern) => pattern.test(fileName))) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a MIME type is compressible
|
||||
*/
|
||||
function isMimeTypeCompressible(mimeType: string): boolean {
|
||||
return GZIP_TYPES.some((type) =>
|
||||
type.endsWith('/') ? mimeType.startsWith(type) : mimeType === type,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditionally compress data based on size and MIME type
|
||||
*/
|
||||
function compressDataIfAppropriate(
|
||||
data: Uint8Array,
|
||||
mimeType: string,
|
||||
): Uint8Array | undefined {
|
||||
if (!ENABLE_GZIP) return undefined
|
||||
if (data.byteLength < GZIP_MIN_BYTES) return undefined
|
||||
if (!isMimeTypeCompressible(mimeType)) return undefined
|
||||
try {
|
||||
return Bun.gzipSync(data.buffer as ArrayBuffer)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create response handler function with ETag and Gzip support
|
||||
*/
|
||||
function createResponseHandler(
|
||||
asset: InMemoryAsset,
|
||||
): (req: Request) => Response {
|
||||
return (req: Request) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': asset.type,
|
||||
'Cache-Control': asset.immutable
|
||||
? 'public, max-age=31536000, immutable'
|
||||
: 'public, max-age=3600',
|
||||
}
|
||||
|
||||
if (ENABLE_ETAG && asset.etag) {
|
||||
const ifNone = req.headers.get('if-none-match')
|
||||
if (ifNone && ifNone === asset.etag) {
|
||||
return new Response(null, {
|
||||
status: 304,
|
||||
headers: { ETag: asset.etag },
|
||||
})
|
||||
}
|
||||
headers.ETag = asset.etag
|
||||
}
|
||||
|
||||
if (
|
||||
ENABLE_GZIP &&
|
||||
asset.gz &&
|
||||
req.headers.get('accept-encoding')?.includes('gzip')
|
||||
) {
|
||||
headers['Content-Encoding'] = 'gzip'
|
||||
headers['Content-Length'] = String(asset.gz.byteLength)
|
||||
const gzCopy = new Uint8Array(asset.gz)
|
||||
return new Response(gzCopy, { status: 200, headers })
|
||||
}
|
||||
|
||||
headers['Content-Length'] = String(asset.raw.byteLength)
|
||||
const rawCopy = new Uint8Array(asset.raw)
|
||||
return new Response(rawCopy, { status: 200, headers })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create composite glob pattern from include patterns
|
||||
*/
|
||||
function createCompositeGlobPattern(): Bun.Glob {
|
||||
const raw = (process.env.ASSET_PRELOAD_INCLUDE_PATTERNS ?? '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
if (raw.length === 0) return new Bun.Glob('**/*')
|
||||
if (raw.length === 1) return new Bun.Glob(raw[0])
|
||||
return new Bun.Glob(`{${raw.join(',')}}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize static routes with intelligent preloading strategy
|
||||
* Small files are loaded into memory, large files are served on-demand
|
||||
*/
|
||||
async function initializeStaticRoutes(
|
||||
clientDirectory: string,
|
||||
): Promise<PreloadResult> {
|
||||
const routes: Record<string, (req: Request) => Response | Promise<Response>> =
|
||||
{}
|
||||
const loaded: AssetMetadata[] = []
|
||||
const skipped: AssetMetadata[] = []
|
||||
|
||||
log.info(`Loading static assets from ${clientDirectory}...`)
|
||||
if (VERBOSE) {
|
||||
console.log(
|
||||
`Max preload size: ${(MAX_PRELOAD_BYTES / 1024 / 1024).toFixed(2)} MB`,
|
||||
)
|
||||
if (INCLUDE_PATTERNS.length > 0) {
|
||||
console.log(
|
||||
`Include patterns: ${process.env.ASSET_PRELOAD_INCLUDE_PATTERNS ?? ''}`,
|
||||
)
|
||||
}
|
||||
if (EXCLUDE_PATTERNS.length > 0) {
|
||||
console.log(
|
||||
`Exclude patterns: ${process.env.ASSET_PRELOAD_EXCLUDE_PATTERNS ?? ''}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let totalPreloadedBytes = 0
|
||||
|
||||
try {
|
||||
const glob = createCompositeGlobPattern()
|
||||
for await (const relativePath of glob.scan({ cwd: clientDirectory })) {
|
||||
const filepath = path.join(clientDirectory, relativePath)
|
||||
const route = `/${relativePath.split(path.sep).join(path.posix.sep)}`
|
||||
|
||||
try {
|
||||
// Get file metadata
|
||||
const file = Bun.file(filepath)
|
||||
|
||||
// Skip if file doesn't exist or is empty
|
||||
if (!(await file.exists()) || file.size === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const metadata: AssetMetadata = {
|
||||
route,
|
||||
size: file.size,
|
||||
type: file.type || 'application/octet-stream',
|
||||
}
|
||||
|
||||
// Determine if file should be preloaded
|
||||
const matchesPattern = isFileEligibleForPreloading(relativePath)
|
||||
const withinSizeLimit = file.size <= MAX_PRELOAD_BYTES
|
||||
|
||||
if (matchesPattern && withinSizeLimit) {
|
||||
// Preload small files into memory with ETag and Gzip support
|
||||
const bytes = new Uint8Array(await file.arrayBuffer())
|
||||
const gz = compressDataIfAppropriate(bytes, metadata.type)
|
||||
const etag = ENABLE_ETAG ? computeEtag(bytes) : undefined
|
||||
const asset: InMemoryAsset = {
|
||||
raw: bytes,
|
||||
gz,
|
||||
etag,
|
||||
type: metadata.type,
|
||||
immutable: true,
|
||||
size: bytes.byteLength,
|
||||
}
|
||||
routes[route] = createResponseHandler(asset)
|
||||
|
||||
loaded.push({ ...metadata, size: bytes.byteLength })
|
||||
totalPreloadedBytes += bytes.byteLength
|
||||
} else {
|
||||
// Serve large or filtered files on-demand
|
||||
routes[route] = () => {
|
||||
const fileOnDemand = Bun.file(filepath)
|
||||
return new Response(fileOnDemand, {
|
||||
headers: {
|
||||
'Content-Type': metadata.type,
|
||||
'Cache-Control': 'public, max-age=3600',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
skipped.push(metadata)
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error && error.name !== 'EISDIR') {
|
||||
log.error(`Failed to load ${filepath}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show detailed file overview only when verbose mode is enabled
|
||||
if (VERBOSE && (loaded.length > 0 || skipped.length > 0)) {
|
||||
const allFiles = [...loaded, ...skipped].sort((a, b) =>
|
||||
a.route.localeCompare(b.route),
|
||||
)
|
||||
|
||||
// Calculate max path length for alignment
|
||||
const maxPathLength = Math.min(
|
||||
Math.max(...allFiles.map((f) => f.route.length)),
|
||||
60,
|
||||
)
|
||||
|
||||
// Format file size with KB and actual gzip size
|
||||
const formatFileSize = (bytes: number, gzBytes?: number) => {
|
||||
const kb = bytes / 1024
|
||||
const sizeStr = kb < 100 ? kb.toFixed(2) : kb.toFixed(1)
|
||||
|
||||
if (gzBytes !== undefined) {
|
||||
const gzKb = gzBytes / 1024
|
||||
const gzStr = gzKb < 100 ? gzKb.toFixed(2) : gzKb.toFixed(1)
|
||||
return {
|
||||
size: sizeStr,
|
||||
gzip: gzStr,
|
||||
}
|
||||
}
|
||||
|
||||
// Rough gzip estimation (typically 30-70% compression) if no actual gzip data
|
||||
const gzipKb = kb * 0.35
|
||||
return {
|
||||
size: sizeStr,
|
||||
gzip: gzipKb < 100 ? gzipKb.toFixed(2) : gzipKb.toFixed(1),
|
||||
}
|
||||
}
|
||||
|
||||
if (loaded.length > 0) {
|
||||
console.log('\n📁 Preloaded into memory:')
|
||||
console.log(
|
||||
'Path │ Size │ Gzip Size',
|
||||
)
|
||||
loaded
|
||||
.sort((a, b) => a.route.localeCompare(b.route))
|
||||
.forEach((file) => {
|
||||
const { size, gzip } = formatFileSize(file.size)
|
||||
const paddedPath = file.route.padEnd(maxPathLength)
|
||||
const sizeStr = `${size.padStart(7)} kB`
|
||||
const gzipStr = `${gzip.padStart(7)} kB`
|
||||
console.log(`${paddedPath} │ ${sizeStr} │ ${gzipStr}`)
|
||||
})
|
||||
}
|
||||
|
||||
if (skipped.length > 0) {
|
||||
console.log('\n💾 Served on-demand:')
|
||||
console.log(
|
||||
'Path │ Size │ Gzip Size',
|
||||
)
|
||||
skipped
|
||||
.sort((a, b) => a.route.localeCompare(b.route))
|
||||
.forEach((file) => {
|
||||
const { size, gzip } = formatFileSize(file.size)
|
||||
const paddedPath = file.route.padEnd(maxPathLength)
|
||||
const sizeStr = `${size.padStart(7)} kB`
|
||||
const gzipStr = `${gzip.padStart(7)} kB`
|
||||
console.log(`${paddedPath} │ ${sizeStr} │ ${gzipStr}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Show detailed verbose info if enabled
|
||||
if (VERBOSE) {
|
||||
if (loaded.length > 0 || skipped.length > 0) {
|
||||
const allFiles = [...loaded, ...skipped].sort((a, b) =>
|
||||
a.route.localeCompare(b.route),
|
||||
)
|
||||
console.log('\n📊 Detailed file information:')
|
||||
console.log(
|
||||
'Status │ Path │ MIME Type │ Reason',
|
||||
)
|
||||
allFiles.forEach((file) => {
|
||||
const isPreloaded = loaded.includes(file)
|
||||
const status = isPreloaded ? 'MEMORY' : 'ON-DEMAND'
|
||||
const reason =
|
||||
!isPreloaded && file.size > MAX_PRELOAD_BYTES
|
||||
? 'too large'
|
||||
: !isPreloaded
|
||||
? 'filtered'
|
||||
: 'preloaded'
|
||||
const route =
|
||||
file.route.length > 30
|
||||
? file.route.substring(0, 27) + '...'
|
||||
: file.route
|
||||
console.log(
|
||||
`${status.padEnd(12)} │ ${route.padEnd(30)} │ ${file.type.padEnd(28)} │ ${reason.padEnd(10)}`,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
console.log('\n📊 No files found to display')
|
||||
}
|
||||
}
|
||||
|
||||
// Log summary after the file list
|
||||
console.log() // Empty line for separation
|
||||
if (loaded.length > 0) {
|
||||
log.success(
|
||||
`Preloaded ${String(loaded.length)} files (${(totalPreloadedBytes / 1024 / 1024).toFixed(2)} MB) into memory`,
|
||||
)
|
||||
} else {
|
||||
log.info('No files preloaded into memory')
|
||||
}
|
||||
|
||||
if (skipped.length > 0) {
|
||||
const tooLarge = skipped.filter((f) => f.size > MAX_PRELOAD_BYTES).length
|
||||
const filtered = skipped.length - tooLarge
|
||||
log.info(
|
||||
`${String(skipped.length)} files will be served on-demand (${String(tooLarge)} too large, ${String(filtered)} filtered)`,
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Failed to load static files from ${clientDirectory}: ${String(error)}`,
|
||||
)
|
||||
}
|
||||
|
||||
return { routes, loaded, skipped }
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the server
|
||||
*/
|
||||
async function initializeServer() {
|
||||
log.header('Starting Production Server')
|
||||
|
||||
// Load TanStack Start server handler
|
||||
let handler: { fetch: (request: Request) => Response | Promise<Response> }
|
||||
try {
|
||||
const serverModule = (await import(SERVER_ENTRY_POINT)) as {
|
||||
default: { fetch: (request: Request) => Response | Promise<Response> }
|
||||
}
|
||||
handler = serverModule.default
|
||||
log.success('TanStack Start application handler initialized')
|
||||
} catch (error) {
|
||||
log.error(`Failed to load server handler: ${String(error)}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Build static routes with intelligent preloading
|
||||
const { routes } = await initializeStaticRoutes(CLIENT_DIRECTORY)
|
||||
|
||||
// Create Bun server
|
||||
const server = Bun.serve({
|
||||
port: SERVER_PORT,
|
||||
|
||||
routes: {
|
||||
// Serve static assets (preloaded or on-demand)
|
||||
...routes,
|
||||
|
||||
// Fallback to TanStack Start handler for all other routes
|
||||
'/*': (req: Request) => {
|
||||
try {
|
||||
return handler.fetch(req)
|
||||
} catch (error) {
|
||||
log.error(`Server handler error: ${String(error)}`)
|
||||
return new Response('Internal Server Error', { status: 500 })
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Global error handler
|
||||
error(error) {
|
||||
log.error(
|
||||
`Uncaught server error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
return new Response('Internal Server Error', { status: 500 })
|
||||
},
|
||||
})
|
||||
|
||||
log.success(`Server listening on http://localhost:${String(server.port)}`)
|
||||
}
|
||||
|
||||
// Initialize the server
|
||||
initializeServer().catch((error: unknown) => {
|
||||
log.error(`Failed to start server: ${String(error)}`)
|
||||
process.exit(1)
|
||||
})
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Update package.json scripts">
|
||||
Add a `start` script to run the custom server:
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"scripts": {
|
||||
"build": "bun --bun vite build",
|
||||
"start": "bun run server.ts" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Build and run">
|
||||
Build your application and start the server:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run build
|
||||
bun run start
|
||||
```
|
||||
|
||||
The server listens on port 3000 by default; set the `PORT` environment variable to change it.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Columns cols={3}>
|
||||
<Card title="Vercel" href="/guides/deployment/vercel" icon="/icons/ecosystem/vercel.svg">
|
||||
Deploy on Vercel
|
||||
</Card>
|
||||
<Card title="Render" href="/guides/deployment/render" icon="/icons/ecosystem/render.svg">
|
||||
Deploy on Render
|
||||
</Card>
|
||||
<Card title="Railway" href="/guides/deployment/railway" icon="/icons/ecosystem/railway.svg">
|
||||
Deploy on Railway
|
||||
</Card>
|
||||
<Card title="DigitalOcean" href="/guides/deployment/digital-ocean" icon="/icons/ecosystem/digitalocean.svg">
|
||||
Deploy on DigitalOcean
|
||||
</Card>
|
||||
<Card title="AWS Lambda" href="/guides/deployment/aws-lambda" icon="/icons/ecosystem/aws.svg">
|
||||
Deploy on AWS Lambda
|
||||
</Card>
|
||||
<Card title="Google Cloud Run" href="/guides/deployment/google-cloud-run" icon="/icons/ecosystem/gcp.svg">
|
||||
Deploy on Google Cloud Run
|
||||
</Card>
|
||||
</Columns>
|
||||
|
||||
---
|
||||
|
||||
## Templates
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card
|
||||
title="Todo App with Tanstack + Bun"
|
||||
img="/images/templates/bun-tanstack-todo.png"
|
||||
href="https://github.com/bun-templates/bun-tanstack-todo"
|
||||
arrow="true"
|
||||
cta="Go to template"
|
||||
>
|
||||
A Todo application built with Bun, TanStack Start, and PostgreSQL.
|
||||
</Card>
|
||||
<Card
|
||||
title="Bun + TanStack Start Application"
|
||||
img="/images/templates/bun-tanstack-basic.png"
|
||||
href="https://github.com/bun-templates/bun-tanstack-basic"
|
||||
arrow="true"
|
||||
cta="Go to template"
|
||||
>
|
||||
A TanStack Start template using Bun with SSR and file-based routing.
|
||||
</Card>
|
||||
<Card
|
||||
title="Basic Bun + Tanstack Starter"
|
||||
img="/images/templates/bun-tanstack-start.png"
|
||||
href="https://github.com/bun-templates/bun-tanstack-start"
|
||||
arrow="true"
|
||||
cta="Go to template"
|
||||
>
|
||||
The basic TanStack starter using the Bun runtime and Bun's file APIs.
|
||||
</Card>
|
||||
</Columns>
|
||||
|
||||
---
|
||||
|
||||
[→ See TanStack Start's hosting documentation](https://tanstack.com/start/latest/docs/framework/react/guide/hosting)
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: Bun Redis with Upstash
|
||||
sidebarTitle: Upstash with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
[Upstash](https://upstash.com/) is a fully managed Redis database as a service. It works with the Redis® API, so you can connect with Bun's native Redis client.
|
||||
|
||||
<Note>TLS is enabled by default for all Upstash Redis databases.</Note>
|
||||
|
||||
---
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a new project">
|
||||
Create a new project with `bun init`:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun init bun-upstash-redis
|
||||
cd bun-upstash-redis
|
||||
```
|
||||
</Step>
|
||||
<Step title="Create an Upstash Redis database">
|
||||
Go to the [Upstash dashboard](https://console.upstash.com/) and create a new Redis database. After completing the [getting started guide](https://upstash.com/docs/redis/overall/getstarted), you'll see your database page with connection information.
|
||||
|
||||
The database page displays two connection methods: HTTP and TLS. For Bun's Redis client, you need the **TLS** connection details; the URL starts with `rediss://`.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
</Step>
|
||||
<Step title="Connect using Bun's Redis client">
|
||||
Set the `REDIS_URL` environment variable in your `.env` file using the Redis endpoint (not the REST URL):
|
||||
|
||||
```ini .env icon="settings"
|
||||
REDIS_URL=rediss://********@********.upstash.io:6379
|
||||
```
|
||||
|
||||
Bun's Redis client reads connection information from `REDIS_URL` by default:
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { redis } from "bun";
|
||||
|
||||
// Reads from process.env.REDIS_URL automatically
|
||||
await redis.set("counter", "0"); // [!code ++]
|
||||
```
|
||||
|
||||
Alternatively, create a custom client with `RedisClient`:
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { RedisClient } from "bun";
|
||||
|
||||
const redis = new RedisClient(process.env.REDIS_URL); // [!code ++]
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Use the Redis client">
|
||||
Use the Redis client to read and write keys in your Upstash database:
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { redis } from "bun";
|
||||
|
||||
// Get a value
|
||||
let counter = await redis.get("counter");
|
||||
|
||||
// Set a value if it doesn't exist
|
||||
if (!counter) {
|
||||
await redis.set("counter", "0");
|
||||
}
|
||||
|
||||
// Increment the counter
|
||||
await redis.incr("counter");
|
||||
|
||||
// Get the updated value
|
||||
counter = await redis.get("counter");
|
||||
console.log(counter);
|
||||
```
|
||||
```txt
|
||||
1
|
||||
```
|
||||
|
||||
The Redis client handles connections automatically. You don't need to connect or disconnect manually for basic operations.
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Build a frontend using Vite and Bun
|
||||
sidebarTitle: "Vite with Bun"
|
||||
mode: center
|
||||
---
|
||||
|
||||
<Note>
|
||||
You can use Vite with Bun, but many projects get faster builds & drop hundreds of dependencies by switching to [HTML
|
||||
imports](/bundler/fullstack).
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
Vite works with Bun with no extra configuration. Get started with one of Vite's templates.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun create vite my-app
|
||||
```
|
||||
|
||||
```txt
|
||||
◇ Select a framework:
|
||||
│ React
|
||||
│
|
||||
◇ Select a variant:
|
||||
│ TypeScript
|
||||
│
|
||||
◇ Which linter to use?
|
||||
│ Oxlint
|
||||
│
|
||||
◇ Install with bun and start now?
|
||||
│ No
|
||||
│
|
||||
◇ Scaffolding project in /path/to/my-app...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then `cd` into the project directory and install dependencies.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
cd my-app
|
||||
bun install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Start the development server with the `vite` CLI using `bunx`.
|
||||
|
||||
The `--bun` flag tells Bun to run Vite's CLI using `bun` instead of `node`. By default, Bun respects Vite's `#!/usr/bin/env node` [shebang line](<https://en.wikipedia.org/wiki/Shebang_(Unix)>).
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bunx --bun vite
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To simplify this command, update the `"dev"` script in `package.json` to the following.
|
||||
|
||||
```json package.json icon="file-json"
|
||||
"scripts": {
|
||||
"dev": "vite", // [!code --]
|
||||
"dev": "bunx --bun vite", // [!code ++]
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "oxlint",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
// ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Now you can start the development server with `bun run dev`.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Build your app for production.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bunx --bun vite build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
For more information, see the [Vite documentation](https://vite.dev/guide/).
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Extract links from a webpage using HTMLRewriter
|
||||
sidebarTitle: Extract links using HTMLRewriter
|
||||
mode: center
|
||||
---
|
||||
|
||||
## Extract links from a webpage
|
||||
|
||||
Bun's [HTMLRewriter](/runtime/html-rewriter) API extracts links from HTML. Chain CSS selectors to match the elements, text, and attributes you want to process. Then pass `.transform` a `Response`, `ArrayBuffer`, or `string`.
|
||||
|
||||
```ts extract-links.ts icon="/icons/typescript.svg"
|
||||
async function extractLinks(url: string) {
|
||||
const links = new Set<string>();
|
||||
const response = await fetch(url);
|
||||
|
||||
const rewriter = new HTMLRewriter().on("a[href]", {
|
||||
element(el) {
|
||||
const href = el.getAttribute("href");
|
||||
if (href) {
|
||||
links.add(href);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Wait for the response to be processed
|
||||
await rewriter.transform(response).blob();
|
||||
console.log([...links]); // ["https://bun.com", "/docs", ...]
|
||||
}
|
||||
|
||||
// Extract all links from the Bun website
|
||||
await extractLinks("https://bun.com");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Convert relative URLs to absolute
|
||||
|
||||
When scraping websites, you often want to convert relative URLs (like `/docs`) to absolute URLs:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```ts extract-links.ts icon="/icons/typescript.svg"
|
||||
async function extractLinksFromURL(url: string) {
|
||||
const response = await fetch(url);
|
||||
const links = new Set<string>();
|
||||
|
||||
const rewriter = new HTMLRewriter().on("a[href]", {
|
||||
element(el) {
|
||||
const href = el.getAttribute("href");
|
||||
if (href) {
|
||||
// Convert relative URLs to absolute // [!code ++]
|
||||
try { // [!code ++]
|
||||
const absoluteURL = new URL(href, url).href; // [!code ++]
|
||||
links.add(absoluteURL);
|
||||
} catch { // [!code ++]
|
||||
links.add(href); // [!code ++]
|
||||
} // [!code ++]
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Wait for the response to be processed
|
||||
await rewriter.transform(response).blob();
|
||||
return [...links];
|
||||
}
|
||||
|
||||
const websiteLinks = await extractLinksFromURL("https://example.com");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [`HTMLRewriter`](/runtime/html-rewriter).
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
title: Extract social share images and Open Graph tags
|
||||
sidebarTitle: OpenGraph tags
|
||||
mode: center
|
||||
---
|
||||
|
||||
## Extract social share images and Open Graph tags
|
||||
|
||||
Bun's [HTMLRewriter](/runtime/html-rewriter) API extracts social share images and Open Graph metadata from HTML by matching CSS selectors against the elements, text, and attributes you want to process. Use it to build link previews, social media cards, or web scrapers.
|
||||
|
||||
```ts extract-social-meta.ts icon="/icons/typescript.svg"
|
||||
interface SocialMetadata {
|
||||
title?: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
url?: string;
|
||||
site_name?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
async function extractSocialMetadata(url: string): Promise<SocialMetadata> {
|
||||
const metadata: SocialMetadata = {};
|
||||
const response = await fetch(url);
|
||||
|
||||
const rewriter = new HTMLRewriter()
|
||||
// Extract Open Graph meta tags
|
||||
.on('meta[property^="og:"]', {
|
||||
element(el) {
|
||||
const property = el.getAttribute("property");
|
||||
const content = el.getAttribute("content");
|
||||
if (property && content) {
|
||||
// Convert "og:image" to "image" etc.
|
||||
const key = property.replace("og:", "") as keyof SocialMetadata;
|
||||
metadata[key] = content;
|
||||
}
|
||||
},
|
||||
})
|
||||
// Extract Twitter Card meta tags as fallback
|
||||
.on('meta[name^="twitter:"]', {
|
||||
element(el) {
|
||||
const name = el.getAttribute("name");
|
||||
const content = el.getAttribute("content");
|
||||
if (name && content) {
|
||||
const key = name.replace("twitter:", "") as keyof SocialMetadata;
|
||||
// Only use Twitter Card data if nothing has set this key yet (OG tags always overwrite it)
|
||||
if (!metadata[key]) {
|
||||
metadata[key] = content;
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
// Fallback to regular meta tags
|
||||
.on('meta[name="description"]', {
|
||||
element(el) {
|
||||
const content = el.getAttribute("content");
|
||||
if (content && !metadata.description) {
|
||||
metadata.description = content;
|
||||
}
|
||||
},
|
||||
})
|
||||
// Fallback to title tag
|
||||
.on("title", {
|
||||
text(text) {
|
||||
if (!metadata.title) {
|
||||
metadata.title = text.text;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Process the response
|
||||
await rewriter.transform(response).blob();
|
||||
|
||||
// Convert relative image URLs to absolute
|
||||
if (metadata.image && !metadata.image.startsWith("http")) {
|
||||
try {
|
||||
metadata.image = new URL(metadata.image, url).href;
|
||||
} catch {
|
||||
// Keep the original URL if parsing fails
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
```
|
||||
|
||||
```ts Example Usage icon="/icons/typescript.svg"
|
||||
// Example usage
|
||||
const metadata = await extractSocialMetadata("https://bun.com");
|
||||
console.log(metadata);
|
||||
// {
|
||||
// title: "Bun — A fast all-in-one JavaScript runtime",
|
||||
// description: "Bundle, install, and run JavaScript & TypeScript — all in Bun. Bun is a fast JavaScript runtime & toolkit with a bundler, test runner, and npm-compatible package manager built in.",
|
||||
// image: "https://bun.com/share_v4.png",
|
||||
// type: "website",
|
||||
// ...
|
||||
// }
|
||||
```
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Start a cluster of HTTP servers
|
||||
description: Run multiple HTTP servers concurrently with the "reusePort" option to share the same port across multiple processes
|
||||
sidebarTitle: Start a cluster of HTTP servers
|
||||
mode: center
|
||||
---
|
||||
|
||||
To run multiple HTTP servers concurrently, use the `reusePort` option in `Bun.serve()`. It shares one port across multiple processes, and incoming requests are load balanced across them.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
import { serve } from "bun";
|
||||
|
||||
const id = Math.random().toString(36).slice(2);
|
||||
|
||||
serve({
|
||||
port: process.env.PORT || 8080,
|
||||
development: false,
|
||||
|
||||
// Share the same port across multiple processes
|
||||
// This is the important part!
|
||||
reusePort: true,
|
||||
|
||||
async fetch(request) {
|
||||
return new Response("Hello from Bun #" + id + "!\n");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<Note>
|
||||
**Linux only** — Windows and macOS ignore the `reusePort` option. This is an operating system limitation with
|
||||
`SO_REUSEPORT`.
|
||||
</Note>
|
||||
|
||||
After saving the file, start your servers on the same port.
|
||||
|
||||
`reusePort` uses the Linux `SO_REUSEPORT` and `SO_REUSEADDR` socket options to ensure fair load balancing across processes. [Learn more about `SO_REUSEPORT` and `SO_REUSEADDR`](https://lwn.net/Articles/542629/)
|
||||
|
||||
```ts cluster.ts icon="/icons/typescript.svg"
|
||||
import { spawn } from "bun";
|
||||
|
||||
const cpus = navigator.hardwareConcurrency; // Number of CPU cores
|
||||
const buns = new Array(cpus);
|
||||
|
||||
for (let i = 0; i < cpus; i++) {
|
||||
buns[i] = spawn({
|
||||
cmd: ["bun", "./server.ts"],
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
stdin: "inherit",
|
||||
});
|
||||
}
|
||||
|
||||
function kill() {
|
||||
for (const bun of buns) {
|
||||
bun.kill();
|
||||
}
|
||||
}
|
||||
|
||||
process.on("SIGINT", kill);
|
||||
process.on("exit", kill);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Bun also implements the `node:cluster` module; `reusePort` is a faster but more limited alternative.
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: fetch with unix domain sockets in Bun
|
||||
sidebarTitle: Fetch with unix domain sockets
|
||||
mode: center
|
||||
---
|
||||
|
||||
In Bun, `fetch()` can send HTTP requests over a [unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket) with the `unix` option.
|
||||
|
||||
```ts fetch-unix.ts icon="/icons/typescript.svg"
|
||||
const unix = "/var/run/docker.sock";
|
||||
|
||||
const response = await fetch("http://localhost/info", { unix });
|
||||
|
||||
const body = await response.json();
|
||||
console.log(body); // { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The `unix` option is the local file path to a unix domain socket. `fetch()` sends the request over that socket instead of a TCP connection. HTTPS is also supported: use the `https://` protocol in the URL instead of `http://`.
|
||||
|
||||
To send a `POST` request to an API endpoint over a unix domain socket:
|
||||
|
||||
```ts fetch-unix.ts icon="/icons/typescript.svg"
|
||||
const response = await fetch("https://hostname/a/path", {
|
||||
unix: "/var/run/path/to/unix.sock",
|
||||
method: "POST",
|
||||
body: JSON.stringify({ message: "Hello from Bun!" }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const body = await response.json();
|
||||
```
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Send an HTTP request using fetch
|
||||
sidebarTitle: Fetch with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
Bun implements the Web-standard [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API for sending HTTP requests. To send a `GET` request to a URL:
|
||||
|
||||
```ts fetch.ts icon="/icons/typescript.svg"
|
||||
const response = await fetch("https://bun.com");
|
||||
const html = await response.text(); // HTML string
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To send a `POST` request to an API endpoint:
|
||||
|
||||
```ts fetch.ts icon="/icons/typescript.svg"
|
||||
const response = await fetch("https://bun.com/api", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ message: "Hello from Bun!" }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
const body = await response.json();
|
||||
```
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
title: Upload files via HTTP using FormData
|
||||
sidebarTitle: Upload files via HTTP using FormData
|
||||
mode: center
|
||||
---
|
||||
|
||||
To upload files over HTTP with Bun, use the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) API. Start with an HTTP server that serves an HTML form.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const server = Bun.serve({
|
||||
port: 4000,
|
||||
async fetch(req) {
|
||||
const url = new URL(req.url);
|
||||
|
||||
// return index.html for root path
|
||||
if (url.pathname === "/")
|
||||
return new Response(Bun.file("index.html"), {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
});
|
||||
|
||||
return new Response("Not Found", { status: 404 });
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Listening on http://localhost:${server.port}`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Define the HTML form in another file, `index.html`.
|
||||
|
||||
```html index.html icon="file-code"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Form</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/action" method="post" enctype="multipart/form-data">
|
||||
<input type="text" name="name" placeholder="Name" />
|
||||
<input type="file" name="profilePicture" />
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Run the server and visit [`localhost:4000`](http://localhost:4000) to see the form.
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
Listening on http://localhost:4000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The form sends a `POST` request with the form data to the `/action` endpoint. Handle that request in the server.
|
||||
|
||||
First, call [`.formData()`](https://developer.mozilla.org/en-US/docs/Web/API/Request/formData) on the incoming `Request` to asynchronously parse its contents into a `FormData` instance. Then use [`.get()`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/get) to extract the `name` and `profilePicture` fields; `name` is a `string` and `profilePicture` is a `Blob`.
|
||||
|
||||
Finally, write the `Blob` to disk with [`Bun.write()`](/runtime/file-io#writing-files-bun-write).
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const server = Bun.serve({
|
||||
port: 4000,
|
||||
async fetch(req) {
|
||||
const url = new URL(req.url);
|
||||
|
||||
// return index.html for root path
|
||||
if (url.pathname === "/")
|
||||
return new Response(Bun.file("index.html"), {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
});
|
||||
|
||||
// parse formdata at /action // [!code ++]
|
||||
if (url.pathname === "/action") { // [!code ++]
|
||||
const formdata = await req.formData(); // [!code ++]
|
||||
const name = formdata.get("name"); // [!code ++]
|
||||
const profilePicture = formdata.get("profilePicture"); // [!code ++]
|
||||
if (!profilePicture) throw new Error("Must upload a profile picture."); // [!code ++]
|
||||
// write profilePicture to disk // [!code ++]
|
||||
await Bun.write("profilePicture.png", profilePicture); // [!code ++]
|
||||
return new Response("Success"); // [!code ++]
|
||||
} // [!code ++]
|
||||
|
||||
return new Response("Not Found", { status: 404 });
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Hot reload an HTTP server
|
||||
sidebarTitle: Hot reload an HTTP server
|
||||
mode: center
|
||||
---
|
||||
|
||||
The [`--hot`](/runtime/watch-mode#hot-mode) flag runs a file with hot reloading enabled. When any module or file changes, Bun re-runs the file.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun --hot run index.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Bun detects when you are running an HTTP server with `Bun.serve()`. It reloads your fetch handler when source files change, _without_ restarting the `bun` process. This makes hot reloads nearly instantaneous.
|
||||
|
||||
<Note>
|
||||
Hot reloading doesn't reload the page in your browser.
|
||||
</Note>
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
port: 3000,
|
||||
fetch(req) {
|
||||
return new Response("Hello world");
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Proxy HTTP requests using fetch()
|
||||
sidebarTitle: Proxy HTTP requests using fetch()
|
||||
mode: center
|
||||
---
|
||||
|
||||
In Bun, `fetch` supports sending requests through an HTTP or HTTPS proxy. Use it on corporate networks or when a request must come from a specific IP address.
|
||||
|
||||
```ts proxy.ts icon="/icons/typescript.svg"
|
||||
await fetch("https://example.com", {
|
||||
// The URL of the proxy server
|
||||
proxy: "https://username:[email protected]:8080",
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The `proxy` option can be a URL string, a `URL` instance, or an object with `url` (a string or a `URL`) and optional `headers`. The URL can include the username and password if the proxy requires authentication. It can be `http://` or `https://`.
|
||||
|
||||
---
|
||||
|
||||
## Custom proxy headers
|
||||
|
||||
To send custom headers to the proxy server (for proxy authentication tokens or custom routing), use the object format:
|
||||
|
||||
```ts proxy-headers.ts icon="/icons/typescript.svg"
|
||||
await fetch("https://example.com", {
|
||||
proxy: {
|
||||
url: "https://proxy.example.com:8080",
|
||||
headers: {
|
||||
"Proxy-Authorization": "Bearer my-token",
|
||||
"X-Proxy-Region": "us-east-1",
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The `headers` property accepts a plain object or a `Headers` instance. Bun sends these headers directly to the proxy server in `CONNECT` requests (for HTTPS targets) or in the proxy request (for HTTP targets).
|
||||
|
||||
If you provide a `Proxy-Authorization` header, it overrides any credentials in the proxy URL.
|
||||
|
||||
---
|
||||
|
||||
## Environment variables
|
||||
|
||||
To use the same proxy for all requests, set the `$HTTP_PROXY` and `$HTTPS_PROXY` environment variables to the proxy URL. Bun uses `$HTTP_PROXY` only for requests to `http://` URLs and `$HTTPS_PROXY` only for requests to `https://` URLs, so set both to proxy every request.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
HTTP_PROXY=https://username:[email protected]:8080 HTTPS_PROXY=https://username:[email protected]:8080 bun run index.ts
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: Common HTTP server usage
|
||||
sidebarTitle: HTTP Server with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
This code starts an HTTP server listening on port `3000`. It demonstrates basic routing with common responses and handles POST data from standard forms or as JSON.
|
||||
|
||||
See [`Bun.serve`](/runtime/http/server) for details.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const server = Bun.serve({
|
||||
async fetch(req) {
|
||||
const path = new URL(req.url).pathname;
|
||||
|
||||
// respond with text/plain
|
||||
if (path === "/") return new Response("Welcome to Bun!");
|
||||
|
||||
// redirect
|
||||
if (path === "/abc") return Response.redirect("/source", 301);
|
||||
|
||||
// send back a file (in this case, *this* file)
|
||||
if (path === "/source") return new Response(Bun.file(import.meta.path));
|
||||
|
||||
// respond with JSON
|
||||
if (path === "/api") return Response.json({ some: "buns", for: "you" });
|
||||
|
||||
// receive JSON data to a POST request
|
||||
if (req.method === "POST" && path === "/api/post") {
|
||||
const data = await req.json();
|
||||
console.log("Received JSON:", data);
|
||||
return Response.json({ success: true, data });
|
||||
}
|
||||
|
||||
// receive POST data from a form
|
||||
if (req.method === "POST" && path === "/form") {
|
||||
const data = await req.formData();
|
||||
console.log(data.get("someField"));
|
||||
return new Response("Success");
|
||||
}
|
||||
|
||||
// 404s
|
||||
return new Response("Page not found", { status: 404 });
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Listening on ${server.url}`);
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: Write a simple HTTP server
|
||||
sidebarTitle: Simple HTTP Server with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
This code starts an HTTP server listening on port `3000`. It responds to every request with a `200` status and the body `"Welcome to Bun!"`.
|
||||
|
||||
See [`Bun.serve`](/runtime/http/server) for details.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const server = Bun.serve({
|
||||
port: 3000,
|
||||
fetch(request) {
|
||||
return new Response("Welcome to Bun!");
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Listening on ${server.url}`);
|
||||
```
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
title: Server-Sent Events (SSE) with Bun
|
||||
sidebarTitle: Server-Sent Events
|
||||
mode: center
|
||||
---
|
||||
|
||||
[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) let you push a stream of text events to the browser over a single HTTP response. The client consumes them with [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource).
|
||||
|
||||
To implement an SSE endpoint in Bun, return a `Response` whose body is a streaming source and set the `Content-Type` header to `text/event-stream`.
|
||||
|
||||
<Note>
|
||||
`Bun.serve` closes idle connections after **10 seconds** by default. A quiet SSE stream counts as idle, so the
|
||||
examples below call `server.timeout(req, 0)` to disable the timeout for the stream. See
|
||||
[`idleTimeout`](/runtime/http/server#idletimeout) for details.
|
||||
</Note>
|
||||
|
||||
## Using an async generator
|
||||
|
||||
In Bun, `new Response` accepts an async generator function directly. An async generator is usually the simplest way to write an SSE endpoint. Each `yield` flushes a chunk to the client. If the client disconnects, the generator's `finally` block runs so you can clean up.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
port: 3000,
|
||||
routes: {
|
||||
"/events": (req, server) => {
|
||||
// SSE streams are often quiet between events. By default,
|
||||
// Bun.serve closes connections after 10 seconds of inactivity.
|
||||
// Disable the idle timeout for this request so the stream
|
||||
// stays open indefinitely.
|
||||
server.timeout(req, 0);
|
||||
|
||||
return new Response(
|
||||
async function* () {
|
||||
yield `data: connected at ${Date.now()}\n\n`;
|
||||
|
||||
// Emit a tick every 5 seconds until the client disconnects.
|
||||
// When the client goes away, the generator is returned
|
||||
// (cancelled) and this loop stops automatically.
|
||||
while (true) {
|
||||
await Bun.sleep(5000);
|
||||
yield `data: tick ${Date.now()}\n\n`;
|
||||
}
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Using a `ReadableStream`
|
||||
|
||||
If your events originate from callbacks (message brokers, timers, external pushes) rather than a linear `await` flow, a `ReadableStream` often fits better. When the client disconnects, Bun calls the stream's `cancel()` method automatically, so you can release any resources you set up in `start()`.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
port: 3000,
|
||||
routes: {
|
||||
"/events": (req, server) => {
|
||||
server.timeout(req, 0);
|
||||
|
||||
let timer: Timer;
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(`data: connected at ${Date.now()}\n\n`);
|
||||
|
||||
timer = setInterval(() => {
|
||||
controller.enqueue(`data: tick ${Date.now()}\n\n`);
|
||||
}, 5000);
|
||||
},
|
||||
cancel() {
|
||||
// Called automatically when the client disconnects.
|
||||
clearInterval(timer);
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Stream a file as an HTTP Response
|
||||
sidebarTitle: Stream file response
|
||||
mode: center
|
||||
---
|
||||
|
||||
[`Bun.file()`](/runtime/file-io#reading-files-bun-file) accepts a path and returns a lazily-loaded `BunFile` instance, which you can pass directly to the `new Response` constructor.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const path = "/path/to/file.txt";
|
||||
const file = Bun.file(path);
|
||||
const resp = new Response(file);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Bun determines the `Content-Type` from the file extension and sets it on the `Response`.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
new Response(Bun.file("./package.json")).headers.get("Content-Type");
|
||||
// => application/json;charset=utf-8
|
||||
|
||||
new Response(Bun.file("./test.txt")).headers.get("Content-Type");
|
||||
// => text/plain;charset=utf-8
|
||||
|
||||
new Response(Bun.file("./index.tsx")).headers.get("Content-Type");
|
||||
// => text/javascript;charset=utf-8
|
||||
|
||||
new Response(Bun.file("./img.png")).headers.get("Content-Type");
|
||||
// => image/png
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Putting it all together with [`Bun.serve()`](/runtime/http/server).
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
// static file server
|
||||
Bun.serve({
|
||||
async fetch(req) {
|
||||
const path = new URL(req.url).pathname;
|
||||
const file = Bun.file(path);
|
||||
return new Response(file);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [`Bun.write()`](/runtime/file-io#writing-files-bun-write).
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Streaming HTTP Server with Async Iterators
|
||||
sidebarTitle: Stream with iterators
|
||||
mode: center
|
||||
---
|
||||
|
||||
In Bun, a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) accepts an async generator function as its body, so you can stream data to the client as it becomes available rather than waiting for the entire response to be ready.
|
||||
|
||||
```ts stream-iterator.ts icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
port: 3000,
|
||||
fetch(req) {
|
||||
return new Response(
|
||||
// An async generator function
|
||||
async function* () {
|
||||
yield "Hello, ";
|
||||
await Bun.sleep(100);
|
||||
yield "world!";
|
||||
|
||||
// you can also yield a TypedArray or Buffer
|
||||
yield new Uint8Array(["\n".charCodeAt(0)]);
|
||||
},
|
||||
{ headers: { "Content-Type": "text/plain" } },
|
||||
);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
You can pass any async iterable directly to `Response`:
|
||||
|
||||
```ts stream-iterator.ts icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
port: 3000,
|
||||
fetch(req) {
|
||||
return new Response(
|
||||
{
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield "Hello, ";
|
||||
await Bun.sleep(100);
|
||||
yield "world!";
|
||||
},
|
||||
},
|
||||
{ headers: { "Content-Type": "text/plain" } },
|
||||
);
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Streaming HTTP Server with Node.js Streams
|
||||
sidebarTitle: Stream with Node.js
|
||||
mode: center
|
||||
---
|
||||
|
||||
In Bun, a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) accepts a Node.js [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams) as its body.
|
||||
|
||||
This works because Bun's `Response` accepts any async iterable as its body, and Node.js streams are async iterables.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
import { Readable } from "stream";
|
||||
import { serve } from "bun";
|
||||
serve({
|
||||
port: 3000,
|
||||
fetch(req) {
|
||||
return new Response(Readable.from(["Hello, ", "world!"]), {
|
||||
headers: { "Content-Type": "text/plain" },
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Configure TLS on an HTTP server
|
||||
sidebarTitle: Configure TLS
|
||||
mode: center
|
||||
---
|
||||
|
||||
Set the `tls` key to configure TLS. Both `key` and `cert` are required: `key` is the contents of your private key and `cert` is the contents of your issued certificate. Use [`Bun.file()`](/runtime/file-io#reading-files-bun-file) to read them.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const server = Bun.serve({
|
||||
fetch: request => new Response("Welcome to Bun!"),
|
||||
tls: {
|
||||
cert: Bun.file("cert.pem"),
|
||||
key: Bun.file("key.pem"),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
By default, Bun trusts the Mozilla-curated list of well-known root CAs. To override this list, pass an array of certificates as `ca`. On a server, Bun uses this list to verify _client_ certificates, so also set `requestCert: true`.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const server = Bun.serve({
|
||||
fetch: request => new Response("Welcome to Bun!"),
|
||||
tls: {
|
||||
cert: Bun.file("cert.pem"),
|
||||
key: Bun.file("key.pem"),
|
||||
ca: [Bun.file("ca1.pem"), Bun.file("ca2.pem")],
|
||||
requestCert: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Guides
|
||||
description: Code samples and walkthroughs for common tasks with Bun
|
||||
contextual: false
|
||||
mode: center
|
||||
---
|
||||
|
||||
import { GuidesList } from "/snippets/guides.jsx";
|
||||
|
||||
<GuidesList />
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Add a development dependency
|
||||
sidebarTitle: Add a dev dependency
|
||||
mode: center
|
||||
---
|
||||
|
||||
To add an npm package as a development dependency, use `bun add --development`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add zod --dev
|
||||
bun add zod -d # shorthand
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This adds the package to `devDependencies` in `package.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"devDependencies": {
|
||||
"zod": "^4.0.0" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [`bun install`](/pm/cli/install).
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Add a Git dependency
|
||||
sidebarTitle: Add a Git dependency
|
||||
mode: center
|
||||
---
|
||||
|
||||
Bun supports directly adding GitHub repositories as dependencies of your project.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add github:lodash/lodash
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This adds the following line to your `package.json`:
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"dependencies": {
|
||||
"lodash": "github:lodash/lodash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Bun supports several protocols for specifying Git dependencies.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add git+https://github.com/lodash/lodash.git
|
||||
bun add git+ssh://github.com/lodash/lodash.git#4.17.21
|
||||
bun add [email protected]:lodash/lodash.git
|
||||
bun add github:lodash/lodash#4.17.21
|
||||
```
|
||||
|
||||
When possible, Bun downloads GitHub dependencies as HTTP tarballs, which is faster.
|
||||
|
||||
---
|
||||
|
||||
See [`bun install`](/pm/cli/install).
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: Add an optional dependency
|
||||
sidebarTitle: Add an optional dependency
|
||||
mode: center
|
||||
---
|
||||
|
||||
To add an npm package as an optional dependency, use the `--optional` flag.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add zod --optional
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This adds the package to `optionalDependencies` in `package.json`.
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"optionalDependencies": {
|
||||
"zod": "^4.0.0" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [`bun install`](/pm/cli/install).
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Add a peer dependency
|
||||
sidebarTitle: Add a peer dependency
|
||||
mode: center
|
||||
---
|
||||
|
||||
To add an npm package as a peer dependency, use the `--peer` flag.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add @types/bun --peer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This adds the package to `peerDependencies` in `package.json`.
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"peerDependencies": {
|
||||
"@types/bun": "^1.3.3" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
`bun install` installs peer dependencies by default, unless they are marked optional in `peerDependenciesMeta`.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"peerDependencies": {
|
||||
"@types/bun": "^1.3.3"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/bun": { // [!code ++]
|
||||
"optional": true // [!code ++]
|
||||
} // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [`bun install`](/pm/cli/install).
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Add a tarball dependency
|
||||
sidebarTitle: Add a tarball dependency
|
||||
mode: center
|
||||
---
|
||||
|
||||
Bun's package manager can install any publicly available tarball URL as a dependency of your project.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add zod@https://registry.npmjs.org/zod/-/zod-3.21.4.tgz
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This command downloads, extracts, and installs the tarball into your project's `node_modules` directory, and adds the following line to your `package.json`:
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"dependencies": {
|
||||
"zod": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
You can now import `zod` as usual.
|
||||
|
||||
```ts
|
||||
import { z } from "zod";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [`bun install`](/pm/cli/install).
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Add a dependency
|
||||
sidebarTitle: Add a dependency
|
||||
mode: center
|
||||
---
|
||||
|
||||
To add an npm package as a dependency, use `bun add`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add zod
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This adds the package to `dependencies` in `package.json`. By default, Bun uses the `^` range specifier, which accepts future minor and patch versions.
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"dependencies": {
|
||||
"zod": "^4.0.0" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To pin your project to the exact version you installed, use `--exact`. This adds the package to `dependencies` without the `^`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add zod --exact
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To specify an exact version or a tag:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add [email protected]
|
||||
bun add zod@next
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [`bun install`](/pm/cli/install).
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Using bun install with an Azure Artifacts npm registry
|
||||
sidebarTitle: Azure Artifacts with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
<Note>
|
||||
[Azure
|
||||
Artifacts'](https://learn.microsoft.com/en-us/azure/devops/artifacts/npm/npmrc?view=azure-devops&tabs=windows%2Cclassic)
|
||||
instructions for `.npmrc` say to base64 encode the password. Do not do this in `bunfig.toml` or `NPM_CONFIG_REGISTRY`
|
||||
as shown below; Bun base64 encodes the password for you. Bun also reads [`.npmrc`](/pm/npmrc) files, and there
|
||||
`_password` must stay base64 encoded, as in Azure's instructions.
|
||||
</Note>
|
||||
|
||||
[Azure Artifacts](https://azure.microsoft.com/en-us/products/devops/artifacts) is a package management system for Azure DevOps. You can use it to host your own private npm registry, along with other types of packages.
|
||||
|
||||
---
|
||||
|
||||
### Configure with bunfig.toml
|
||||
|
||||
---
|
||||
|
||||
To use Azure Artifacts with `bun install`, add a `bunfig.toml` file to your project with the following contents. Replace `my-azure-devops-org` with the name of your Azure DevOps organization and `my-feed` with the name of your feed. If the feed is project-scoped, the URL also includes the project name: `https://pkgs.dev.azure.com/my-azure-devops-org/my-project/_packaging/my-feed/npm/registry/`. The `username` can be any non-empty string.
|
||||
|
||||
```toml bunfig.toml icon="settings"
|
||||
[install.registry]
|
||||
url = "https://pkgs.dev.azure.com/my-azure-devops-org/_packaging/my-feed/npm/registry/"
|
||||
username = "my-azure-artifacts-user"
|
||||
# You can use an environment variable here
|
||||
password = "$NPM_PASSWORD"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then assign your Azure Personal Access Token to the `NPM_PASSWORD` environment variable. Bun [automatically reads](/runtime/environment-variables) `.env` files, so create a file called `.env` in your project root. Don't base64 encode the token; Bun does that for you.
|
||||
|
||||
```ini .env icon="settings"
|
||||
NPM_PASSWORD=<paste token here>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Configure with environment variables
|
||||
|
||||
---
|
||||
|
||||
To configure Azure Artifacts without `bunfig.toml`, set the `NPM_CONFIG_REGISTRY` environment variable. Append `:username=<USERNAME>` and `:_password=<PASSWORD>` to the URL, as shown below. Replace `<USERNAME>` and `<PASSWORD>` with your own values.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
NPM_CONFIG_REGISTRY=https://pkgs.dev.azure.com/my-azure-devops-org/_packaging/my-feed/npm/registry/:username=<USERNAME>:_password=<PASSWORD>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Don't base64 encode the password
|
||||
|
||||
---
|
||||
|
||||
[Azure Artifacts'](https://learn.microsoft.com/en-us/azure/devops/artifacts/npm/npmrc?view=azure-devops&tabs=windows%2Cclassic) instructions for `.npmrc` say to base64 encode the password. Do not do this in `bunfig.toml` or `NPM_CONFIG_REGISTRY`; Bun base64 encodes the password for you. Bun also reads [`.npmrc`](/pm/npmrc) files, and there `_password` must stay base64 encoded, as in Azure's instructions.
|
||||
|
||||
<Note>
|
||||
Azure DevOps personal access tokens are 84 characters long. A base64-encoded one is 112 characters long and does not
|
||||
end with `=`.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
To decode a base64-encoded password, open your browser console and run:
|
||||
|
||||
```js browser icon="computer"
|
||||
atob("<base64-encoded password>");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Alternatively, use the `base64` command line tool, though the password may end up in your shell history:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
echo "base64-encoded-password" | base64 --decode
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Install dependencies with Bun in GitHub Actions
|
||||
sidebarTitle: Install Bun in GitHub Actions
|
||||
mode: center
|
||||
---
|
||||
|
||||
Use the official [`setup-bun`](https://github.com/oven-sh/setup-bun) GitHub Action to install `bun` in your GitHub Actions runner.
|
||||
|
||||
```yaml workflow.yml icon="file-code"
|
||||
name: my-workflow
|
||||
jobs:
|
||||
my-job:
|
||||
name: my-job
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# ...
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2 # [!code ++]
|
||||
|
||||
# run any `bun` or `bunx` command
|
||||
- run: bun install # [!code ++]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To specify a version of Bun to install:
|
||||
|
||||
```yaml workflow.yml icon="file-code"
|
||||
name: my-workflow
|
||||
jobs:
|
||||
my-job:
|
||||
name: my-job
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# ...
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with: # [!code ++]
|
||||
bun-version: "latest" # or "canary" # [!code ++]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See the [`setup-bun` README](https://github.com/oven-sh/setup-bun).
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Override the default npm registry for bun install
|
||||
sidebarTitle: Override the default npm registry
|
||||
mode: center
|
||||
---
|
||||
|
||||
The default registry is `registry.npmjs.org`. Override it globally in `bunfig.toml`.
|
||||
|
||||
```toml bunfig.toml icon="settings"
|
||||
[install]
|
||||
# set default registry as a string
|
||||
registry = "https://registry.npmjs.org"
|
||||
|
||||
# if needed, set a token
|
||||
# registry = { url = "https://registry.npmjs.org", token = "123456" }
|
||||
|
||||
# if needed, set a username/password
|
||||
# registry = "https://username:[email protected]"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Your `bunfig.toml` can reference environment variables. `bun install` automatically loads environment variables from `.env.production.local`, `.env.local`, `.env.production`, and `.env`, regardless of `NODE_ENV`. It does not read `.env.development` or `.env.test`. See [Environment variables](/runtime/environment-variables).
|
||||
|
||||
```toml bunfig.toml icon="settings"
|
||||
[install]
|
||||
registry = { url = "https://registry.npmjs.org", token = "$npm_token" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [`bun install`](/pm/cli/install).
|
||||
@@ -0,0 +1,230 @@
|
||||
---
|
||||
title: Migrate from npm install to bun install
|
||||
sidebarTitle: Migrate from npm to bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
`bun install` is a fast, Node.js-compatible npm client.
|
||||
|
||||
To migrate from `npm install` to `bun install`, run `bun install` instead of `npm install`.
|
||||
|
||||
- **Designed for Node.js & Bun**: `bun install` installs a Node.js compatible `node_modules` folder. You can use it in place of `npm install` for Node.js projects without any code changes and without using Bun's runtime.
|
||||
- **Automatically converts `package-lock.json`** to Bun's `bun.lock` lockfile format, preserving your existing resolved dependency versions. You can secretly use `bun install` in place of `npm install` at work without anyone noticing.
|
||||
- **`.npmrc` compatible**: `bun install` reads npm registry configuration from npm's `.npmrc`, so you can use the same configuration for both npm and Bun.
|
||||
- **Hardlinks**: On Windows and Linux, `bun install` uses hardlinks to save disk space and speed up installs.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
# It only takes one command to migrate
|
||||
bun i
|
||||
|
||||
# To add dependencies:
|
||||
bun i @types/bun
|
||||
|
||||
# To add devDependencies:
|
||||
bun i -d @types/bun
|
||||
|
||||
# To remove a dependency:
|
||||
bun rm @types/bun
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Run package.json scripts faster
|
||||
|
||||
Run scripts from `package.json`, executables from `node_modules/.bin` (like `npx`), and JavaScript/TypeScript files (like `node`), all with a single command.
|
||||
|
||||
| NPM | Bun |
|
||||
| ------------------ | ---------------- |
|
||||
| `npm run <script>` | `bun <script>` |
|
||||
| `npm exec <bin>` | `bun <bin>` |
|
||||
| `node <file>` | `bun <file>` |
|
||||
| `npx <package>` | `bunx <package>` |
|
||||
|
||||
`bun run <executable>` uses the locally-installed executable.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
# Run a package.json script:
|
||||
bun my-script
|
||||
bun run my-script
|
||||
|
||||
# Run an executable in node_modules/.bin:
|
||||
bun my-executable # such as tsc, esbuild, etc.
|
||||
bun run my-executable
|
||||
|
||||
# Run a JavaScript/TypeScript file:
|
||||
bun ./index.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workspaces? Yes.
|
||||
|
||||
`bun install` supports workspaces similarly to npm, with more features.
|
||||
|
||||
In `package.json`, set `"workspaces"` to an array of relative paths.
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"name": "my-app",
|
||||
"workspaces": ["packages/*", "apps/*"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Filter scripts by workspace name
|
||||
|
||||
In Bun, the `--filter` flag accepts a glob pattern and runs the command concurrently for every workspace package whose `name` matches it, respecting dependency order.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun --filter 'lib-*' my-script
|
||||
# instead of:
|
||||
# npm run --workspace lib-foo --workspace lib-bar my-script
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Update dependencies
|
||||
|
||||
`bun update <package>` updates a dependency to the latest version that satisfies the semver range in `package.json`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
# Update a single dependency
|
||||
bun update @types/bun
|
||||
|
||||
# Update all dependencies
|
||||
bun update
|
||||
|
||||
# Ignore semver, update to the latest version
|
||||
bun update @types/bun --latest
|
||||
|
||||
# Update a dependency to a specific version
|
||||
bun update @types/[email protected]
|
||||
|
||||
# Update all dependencies to the latest versions
|
||||
bun update --latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### View outdated dependencies
|
||||
|
||||
To view outdated dependencies, run `bun outdated`. It works like `npm outdated`, with more compact output.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun outdated
|
||||
```
|
||||
|
||||
```txt
|
||||
┌────────────────────────────────────────┬─────────┬────────┬────────┐
|
||||
│ Package │ Current │ Update │ Latest │
|
||||
├────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ @types/bun (dev) │ 1.1.6 │ 1.1.10 │ 1.1.10 │
|
||||
├────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ @types/react (dev) │ 18.3.3 │ 18.3.8 │ 18.3.8 │
|
||||
├────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ @typescript-eslint/eslint-plugin (dev) │ 7.16.1 │ 7.18.0 │ 8.6.0 │
|
||||
├────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ @typescript-eslint/parser (dev) │ 7.16.1 │ 7.18.0 │ 8.6.0 │
|
||||
├────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ @vscode/debugadapter (dev) │ 1.66.0 │ 1.67.0 │ 1.67.0 │
|
||||
├────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ esbuild (dev) │ 0.21.5 │ 0.21.5 │ 0.24.0 │
|
||||
├────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ eslint (dev) │ 9.7.0 │ 9.11.0 │ 9.11.0 │
|
||||
├────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ mitata (dev) │ 0.1.11 │ 0.1.14 │ 1.0.2 │
|
||||
├────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ prettier-plugin-organize-imports (dev) │ 4.0.0 │ 4.1.0 │ 4.1.0 │
|
||||
├────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ source-map-js (dev) │ 1.2.0 │ 1.2.1 │ 1.2.1 │
|
||||
├────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ typescript (dev) │ 5.5.3 │ 5.6.2 │ 5.6.2 │
|
||||
└────────────────────────────────────────┴─────────┴────────┴────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## List installed packages
|
||||
|
||||
`bun pm ls` lists the packages installed in the `node_modules` folder, using Bun's lockfile as the source of truth. Pass the `-a` flag to also list transitive dependencies.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
# List top-level installed packages:
|
||||
bun pm ls
|
||||
```
|
||||
|
||||
```txt
|
||||
my-pkg node_modules (781)
|
||||
├── @types/[email protected]
|
||||
├── @types/[email protected]
|
||||
├── @types/[email protected]
|
||||
├── [email protected]
|
||||
├── [email protected]
|
||||
...
|
||||
```
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
# List all installed packages:
|
||||
bun pm ls -a
|
||||
```
|
||||
|
||||
```txt
|
||||
my-pkg node_modules
|
||||
├── @alloc/[email protected]
|
||||
├── @isaacs/[email protected]
|
||||
│ └── [email protected]
|
||||
│ └── [email protected]
|
||||
├── @jridgewell/[email protected]
|
||||
├── @jridgewell/[email protected]
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a package tarball
|
||||
|
||||
`bun pm pack` creates a tarball of the package in the current directory.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
# Create a tarball
|
||||
bun pm pack
|
||||
```
|
||||
|
||||
```txt
|
||||
Total files: 46
|
||||
Shasum: 2ee19b6f0c6b001358449ca0eadead703f326216
|
||||
Integrity: sha512-ZV0lzWTEkGAMz[...]Gl4f8lA9sl97g==
|
||||
Unpacked size: 0.41MB
|
||||
Packed size: 117.50KB
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Shebang
|
||||
|
||||
If the package references `node` in the `#!/usr/bin/env node` shebang, `bun run` respects it by default and uses the system's `node` executable. To force it to use Bun instead, pass `--bun` to `bun run`.
|
||||
|
||||
When you pass `--bun`, Bun creates a symlink to the locally-installed Bun executable named `"node"` in a temporary directory and adds it to your `PATH` for the duration of the script.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
# Force using Bun's runtime instead of node
|
||||
bun --bun my-script
|
||||
|
||||
# This also works:
|
||||
bun run --bun my-script
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Global installs
|
||||
|
||||
Install packages globally with `bun i -g <package>`. By default, they go into a `.bun/install/global/node_modules` folder inside your home directory.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
# Install a package globally
|
||||
bun i -g eslint
|
||||
|
||||
# Run a globally-installed package without the `bun run` prefix
|
||||
eslint --init
|
||||
```
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: Configure git to diff Bun's lockb lockfile
|
||||
sidebarTitle: Configure git to diff Bun's lockfile
|
||||
mode: center
|
||||
---
|
||||
|
||||
<Note>
|
||||
Bun v1.1.39 introduced `bun.lock`, a JSONC formatted lockfile. `bun.lock` is human-readable and git-diffable without
|
||||
configuration, at no cost to performance. In 1.2.0+ it is the default format for new projects. See [the lockfile
|
||||
docs](/pm/lockfile#text-based-lockfile).
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
To teach `git` how to generate a human-readable diff of Bun's binary lockfile format (`.lockb`), add the following to your local or global `.gitattributes` file:
|
||||
|
||||
```js gitattributes icon="file-code"
|
||||
*.lockb binary diff=lockb
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then add the following to your local git config:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
git config diff.lockb.textconv bun
|
||||
git config diff.lockb.binary true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To configure this for every repository, add the following to your global git config:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
git config --global diff.lockb.textconv bun
|
||||
git config --global diff.lockb.binary true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How this works
|
||||
|
||||
- `textconv` tells git to run bun on the file before diffing
|
||||
- `binary` tells git to treat the file as binary (so it doesn't try to diff it line-by-line)
|
||||
|
||||
Running Bun on the lockfile (`bun ./bun.lockb`) prints a human-readable version of it, which `git diff` then diffs.
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: Using bun install with Artifactory
|
||||
sidebarTitle: JFrog Artifactory with Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
[JFrog Artifactory](https://jfrog.com/artifactory/) is a package management system for npm, Docker, Maven, NuGet, Ruby, Helm, and more. You can use it to host your own private npm registry, along with other types of packages.
|
||||
|
||||
To use it with `bun install`, add a `bunfig.toml` file to your project with the following contents:
|
||||
|
||||
---
|
||||
|
||||
### Configure with bunfig.toml
|
||||
|
||||
Replace `MY_SUBDOMAIN` with your JFrog Artifactory subdomain, such as `jarred1234`. Replace `MY_TOKEN` with your base64-encoded `username:password`. Bun sends this value as-is in a `Basic` `Authorization` header. To authenticate with a JFrog access token instead, use `_authToken=` in place of `_auth=`. Bun sends that value as a `Bearer` token.
|
||||
|
||||
```toml bunfig.toml icon="settings"
|
||||
[install.registry]
|
||||
url = "https://MY_SUBDOMAIN.jfrog.io/artifactory/api/npm/npm/_auth=MY_TOKEN"
|
||||
# To authenticate with an access token instead
|
||||
# url = "https://MY_SUBDOMAIN.jfrog.io/artifactory/api/npm/npm/_authToken=MY_TOKEN"
|
||||
# You can use an environment variable here
|
||||
# url = "$NPM_CONFIG_REGISTRY"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Configure with `$NPM_CONFIG_REGISTRY`
|
||||
|
||||
As with npm, you can set the `NPM_CONFIG_REGISTRY` environment variable to configure JFrog Artifactory for `bun install`.
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: Install a package under a different name
|
||||
sidebarTitle: Install with alias
|
||||
mode: center
|
||||
---
|
||||
|
||||
To install an npm package under an alias:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun add my-custom-name@npm:zod
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
You can now import the `zod` package as `my-custom-name`.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { z } from "my-custom-name";
|
||||
|
||||
z.string();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [`bun install`](/pm/cli/install).
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user