mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 11:59:00 +00:00
Merge remote-tracking branch 'origin/main' into claude/add-bun-ms
# Conflicts: # bench/runner.mjs # bunfig.toml # docs/cli/install.md # docs/runtime/bunfig.mdx # docs/runtime/utils.mdx # src/install/PackageManager/PackageManagerEnqueue.zig
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
---
|
||||
title: "Auto-install"
|
||||
description: "Bun's automatic package installation feature for standalone script execution"
|
||||
---
|
||||
|
||||
If no `node_modules` directory is found in the working directory or higher, Bun will abandon Node.js-style module resolution in favor of the **Bun module resolution algorithm**.
|
||||
|
||||
Under Bun-style module resolution, all imported packages are auto-installed on the fly into a [global module cache](https://bun.com/docs/install/cache) during execution (the same cache used by [`bun install`](https://bun.com/docs/cli/install)).
|
||||
Under Bun-style module resolution, all imported packages are auto-installed on the fly into a [global module cache](/pm/global-cache) during execution (the same cache used by [`bun install`](/pm/cli/install)).
|
||||
|
||||
```ts
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { foo } from "foo"; // install `latest` version
|
||||
|
||||
foo();
|
||||
@@ -10,6 +15,8 @@ foo();
|
||||
|
||||
The first time you run this script, Bun will auto-install `"foo"` and cache it. The next time you run the script, it will use the cached version.
|
||||
|
||||
---
|
||||
|
||||
## Version resolution
|
||||
|
||||
To determine which version to install, Bun follows the following algorithm:
|
||||
@@ -18,6 +25,8 @@ To determine which version to install, Bun follows the following algorithm:
|
||||
2. Otherwise, scan up the tree for a `package.json` that includes `"foo"` as a dependency. If found, use the specified semver version or version range.
|
||||
3. Otherwise, use `latest`.
|
||||
|
||||
---
|
||||
|
||||
## Cache behavior
|
||||
|
||||
Once a version or version range has been determined, Bun will:
|
||||
@@ -26,20 +35,26 @@ Once a version or version range has been determined, Bun will:
|
||||
2. When resolving `latest`, Bun will check if `package@latest` has been downloaded and cached in the last _24 hours_. If so, use it.
|
||||
3. Otherwise, download and install the appropriate version from the `npm` registry.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
Packages are installed and cached into `<cache>/<pkg>@<version>`, so multiple versions of the same package can be cached at once. Additionally, a symlink is created under `<cache>/<pkg>/<version>` to make it faster to look up all versions of a package that exist in the cache.
|
||||
|
||||
---
|
||||
|
||||
## Version specifiers
|
||||
|
||||
This entire resolution algorithm can be short-circuited by specifying a version or version range directly in your import statement.
|
||||
|
||||
```ts
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { z } from "zod@3.0.0"; // specific version
|
||||
import { z } from "zod@next"; // npm tag
|
||||
import { z } from "zod@^3.20.0"; // semver range
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benefits
|
||||
|
||||
This auto-installation approach is useful for a few reasons:
|
||||
@@ -49,46 +64,34 @@ This auto-installation approach is useful for a few reasons:
|
||||
- **Convenience** — There's no need to run `npm install` or `bun install` before running a file or script. Just `bun run` it.
|
||||
- **Backwards compatibility** — Because Bun still respects the versions specified in `package.json` if one exists, you can switch to Bun-style resolution with a single command: `rm -rf node_modules`.
|
||||
|
||||
---
|
||||
|
||||
## Limitations
|
||||
|
||||
- No Intellisense. TypeScript auto-completion in IDEs relies on the existence of type declaration files inside `node_modules`. We are investigating various solutions to this.
|
||||
- No [patch-package](https://github.com/ds300/patch-package) support
|
||||
|
||||
<!-- - The implementation details of Bun's install cache will change between versions. Don't think of it as an API. To reliably resolve packages, use Bun's builtin APIs (such as `Bun.resolveSync` or `import.meta.resolve`) instead of relying on the filesystem directly. Bun will likely move to a binary archive format where packages may not correspond to files/folders on disk at all - so if you depend on the filesystem structure instead of the JavaScript API, your code will eventually break. -->
|
||||
|
||||
<!-- ## Customizing behavior
|
||||
|
||||
To prefer locally-installed versions of packages. Instead of checking npm for latest versions, you can pass the `--prefer-offline` flag to prefer locally-installed versions of packages.
|
||||
|
||||
```bash
|
||||
$ bun run --prefer-offline my-script.ts
|
||||
```
|
||||
|
||||
This will check the install cache for installed versions of packages before checking the npm registry. If no matching version of a package is installed, only then will it check npm for the latest version.
|
||||
|
||||
#### Prefer latest
|
||||
|
||||
To always use the latest version of a package, you can pass the `--prefer-latest` flag.
|
||||
|
||||
```bash
|
||||
$ bun run --prefer-latest my-script.ts
|
||||
``` -->
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
{% details summary="How is this different from what pnpm does?" %}
|
||||
<AccordionGroup>
|
||||
|
||||
With pnpm, you have to run `pnpm install`, which creates a `node_modules` folder of symlinks for the runtime to resolve. By contrast, Bun resolves dependencies on the fly when you run a file; there's no need to run any `install` command ahead of time. Bun also doesn't create a `node_modules` folder.
|
||||
<Accordion title="How is this different from what pnpm does?">
|
||||
|
||||
{% /details %}
|
||||
With pnpm, you have to run `pnpm install`, which creates a `node_modules` folder of symlinks for the runtime to resolve. By contrast, Bun resolves dependencies on the fly when you run a file; there's no need to run any `install` command ahead of time. Bun also doesn't create a `node_modules` folder.
|
||||
|
||||
{% details summary="How is this different from Yarn Plug'N'Play does?" %}
|
||||
With Yarn, you must run `yarn install` before you run a script. By contrast, Bun resolves dependencies on the fly when you run a file; there's no need to run any `install` command ahead of time.
|
||||
</Accordion>
|
||||
|
||||
Yarn Plug'N'Play also uses zip files to store dependencies. This makes dependency loading [slower at runtime](https://twitter.com/jarredsumner/status/1458207919636287490), as random access reads on zip files tend to be slower than the equivalent disk lookup.
|
||||
{% /details %}
|
||||
<Accordion title="How is this different from Yarn Plug'N'Play does?">
|
||||
With Yarn, you must run `yarn install` before you run a script. By contrast, Bun resolves dependencies on the fly when you run a file; there's no need to run any `install` command ahead of time.
|
||||
|
||||
{% details summary="How is this different from what Deno does?" %}
|
||||
Yarn Plug'N'Play also uses zip files to store dependencies. This makes dependency loading [slower at runtime](https://twitter.com/jarredsumner/status/1458207919636287490), as random access reads on zip files tend to be slower than the equivalent disk lookup.
|
||||
</Accordion>
|
||||
|
||||
Deno requires an `npm:` specifier before each npm `import`, lacks support for import maps via `compilerOptions.paths` in `tsconfig.json`, and has incomplete support for `package.json` settings. Unlike Deno, Bun does not currently support URL imports.
|
||||
{% /details %}
|
||||
<Accordion title="How is this different from what Deno does?">
|
||||
|
||||
Deno requires an `npm:` specifier before each npm `import`, lacks support for import maps via `compilerOptions.paths` in `tsconfig.json`, and has incomplete support for `package.json` settings. Unlike Deno, Bun does not currently support URL imports.
|
||||
</Accordion>
|
||||
|
||||
</AccordionGroup>
|
||||
846
docs/runtime/binary-data.mdx
Normal file
846
docs/runtime/binary-data.mdx
Normal file
@@ -0,0 +1,846 @@
|
||||
---
|
||||
title: Binary Data
|
||||
description: Working with binary data in JavaScript
|
||||
---
|
||||
|
||||
This page is intended as an introduction to working with binary data in JavaScript. Bun implements a number of data types and utilities for working with binary data, most of which are Web-standard. Any Bun-specific APIs will be noted as such.
|
||||
|
||||
Below is a quick "cheat sheet" that doubles as a table of contents. Click an item in the left column to jump to that section.
|
||||
|
||||
| Class | Description |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`TypedArray`](#typedarray) | A family of classes that provide an `Array`-like interface for interacting with binary data. Includes `Uint8Array`, `Uint16Array`, `Int8Array`, and more. |
|
||||
| [`Buffer`](#buffer) | A subclass of `Uint8Array` that implements a wide range of convenience methods. Unlike the other elements in this table, this is a Node.js API (which Bun implements). It can't be used in the browser. |
|
||||
| [`DataView`](#dataview) | A class that provides a `get/set` API for writing some number of bytes to an `ArrayBuffer` at a particular byte offset. Often used reading or writing binary protocols. |
|
||||
| [`Blob`](#blob) | A readonly blob of binary data usually representing a file. Has a MIME `type`, a `size`, and methods for converting to `ArrayBuffer`, `ReadableStream`, and string. |
|
||||
| [`File`](#file) | A subclass of `Blob` that represents a file. Has a `name` and `lastModified` timestamp. There is experimental support in Node.js v20. |
|
||||
| [`BunFile`](#bunfile) | _Bun only_. A subclass of `Blob` that represents a lazily-loaded file on disk. Created with `Bun.file(path)`. |
|
||||
|
||||
---
|
||||
|
||||
## `ArrayBuffer` and views
|
||||
|
||||
Until 2009, there was no language-native way to store and manipulate binary data in JavaScript. ECMAScript v5 introduced a range of new mechanisms for this. The most fundamental building block is `ArrayBuffer`, a simple data structure that represents a sequence of bytes in memory.
|
||||
|
||||
```ts
|
||||
// this buffer can store 8 bytes
|
||||
const buf = new ArrayBuffer(8);
|
||||
```
|
||||
|
||||
Despite the name, it isn't an array and supports none of the array methods and operators one might expect. In fact, there is no way to directly read or write values from an `ArrayBuffer`. There's very little you can do with one except check its size and create "slices" from it.
|
||||
|
||||
```ts
|
||||
const buf = new ArrayBuffer(8);
|
||||
buf.byteLength; // => 8
|
||||
|
||||
const slice = buf.slice(0, 4); // returns new ArrayBuffer
|
||||
slice.byteLength; // => 4
|
||||
```
|
||||
|
||||
To do anything interesting we need a construct known as a "view". A view is a class that _wraps_ an `ArrayBuffer` instance and lets you read and manipulate the underlying data. There are two types of views: _typed arrays_ and `DataView`.
|
||||
|
||||
### `DataView`
|
||||
|
||||
The `DataView` class is a lower-level interface for reading and manipulating the data in an `ArrayBuffer`.
|
||||
|
||||
Below we create a new `DataView` and set the first byte to 3.
|
||||
|
||||
```ts
|
||||
const buf = new ArrayBuffer(4);
|
||||
// [0b00000000, 0b00000000, 0b00000000, 0b00000000]
|
||||
|
||||
const dv = new DataView(buf);
|
||||
dv.setUint8(0, 3); // write value 3 at byte offset 0
|
||||
dv.getUint8(0); // => 3
|
||||
// [0b00000011, 0b00000000, 0b00000000, 0b00000000]
|
||||
```
|
||||
|
||||
Now let's write a `Uint16` at byte offset `1`. This requires two bytes. We're using the value `513`, which is `2 * 256 + 1`; in bytes, that's `00000010 00000001`.
|
||||
|
||||
```ts
|
||||
dv.setUint16(1, 513);
|
||||
// [0b00000011, 0b00000010, 0b00000001, 0b00000000]
|
||||
|
||||
console.log(dv.getUint16(1)); // => 513
|
||||
```
|
||||
|
||||
We've now assigned a value to the first three bytes in our underlying `ArrayBuffer`. Even though the second and third bytes were created using `setUint16()`, we can still read each of its component bytes using `getUint8()`.
|
||||
|
||||
```ts
|
||||
console.log(dv.getUint8(1)); // => 2
|
||||
console.log(dv.getUint8(2)); // => 1
|
||||
```
|
||||
|
||||
Attempting to write a value that requires more space than is available in the underlying `ArrayBuffer` will cause an error. Below we attempt to write a `Float64` (which requires 8 bytes) at byte offset `0`, but there are only four total bytes in the buffer.
|
||||
|
||||
```ts
|
||||
dv.setFloat64(0, 3.1415);
|
||||
// ^ RangeError: Out of bounds access
|
||||
```
|
||||
|
||||
The following methods are available on `DataView`:
|
||||
|
||||
| Getters | Setters |
|
||||
| -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`getBigInt64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigInt64) | [`setBigInt64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigInt64) |
|
||||
| [`getBigUint64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigUint64) | [`setBigUint64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigUint64) |
|
||||
| [`getFloat32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat32) | [`setFloat32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat32) |
|
||||
| [`getFloat64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat64) | [`setFloat64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat64) |
|
||||
| [`getInt16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt16) | [`setInt16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt16) |
|
||||
| [`getInt32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32) | [`setInt32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt32) |
|
||||
| [`getInt8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt8) | [`setInt8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt8) |
|
||||
| [`getUint16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint16) | [`setUint16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint16) |
|
||||
| [`getUint32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint32) | [`setUint32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint32) |
|
||||
| [`getUint8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint8) | [`setUint8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint8) |
|
||||
|
||||
### `TypedArray`
|
||||
|
||||
Typed arrays are a family of classes that provide an `Array`-like interface for interacting with data in an `ArrayBuffer`. Whereas a `DataView` lets you write numbers of varying size at a particular offset, a `TypedArray` interprets the underlying bytes as an array of numbers, each of a fixed size.
|
||||
|
||||
<Note>
|
||||
It's common to refer to this family of classes collectively by their shared superclass `TypedArray`. This class as
|
||||
_internal_ to JavaScript; you can't directly create instances of it, and `TypedArray` is not defined in the global
|
||||
scope. Think of it as an `interface` or an abstract class.
|
||||
</Note>
|
||||
|
||||
```ts
|
||||
const buffer = new ArrayBuffer(3);
|
||||
const arr = new Uint8Array(buffer);
|
||||
|
||||
// contents are initialized to zero
|
||||
console.log(arr); // Uint8Array(3) [0, 0, 0]
|
||||
|
||||
// assign values like an array
|
||||
arr[0] = 0;
|
||||
arr[1] = 10;
|
||||
arr[2] = 255;
|
||||
arr[3] = 255; // no-op, out of bounds
|
||||
```
|
||||
|
||||
While an `ArrayBuffer` is a generic sequence of bytes, these typed array classes interpret the bytes as an array of numbers of a given byte size.
|
||||
The top row contains the raw bytes, and the later rows contain how these bytes will be interpreted when _viewed_ using different typed array classes.
|
||||
|
||||
The following classes are typed arrays, along with a description of how they interpret the bytes in an `ArrayBuffer`:
|
||||
|
||||
Here's the first table formatted as a markdown table:
|
||||
|
||||
| Class | Description |
|
||||
| ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) | Every one (1) byte is interpreted as an unsigned 8-bit integer. Range 0 to 255. |
|
||||
| [`Uint16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array) | Every two (2) bytes are interpreted as an unsigned 16-bit integer. Range 0 to 65535. |
|
||||
| [`Uint32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array) | Every four (4) bytes are interpreted as an unsigned 32-bit integer. Range 0 to 4294967295. |
|
||||
| [`Int8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array) | Every one (1) byte is interpreted as a signed 8-bit integer. Range -128 to 127. |
|
||||
| [`Int16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array) | Every two (2) bytes are interpreted as a signed 16-bit integer. Range -32768 to 32767. |
|
||||
| [`Int32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array) | Every four (4) bytes are interpreted as a signed 32-bit integer. Range -2147483648 to 2147483647. |
|
||||
| [`Float16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float16Array) | Every two (2) bytes are interpreted as a 16-bit floating point number. Range -6.104e5 to 6.55e4. |
|
||||
| [`Float32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array) | Every four (4) bytes are interpreted as a 32-bit floating point number. Range -3.4e38 to 3.4e38. |
|
||||
| [`Float64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array) | Every eight (8) bytes are interpreted as a 64-bit floating point number. Range -1.7e308 to 1.7e308. |
|
||||
| [`BigInt64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array) | Every eight (8) bytes are interpreted as a signed `BigInt`. Range -9223372036854775808 to 9223372036854775807 (though `BigInt` is capable of representing larger numbers). |
|
||||
| [`BigUint64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array) | Every eight (8) bytes are interpreted as an unsigned `BigInt`. Range 0 to 18446744073709551615 (though `BigInt` is capable of representing larger numbers). |
|
||||
| [`Uint8ClampedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray) | Same as `Uint8Array`, but automatically "clamps" to the range 0-255 when assigning a value to an element. |
|
||||
|
||||
The table below demonstrates how the bytes in an `ArrayBuffer` are interpreted when viewed using different typed array classes.
|
||||
|
||||
| | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | Byte 7 |
|
||||
| ---------------- | ------------------- | ---------- | ------------------- | ---------- | -------------------- | ---------- | -------------------- | ---------- |
|
||||
| `ArrayBuffer` | `00000000` | `00000001` | `00000010` | `00000011` | `00000100` | `00000101` | `00000110` | `00000111` |
|
||||
| `Uint8Array` | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
||||
| `Uint16Array` | 256 (`1 * 256 + 0`) | | 770 (`3 * 256 + 2`) | | 1284 (`5 * 256 + 4`) | | 1798 (`7 * 256 + 6`) | |
|
||||
| `Uint32Array` | 50462976 | | | | 117835012 | | | |
|
||||
| `BigUint64Array` | 506097522914230528n | | | | | | | |
|
||||
|
||||
To create a typed array from a pre-defined `ArrayBuffer`:
|
||||
|
||||
```ts
|
||||
// create typed array from ArrayBuffer
|
||||
const buf = new ArrayBuffer(10);
|
||||
const arr = new Uint8Array(buf);
|
||||
|
||||
arr[0] = 30;
|
||||
arr[1] = 60;
|
||||
|
||||
// all elements are initialized to zero
|
||||
console.log(arr); // => Uint8Array(10) [ 30, 60, 0, 0, 0, 0, 0, 0, 0, 0 ];
|
||||
```
|
||||
|
||||
If we tried to instantiate a `Uint32Array` from this same `ArrayBuffer`, we'd get an error.
|
||||
|
||||
```ts
|
||||
const buf = new ArrayBuffer(10);
|
||||
const arr = new Uint32Array(buf);
|
||||
// ^ RangeError: ArrayBuffer length minus the byteOffset
|
||||
// is not a multiple of the element size
|
||||
```
|
||||
|
||||
A `Uint32` value requires four bytes (16 bits). Because the `ArrayBuffer` is 10 bytes long, there's no way to cleanly divide its contents into 4-byte chunks.
|
||||
|
||||
To fix this, we can create a typed array over a particular "slice" of an `ArrayBuffer`. The `Uint16Array` below only "views" the _first_ 8 bytes of the underlying `ArrayBuffer`. To achieve these, we specify a `byteOffset` of `0` and a `length` of `2`, which indicates the number of `Uint32` numbers we want our array to hold.
|
||||
|
||||
```ts
|
||||
// create typed array from ArrayBuffer slice
|
||||
const buf = new ArrayBuffer(10);
|
||||
const arr = new Uint32Array(buf, 0, 2);
|
||||
|
||||
/*
|
||||
buf _ _ _ _ _ _ _ _ _ _ 10 bytes
|
||||
arr [_______,_______] 2 4-byte elements
|
||||
*/
|
||||
|
||||
arr.byteOffset; // 0
|
||||
arr.length; // 2
|
||||
```
|
||||
|
||||
You don't need to explicitly create an `ArrayBuffer` instance; you can instead directly specify a length in the typed array constructor:
|
||||
|
||||
```ts
|
||||
const arr2 = new Uint8Array(5);
|
||||
|
||||
// all elements are initialized to zero
|
||||
// => Uint8Array(5) [0, 0, 0, 0, 0]
|
||||
```
|
||||
|
||||
Typed arrays can also be instantiated directly from an array of numbers, or another typed array:
|
||||
|
||||
```ts
|
||||
// from an array of numbers
|
||||
const arr1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
arr1[0]; // => 0;
|
||||
arr1[7]; // => 7;
|
||||
|
||||
// from another typed array
|
||||
const arr2 = new Uint8Array(arr);
|
||||
```
|
||||
|
||||
Broadly speaking, typed arrays provide the same methods as regular arrays, with a few exceptions. For example, `push` and `pop` are not available on typed arrays, because they would require resizing the underlying `ArrayBuffer`.
|
||||
|
||||
```ts
|
||||
const arr = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
|
||||
// supports common array methods
|
||||
arr.filter(n => n > 128); // Uint8Array(1) [255]
|
||||
arr.map(n => n * 2); // Uint8Array(8) [0, 2, 4, 6, 8, 10, 12, 14]
|
||||
arr.reduce((acc, n) => acc + n, 0); // 28
|
||||
arr.forEach(n => console.log(n)); // 0 1 2 3 4 5 6 7
|
||||
arr.every(n => n < 10); // true
|
||||
arr.find(n => n > 5); // 6
|
||||
arr.includes(5); // true
|
||||
arr.indexOf(5); // 5
|
||||
```
|
||||
|
||||
Refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) for more information on the properties and methods of typed arrays.
|
||||
|
||||
### `Uint8Array`
|
||||
|
||||
It's worth specifically highlighting `Uint8Array`, as it represents a classic "byte array"—a sequence of 8-bit unsigned integers between 0 and 255. This is the most common typed array you'll encounter in JavaScript.
|
||||
|
||||
In Bun, and someday in other JavaScript engines, it has methods available for converting between byte arrays and serialized representations of those arrays as base64 or hex strings.
|
||||
|
||||
```ts
|
||||
new Uint8Array([1, 2, 3, 4, 5]).toBase64(); // "AQIDBA=="
|
||||
Uint8Array.fromBase64("AQIDBA=="); // Uint8Array(4) [1, 2, 3, 4, 5]
|
||||
|
||||
new Uint8Array([255, 254, 253, 252, 251]).toHex(); // "fffefdfcfb=="
|
||||
Uint8Array.fromHex("fffefdfcfb"); // Uint8Array(5) [255, 254, 253, 252, 251]
|
||||
```
|
||||
|
||||
It is the return value of [`TextEncoder#encode`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder), and the input type of [`TextDecoder#decode`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder), two utility classes designed to translate strings and various binary encodings, most notably `"utf-8"`.
|
||||
|
||||
```ts
|
||||
const encoder = new TextEncoder();
|
||||
const bytes = encoder.encode("hello world");
|
||||
// => Uint8Array(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const text = decoder.decode(bytes);
|
||||
// => hello world
|
||||
```
|
||||
|
||||
### `Buffer`
|
||||
|
||||
Bun implements `Buffer`, a Node.js API for working with binary data that pre-dates the introduction of typed arrays in the JavaScript spec. It has since been re-implemented as a subclass of `Uint8Array`. It provides a wide range of methods, including several Array-like and `DataView`-like methods.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello world");
|
||||
// => Buffer(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]
|
||||
|
||||
buf.length; // => 11
|
||||
buf[0]; // => 104, ascii for 'h'
|
||||
buf.writeUInt8(72, 0); // => ascii for 'H'
|
||||
|
||||
console.log(buf.toString());
|
||||
// => Hello world
|
||||
```
|
||||
|
||||
For complete documentation, refer to the [Node.js documentation](https://nodejs.org/api/buffer.html).
|
||||
|
||||
## `Blob`
|
||||
|
||||
`Blob` is a Web API commonly used for representing files. `Blob` was initially implemented in browsers (unlike `ArrayBuffer` which is part of JavaScript itself), but it is now supported in Node and Bun.
|
||||
|
||||
It isn't common to directly create `Blob` instances. More often, you'll receive instances of `Blob` from an external source (like an `<input type="file">` element in the browser) or library. That said, it is possible to create a `Blob` from one or more string or binary "blob parts".
|
||||
|
||||
```ts
|
||||
const blob = new Blob(["<html>Hello</html>"], {
|
||||
type: "text/html",
|
||||
});
|
||||
|
||||
blob.type; // => text/html
|
||||
blob.size; // => 19
|
||||
```
|
||||
|
||||
These parts can be `string`, `ArrayBuffer`, `TypedArray`, `DataView`, or other `Blob` instances. The blob parts are concatenated together in the order they are provided.
|
||||
|
||||
```ts
|
||||
const blob = new Blob([
|
||||
"<html>",
|
||||
new Blob(["<body>"]),
|
||||
new Uint8Array([104, 101, 108, 108, 111]), // "hello" in binary
|
||||
"</body></html>",
|
||||
]);
|
||||
```
|
||||
|
||||
The contents of a `Blob` can be asynchronously read in various formats.
|
||||
|
||||
```ts
|
||||
await blob.text(); // => <html><body>hello</body></html>
|
||||
await blob.bytes(); // => Uint8Array (copies contents)
|
||||
await blob.arrayBuffer(); // => ArrayBuffer (copies contents)
|
||||
await blob.stream(); // => ReadableStream
|
||||
```
|
||||
|
||||
### `BunFile`
|
||||
|
||||
`BunFile` is a subclass of `Blob` used to represent a lazily-loaded file on disk. Like `File`, it adds a `name` and `lastModified` property. Unlike `File`, it does not require the file to be loaded into memory.
|
||||
|
||||
```ts
|
||||
const file = Bun.file("index.txt");
|
||||
// => BunFile
|
||||
```
|
||||
|
||||
### `File`
|
||||
|
||||
<Warning>Browser only. Experimental support in Node.js 20.</Warning>
|
||||
|
||||
[`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) is a subclass of `Blob` that adds a `name` and `lastModified` property. It's commonly used in the browser to represent files uploaded via a `<input type="file">` element. Node.js and Bun implement `File`.
|
||||
|
||||
```ts
|
||||
// on browser!
|
||||
// <input type="file" id="file" />
|
||||
|
||||
const files = document.getElementById("file").files;
|
||||
// => File[]
|
||||
```
|
||||
|
||||
```ts
|
||||
const file = new File(["<html>Hello</html>"], "index.html", {
|
||||
type: "text/html",
|
||||
});
|
||||
```
|
||||
|
||||
Refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Blob) for complete docs information.
|
||||
|
||||
---
|
||||
|
||||
## Streams
|
||||
|
||||
Streams are an important abstraction for working with binary data without loading it all into memory at once. They are commonly used for reading and writing files, sending and receiving network requests, and processing large amounts of data.
|
||||
|
||||
Bun implements the Web APIs [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) and [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
|
||||
|
||||
<Note>
|
||||
Bun also implements the `node:stream` module, including
|
||||
[`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams),
|
||||
[`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), and
|
||||
[`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). For complete documentation, refer
|
||||
to the Node.js docs.
|
||||
</Note>
|
||||
|
||||
To create a simple readable stream:
|
||||
|
||||
```ts
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue("hello");
|
||||
controller.enqueue("world");
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The contents of this stream can be read chunk-by-chunk with `for await` syntax.
|
||||
|
||||
```ts
|
||||
for await (const chunk of stream) {
|
||||
console.log(chunk);
|
||||
}
|
||||
|
||||
// => "hello"
|
||||
// => "world"
|
||||
```
|
||||
|
||||
For a more complete discussion of streams in Bun, see [API > Streams](/runtime/streams).
|
||||
|
||||
---
|
||||
|
||||
## Conversion
|
||||
|
||||
Converting from one binary format to another is a common task. This section is intended as a reference.
|
||||
|
||||
### From `ArrayBuffer`
|
||||
|
||||
Since `ArrayBuffer` stores the data that underlies other binary structures like `TypedArray`, the snippets below are not _converting_ from `ArrayBuffer` to another format. Instead, they are _creating_ a new instance using the data stored underlying data.
|
||||
|
||||
#### To `TypedArray`
|
||||
|
||||
```ts
|
||||
new Uint8Array(buf);
|
||||
```
|
||||
|
||||
#### To `DataView`
|
||||
|
||||
```ts
|
||||
new DataView(buf);
|
||||
```
|
||||
|
||||
#### To `Buffer`
|
||||
|
||||
```ts
|
||||
// create Buffer over entire ArrayBuffer
|
||||
Buffer.from(buf);
|
||||
|
||||
// create Buffer over a slice of the ArrayBuffer
|
||||
Buffer.from(buf, 0, 10);
|
||||
```
|
||||
|
||||
#### To `string`
|
||||
|
||||
As UTF-8:
|
||||
|
||||
```ts
|
||||
new TextDecoder().decode(buf);
|
||||
```
|
||||
|
||||
#### To `number[]`
|
||||
|
||||
```ts
|
||||
Array.from(new Uint8Array(buf));
|
||||
```
|
||||
|
||||
#### To `Blob`
|
||||
|
||||
```ts
|
||||
new Blob([buf], { type: "text/plain" });
|
||||
```
|
||||
|
||||
#### To `ReadableStream`
|
||||
|
||||
The following snippet creates a `ReadableStream` and enqueues the entire `ArrayBuffer` as a single chunk.
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(buf);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Accordion title="With chunking">
|
||||
To stream the `ArrayBuffer` in chunks, use a `Uint8Array` view and enqueue each chunk.
|
||||
|
||||
```ts
|
||||
const view = new Uint8Array(buf);
|
||||
const chunkSize = 1024;
|
||||
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
for (let i = 0; i < view.length; i += chunkSize) {
|
||||
controller.enqueue(view.slice(i, i + chunkSize));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
### From `TypedArray`
|
||||
|
||||
#### To `ArrayBuffer`
|
||||
|
||||
This retrieves the underlying `ArrayBuffer`. Note that a `TypedArray` can be a view of a _slice_ of the underlying buffer, so the sizes may differ.
|
||||
|
||||
```ts
|
||||
arr.buffer;
|
||||
```
|
||||
|
||||
#### To `DataView`
|
||||
|
||||
To creates a `DataView` over the same byte range as the TypedArray.
|
||||
|
||||
```ts
|
||||
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
||||
```
|
||||
|
||||
#### To `Buffer`
|
||||
|
||||
```ts
|
||||
Buffer.from(arr);
|
||||
```
|
||||
|
||||
#### To `string`
|
||||
|
||||
As UTF-8:
|
||||
|
||||
```ts
|
||||
new TextDecoder().decode(arr);
|
||||
```
|
||||
|
||||
#### To `number[]`
|
||||
|
||||
```ts
|
||||
Array.from(arr);
|
||||
```
|
||||
|
||||
#### To `Blob`
|
||||
|
||||
```ts
|
||||
// only if arr is a view of its entire backing TypedArray
|
||||
new Blob([arr.buffer], { type: "text/plain" });
|
||||
```
|
||||
|
||||
#### To `ReadableStream`
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(arr);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Accordion title="With chunking">
|
||||
|
||||
To stream the `ArrayBuffer` in chunks, split the `TypedArray` into chunks and enqueue each one individually.
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
for (let i = 0; i < arr.length; i += chunkSize) {
|
||||
controller.enqueue(arr.slice(i, i + chunkSize));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
### From `DataView`
|
||||
|
||||
#### To `ArrayBuffer`
|
||||
|
||||
```ts
|
||||
view.buffer;
|
||||
```
|
||||
|
||||
#### To `TypedArray`
|
||||
|
||||
Only works if the `byteLength` of the `DataView` is a multiple of the `BYTES_PER_ELEMENT` of the `TypedArray` subclass.
|
||||
|
||||
```ts
|
||||
new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
||||
new Uint16Array(view.buffer, view.byteOffset, view.byteLength / 2);
|
||||
new Uint32Array(view.buffer, view.byteOffset, view.byteLength / 4);
|
||||
// etc...
|
||||
```
|
||||
|
||||
#### To `Buffer`
|
||||
|
||||
```ts
|
||||
Buffer.from(view.buffer, view.byteOffset, view.byteLength);
|
||||
```
|
||||
|
||||
#### To `string`
|
||||
|
||||
As UTF-8:
|
||||
|
||||
```ts
|
||||
new TextDecoder().decode(view);
|
||||
```
|
||||
|
||||
#### To `number[]`
|
||||
|
||||
```ts
|
||||
Array.from(view);
|
||||
```
|
||||
|
||||
#### To `Blob`
|
||||
|
||||
```ts
|
||||
new Blob([view.buffer], { type: "text/plain" });
|
||||
```
|
||||
|
||||
#### To `ReadableStream`
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(view.buffer);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Accordion title="With chunking">
|
||||
To stream the `ArrayBuffer` in chunks, split the `DataView` into chunks and enqueue each one individually.
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
for (let i = 0; i < view.byteLength; i += chunkSize) {
|
||||
controller.enqueue(view.buffer.slice(i, i + chunkSize));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
### From `Buffer`
|
||||
|
||||
#### To `ArrayBuffer`
|
||||
|
||||
```ts
|
||||
buf.buffer;
|
||||
```
|
||||
|
||||
#### To `TypedArray`
|
||||
|
||||
```ts
|
||||
new Uint8Array(buf);
|
||||
```
|
||||
|
||||
#### To `DataView`
|
||||
|
||||
```ts
|
||||
new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
||||
```
|
||||
|
||||
#### To `string`
|
||||
|
||||
As UTF-8:
|
||||
|
||||
```ts
|
||||
buf.toString();
|
||||
```
|
||||
|
||||
As base64:
|
||||
|
||||
```ts
|
||||
buf.toString("base64");
|
||||
```
|
||||
|
||||
As hex:
|
||||
|
||||
```ts
|
||||
buf.toString("hex");
|
||||
```
|
||||
|
||||
#### To `number[]`
|
||||
|
||||
```ts
|
||||
Array.from(buf);
|
||||
```
|
||||
|
||||
#### To `Blob`
|
||||
|
||||
```ts
|
||||
new Blob([buf], { type: "text/plain" });
|
||||
```
|
||||
|
||||
#### To `ReadableStream`
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(buf);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Accordion title="With chunking">
|
||||
To stream the `ArrayBuffer` in chunks, split the `Buffer` into chunks and enqueue each one individually.
|
||||
|
||||
```ts
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
for (let i = 0; i < buf.length; i += chunkSize) {
|
||||
controller.enqueue(buf.slice(i, i + chunkSize));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
### From `Blob`
|
||||
|
||||
#### To `ArrayBuffer`
|
||||
|
||||
The `Blob` class provides a convenience method for this purpose.
|
||||
|
||||
```ts
|
||||
await blob.arrayBuffer();
|
||||
```
|
||||
|
||||
#### To `TypedArray`
|
||||
|
||||
```ts
|
||||
await blob.bytes();
|
||||
```
|
||||
|
||||
#### To `DataView`
|
||||
|
||||
```ts
|
||||
new DataView(await blob.arrayBuffer());
|
||||
```
|
||||
|
||||
#### To `Buffer`
|
||||
|
||||
```ts
|
||||
Buffer.from(await blob.arrayBuffer());
|
||||
```
|
||||
|
||||
#### To `string`
|
||||
|
||||
As UTF-8:
|
||||
|
||||
```ts
|
||||
await blob.text();
|
||||
```
|
||||
|
||||
#### To `number[]`
|
||||
|
||||
```ts
|
||||
Array.from(await blob.bytes());
|
||||
```
|
||||
|
||||
#### To `ReadableStream`
|
||||
|
||||
```ts
|
||||
blob.stream();
|
||||
```
|
||||
|
||||
### From `ReadableStream`
|
||||
|
||||
It's common to use [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) as a convenient intermediate representation to make it easier to convert `ReadableStream` to other formats.
|
||||
|
||||
```ts
|
||||
stream; // ReadableStream
|
||||
|
||||
const buffer = new Response(stream).arrayBuffer();
|
||||
```
|
||||
|
||||
However this approach is verbose and adds overhead that slows down overall performance unnecessarily. Bun implements a set of optimized convenience functions for converting `ReadableStream` various binary formats.
|
||||
|
||||
#### To `ArrayBuffer`
|
||||
|
||||
```ts
|
||||
// with Response
|
||||
new Response(stream).arrayBuffer();
|
||||
|
||||
// with Bun function
|
||||
Bun.readableStreamToArrayBuffer(stream);
|
||||
```
|
||||
|
||||
#### To `Uint8Array`
|
||||
|
||||
```ts
|
||||
// with Response
|
||||
new Response(stream).bytes();
|
||||
|
||||
// with Bun function
|
||||
Bun.readableStreamToBytes(stream);
|
||||
```
|
||||
|
||||
#### To `TypedArray`
|
||||
|
||||
```ts
|
||||
// with Response
|
||||
const buf = await new Response(stream).arrayBuffer();
|
||||
new Int8Array(buf);
|
||||
|
||||
// with Bun function
|
||||
new Int8Array(Bun.readableStreamToArrayBuffer(stream));
|
||||
```
|
||||
|
||||
#### To `DataView`
|
||||
|
||||
```ts
|
||||
// with Response
|
||||
const buf = await new Response(stream).arrayBuffer();
|
||||
new DataView(buf);
|
||||
|
||||
// with Bun function
|
||||
new DataView(Bun.readableStreamToArrayBuffer(stream));
|
||||
```
|
||||
|
||||
#### To `Buffer`
|
||||
|
||||
```ts
|
||||
// with Response
|
||||
const buf = await new Response(stream).arrayBuffer();
|
||||
Buffer.from(buf);
|
||||
|
||||
// with Bun function
|
||||
Buffer.from(Bun.readableStreamToArrayBuffer(stream));
|
||||
```
|
||||
|
||||
#### To `string`
|
||||
|
||||
As UTF-8:
|
||||
|
||||
```ts
|
||||
// with Response
|
||||
await new Response(stream).text();
|
||||
|
||||
// with Bun function
|
||||
await Bun.readableStreamToText(stream);
|
||||
```
|
||||
|
||||
#### To `number[]`
|
||||
|
||||
```ts
|
||||
// with Response
|
||||
const arr = await new Response(stream).bytes();
|
||||
Array.from(arr);
|
||||
|
||||
// with Bun function
|
||||
Array.from(new Uint8Array(Bun.readableStreamToArrayBuffer(stream)));
|
||||
```
|
||||
|
||||
Bun provides a utility for resolving a `ReadableStream` to an array of its chunks. Each chunk may be a string, typed array, or `ArrayBuffer`.
|
||||
|
||||
```ts
|
||||
// with Bun function
|
||||
Bun.readableStreamToArray(stream);
|
||||
```
|
||||
|
||||
#### To `Blob`
|
||||
|
||||
```ts
|
||||
new Response(stream).blob();
|
||||
```
|
||||
|
||||
#### To `ReadableStream`
|
||||
|
||||
To split a `ReadableStream` into two streams that can be consumed independently:
|
||||
|
||||
```ts
|
||||
const [a, b] = stream.tee();
|
||||
```
|
||||
@@ -1,207 +0,0 @@
|
||||
Bun implements a set of native APIs on the `Bun` global object and through a number of built-in modules. These APIs are heavily optimized and represent the canonical "Bun-native" way to implement some common functionality.
|
||||
|
||||
Bun strives to implement standard Web APIs wherever possible. Bun introduces new APIs primarily for server-side tasks where no standard exists, such as file I/O and starting an HTTP server. In these cases, Bun's approach still builds atop standard APIs like `Blob`, `URL`, and `Request`.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
fetch(req: Request) {
|
||||
return new Response("Success!");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Click the link in the right column to jump to the associated documentation.
|
||||
|
||||
{% table %}
|
||||
|
||||
- Topic
|
||||
- APIs
|
||||
|
||||
---
|
||||
|
||||
- HTTP Server
|
||||
- [`Bun.serve`](https://bun.com/docs/api/http#bun-serve)
|
||||
|
||||
---
|
||||
|
||||
- Shell
|
||||
- [`$`](https://bun.com/docs/runtime/shell)
|
||||
|
||||
---
|
||||
|
||||
- Bundler
|
||||
- [`Bun.build`](https://bun.com/docs/bundler)
|
||||
|
||||
---
|
||||
|
||||
- File I/O
|
||||
- [`Bun.file`](https://bun.com/docs/api/file-io#reading-files-bun-file), [`Bun.write`](https://bun.com/docs/api/file-io#writing-files-bun-write), `Bun.stdin`, `Bun.stdout`, `Bun.stderr`
|
||||
|
||||
---
|
||||
|
||||
- Child Processes
|
||||
- [`Bun.spawn`](https://bun.com/docs/api/spawn#spawn-a-process-bun-spawn), [`Bun.spawnSync`](https://bun.com/docs/api/spawn#blocking-api-bun-spawnsync)
|
||||
|
||||
---
|
||||
|
||||
- TCP Sockets
|
||||
- [`Bun.listen`](https://bun.com/docs/api/tcp#start-a-server-bun-listen), [`Bun.connect`](https://bun.com/docs/api/tcp#start-a-server-bun-listen)
|
||||
|
||||
---
|
||||
|
||||
- UDP Sockets
|
||||
- [`Bun.udpSocket`](https://bun.com/docs/api/udp)
|
||||
|
||||
---
|
||||
|
||||
- WebSockets
|
||||
- `new WebSocket()` (client), [`Bun.serve`](https://bun.com/docs/api/websockets) (server)
|
||||
|
||||
---
|
||||
|
||||
- Transpiler
|
||||
- [`Bun.Transpiler`](https://bun.com/docs/api/transpiler)
|
||||
|
||||
---
|
||||
|
||||
- Routing
|
||||
- [`Bun.FileSystemRouter`](https://bun.com/docs/api/file-system-router)
|
||||
|
||||
---
|
||||
|
||||
- Streaming HTML
|
||||
- [`HTMLRewriter`](https://bun.com/docs/api/html-rewriter)
|
||||
|
||||
---
|
||||
|
||||
- Hashing
|
||||
- [`Bun.password`](https://bun.com/docs/api/hashing#bun-password), [`Bun.hash`](https://bun.com/docs/api/hashing#bun-hash), [`Bun.CryptoHasher`](https://bun.com/docs/api/hashing#bun-cryptohasher), `Bun.sha`
|
||||
|
||||
---
|
||||
|
||||
- SQLite
|
||||
- [`bun:sqlite`](https://bun.com/docs/api/sqlite)
|
||||
|
||||
---
|
||||
|
||||
- PostgreSQL Client
|
||||
- [`Bun.SQL`](https://bun.com/docs/api/sql), `Bun.sql`
|
||||
|
||||
---
|
||||
|
||||
- Redis (Valkey) Client
|
||||
- [`Bun.RedisClient`](https://bun.com/docs/api/redis), `Bun.redis`
|
||||
|
||||
---
|
||||
|
||||
- FFI (Foreign Function Interface)
|
||||
- [`bun:ffi`](https://bun.com/docs/api/ffi)
|
||||
|
||||
---
|
||||
|
||||
- DNS
|
||||
- [`Bun.dns.lookup`](https://bun.com/docs/api/dns), `Bun.dns.prefetch`, `Bun.dns.getCacheStats`
|
||||
|
||||
---
|
||||
|
||||
- Testing
|
||||
- [`bun:test`](https://bun.com/docs/cli/test)
|
||||
|
||||
---
|
||||
|
||||
- Workers
|
||||
- [`new Worker()`](https://bun.com/docs/api/workers)
|
||||
|
||||
---
|
||||
|
||||
- Module Loaders
|
||||
- [`Bun.plugin`](https://bun.com/docs/bundler/plugins)
|
||||
|
||||
---
|
||||
|
||||
- Glob
|
||||
- [`Bun.Glob`](https://bun.com/docs/api/glob)
|
||||
|
||||
---
|
||||
|
||||
- Cookies
|
||||
- [`Bun.Cookie`](https://bun.com/docs/api/cookie), [`Bun.CookieMap`](https://bun.com/docs/api/cookie)
|
||||
|
||||
---
|
||||
|
||||
- Node-API
|
||||
- [`Node-API`](https://bun.com/docs/api/node-api)
|
||||
|
||||
---
|
||||
|
||||
- `import.meta`
|
||||
- [`import.meta`](https://bun.com/docs/api/import-meta)
|
||||
|
||||
---
|
||||
|
||||
- Utilities
|
||||
- [`Bun.version`](https://bun.com/docs/api/utils#bun-version), [`Bun.revision`](https://bun.com/docs/api/utils#bun-revision), [`Bun.env`](https://bun.com/docs/api/utils#bun-env), [`Bun.main`](https://bun.com/docs/api/utils#bun-main)
|
||||
|
||||
---
|
||||
|
||||
- Sleep & Timing
|
||||
- [`Bun.sleep()`](https://bun.com/docs/api/utils#bun-sleep), [`Bun.sleepSync()`](https://bun.com/docs/api/utils#bun-sleepsync), [`Bun.nanoseconds()`](https://bun.com/docs/api/utils#bun-nanoseconds)
|
||||
|
||||
---
|
||||
|
||||
- Random & UUID
|
||||
- [`Bun.randomUUIDv7()`](https://bun.com/docs/api/utils#bun-randomuuidv7)
|
||||
|
||||
---
|
||||
|
||||
- System & Environment
|
||||
- [`Bun.which()`](https://bun.com/docs/api/utils#bun-which)
|
||||
|
||||
---
|
||||
|
||||
- Comparison & Inspection
|
||||
- [`Bun.peek()`](https://bun.com/docs/api/utils#bun-peek), [`Bun.deepEquals()`](https://bun.com/docs/api/utils#bun-deepequals), `Bun.deepMatch`, [`Bun.inspect()`](https://bun.com/docs/api/utils#bun-inspect)
|
||||
|
||||
---
|
||||
|
||||
- String & Text Processing
|
||||
- [`Bun.escapeHTML()`](https://bun.com/docs/api/utils#bun-escapehtml), [`Bun.stringWidth()`](https://bun.com/docs/api/utils#bun-stringwidth), `Bun.indexOfLine`
|
||||
|
||||
---
|
||||
|
||||
- URL & Path Utilities
|
||||
- [`Bun.fileURLToPath()`](https://bun.com/docs/api/utils#bun-fileurltopath), [`Bun.pathToFileURL()`](https://bun.com/docs/api/utils#bun-pathtofileurl)
|
||||
|
||||
---
|
||||
|
||||
- Compression
|
||||
- [`Bun.gzipSync()`](https://bun.com/docs/api/utils#bun-gzipsync), [`Bun.gunzipSync()`](https://bun.com/docs/api/utils#bun-gunzipsync), [`Bun.deflateSync()`](https://bun.com/docs/api/utils#bun-deflatesync), [`Bun.inflateSync()`](https://bun.com/docs/api/utils#bun-inflatesync), `Bun.zstdCompressSync()`, `Bun.zstdDecompressSync()`, `Bun.zstdCompress()`, `Bun.zstdDecompress()`
|
||||
|
||||
---
|
||||
|
||||
- Stream Processing
|
||||
- [`Bun.readableStreamTo*()`](https://bun.com/docs/api/utils#bun-readablestreamto), `Bun.readableStreamToBytes()`, `Bun.readableStreamToBlob()`, `Bun.readableStreamToFormData()`, `Bun.readableStreamToJSON()`, `Bun.readableStreamToArray()`
|
||||
|
||||
---
|
||||
|
||||
- Memory & Buffer Management
|
||||
- `Bun.ArrayBufferSink`, `Bun.allocUnsafe`, `Bun.concatArrayBuffers`
|
||||
|
||||
---
|
||||
|
||||
- Module Resolution
|
||||
- [`Bun.resolveSync()`](https://bun.com/docs/api/utils#bun-resolvesync)
|
||||
|
||||
---
|
||||
|
||||
- Parsing & Formatting
|
||||
- [`Bun.semver`](https://bun.com/docs/api/semver), `Bun.TOML.parse`, [`Bun.YAML.parse`](https://bun.com/docs/api/yaml), [`Bun.color`](https://bun.com/docs/api/color)
|
||||
|
||||
---
|
||||
|
||||
- Low-level / Internals
|
||||
- `Bun.mmap`, `Bun.gc`, `Bun.generateHeapSnapshot`, [`bun:jsc`](https://bun.com/reference/bun/jsc)
|
||||
|
||||
---
|
||||
|
||||
{% /table %}
|
||||
59
docs/runtime/bun-apis.mdx
Normal file
59
docs/runtime/bun-apis.mdx
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: "Bun APIs"
|
||||
description: "Overview of Bun's native APIs available on the Bun global object and built-in modules"
|
||||
mode: center
|
||||
---
|
||||
|
||||
Bun implements a set of native APIs on the `Bun` global object and through a number of built-in modules. These APIs are heavily optimized and represent the canonical "Bun-native" way to implement some common functionality.
|
||||
|
||||
Bun strives to implement standard Web APIs wherever possible. Bun introduces new APIs primarily for server-side tasks where no standard exists, such as file I/O and starting an HTTP server. In these cases, Bun's approach still builds atop standard APIs like `Blob`, `URL`, and `Request`.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
fetch(req: Request) {
|
||||
return new Response("Success!");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Click the link in the right column to jump to the associated documentation.
|
||||
|
||||
| Topic | APIs |
|
||||
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| HTTP Server | [`Bun.serve`](/runtime/http/server) |
|
||||
| Shell | [`$`](/runtime/shell) |
|
||||
| Bundler | [`Bun.build`](/bundler) |
|
||||
| File I/O | [`Bun.file`](/runtime/file-io#reading-files-bun-file), [`Bun.write`](/runtime/file-io#writing-files-bun-write), `Bun.stdin`, `Bun.stdout`, `Bun.stderr` |
|
||||
| Child Processes | [`Bun.spawn`](/runtime/child-process#spawn-a-process-bun-spawn), [`Bun.spawnSync`](/runtime/child-process#blocking-api-bun-spawnsync) |
|
||||
| TCP Sockets | [`Bun.listen`](/runtime/networking/tcp#start-a-server-bun-listen), [`Bun.connect`](/runtime/networking/tcp#start-a-server-bun-listen) |
|
||||
| UDP Sockets | [`Bun.udpSocket`](/runtime/networking/udp) |
|
||||
| WebSockets | `new WebSocket()` (client), [`Bun.serve`](/runtime/http/websockets) (server) |
|
||||
| Transpiler | [`Bun.Transpiler`](/runtime/transpiler) |
|
||||
| Routing | [`Bun.FileSystemRouter`](/runtime/file-system-router) |
|
||||
| Streaming HTML | [`HTMLRewriter`](/runtime/html-rewriter) |
|
||||
| Hashing | [`Bun.password`](/runtime/hashing#bun-password), [`Bun.hash`](/runtime/hashing#bun-hash), [`Bun.CryptoHasher`](/runtime/hashing#bun-cryptohasher), `Bun.sha` |
|
||||
| SQLite | [`bun:sqlite`](/runtime/sqlite) |
|
||||
| PostgreSQL Client | [`Bun.SQL`](/runtime/sql), `Bun.sql` |
|
||||
| Redis (Valkey) Client | [`Bun.RedisClient`](/runtime/redis), `Bun.redis` |
|
||||
| FFI (Foreign Function Interface) | [`bun:ffi`](/runtime/ffi) |
|
||||
| DNS | [`Bun.dns.lookup`](/runtime/networking/dns), `Bun.dns.prefetch`, `Bun.dns.getCacheStats` |
|
||||
| Testing | [`bun:test`](/test) |
|
||||
| Workers | [`new Worker()`](/runtime/workers) |
|
||||
| Module Loaders | [`Bun.plugin`](/bundler/plugins) |
|
||||
| Glob | [`Bun.Glob`](/runtime/glob) |
|
||||
| Cookies | [`Bun.Cookie`](/runtime/cookies), [`Bun.CookieMap`](/runtime/cookies) |
|
||||
| Node-API | [`Node-API`](/runtime/node-api) |
|
||||
| `import.meta` | [`import.meta`](/runtime/module-resolution#import-meta) |
|
||||
| Utilities | [`Bun.version`](/runtime/utils#bun-version), [`Bun.revision`](/runtime/utils#bun-revision), [`Bun.env`](/runtime/utils#bun-env), [`Bun.main`](/runtime/utils#bun-main) |
|
||||
| Sleep & Timing | [`Bun.sleep()`](/runtime/utils#bun-sleep), [`Bun.sleepSync()`](/runtime/utils#bun-sleepsync), [`Bun.nanoseconds()`](/runtime/utils#bun-nanoseconds) |
|
||||
| Random & UUID | [`Bun.randomUUIDv7()`](/runtime/utils#bun-randomuuidv7) |
|
||||
| System & Environment | [`Bun.which()`](/runtime/utils#bun-which) |
|
||||
| Comparison & Inspection | [`Bun.peek()`](/runtime/utils#bun-peek), [`Bun.deepEquals()`](/runtime/utils#bun-deepequals), `Bun.deepMatch`, [`Bun.inspect()`](/runtime/utils#bun-inspect) |
|
||||
| String & Text Processing | [`Bun.escapeHTML()`](/runtime/utils#bun-escapehtml), [`Bun.stringWidth()`](/runtime/utils#bun-stringwidth), `Bun.indexOfLine` |
|
||||
| URL & Path Utilities | [`Bun.fileURLToPath()`](/runtime/utils#bun-fileurltopath), [`Bun.pathToFileURL()`](/runtime/utils#bun-pathtofileurl) |
|
||||
| Compression | [`Bun.gzipSync()`](/runtime/utils#bun-gzipsync), [`Bun.gunzipSync()`](/runtime/utils#bun-gunzipsync), [`Bun.deflateSync()`](/runtime/utils#bun-deflatesync), [`Bun.inflateSync()`](/runtime/utils#bun-inflatesync), `Bun.zstdCompressSync()`, `Bun.zstdDecompressSync()`, `Bun.zstdCompress()`, `Bun.zstdDecompress()` |
|
||||
| Stream Processing | [`Bun.readableStreamTo*()`](/runtime/utils#bun-readablestreamto), `Bun.readableStreamToBytes()`, `Bun.readableStreamToBlob()`, `Bun.readableStreamToFormData()`, `Bun.readableStreamToJSON()`, `Bun.readableStreamToArray()` |
|
||||
| Memory & Buffer Management | `Bun.ArrayBufferSink`, `Bun.allocUnsafe`, `Bun.concatArrayBuffers` |
|
||||
| Module Resolution | [`Bun.resolveSync()`](/runtime/utils#bun-resolvesync) |
|
||||
| Parsing & Formatting | [`Bun.semver`](/runtime/semver), `Bun.TOML.parse`, [`Bun.color`](/runtime/color) |
|
||||
| Low-level / Internals | `Bun.mmap`, `Bun.gc`, `Bun.generateHeapSnapshot`, [`bun:jsc`](https://bun.com/reference/bun/jsc) |
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: "bunfig.toml"
|
||||
description: "Configure Bun's behavior using its configuration file bunfig.toml"
|
||||
---
|
||||
|
||||
Bun's behavior can be configured using its configuration file, `bunfig.toml`.
|
||||
|
||||
In general, Bun relies on pre-existing configuration files like `package.json` and `tsconfig.json` to configure its behavior. `bunfig.toml` is only necessary for configuring Bun-specific things. This file is optional, and Bun will work out of the box without it.
|
||||
@@ -21,7 +26,7 @@ Bun's runtime behavior is configured using top-level fields in the `bunfig.toml`
|
||||
|
||||
An array of scripts/plugins to execute before running a file or script.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
# scripts to run before `bun run`-ing a file or script
|
||||
# register plugins by adding them to this list
|
||||
preload = ["./preload.ts"]
|
||||
@@ -31,7 +36,7 @@ preload = ["./preload.ts"]
|
||||
|
||||
Configure how Bun handles JSX. You can also set these fields in the `compilerOptions` of your `tsconfig.json`, but they are supported here as well for non-TypeScript projects.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
jsx = "react"
|
||||
jsxFactory = "h"
|
||||
jsxFragment = "Fragment"
|
||||
@@ -40,16 +45,16 @@ jsxImportSource = "react"
|
||||
|
||||
Refer to the tsconfig docs for more information on these fields.
|
||||
|
||||
- [jsx](https://www.typescriptlang.org/tsconfig#jsx)
|
||||
- [jsxFactory](https://www.typescriptlang.org/tsconfig#jsxFactory)
|
||||
- [jsxFragment](https://www.typescriptlang.org/tsconfig#jsxFragment)
|
||||
- [jsxImportSource](https://www.typescriptlang.org/tsconfig#jsxImportSource)
|
||||
- [`jsx`](https://www.typescriptlang.org/tsconfig#jsx)
|
||||
- [`jsxFactory`](https://www.typescriptlang.org/tsconfig#jsxFactory)
|
||||
- [`jsxFragment`](https://www.typescriptlang.org/tsconfig#jsxFragment)
|
||||
- [`jsxImportSource`](https://www.typescriptlang.org/tsconfig#jsxImportSource)
|
||||
|
||||
### `smol`
|
||||
|
||||
Enable `smol` mode. This reduces memory usage at the cost of performance.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
# Reduce memory usage at the cost of performance
|
||||
smol = true
|
||||
```
|
||||
@@ -58,7 +63,7 @@ smol = true
|
||||
|
||||
Set the log level. This can be one of `"debug"`, `"warn"`, or `"error"`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
logLevel = "debug" # "debug" | "warn" | "error"
|
||||
```
|
||||
|
||||
@@ -66,7 +71,7 @@ logLevel = "debug" # "debug" | "warn" | "error"
|
||||
|
||||
The `define` field allows you to replace certain global identifiers with constant expressions. Bun will replace any usage of the identifier with the expression. The expression should be a JSON string.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[define]
|
||||
# Replace any usage of "process.env.bagel" with the string `lox`.
|
||||
# The values are parsed as JSON, except single-quoted strings are supported and `'undefined'` becomes `undefined` in JS.
|
||||
@@ -78,7 +83,7 @@ The `define` field allows you to replace certain global identifiers with constan
|
||||
|
||||
Configure how Bun maps file extensions to loaders. This is useful for loading files that aren't natively supported by Bun.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[loader]
|
||||
# when a .bagel file is imported, treat it like a tsx file
|
||||
".bagel" = "tsx"
|
||||
@@ -94,7 +99,6 @@ Bun supports the following loaders:
|
||||
- `file`
|
||||
- `json`
|
||||
- `toml`
|
||||
- `yaml`
|
||||
- `wasm`
|
||||
- `napi`
|
||||
- `base64`
|
||||
@@ -103,9 +107,11 @@ Bun supports the following loaders:
|
||||
|
||||
### `telemetry`
|
||||
|
||||
The `telemetry` field permit to enable/disable the analytics records. Bun records bundle timings (so we can answer with data, "is Bun getting faster?") and feature usage (e.g., "are people actually using macros?"). The request body size is about 60 bytes, so it's not a lot of data. By default the telemetry is enabled. Equivalent of `DO_NOT_TRACK` env variable.
|
||||
The `telemetry` field is used to enable/disable analytics. By default, telemetry is enabled. This is equivalent to the `DO_NOT_TRACK` environment variable.
|
||||
|
||||
```toml
|
||||
Currently we do not collect telemetry and this setting is only used for enabling/disabling anonymous crash reports, but in the future we plan to collect information like which Bun APIs are used most or how long `bun build` takes.
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
telemetry = false
|
||||
```
|
||||
|
||||
@@ -117,7 +123,7 @@ Configure console output behavior.
|
||||
|
||||
Set the default depth for `console.log()` object inspection. Default `2`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[console]
|
||||
depth = 3
|
||||
```
|
||||
@@ -128,7 +134,7 @@ This controls how deeply nested objects are displayed in console output. Higher
|
||||
|
||||
The test runner is configured under the `[test]` section of your bunfig.toml.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
# configuration goes here
|
||||
```
|
||||
@@ -137,7 +143,7 @@ The test runner is configured under the `[test]` section of your bunfig.toml.
|
||||
|
||||
The root directory to run tests from. Default `.`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
root = "./__tests__"
|
||||
```
|
||||
@@ -146,7 +152,7 @@ root = "./__tests__"
|
||||
|
||||
Same as the top-level `preload` field, but only applies to `bun test`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
preload = ["./setup.ts"]
|
||||
```
|
||||
@@ -155,7 +161,7 @@ preload = ["./setup.ts"]
|
||||
|
||||
Same as the top-level `smol` field, but only applies to `bun test`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
smol = true
|
||||
```
|
||||
@@ -164,7 +170,7 @@ smol = true
|
||||
|
||||
Enables coverage reporting. Default `false`. Use `--coverage` to override.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
coverage = false
|
||||
```
|
||||
@@ -173,7 +179,7 @@ coverage = false
|
||||
|
||||
To specify a coverage threshold. By default, no threshold is set. If your test suite does not meet or exceed this threshold, `bun test` will exit with a non-zero exit code to indicate the failure.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
|
||||
# to require 90% line-level and function-level coverage
|
||||
@@ -182,7 +188,7 @@ coverageThreshold = 0.9
|
||||
|
||||
Different thresholds can be specified for line-wise, function-wise, and statement-wise coverage.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
coverageThreshold = { line = 0.7, function = 0.8, statement = 0.9 }
|
||||
```
|
||||
@@ -191,7 +197,7 @@ coverageThreshold = { line = 0.7, function = 0.8, statement = 0.9 }
|
||||
|
||||
Whether to skip test files when computing coverage statistics. Default `false`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
coverageSkipTestFiles = false
|
||||
```
|
||||
@@ -200,7 +206,7 @@ coverageSkipTestFiles = false
|
||||
|
||||
Exclude specific files or file patterns from coverage reports using glob patterns. Can be a single string pattern or an array of patterns.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
# Single pattern
|
||||
coveragePathIgnorePatterns = "**/*.spec.ts"
|
||||
@@ -218,7 +224,7 @@ coveragePathIgnorePatterns = [
|
||||
|
||||
By default, coverage reports will be printed to the console. For persistent code coverage reports in CI environments and for other tools use `lcov`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
coverageReporter = ["text", "lcov"] # default ["text"]
|
||||
```
|
||||
@@ -227,33 +233,16 @@ coverageReporter = ["text", "lcov"] # default ["text"]
|
||||
|
||||
Set path where coverage reports will be saved. Please notice, that it works only for persistent `coverageReporter` like `lcov`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
coverageDir = "path/to/somewhere" # default "coverage"
|
||||
```
|
||||
|
||||
### `test.concurrentTestGlob`
|
||||
|
||||
Specify a glob pattern to automatically run matching test files with concurrent test execution enabled. Test files matching this pattern will behave as if the `--concurrent` flag was passed, running all tests within those files concurrently.
|
||||
|
||||
```toml
|
||||
[test]
|
||||
concurrentTestGlob = "**/concurrent-*.test.ts"
|
||||
```
|
||||
|
||||
This is useful for:
|
||||
|
||||
- Gradually migrating test suites to concurrent execution
|
||||
- Running integration tests concurrently while keeping unit tests sequential
|
||||
- Separating fast concurrent tests from tests that require sequential execution
|
||||
|
||||
The `--concurrent` CLI flag will override this setting when specified.
|
||||
|
||||
### `test.randomize`
|
||||
|
||||
Run tests in random order. Default `false`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
randomize = true
|
||||
```
|
||||
@@ -266,7 +255,7 @@ The `--randomize` CLI flag will override this setting when specified.
|
||||
|
||||
Set the random seed for test randomization. This option requires `randomize` to be `true`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
randomize = true
|
||||
seed = 2444615283
|
||||
@@ -280,7 +269,7 @@ The `--seed` CLI flag will override this setting when specified.
|
||||
|
||||
Re-run each test file a specified number of times. Default `0` (run once).
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
rerunEach = 3
|
||||
```
|
||||
@@ -289,11 +278,63 @@ This is useful for catching flaky tests or non-deterministic behavior. Each test
|
||||
|
||||
The `--rerun-each` CLI flag will override this setting when specified.
|
||||
|
||||
### `test.concurrentTestGlob`
|
||||
|
||||
Specify a glob pattern to automatically run matching test files with concurrent test execution enabled. Test files matching this pattern will behave as if the `--concurrent` flag was passed, running all tests within those files concurrently.
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
concurrentTestGlob = "**/concurrent-*.test.ts"
|
||||
```
|
||||
|
||||
This is useful for:
|
||||
|
||||
- Gradually migrating test suites to concurrent execution
|
||||
- Running integration tests concurrently while keeping unit tests sequential
|
||||
- Separating fast concurrent tests from tests that require sequential execution
|
||||
|
||||
The `--concurrent` CLI flag will override this setting when specified.
|
||||
|
||||
### `test.onlyFailures`
|
||||
|
||||
When enabled, only failed tests are displayed in the output. This helps reduce noise in large test suites by hiding passing tests. Default `false`.
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
onlyFailures = true
|
||||
```
|
||||
|
||||
This is equivalent to using the `--only-failures` flag when running `bun test`.
|
||||
|
||||
### `test.reporter`
|
||||
|
||||
Configure the test reporter settings.
|
||||
|
||||
#### `test.reporter.dots`
|
||||
|
||||
Enable the dots reporter, which displays a compact output showing a dot for each test. Default `false`.
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test.reporter]
|
||||
dots = true
|
||||
```
|
||||
|
||||
#### `test.reporter.junit`
|
||||
|
||||
Enable JUnit XML reporting and specify the output file path.
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test.reporter]
|
||||
junit = "test-results.xml"
|
||||
```
|
||||
|
||||
This generates a JUnit XML report that can be consumed by CI systems and other tools.
|
||||
|
||||
## Package manager
|
||||
|
||||
Package management is a complex issue; to support a range of use cases, the behavior of `bun install` can be configured under the `[install]` section.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
# configuration here
|
||||
```
|
||||
@@ -302,7 +343,7 @@ Package management is a complex issue; to support a range of use cases, the beha
|
||||
|
||||
Whether to install optional dependencies. Default `true`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
optional = true
|
||||
```
|
||||
@@ -311,7 +352,7 @@ optional = true
|
||||
|
||||
Whether to install development dependencies. Default `true`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
dev = true
|
||||
```
|
||||
@@ -320,7 +361,7 @@ dev = true
|
||||
|
||||
Whether to install peer dependencies. Default `true`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
peer = true
|
||||
```
|
||||
@@ -331,7 +372,7 @@ Whether `bun install` will run in "production mode". Default `false`.
|
||||
|
||||
In production mode, `"devDependencies"` are not installed. You can use `--production` in the CLI to override this setting.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
production = false
|
||||
```
|
||||
@@ -342,7 +383,7 @@ Whether to set an exact version in `package.json`. Default `false`.
|
||||
|
||||
By default Bun uses caret ranges; if the `latest` version of a package is `2.4.1`, the version range in your `package.json` will be `^2.4.1`. This indicates that any version from `2.4.1` up to (but not including) `3.0.0` is acceptable.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
exact = false
|
||||
```
|
||||
@@ -353,80 +394,34 @@ If false, generate a binary `bun.lockb` instead of a text-based `bun.lock` file
|
||||
|
||||
Default `true` (since Bun v1.2).
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
saveTextLockfile = false
|
||||
```
|
||||
|
||||
<!--
|
||||
### `install.prefer`
|
||||
|
||||
Whether the package manager should prefer offline or online dependency resolution. Default `"online"`.
|
||||
|
||||
```toml
|
||||
[install]
|
||||
prefer = "online"
|
||||
```
|
||||
|
||||
Valid values are:
|
||||
|
||||
{% table %}
|
||||
|
||||
---
|
||||
|
||||
- `"online"`
|
||||
- Prefer online resolution. This is the default. If a package is not found in the local cache, it will be downloaded from the registry.
|
||||
|
||||
---
|
||||
|
||||
- `"offline"`
|
||||
- Prefer offline resolution. When possible, packages will be installed from the global cache. This minimizes the fraction of the time Bun will check for newer versions from the registry. If a package is not found in the global cache, it will be downloaded from the registry.
|
||||
|
||||
{% /table %} -->
|
||||
|
||||
### `install.auto`
|
||||
|
||||
To configure Bun's package auto-install behavior. Default `"auto"` — when no `node_modules` folder is found, Bun will automatically install dependencies on the fly during execution.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
auto = "auto"
|
||||
```
|
||||
|
||||
Valid values are:
|
||||
|
||||
{% table %}
|
||||
|
||||
- Value
|
||||
- Description
|
||||
|
||||
---
|
||||
|
||||
- `"auto"`
|
||||
- Resolve modules from local `node_modules` if it exists. Otherwise, auto-install dependencies on the fly.
|
||||
|
||||
---
|
||||
|
||||
- `"force"`
|
||||
- Always auto-install dependencies, even if `node_modules` exists.
|
||||
|
||||
---
|
||||
|
||||
- `"disable"`
|
||||
- Never auto-install dependencies.
|
||||
|
||||
---
|
||||
|
||||
- `"fallback"`
|
||||
- Check local `node_modules` first, then auto-install any packages that aren't found. You can enable this from the CLI with `bun -i`.
|
||||
|
||||
{% /table %}
|
||||
| Value | Description |
|
||||
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `"auto"` | Resolve modules from local `node_modules` if it exists. Otherwise, auto-install dependencies on the fly. |
|
||||
| `"force"` | Always auto-install dependencies, even if `node_modules` exists. |
|
||||
| `"disable"` | Never auto-install dependencies. |
|
||||
| `"fallback"` | Check local `node_modules` first, then auto-install any packages that aren't found. You can enable this from the CLI with `bun -i`. |
|
||||
|
||||
### `install.frozenLockfile`
|
||||
|
||||
When true, `bun install` will not update `bun.lock`. Default `false`. If `package.json` and the existing `bun.lock` are not in agreement, this will error.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
frozenLockfile = false
|
||||
```
|
||||
@@ -435,7 +430,7 @@ frozenLockfile = false
|
||||
|
||||
Whether `bun install` will actually install dependencies. Default `false`. When true, it's equivalent to setting `--dry-run` on all `bun install` commands.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
dryRun = false
|
||||
```
|
||||
@@ -446,7 +441,7 @@ To configure the directory where Bun puts globally installed packages.
|
||||
|
||||
Environment variable: `BUN_INSTALL_GLOBAL_DIR`
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
# where `bun install --global` installs packages
|
||||
globalDir = "~/.bun/install/global"
|
||||
@@ -458,7 +453,8 @@ To configure the directory where Bun installs globally installed binaries and CL
|
||||
|
||||
Environment variable: `BUN_INSTALL_BIN`
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
# where globally-installed package bins are linked
|
||||
globalBinDir = "~/.bun/bin"
|
||||
```
|
||||
@@ -467,7 +463,7 @@ globalBinDir = "~/.bun/bin"
|
||||
|
||||
The default registry is `https://registry.npmjs.org/`. This can be globally configured in `bunfig.toml`:
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
# set default registry as a string
|
||||
registry = "https://registry.npmjs.org"
|
||||
@@ -483,7 +479,7 @@ To configure how workspace packages are linked, use the `install.linkWorkspacePa
|
||||
|
||||
Whether to link workspace packages from the monorepo root to their respective `node_modules` directories. Default `true`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
linkWorkspacePackages = true
|
||||
```
|
||||
@@ -492,7 +488,7 @@ linkWorkspacePackages = true
|
||||
|
||||
To configure a registry for a particular scope (e.g. `@myorg/<package>`) use `install.scopes`. You can reference environment variables with `$variable` notation.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install.scopes]
|
||||
# registry as string
|
||||
myorg = "https://username:password@registry.myorg.com/"
|
||||
@@ -509,7 +505,7 @@ myorg = { token = "$npm_token", url = "https://registry.myorg.com/" }
|
||||
|
||||
To configure a CA certificate, use `install.ca` or `install.cafile` to specify a path to a CA certificate file.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
# The CA certificate as a string
|
||||
ca = "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
|
||||
@@ -522,7 +518,7 @@ cafile = "path/to/cafile"
|
||||
|
||||
To configure the cache behavior:
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install.cache]
|
||||
|
||||
# the directory to use for the cache
|
||||
@@ -542,79 +538,41 @@ To configure lockfile behavior, use the `install.lockfile` section.
|
||||
|
||||
Whether to generate a lockfile on `bun install`. Default `true`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install.lockfile]
|
||||
save = true
|
||||
```
|
||||
|
||||
Whether to generate a non-Bun lockfile alongside `bun.lock`. (A `bun.lock` will always be created.) Currently `"yarn"` is the only supported value.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install.lockfile]
|
||||
print = "yarn"
|
||||
```
|
||||
|
||||
### `install.security.scanner`
|
||||
|
||||
Configure a security scanner to scan packages for vulnerabilities before installation.
|
||||
|
||||
First, install a security scanner from npm:
|
||||
|
||||
```bash
|
||||
$ bun add -d @acme/bun-security-scanner
|
||||
```
|
||||
|
||||
Then configure it in your `bunfig.toml`:
|
||||
|
||||
```toml
|
||||
[install.security]
|
||||
scanner = "@acme/bun-security-scanner"
|
||||
```
|
||||
|
||||
When a security scanner is configured:
|
||||
|
||||
- Auto-install is automatically disabled for security
|
||||
- Packages are scanned before installation
|
||||
- Installation is cancelled if fatal issues are found
|
||||
- Security warnings are displayed during installation
|
||||
|
||||
Learn more about [using and writing security scanners](/docs/install/security-scanner-api).
|
||||
|
||||
### `install.linker`
|
||||
|
||||
Configure the default linker strategy. Default `"hoisted"`.
|
||||
Configure the linker strategy for installing dependencies. Defaults to `"isolated"` for new workspaces, `"hoisted"` for new single-package projects and existing projects (made pre-v1.3.2).
|
||||
|
||||
For complete documentation refer to [Package manager > Isolated installs](https://bun.com/docs/install/isolated).
|
||||
For complete documentation refer to [Package manager > Isolated installs](/pm/isolated-installs).
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
linker = "hoisted"
|
||||
```
|
||||
|
||||
Valid values are:
|
||||
|
||||
{% table %}
|
||||
|
||||
- Value
|
||||
- Description
|
||||
|
||||
---
|
||||
|
||||
- `"hoisted"`
|
||||
- Link dependencies in a shared `node_modules` directory.
|
||||
|
||||
---
|
||||
|
||||
- `"isolated"`
|
||||
- Link dependencies inside each package installation.
|
||||
|
||||
{% /table %}
|
||||
| Value | Description |
|
||||
| ------------ | ------------------------------------------------------- |
|
||||
| `"hoisted"` | Link dependencies in a shared `node_modules` directory. |
|
||||
| `"isolated"` | Link dependencies inside each package installation. |
|
||||
|
||||
### `install.minimumReleaseAge`
|
||||
|
||||
Configure a minimum age for npm package versions. Package versions published more recently than this threshold will be filtered out during installation. Default is `null` (disabled).
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
# Only install package versions published at least 3 days ago
|
||||
minimumReleaseAge = "3d"
|
||||
@@ -622,12 +580,9 @@ minimumReleaseAge = "3d"
|
||||
minimumReleaseAgeExcludes = ["@types/bun", "typescript"]
|
||||
```
|
||||
|
||||
For more details see [Minimum release age](https://bun.com/docs/cli/install#minimum-release-age) in the install documentation.
|
||||
For more details see [Minimum release age](/cli/install#minimum-release-age) in the install documentation.
|
||||
|
||||
<!-- ## Debugging -->
|
||||
|
||||
<!--
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[debug]
|
||||
# When navigating to a blob: or src: link, open the file in your editor
|
||||
# If not, it tries $EDITOR or $VISUAL
|
||||
@@ -645,7 +600,46 @@ editor = "code"
|
||||
# - "vim","vi"
|
||||
# - "emacs"
|
||||
```
|
||||
-->
|
||||
|
||||
### `install.security.scanner`
|
||||
|
||||
Configure a security scanner to scan packages for vulnerabilities before installation.
|
||||
|
||||
First, install a security scanner from npm:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun add -d @acme/bun-security-scanner
|
||||
```
|
||||
|
||||
Then configure it in your `bunfig.toml`:
|
||||
|
||||
```toml bunfig.toml icon="settings"
|
||||
[install.security]
|
||||
scanner = "@acme/bun-security-scanner"
|
||||
```
|
||||
|
||||
When a security scanner is configured:
|
||||
|
||||
- Auto-install is automatically disabled for security
|
||||
- Packages are scanned before installation
|
||||
- Installation is cancelled if fatal issues are found
|
||||
- Security warnings are displayed during installation
|
||||
|
||||
Learn more about [using and writing security scanners](/pm/security-scanner-api).
|
||||
|
||||
### `install.minimumReleaseAge`
|
||||
|
||||
Configure a minimum age (in seconds) for npm package versions. Package versions published more recently than this threshold will be filtered out during installation. Default is `null` (disabled).
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[install]
|
||||
# Only install package versions published at least 3 days ago
|
||||
minimumReleaseAge = 259200
|
||||
# These packages will bypass the 3-day minimum age requirement
|
||||
minimumReleaseAgeExcludes = ["@types/bun", "typescript"]
|
||||
```
|
||||
|
||||
For more details see [Minimum release age](/pm/cli/install#minimum-release-age) in the install documentation.
|
||||
|
||||
## `bun run`
|
||||
|
||||
@@ -659,7 +653,7 @@ The shell to use when running package.json scripts via `bun run` or `bun`. On Wi
|
||||
|
||||
To always use the system shell instead of Bun's shell (default behavior unless Windows):
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[run]
|
||||
# default outside of Windows
|
||||
shell = "system"
|
||||
@@ -667,7 +661,7 @@ shell = "system"
|
||||
|
||||
To always use Bun's shell instead of the system shell:
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[run]
|
||||
# default on Windows
|
||||
shell = "bun"
|
||||
@@ -681,7 +675,7 @@ This means that if you have a script that runs `node`, it will actually run `bun
|
||||
|
||||
By default, this is enabled if `node` is not already in your `$PATH`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[run]
|
||||
# equivalent to `bun --bun` for all `bun run` commands
|
||||
bun = true
|
||||
@@ -690,8 +684,8 @@ bun = true
|
||||
You can test this by running:
|
||||
|
||||
```sh
|
||||
$ bun --bun which node # /path/to/bun
|
||||
$ bun which node # /path/to/node
|
||||
bun --bun which node # /path/to/bun
|
||||
bun which node # /path/to/node
|
||||
```
|
||||
|
||||
This option is equivalent to prefixing all `bun run` commands with `--bun`:
|
||||
@@ -708,23 +702,29 @@ If set to `false`, this will disable the `node` symlink.
|
||||
|
||||
When `true`, suppresses the output of the command being run by `bun run` or `bun`.
|
||||
|
||||
```toml
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[run]
|
||||
silent = true
|
||||
```
|
||||
|
||||
Without this option, the command being run will be printed to the console:
|
||||
|
||||
```sh
|
||||
$ bun run dev
|
||||
> $ echo "Running \"dev\"..."
|
||||
```sh terminal icon="terminal"
|
||||
bun run dev
|
||||
echo "Running \"dev\"..."
|
||||
```
|
||||
|
||||
```txt
|
||||
Running "dev"...
|
||||
```
|
||||
|
||||
With this option, the command being run will not be printed to the console:
|
||||
|
||||
```sh
|
||||
$ bun run dev
|
||||
bun run dev
|
||||
```
|
||||
|
||||
```txt
|
||||
Running "dev"...
|
||||
```
|
||||
|
||||
204
docs/runtime/c-compiler.mdx
Normal file
204
docs/runtime/c-compiler.mdx
Normal file
@@ -0,0 +1,204 @@
|
||||
---
|
||||
title: C Compiler
|
||||
description: Compile and run C from JavaScript with low overhead
|
||||
---
|
||||
|
||||
`bun:ffi` has experimental support for compiling and running C from JavaScript with low overhead.
|
||||
|
||||
---
|
||||
|
||||
## Usage (cc in `bun:ffi`)
|
||||
|
||||
See the [introduction blog post](https://bun.com/blog/compile-and-run-c-in-js) for more information.
|
||||
|
||||
JavaScript:
|
||||
|
||||
```ts hello.ts icon="file-code"
|
||||
import { cc } from "bun:ffi";
|
||||
import source from "./hello.c" with { type: "file" };
|
||||
|
||||
const {
|
||||
symbols: { hello },
|
||||
} = cc({
|
||||
source,
|
||||
symbols: {
|
||||
hello: {
|
||||
args: [],
|
||||
returns: "int",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log("What is the answer to the universe?", hello());
|
||||
```
|
||||
|
||||
C source:
|
||||
|
||||
```c hello.c
|
||||
int hello() {
|
||||
return 42;
|
||||
}
|
||||
```
|
||||
|
||||
When you run `hello.js`, it will print:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun hello.js
|
||||
What is the answer to the universe? 42
|
||||
```
|
||||
|
||||
Under the hood, `cc` uses [TinyCC](https://bellard.org/tcc/) to compile the C code and then link it with the JavaScript runtime, efficiently converting types in-place.
|
||||
|
||||
### Primitive types
|
||||
|
||||
The same `FFIType` values in [`dlopen`](/runtime/ffi) are supported in `cc`.
|
||||
|
||||
| `FFIType` | C Type | Aliases |
|
||||
| ---------- | -------------- | --------------------------- |
|
||||
| cstring | `char*` | |
|
||||
| function | `(void*)(*)()` | `fn`, `callback` |
|
||||
| ptr | `void*` | `pointer`, `void*`, `char*` |
|
||||
| i8 | `int8_t` | `int8_t` |
|
||||
| i16 | `int16_t` | `int16_t` |
|
||||
| i32 | `int32_t` | `int32_t`, `int` |
|
||||
| i64 | `int64_t` | `int64_t` |
|
||||
| i64_fast | `int64_t` | |
|
||||
| u8 | `uint8_t` | `uint8_t` |
|
||||
| u16 | `uint16_t` | `uint16_t` |
|
||||
| u32 | `uint32_t` | `uint32_t` |
|
||||
| u64 | `uint64_t` | `uint64_t` |
|
||||
| u64_fast | `uint64_t` | |
|
||||
| f32 | `float` | `float` |
|
||||
| f64 | `double` | `double` |
|
||||
| bool | `bool` | |
|
||||
| char | `char` | |
|
||||
| napi_env | `napi_env` | |
|
||||
| napi_value | `napi_value` | |
|
||||
|
||||
### Strings, objects, and non-primitive types
|
||||
|
||||
To make it easier to work with strings, objects, and other non-primitive types that don't map 1:1 to C types, `cc` supports N-API.
|
||||
|
||||
To pass or receive a JavaScript values without any type conversions from a C function, you can use `napi_value`.
|
||||
|
||||
You can also pass a `napi_env` to receive the N-API environment used to call the JavaScript function.
|
||||
|
||||
#### Returning a C string to JavaScript
|
||||
|
||||
For example, if you have a string in C, you can return it to JavaScript like this:
|
||||
|
||||
```ts hello.ts
|
||||
import { cc } from "bun:ffi";
|
||||
import source from "./hello.c" with { type: "file" };
|
||||
|
||||
const {
|
||||
symbols: { hello },
|
||||
} = cc({
|
||||
source,
|
||||
symbols: {
|
||||
hello: {
|
||||
args: ["napi_env"],
|
||||
returns: "napi_value",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = hello();
|
||||
```
|
||||
|
||||
And in C:
|
||||
|
||||
```c hello.c
|
||||
#include <node/node_api.h>
|
||||
|
||||
napi_value hello(napi_env env) {
|
||||
napi_value result;
|
||||
napi_create_string_utf8(env, "Hello, Napi!", NAPI_AUTO_LENGTH, &result);
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
You can also use this to return other types like objects and arrays:
|
||||
|
||||
```c hello.c
|
||||
#include <node/node_api.h>
|
||||
|
||||
napi_value hello(napi_env env) {
|
||||
napi_value result;
|
||||
napi_create_object(env, &result);
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### `cc` Reference
|
||||
|
||||
#### `library: string[]`
|
||||
|
||||
The `library` array is used to specify the libraries that should be linked with the C code.
|
||||
|
||||
```ts
|
||||
type Library = string[];
|
||||
|
||||
cc({
|
||||
source: "hello.c",
|
||||
library: ["sqlite3"],
|
||||
});
|
||||
```
|
||||
|
||||
#### `symbols`
|
||||
|
||||
The `symbols` object is used to specify the functions and variables that should be exposed to JavaScript.
|
||||
|
||||
```ts
|
||||
type Symbols = {
|
||||
[key: string]: {
|
||||
args: FFIType[];
|
||||
returns: FFIType;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
#### `source`
|
||||
|
||||
The `source` is a file path to the C code that should be compiled and linked with the JavaScript runtime.
|
||||
|
||||
```ts
|
||||
type Source = string | URL | BunFile;
|
||||
|
||||
cc({
|
||||
source: "hello.c",
|
||||
symbols: {
|
||||
hello: {
|
||||
args: [],
|
||||
returns: "int",
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### `flags: string | string[]`
|
||||
|
||||
The `flags` is an optional array of strings that should be passed to the TinyCC compiler.
|
||||
|
||||
```ts
|
||||
type Flags = string | string[];
|
||||
```
|
||||
|
||||
These are flags like `-I` for include directories and `-D` for preprocessor definitions.
|
||||
|
||||
#### `define: Record<string, string>`
|
||||
|
||||
The `define` is an optional object that should be passed to the TinyCC compiler.
|
||||
|
||||
```ts
|
||||
type Defines = Record<string, string>;
|
||||
|
||||
cc({
|
||||
source: "hello.c",
|
||||
define: {
|
||||
NDEBUG: "1",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
These are preprocessor definitions passed to the TinyCC compiler.
|
||||
532
docs/runtime/child-process.mdx
Normal file
532
docs/runtime/child-process.mdx
Normal file
@@ -0,0 +1,532 @@
|
||||
---
|
||||
title: Spawn
|
||||
description: Spawn child processes with `Bun.spawn` or `Bun.spawnSync`
|
||||
---
|
||||
|
||||
## Spawn a process (`Bun.spawn()`)
|
||||
|
||||
Provide a command as an array of strings. The result of `Bun.spawn()` is a `Bun.Subprocess` object.
|
||||
|
||||
```ts
|
||||
const proc = Bun.spawn(["bun", "--version"]);
|
||||
console.log(await proc.exited); // 0
|
||||
```
|
||||
|
||||
The second argument to `Bun.spawn` is a parameters object that can be used to configure the subprocess.
|
||||
|
||||
```ts
|
||||
const proc = Bun.spawn(["bun", "--version"], {
|
||||
cwd: "./path/to/subdir", // specify a working directory
|
||||
env: { ...process.env, FOO: "bar" }, // specify environment variables
|
||||
onExit(proc, exitCode, signalCode, error) {
|
||||
// exit handler
|
||||
},
|
||||
});
|
||||
|
||||
proc.pid; // process ID of subprocess
|
||||
```
|
||||
|
||||
## Input stream
|
||||
|
||||
By default, the input stream of the subprocess is undefined; it can be configured with the `stdin` parameter.
|
||||
|
||||
```ts
|
||||
const proc = Bun.spawn(["cat"], {
|
||||
stdin: await fetch("https://raw.githubusercontent.com/oven-sh/bun/main/examples/hashing.js"),
|
||||
});
|
||||
|
||||
const text = await proc.stdout.text();
|
||||
console.log(text); // "const input = "hello world".repeat(400); ..."
|
||||
```
|
||||
|
||||
| Value | Description |
|
||||
| ------------------------ | ------------------------------------------------ |
|
||||
| `null` | **Default.** Provide no input to the subprocess |
|
||||
| `"pipe"` | Return a `FileSink` for fast incremental writing |
|
||||
| `"inherit"` | Inherit the `stdin` of the parent process |
|
||||
| `Bun.file()` | Read from the specified file |
|
||||
| `TypedArray \| DataView` | Use a binary buffer as input |
|
||||
| `Response` | Use the response `body` as input |
|
||||
| `Request` | Use the request `body` as input |
|
||||
| `ReadableStream` | Use a readable stream as input |
|
||||
| `Blob` | Use a blob as input |
|
||||
| `number` | Read from the file with a given file descriptor |
|
||||
|
||||
The `"pipe"` option lets incrementally write to the subprocess's input stream from the parent process.
|
||||
|
||||
```ts
|
||||
const proc = Bun.spawn(["cat"], {
|
||||
stdin: "pipe", // return a FileSink for writing
|
||||
});
|
||||
|
||||
// enqueue string data
|
||||
proc.stdin.write("hello");
|
||||
|
||||
// enqueue binary data
|
||||
const enc = new TextEncoder();
|
||||
proc.stdin.write(enc.encode(" world!"));
|
||||
|
||||
// send buffered data
|
||||
proc.stdin.flush();
|
||||
|
||||
// close the input stream
|
||||
proc.stdin.end();
|
||||
```
|
||||
|
||||
Passing a `ReadableStream` to `stdin` lets you pipe data from a JavaScript `ReadableStream` directly to the subprocess's input:
|
||||
|
||||
```ts
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue("Hello from ");
|
||||
controller.enqueue("ReadableStream!");
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
||||
const proc = Bun.spawn(["cat"], {
|
||||
stdin: stream,
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const output = await proc.stdout.text();
|
||||
console.log(output); // "Hello from ReadableStream!"
|
||||
```
|
||||
|
||||
## Output streams
|
||||
|
||||
You can read results from the subprocess via the `stdout` and `stderr` properties. By default these are instances of `ReadableStream`.
|
||||
|
||||
```ts
|
||||
const proc = Bun.spawn(["bun", "--version"]);
|
||||
const text = await proc.stdout.text();
|
||||
console.log(text); // => "1.3.3\n"
|
||||
```
|
||||
|
||||
Configure the output stream by passing one of the following values to `stdout/stderr`:
|
||||
|
||||
| Value | Description |
|
||||
| ------------ | --------------------------------------------------------------------------------------------------- |
|
||||
| `"pipe"` | **Default for `stdout`.** Pipe the output to a `ReadableStream` on the returned `Subprocess` object |
|
||||
| `"inherit"` | **Default for `stderr`.** Inherit from the parent process |
|
||||
| `"ignore"` | Discard the output |
|
||||
| `Bun.file()` | Write to the specified file |
|
||||
| `number` | Write to the file with the given file descriptor |
|
||||
|
||||
## Exit handling
|
||||
|
||||
Use the `onExit` callback to listen for the process exiting or being killed.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const proc = Bun.spawn(["bun", "--version"], {
|
||||
onExit(proc, exitCode, signalCode, error) {
|
||||
// exit handler
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
For convenience, the `exited` property is a `Promise` that resolves when the process exits.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const proc = Bun.spawn(["bun", "--version"]);
|
||||
|
||||
await proc.exited; // resolves when process exit
|
||||
proc.killed; // boolean — was the process killed?
|
||||
proc.exitCode; // null | number
|
||||
proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...
|
||||
```
|
||||
|
||||
To kill a process:
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const proc = Bun.spawn(["bun", "--version"]);
|
||||
proc.kill();
|
||||
proc.killed; // true
|
||||
|
||||
proc.kill(15); // specify a signal code
|
||||
proc.kill("SIGTERM"); // specify a signal name
|
||||
```
|
||||
|
||||
The parent `bun` process will not terminate until all child processes have exited. Use `proc.unref()` to detach the child process from the parent.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const proc = Bun.spawn(["bun", "--version"]);
|
||||
proc.unref();
|
||||
```
|
||||
|
||||
## Resource usage
|
||||
|
||||
You can get information about the process's resource usage after it has exited:
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const proc = Bun.spawn(["bun", "--version"]);
|
||||
await proc.exited;
|
||||
|
||||
const usage = proc.resourceUsage();
|
||||
console.log(`Max memory used: ${usage.maxRSS} bytes`);
|
||||
console.log(`CPU time (user): ${usage.cpuTime.user} µs`);
|
||||
console.log(`CPU time (system): ${usage.cpuTime.system} µs`);
|
||||
```
|
||||
|
||||
## Using AbortSignal
|
||||
|
||||
You can abort a subprocess using an `AbortSignal`:
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
|
||||
const proc = Bun.spawn({
|
||||
cmd: ["sleep", "100"],
|
||||
signal,
|
||||
});
|
||||
|
||||
// Later, to abort the process:
|
||||
controller.abort();
|
||||
```
|
||||
|
||||
## Using timeout and killSignal
|
||||
|
||||
You can set a timeout for a subprocess to automatically terminate after a specific duration:
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
// Kill the process after 5 seconds
|
||||
const proc = Bun.spawn({
|
||||
cmd: ["sleep", "10"],
|
||||
timeout: 5000, // 5 seconds in milliseconds
|
||||
});
|
||||
|
||||
await proc.exited; // Will resolve after 5 seconds
|
||||
```
|
||||
|
||||
By default, timed-out processes are killed with the `SIGTERM` signal. You can specify a different signal with the `killSignal` option:
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
// Kill the process with SIGKILL after 5 seconds
|
||||
const proc = Bun.spawn({
|
||||
cmd: ["sleep", "10"],
|
||||
timeout: 5000,
|
||||
killSignal: "SIGKILL", // Can be string name or signal number
|
||||
});
|
||||
```
|
||||
|
||||
The `killSignal` option also controls which signal is sent when an AbortSignal is aborted.
|
||||
|
||||
## Using maxBuffer
|
||||
|
||||
For spawnSync, you can limit the maximum number of bytes of output before the process is killed:
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
// Kill 'yes' after it emits over 100 bytes of output
|
||||
const result = Bun.spawnSync({
|
||||
cmd: ["yes"], // or ["bun", "exec", "yes"] on Windows
|
||||
maxBuffer: 100,
|
||||
});
|
||||
// process exits
|
||||
```
|
||||
|
||||
## Inter-process communication (IPC)
|
||||
|
||||
Bun supports direct inter-process communication channel between two `bun` processes. To receive messages from a spawned Bun subprocess, specify an `ipc` handler.
|
||||
|
||||
```ts parent.ts icon="/icons/typescript.svg"
|
||||
const child = Bun.spawn(["bun", "child.ts"], {
|
||||
ipc(message) {
|
||||
/**
|
||||
* The message received from the sub process
|
||||
**/
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The parent process can send messages to the subprocess using the `.send()` method on the returned `Subprocess` instance. A reference to the sending subprocess is also available as the second argument in the `ipc` handler.
|
||||
|
||||
```ts parent.ts icon="/icons/typescript.svg"
|
||||
const childProc = Bun.spawn(["bun", "child.ts"], {
|
||||
ipc(message, childProc) {
|
||||
/**
|
||||
* The message received from the sub process
|
||||
**/
|
||||
childProc.send("Respond to child");
|
||||
},
|
||||
});
|
||||
|
||||
childProc.send("I am your father"); // The parent can send messages to the child as well
|
||||
```
|
||||
|
||||
Meanwhile the child process can send messages to its parent using with `process.send()` and receive messages with `process.on("message")`. This is the same API used for `child_process.fork()` in Node.js.
|
||||
|
||||
```ts child.ts
|
||||
process.send("Hello from child as string");
|
||||
process.send({ message: "Hello from child as object" });
|
||||
|
||||
process.on("message", message => {
|
||||
// print message from parent
|
||||
console.log(message);
|
||||
});
|
||||
```
|
||||
|
||||
```ts child.ts
|
||||
// send a string
|
||||
process.send("Hello from child as string");
|
||||
|
||||
// send an object
|
||||
process.send({ message: "Hello from child as object" });
|
||||
```
|
||||
|
||||
The `serialization` option controls the underlying communication format between the two processes:
|
||||
|
||||
- `advanced`: (default) Messages are serialized using the JSC `serialize` API, which supports cloning [everything `structuredClone` supports](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). This does not support transferring ownership of objects.
|
||||
- `json`: Messages are serialized using `JSON.stringify` and `JSON.parse`, which does not support as many object types as `advanced` does.
|
||||
|
||||
To disconnect the IPC channel from the parent process, call:
|
||||
|
||||
```ts
|
||||
childProc.disconnect();
|
||||
```
|
||||
|
||||
### IPC between Bun & Node.js
|
||||
|
||||
To use IPC between a `bun` process and a Node.js process, set `serialization: "json"` in `Bun.spawn`. This is because Node.js and Bun use different JavaScript engines with different object serialization formats.
|
||||
|
||||
```js bun-node-ipc.js icon="file-code"
|
||||
if (typeof Bun !== "undefined") {
|
||||
const prefix = `[bun ${process.versions.bun} 🐇]`;
|
||||
const node = Bun.spawn({
|
||||
cmd: ["node", __filename],
|
||||
ipc({ message }) {
|
||||
console.log(message);
|
||||
node.send({ message: `${prefix} 👋 hey node` });
|
||||
node.kill();
|
||||
},
|
||||
stdio: ["inherit", "inherit", "inherit"],
|
||||
serialization: "json",
|
||||
});
|
||||
|
||||
node.send({ message: `${prefix} 👋 hey node` });
|
||||
} else {
|
||||
const prefix = `[node ${process.version}]`;
|
||||
process.on("message", ({ message }) => {
|
||||
console.log(message);
|
||||
process.send({ message: `${prefix} 👋 hey bun` });
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Blocking API (`Bun.spawnSync()`)
|
||||
|
||||
Bun provides a synchronous equivalent of `Bun.spawn` called `Bun.spawnSync`. This is a blocking API that supports the same inputs and parameters as `Bun.spawn`. It returns a `SyncSubprocess` object, which differs from `Subprocess` in a few ways.
|
||||
|
||||
1. It contains a `success` property that indicates whether the process exited with a zero exit code.
|
||||
2. The `stdout` and `stderr` properties are instances of `Buffer` instead of `ReadableStream`.
|
||||
3. There is no `stdin` property. Use `Bun.spawn` to incrementally write to the subprocess's input stream.
|
||||
|
||||
```ts
|
||||
const proc = Bun.spawnSync(["echo", "hello"]);
|
||||
|
||||
console.log(proc.stdout.toString());
|
||||
// => "hello\n"
|
||||
```
|
||||
|
||||
As a rule of thumb, the asynchronous `Bun.spawn` API is better for HTTP servers and apps, and `Bun.spawnSync` is better for building command-line tools.
|
||||
|
||||
---
|
||||
|
||||
## Benchmarks
|
||||
|
||||
<Note>
|
||||
⚡️ Under the hood, `Bun.spawn` and `Bun.spawnSync` use
|
||||
[`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html).
|
||||
</Note>
|
||||
|
||||
Bun's `spawnSync` spawns processes 60% faster than the Node.js `child_process` module.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun spawn.mjs
|
||||
```
|
||||
|
||||
```txt
|
||||
cpu: Apple M1 Max
|
||||
runtime: bun 1.x (arm64-darwin)
|
||||
|
||||
benchmark time (avg) (min … max) p75 p99 p995
|
||||
--------------------------------------------------------- -----------------------------
|
||||
spawnSync echo hi 888.14 µs/iter (821.83 µs … 1.2 ms) 905.92 µs 1 ms 1.03 ms
|
||||
```
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
node spawn.node.mjs
|
||||
```
|
||||
|
||||
```txt
|
||||
cpu: Apple M1 Max
|
||||
runtime: node v18.9.1 (arm64-darwin)
|
||||
|
||||
benchmark time (avg) (min … max) p75 p99 p995
|
||||
--------------------------------------------------------- -----------------------------
|
||||
spawnSync echo hi 1.47 ms/iter (1.14 ms … 2.64 ms) 1.57 ms 2.37 ms 2.52 ms
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
A reference of the Spawn API and types are shown below. The real types have complex generics to strongly type the `Subprocess` streams with the options passed to `Bun.spawn` and `Bun.spawnSync`. For full details, find these types as defined [bun.d.ts](https://github.com/oven-sh/bun/blob/main/packages/bun-types/bun.d.ts).
|
||||
|
||||
```ts See Typescript Definitions expandable
|
||||
interface Bun {
|
||||
spawn(command: string[], options?: SpawnOptions.OptionsObject): Subprocess;
|
||||
spawnSync(command: string[], options?: SpawnOptions.OptionsObject): SyncSubprocess;
|
||||
|
||||
spawn(options: { cmd: string[] } & SpawnOptions.OptionsObject): Subprocess;
|
||||
spawnSync(options: { cmd: string[] } & SpawnOptions.OptionsObject): SyncSubprocess;
|
||||
}
|
||||
|
||||
namespace SpawnOptions {
|
||||
interface OptionsObject {
|
||||
cwd?: string;
|
||||
env?: Record<string, string | undefined>;
|
||||
stdio?: [Writable, Readable, Readable];
|
||||
stdin?: Writable;
|
||||
stdout?: Readable;
|
||||
stderr?: Readable;
|
||||
onExit?(
|
||||
subprocess: Subprocess,
|
||||
exitCode: number | null,
|
||||
signalCode: number | null,
|
||||
error?: ErrorLike,
|
||||
): void | Promise<void>;
|
||||
ipc?(message: any, subprocess: Subprocess): void;
|
||||
serialization?: "json" | "advanced";
|
||||
windowsHide?: boolean;
|
||||
windowsVerbatimArguments?: boolean;
|
||||
argv0?: string;
|
||||
signal?: AbortSignal;
|
||||
timeout?: number;
|
||||
killSignal?: string | number;
|
||||
maxBuffer?: number;
|
||||
}
|
||||
|
||||
type Readable =
|
||||
| "pipe"
|
||||
| "inherit"
|
||||
| "ignore"
|
||||
| null // equivalent to "ignore"
|
||||
| undefined // to use default
|
||||
| BunFile
|
||||
| ArrayBufferView
|
||||
| number;
|
||||
|
||||
type Writable =
|
||||
| "pipe"
|
||||
| "inherit"
|
||||
| "ignore"
|
||||
| null // equivalent to "ignore"
|
||||
| undefined // to use default
|
||||
| BunFile
|
||||
| ArrayBufferView
|
||||
| number
|
||||
| ReadableStream
|
||||
| Blob
|
||||
| Response
|
||||
| Request;
|
||||
}
|
||||
|
||||
interface Subprocess extends AsyncDisposable {
|
||||
readonly stdin: FileSink | number | undefined;
|
||||
readonly stdout: ReadableStream<Uint8Array> | number | undefined;
|
||||
readonly stderr: ReadableStream<Uint8Array> | number | undefined;
|
||||
readonly readable: ReadableStream<Uint8Array> | number | undefined;
|
||||
readonly pid: number;
|
||||
readonly exited: Promise<number>;
|
||||
readonly exitCode: number | null;
|
||||
readonly signalCode: NodeJS.Signals | null;
|
||||
readonly killed: boolean;
|
||||
|
||||
kill(exitCode?: number | NodeJS.Signals): void;
|
||||
ref(): void;
|
||||
unref(): void;
|
||||
|
||||
send(message: any): void;
|
||||
disconnect(): void;
|
||||
resourceUsage(): ResourceUsage | undefined;
|
||||
}
|
||||
|
||||
interface SyncSubprocess {
|
||||
stdout: Buffer | undefined;
|
||||
stderr: Buffer | undefined;
|
||||
exitCode: number;
|
||||
success: boolean;
|
||||
resourceUsage: ResourceUsage;
|
||||
signalCode?: string;
|
||||
exitedDueToTimeout?: true;
|
||||
pid: number;
|
||||
}
|
||||
|
||||
interface ResourceUsage {
|
||||
contextSwitches: {
|
||||
voluntary: number;
|
||||
involuntary: number;
|
||||
};
|
||||
|
||||
cpuTime: {
|
||||
user: number;
|
||||
system: number;
|
||||
total: number;
|
||||
};
|
||||
maxRSS: number;
|
||||
|
||||
messages: {
|
||||
sent: number;
|
||||
received: number;
|
||||
};
|
||||
ops: {
|
||||
in: number;
|
||||
out: number;
|
||||
};
|
||||
shmSize: number;
|
||||
signalCount: number;
|
||||
swapCount: number;
|
||||
}
|
||||
|
||||
type Signal =
|
||||
| "SIGABRT"
|
||||
| "SIGALRM"
|
||||
| "SIGBUS"
|
||||
| "SIGCHLD"
|
||||
| "SIGCONT"
|
||||
| "SIGFPE"
|
||||
| "SIGHUP"
|
||||
| "SIGILL"
|
||||
| "SIGINT"
|
||||
| "SIGIO"
|
||||
| "SIGIOT"
|
||||
| "SIGKILL"
|
||||
| "SIGPIPE"
|
||||
| "SIGPOLL"
|
||||
| "SIGPROF"
|
||||
| "SIGPWR"
|
||||
| "SIGQUIT"
|
||||
| "SIGSEGV"
|
||||
| "SIGSTKFLT"
|
||||
| "SIGSTOP"
|
||||
| "SIGSYS"
|
||||
| "SIGTERM"
|
||||
| "SIGTRAP"
|
||||
| "SIGTSTP"
|
||||
| "SIGTTIN"
|
||||
| "SIGTTOU"
|
||||
| "SIGUNUSED"
|
||||
| "SIGURG"
|
||||
| "SIGUSR1"
|
||||
| "SIGUSR2"
|
||||
| "SIGVTALRM"
|
||||
| "SIGWINCH"
|
||||
| "SIGXCPU"
|
||||
| "SIGXFSZ"
|
||||
| "SIGBREAK"
|
||||
| "SIGLOST"
|
||||
| "SIGINFO";
|
||||
```
|
||||
267
docs/runtime/color.mdx
Normal file
267
docs/runtime/color.mdx
Normal file
@@ -0,0 +1,267 @@
|
||||
---
|
||||
title: Color
|
||||
description: Format colors as CSS, ANSI, numbers, hex strings, and more
|
||||
---
|
||||
|
||||
`Bun.color(input, outputFormat?)` leverages Bun's CSS parser to parse, normalize, and convert colors from user input to a variety of output formats, including:
|
||||
|
||||
| Format | Example |
|
||||
| ------------ | -------------------------------- |
|
||||
| `"css"` | `"red"` |
|
||||
| `"ansi"` | `"\x1b[38;2;255;0;0m"` |
|
||||
| `"ansi-16"` | `"\x1b[38;5;\tm"` |
|
||||
| `"ansi-256"` | `"\x1b[38;5;196m"` |
|
||||
| `"ansi-16m"` | `"\x1b[38;2;255;0;0m"` |
|
||||
| `"number"` | `0x1a2b3c` |
|
||||
| `"rgb"` | `"rgb(255, 99, 71)"` |
|
||||
| `"rgba"` | `"rgba(255, 99, 71, 0.5)"` |
|
||||
| `"hsl"` | `"hsl(120, 50%, 50%)"` |
|
||||
| `"hex"` | `"#1a2b3c"` |
|
||||
| `"HEX"` | `"#1A2B3C"` |
|
||||
| `"{rgb}"` | `{ r: 255, g: 99, b: 71 }` |
|
||||
| `"{rgba}"` | `{ r: 255, g: 99, b: 71, a: 1 }` |
|
||||
| `"[rgb]"` | `[ 255, 99, 71 ]` |
|
||||
| `"[rgba]"` | `[ 255, 99, 71, 255]` |
|
||||
|
||||
There are many different ways to use this API:
|
||||
|
||||
- Validate and normalize colors to persist in a database (`number` is the most database-friendly)
|
||||
- Convert colors to different formats
|
||||
- Colorful logging beyond the 16 colors many use today (use `ansi` if you don't want to figure out what the user's terminal supports, otherwise use `ansi-16`, `ansi-256`, or `ansi-16m` for how many colors the terminal supports)
|
||||
- Format colors for use in CSS injected into HTML
|
||||
- Get the `r`, `g`, `b`, and `a` color components as JavaScript objects or numbers from a CSS color string
|
||||
|
||||
You can think of this as an alternative to the popular npm packages [`color`](https://github.com/Qix-/color) and [`tinycolor2`](https://github.com/bgrins/TinyColor) except with full support for parsing CSS color strings and zero dependencies built directly into Bun.
|
||||
|
||||
### Flexible input
|
||||
|
||||
You can pass in any of the following:
|
||||
|
||||
- Standard CSS color names like `"red"`
|
||||
- Numbers like `0xff0000`
|
||||
- Hex strings like `"#f00"`
|
||||
- RGB strings like `"rgb(255, 0, 0)"`
|
||||
- RGBA strings like `"rgba(255, 0, 0, 1)"`
|
||||
- HSL strings like `"hsl(0, 100%, 50%)"`
|
||||
- HSLA strings like `"hsla(0, 100%, 50%, 1)"`
|
||||
- RGB objects like `{ r: 255, g: 0, b: 0 }`
|
||||
- RGBA objects like `{ r: 255, g: 0, b: 0, a: 1 }`
|
||||
- RGB arrays like `[255, 0, 0]`
|
||||
- RGBA arrays like `[255, 0, 0, 255]`
|
||||
- LAB strings like `"lab(50% 50% 50%)"`
|
||||
- ... anything else that CSS can parse as a single color value
|
||||
|
||||
### Format colors as CSS
|
||||
|
||||
The `"css"` format outputs valid CSS for use in stylesheets, inline styles, CSS variables, css-in-js, etc. It returns the most compact representation of the color as a string.
|
||||
|
||||
```ts
|
||||
Bun.color("red", "css"); // "red"
|
||||
Bun.color(0xff0000, "css"); // "#f000"
|
||||
Bun.color("#f00", "css"); // "red"
|
||||
Bun.color("#ff0000", "css"); // "red"
|
||||
Bun.color("rgb(255, 0, 0)", "css"); // "red"
|
||||
Bun.color("rgba(255, 0, 0, 1)", "css"); // "red"
|
||||
Bun.color("hsl(0, 100%, 50%)", "css"); // "red"
|
||||
Bun.color("hsla(0, 100%, 50%, 1)", "css"); // "red"
|
||||
Bun.color({ r: 255, g: 0, b: 0 }, "css"); // "red"
|
||||
Bun.color({ r: 255, g: 0, b: 0, a: 1 }, "css"); // "red"
|
||||
Bun.color([255, 0, 0], "css"); // "red"
|
||||
Bun.color([255, 0, 0, 255], "css"); // "red"
|
||||
```
|
||||
|
||||
If the input is unknown or fails to parse, `Bun.color` returns `null`.
|
||||
|
||||
### Format colors as ANSI (for terminals)
|
||||
|
||||
The `"ansi"` format outputs ANSI escape codes for use in terminals to make text colorful.
|
||||
|
||||
```ts
|
||||
Bun.color("red", "ansi"); // "\u001b[38;2;255;0;0m"
|
||||
Bun.color(0xff0000, "ansi"); // "\u001b[38;2;255;0;0m"
|
||||
Bun.color("#f00", "ansi"); // "\u001b[38;2;255;0;0m"
|
||||
Bun.color("#ff0000", "ansi"); // "\u001b[38;2;255;0;0m"
|
||||
Bun.color("rgb(255, 0, 0)", "ansi"); // "\u001b[38;2;255;0;0m"
|
||||
Bun.color("rgba(255, 0, 0, 1)", "ansi"); // "\u001b[38;2;255;0;0m"
|
||||
Bun.color("hsl(0, 100%, 50%)", "ansi"); // "\u001b[38;2;255;0;0m"
|
||||
Bun.color("hsla(0, 100%, 50%, 1)", "ansi"); // "\u001b[38;2;255;0;0m"
|
||||
Bun.color({ r: 255, g: 0, b: 0 }, "ansi"); // "\u001b[38;2;255;0;0m"
|
||||
Bun.color({ r: 255, g: 0, b: 0, a: 1 }, "ansi"); // "\u001b[38;2;255;0;0m"
|
||||
Bun.color([255, 0, 0], "ansi"); // "\u001b[38;2;255;0;0m"
|
||||
Bun.color([255, 0, 0, 255], "ansi"); // "\u001b[38;2;255;0;0m"
|
||||
```
|
||||
|
||||
This gets the color depth of stdout and automatically chooses one of `"ansi-16m"`, `"ansi-256"`, `"ansi-16"` based on the environment variables. If stdout doesn't support any form of ANSI color, it returns an empty string. As with the rest of Bun's color API, if the input is unknown or fails to parse, it returns `null`.
|
||||
|
||||
#### 24-bit ANSI colors (`ansi-16m`)
|
||||
|
||||
The `"ansi-16m"` format outputs 24-bit ANSI colors for use in terminals to make text colorful. 24-bit color means you can display 16 million colors on supported terminals, and requires a modern terminal that supports it.
|
||||
|
||||
This converts the input color to RGBA, and then outputs that as an ANSI color.
|
||||
|
||||
```ts
|
||||
Bun.color("red", "ansi-16m"); // "\x1b[38;2;255;0;0m"
|
||||
Bun.color(0xff0000, "ansi-16m"); // "\x1b[38;2;255;0;0m"
|
||||
Bun.color("#f00", "ansi-16m"); // "\x1b[38;2;255;0;0m"
|
||||
Bun.color("#ff0000", "ansi-16m"); // "\x1b[38;2;255;0;0m"
|
||||
```
|
||||
|
||||
#### 256 ANSI colors (`ansi-256`)
|
||||
|
||||
The `"ansi-256"` format approximates the input color to the nearest of the 256 ANSI colors supported by some terminals.
|
||||
|
||||
```ts
|
||||
Bun.color("red", "ansi-256"); // "\u001b[38;5;196m"
|
||||
Bun.color(0xff0000, "ansi-256"); // "\u001b[38;5;196m"
|
||||
Bun.color("#f00", "ansi-256"); // "\u001b[38;5;196m"
|
||||
Bun.color("#ff0000", "ansi-256"); // "\u001b[38;5;196m"
|
||||
```
|
||||
|
||||
To convert from RGBA to one of the 256 ANSI colors, we ported the algorithm that [`tmux` uses](https://github.com/tmux/tmux/blob/dae2868d1227b95fd076fb4a5efa6256c7245943/colour.c#L44-L55).
|
||||
|
||||
#### 16 ANSI colors (`ansi-16`)
|
||||
|
||||
The `"ansi-16"` format approximates the input color to the nearest of the 16 ANSI colors supported by most terminals.
|
||||
|
||||
```ts
|
||||
Bun.color("red", "ansi-16"); // "\u001b[38;5;\tm"
|
||||
Bun.color(0xff0000, "ansi-16"); // "\u001b[38;5;\tm"
|
||||
Bun.color("#f00", "ansi-16"); // "\u001b[38;5;\tm"
|
||||
Bun.color("#ff0000", "ansi-16"); // "\u001b[38;5;\tm"
|
||||
```
|
||||
|
||||
This works by first converting the input to a 24-bit RGB color space, then to `ansi-256`, and then we convert that to the nearest 16 ANSI color.
|
||||
|
||||
### Format colors as numbers
|
||||
|
||||
The `"number"` format outputs a 24-bit number for use in databases, configuration, or any other use case where a compact representation of the color is desired.
|
||||
|
||||
```ts
|
||||
Bun.color("red", "number"); // 16711680
|
||||
Bun.color(0xff0000, "number"); // 16711680
|
||||
Bun.color({ r: 255, g: 0, b: 0 }, "number"); // 16711680
|
||||
Bun.color([255, 0, 0], "number"); // 16711680
|
||||
Bun.color("rgb(255, 0, 0)", "number"); // 16711680
|
||||
Bun.color("rgba(255, 0, 0, 1)", "number"); // 16711680
|
||||
Bun.color("hsl(0, 100%, 50%)", "number"); // 16711680
|
||||
Bun.color("hsla(0, 100%, 50%, 1)", "number"); // 16711680
|
||||
```
|
||||
|
||||
### Get the red, green, blue, and alpha channels
|
||||
|
||||
You can use the `"{rgba}"`, `"{rgb}"`, `"[rgba]"` and `"[rgb]"` formats to get the red, green, blue, and alpha channels as objects or arrays.
|
||||
|
||||
#### `{rgba}` object
|
||||
|
||||
The `"{rgba}"` format outputs an object with the red, green, blue, and alpha channels.
|
||||
|
||||
```ts
|
||||
type RGBAObject = {
|
||||
// 0 - 255
|
||||
r: number;
|
||||
// 0 - 255
|
||||
g: number;
|
||||
// 0 - 255
|
||||
b: number;
|
||||
// 0 - 1
|
||||
a: number;
|
||||
};
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
Bun.color("hsl(0, 0%, 50%)", "{rgba}"); // { r: 128, g: 128, b: 128, a: 1 }
|
||||
Bun.color("red", "{rgba}"); // { r: 255, g: 0, b: 0, a: 1 }
|
||||
Bun.color(0xff0000, "{rgba}"); // { r: 255, g: 0, b: 0, a: 1 }
|
||||
Bun.color({ r: 255, g: 0, b: 0 }, "{rgba}"); // { r: 255, g: 0, b: 0, a: 1 }
|
||||
Bun.color([255, 0, 0], "{rgba}"); // { r: 255, g: 0, b: 0, a: 1 }
|
||||
```
|
||||
|
||||
To behave similarly to CSS, the `a` channel is a decimal number between `0` and `1`.
|
||||
|
||||
The `"{rgb}"` format is similar, but it doesn't include the alpha channel.
|
||||
|
||||
```ts
|
||||
Bun.color("hsl(0, 0%, 50%)", "{rgb}"); // { r: 128, g: 128, b: 128 }
|
||||
Bun.color("red", "{rgb}"); // { r: 255, g: 0, b: 0 }
|
||||
Bun.color(0xff0000, "{rgb}"); // { r: 255, g: 0, b: 0 }
|
||||
Bun.color({ r: 255, g: 0, b: 0 }, "{rgb}"); // { r: 255, g: 0, b: 0 }
|
||||
Bun.color([255, 0, 0], "{rgb}"); // { r: 255, g: 0, b: 0 }
|
||||
```
|
||||
|
||||
#### `[rgba]` array
|
||||
|
||||
The `"[rgba]"` format outputs an array with the red, green, blue, and alpha channels.
|
||||
|
||||
```ts
|
||||
// All values are 0 - 255
|
||||
type RGBAArray = [number, number, number, number];
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
Bun.color("hsl(0, 0%, 50%)", "[rgba]"); // [128, 128, 128, 255]
|
||||
Bun.color("red", "[rgba]"); // [255, 0, 0, 255]
|
||||
Bun.color(0xff0000, "[rgba]"); // [255, 0, 0, 255]
|
||||
Bun.color({ r: 255, g: 0, b: 0 }, "[rgba]"); // [255, 0, 0, 255]
|
||||
Bun.color([255, 0, 0], "[rgba]"); // [255, 0, 0, 255]
|
||||
```
|
||||
|
||||
Unlike the `"{rgba}"` format, the alpha channel is an integer between `0` and `255`. This is useful for typed arrays where each channel must be the same underlying type.
|
||||
|
||||
The `"[rgb]"` format is similar, but it doesn't include the alpha channel.
|
||||
|
||||
```ts
|
||||
Bun.color("hsl(0, 0%, 50%)", "[rgb]"); // [128, 128, 128]
|
||||
Bun.color("red", "[rgb]"); // [255, 0, 0]
|
||||
Bun.color(0xff0000, "[rgb]"); // [255, 0, 0]
|
||||
Bun.color({ r: 255, g: 0, b: 0 }, "[rgb]"); // [255, 0, 0]
|
||||
Bun.color([255, 0, 0], "[rgb]"); // [255, 0, 0]
|
||||
```
|
||||
|
||||
### Format colors as hex strings
|
||||
|
||||
The `"hex"` format outputs a lowercase hex string for use in CSS or other contexts.
|
||||
|
||||
```ts
|
||||
Bun.color("hsl(0, 0%, 50%)", "hex"); // "#808080"
|
||||
Bun.color("red", "hex"); // "#ff0000"
|
||||
Bun.color(0xff0000, "hex"); // "#ff0000"
|
||||
Bun.color({ r: 255, g: 0, b: 0 }, "hex"); // "#ff0000"
|
||||
Bun.color([255, 0, 0], "hex"); // "#ff0000"
|
||||
```
|
||||
|
||||
The `"HEX"` format is similar, but it outputs a hex string with uppercase letters instead of lowercase letters.
|
||||
|
||||
```ts
|
||||
Bun.color("hsl(0, 0%, 50%)", "HEX"); // "#808080"
|
||||
Bun.color("red", "HEX"); // "#FF0000"
|
||||
Bun.color(0xff0000, "HEX"); // "#FF0000"
|
||||
Bun.color({ r: 255, g: 0, b: 0 }, "HEX"); // "#FF0000"
|
||||
Bun.color([255, 0, 0], "HEX"); // "#FF0000"
|
||||
```
|
||||
|
||||
### Bundle-time client-side color formatting
|
||||
|
||||
Like many of Bun's APIs, you can use macros to invoke `Bun.color` at bundle-time for use in client-side JavaScript builds:
|
||||
|
||||
```ts client-side.ts
|
||||
import { color } from "bun" with { type: "macro" };
|
||||
|
||||
console.log(color("#f00", "css"));
|
||||
```
|
||||
|
||||
Then, build the client-side code:
|
||||
|
||||
```sh
|
||||
bun build ./client-side.ts
|
||||
```
|
||||
|
||||
This will output the following to `client-side.js`:
|
||||
|
||||
```js
|
||||
// client-side.ts
|
||||
console.log("red");
|
||||
```
|
||||
67
docs/runtime/console.mdx
Normal file
67
docs/runtime/console.mdx
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Console
|
||||
description: The console object in Bun
|
||||
---
|
||||
|
||||
<Note>
|
||||
Bun provides a browser- and Node.js-compatible [console](https://developer.mozilla.org/en-US/docs/Web/API/console)
|
||||
global. This page only documents Bun-native APIs.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Object inspection depth
|
||||
|
||||
Bun allows you to configure how deeply nested objects are displayed in `console.log()` output:
|
||||
|
||||
- **CLI flag**: Use `--console-depth <number>` to set the depth for a single run
|
||||
- **Configuration**: Set `console.depth` in your `bunfig.toml` for persistent configuration
|
||||
- **Default**: Objects are inspected to a depth of `2` levels
|
||||
|
||||
```js
|
||||
const nested = { a: { b: { c: { d: "deep" } } } };
|
||||
console.log(nested);
|
||||
// Default (depth 2): { a: { b: [Object] } }
|
||||
// With depth 4: { a: { b: { c: { d: 'deep' } } } }
|
||||
```
|
||||
|
||||
The CLI flag takes precedence over the configuration file setting.
|
||||
|
||||
---
|
||||
|
||||
## Reading from stdin
|
||||
|
||||
In Bun, the `console` object can be used as an `AsyncIterable` to sequentially read lines from `process.stdin`.
|
||||
|
||||
```ts adder.ts icon="/icons/typescript.svg"
|
||||
for await (const line of console) {
|
||||
console.log(line);
|
||||
}
|
||||
```
|
||||
|
||||
This is useful for implementing interactive programs, like the following addition calculator.
|
||||
|
||||
```ts adder.ts icon="/icons/typescript.svg"
|
||||
console.log(`Let's add some numbers!`);
|
||||
console.write(`Count: 0\n> `);
|
||||
|
||||
let count = 0;
|
||||
for await (const line of console) {
|
||||
count += Number(line);
|
||||
console.write(`Count: ${count}\n> `);
|
||||
}
|
||||
```
|
||||
|
||||
To run the file:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun adder.ts
|
||||
Let's add some numbers!
|
||||
Count: 0
|
||||
> 5
|
||||
Count: 5
|
||||
> 5
|
||||
Count: 10
|
||||
> 5
|
||||
Count: 15
|
||||
```
|
||||
454
docs/runtime/cookies.mdx
Normal file
454
docs/runtime/cookies.mdx
Normal file
@@ -0,0 +1,454 @@
|
||||
---
|
||||
title: Cookies
|
||||
description: Use Bun's native APIs for working with HTTP cookies
|
||||
---
|
||||
|
||||
Bun provides native APIs for working with HTTP cookies through `Bun.Cookie` and `Bun.CookieMap`. These APIs offer fast, easy-to-use methods for parsing, generating, and manipulating cookies in HTTP requests and responses.
|
||||
|
||||
## CookieMap class
|
||||
|
||||
`Bun.CookieMap` provides a Map-like interface for working with collections of cookies. It implements the `Iterable` interface, allowing you to use it with `for...of` loops and other iteration methods.
|
||||
|
||||
```ts title="cookies.ts" icon="/icons/typescript.svg"
|
||||
// Empty cookie map
|
||||
const cookies = new Bun.CookieMap();
|
||||
|
||||
// From a cookie string
|
||||
const cookies1 = new Bun.CookieMap("name=value; foo=bar");
|
||||
|
||||
// From an object
|
||||
const cookies2 = new Bun.CookieMap({
|
||||
session: "abc123",
|
||||
theme: "dark",
|
||||
});
|
||||
|
||||
// From an array of name/value pairs
|
||||
const cookies3 = new Bun.CookieMap([
|
||||
["session", "abc123"],
|
||||
["theme", "dark"],
|
||||
]);
|
||||
```
|
||||
|
||||
### In HTTP servers
|
||||
|
||||
In Bun's HTTP server, the `cookies` property on the request object (in `routes`) is an instance of `CookieMap`:
|
||||
|
||||
```ts title="server.ts" icon="/icons/typescript.svg"
|
||||
const server = Bun.serve({
|
||||
routes: {
|
||||
"/": req => {
|
||||
// Access request cookies
|
||||
const cookies = req.cookies;
|
||||
|
||||
// Get a specific cookie
|
||||
const sessionCookie = cookies.get("session");
|
||||
if (sessionCookie != null) {
|
||||
console.log(sessionCookie);
|
||||
}
|
||||
|
||||
// Check if a cookie exists
|
||||
if (cookies.has("theme")) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Set a cookie, it will be automatically applied to the response
|
||||
cookies.set("visited", "true");
|
||||
|
||||
return new Response("Hello");
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log("Server listening at: " + server.url);
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
#### `get(name: string): string | null`
|
||||
|
||||
Retrieves a cookie by name. Returns `null` if the cookie doesn't exist.
|
||||
|
||||
```ts title="get-cookie.ts" icon="/icons/typescript.svg"
|
||||
// Get by name
|
||||
const cookie = cookies.get("session");
|
||||
|
||||
if (cookie != null) {
|
||||
console.log(cookie);
|
||||
}
|
||||
```
|
||||
|
||||
#### `has(name: string): boolean`
|
||||
|
||||
Checks if a cookie with the given name exists.
|
||||
|
||||
```ts title="has-cookie.ts" icon="/icons/typescript.svg"
|
||||
// Check if cookie exists
|
||||
if (cookies.has("session")) {
|
||||
// Cookie exists
|
||||
}
|
||||
```
|
||||
|
||||
#### `set(name: string, value: string): void`
|
||||
|
||||
#### `set(options: CookieInit): void`
|
||||
|
||||
#### `set(cookie: Cookie): void`
|
||||
|
||||
Adds or updates a cookie in the map. Cookies default to `{ path: "/", sameSite: "lax" }`.
|
||||
|
||||
```ts title="set-cookie.ts" icon="/icons/typescript.svg"
|
||||
// Set by name and value
|
||||
cookies.set("session", "abc123");
|
||||
|
||||
// Set using options object
|
||||
cookies.set({
|
||||
name: "theme",
|
||||
value: "dark",
|
||||
maxAge: 3600,
|
||||
secure: true,
|
||||
});
|
||||
|
||||
// Set using Cookie instance
|
||||
const cookie = new Bun.Cookie("visited", "true");
|
||||
cookies.set(cookie);
|
||||
```
|
||||
|
||||
#### `delete(name: string): void`
|
||||
|
||||
#### `delete(options: CookieStoreDeleteOptions): void`
|
||||
|
||||
Removes a cookie from the map. When applied to a Response, this adds a cookie with an empty string value and an expiry date in the past. A cookie will only delete successfully on the browser if the domain and path is the same as it was when the cookie was created.
|
||||
|
||||
```ts title="delete-cookie.ts" icon="/icons/typescript.svg"
|
||||
// Delete by name using default domain and path.
|
||||
cookies.delete("session");
|
||||
|
||||
// Delete with domain/path options.
|
||||
cookies.delete({
|
||||
name: "session",
|
||||
domain: "example.com",
|
||||
path: "/admin",
|
||||
});
|
||||
```
|
||||
|
||||
#### `toJSON(): Record<string, string>`
|
||||
|
||||
Converts the cookie map to a serializable format.
|
||||
|
||||
```ts title="cookie-to-json.ts" icon="/icons/typescript.svg"
|
||||
const json = cookies.toJSON();
|
||||
```
|
||||
|
||||
#### `toSetCookieHeaders(): string[]`
|
||||
|
||||
Returns an array of values for Set-Cookie headers that can be used to apply all cookie changes.
|
||||
|
||||
When using `Bun.serve()`, you don't need to call this method explicitly. Any changes made to the `req.cookies` map are automatically applied to the response headers. This method is primarily useful when working with other HTTP server implementations.
|
||||
|
||||
```js title="node-server.js" icon="file-code"
|
||||
import { createServer } from "node:http";
|
||||
import { CookieMap } from "bun";
|
||||
|
||||
const server = createServer((req, res) => {
|
||||
const cookieHeader = req.headers.cookie || "";
|
||||
const cookies = new CookieMap(cookieHeader);
|
||||
|
||||
cookies.set("view-count", Number(cookies.get("view-count") || "0") + 1);
|
||||
cookies.delete("session");
|
||||
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "text/plain",
|
||||
"Set-Cookie": cookies.toSetCookieHeaders(),
|
||||
});
|
||||
res.end(`Found ${cookies.size} cookies`);
|
||||
});
|
||||
|
||||
server.listen(3000, () => {
|
||||
console.log("Server running at http://localhost:3000/");
|
||||
});
|
||||
```
|
||||
|
||||
### Iteration
|
||||
|
||||
`CookieMap` provides several methods for iteration:
|
||||
|
||||
```ts title="iterate-cookies.ts" icon="/icons/typescript.svg"
|
||||
// Iterate over [name, cookie] entries
|
||||
for (const [name, value] of cookies) {
|
||||
console.log(`${name}: ${value}`);
|
||||
}
|
||||
|
||||
// Using entries()
|
||||
for (const [name, value] of cookies.entries()) {
|
||||
console.log(`${name}: ${value}`);
|
||||
}
|
||||
|
||||
// Using keys()
|
||||
for (const name of cookies.keys()) {
|
||||
console.log(name);
|
||||
}
|
||||
|
||||
// Using values()
|
||||
for (const value of cookies.values()) {
|
||||
console.log(value);
|
||||
}
|
||||
|
||||
// Using forEach
|
||||
cookies.forEach((value, name) => {
|
||||
console.log(`${name}: ${value}`);
|
||||
});
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
#### `size: number`
|
||||
|
||||
Returns the number of cookies in the map.
|
||||
|
||||
```ts title="cookie-size.ts" icon="/icons/typescript.svg"
|
||||
console.log(cookies.size); // Number of cookies
|
||||
```
|
||||
|
||||
## Cookie class
|
||||
|
||||
`Bun.Cookie` represents an HTTP cookie with its name, value, and attributes.
|
||||
|
||||
```ts title="cookie-class.ts" icon="/icons/typescript.svg"
|
||||
import { Cookie } from "bun";
|
||||
|
||||
// Create a basic cookie
|
||||
const cookie = new Bun.Cookie("name", "value");
|
||||
|
||||
// Create a cookie with options
|
||||
const secureSessionCookie = new Bun.Cookie("session", "abc123", {
|
||||
domain: "example.com",
|
||||
path: "/admin",
|
||||
expires: new Date(Date.now() + 86400000), // 1 day
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
});
|
||||
|
||||
// Parse from a cookie string
|
||||
const parsedCookie = new Bun.Cookie("name=value; Path=/; HttpOnly");
|
||||
|
||||
// Create from an options object
|
||||
const objCookie = new Bun.Cookie({
|
||||
name: "theme",
|
||||
value: "dark",
|
||||
maxAge: 3600,
|
||||
secure: true,
|
||||
});
|
||||
```
|
||||
|
||||
### Constructors
|
||||
|
||||
```ts title="constructors.ts" icon="/icons/typescript.svg"
|
||||
// Basic constructor with name/value
|
||||
new Bun.Cookie(name: string, value: string);
|
||||
|
||||
// Constructor with name, value, and options
|
||||
new Bun.Cookie(name: string, value: string, options: CookieInit);
|
||||
|
||||
// Constructor from cookie string
|
||||
new Bun.Cookie(cookieString: string);
|
||||
|
||||
// Constructor from cookie object
|
||||
new Bun.Cookie(options: CookieInit);
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
```ts title="cookie-properties.ts" icon="/icons/typescript.svg"
|
||||
cookie.name; // string - Cookie name
|
||||
cookie.value; // string - Cookie value
|
||||
cookie.domain; // string | null - Domain scope (null if not specified)
|
||||
cookie.path; // string - URL path scope (defaults to "/")
|
||||
cookie.expires; // number | undefined - Expiration timestamp (ms since epoch)
|
||||
cookie.secure; // boolean - Require HTTPS
|
||||
cookie.sameSite; // "strict" | "lax" | "none" - SameSite setting
|
||||
cookie.partitioned; // boolean - Whether the cookie is partitioned (CHIPS)
|
||||
cookie.maxAge; // number | undefined - Max age in seconds
|
||||
cookie.httpOnly; // boolean - Accessible only via HTTP (not JavaScript)
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
#### `isExpired(): boolean`
|
||||
|
||||
Checks if the cookie has expired.
|
||||
|
||||
```ts title="is-expired.ts" icon="/icons/typescript.svg"
|
||||
// Expired cookie (Date in the past)
|
||||
const expiredCookie = new Bun.Cookie("name", "value", {
|
||||
expires: new Date(Date.now() - 1000),
|
||||
});
|
||||
console.log(expiredCookie.isExpired()); // true
|
||||
|
||||
// Valid cookie (Using maxAge instead of expires)
|
||||
const validCookie = new Bun.Cookie("name", "value", {
|
||||
maxAge: 3600, // 1 hour in seconds
|
||||
});
|
||||
console.log(validCookie.isExpired()); // false
|
||||
|
||||
// Session cookie (no expiration)
|
||||
const sessionCookie = new Bun.Cookie("name", "value");
|
||||
console.log(sessionCookie.isExpired()); // false
|
||||
```
|
||||
|
||||
#### `serialize(): string`
|
||||
|
||||
#### `toString(): string`
|
||||
|
||||
Returns a string representation of the cookie suitable for a `Set-Cookie` header.
|
||||
|
||||
```ts title="serialize-cookie.ts" icon="/icons/typescript.svg"
|
||||
const cookie = new Bun.Cookie("session", "abc123", {
|
||||
domain: "example.com",
|
||||
path: "/admin",
|
||||
expires: new Date(Date.now() + 86400000),
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: "strict",
|
||||
});
|
||||
|
||||
console.log(cookie.serialize());
|
||||
// => "session=abc123; Domain=example.com; Path=/admin; Expires=Sun, 19 Mar 2025 15:03:26 GMT; Secure; HttpOnly; SameSite=strict"
|
||||
console.log(cookie.toString());
|
||||
// => "session=abc123; Domain=example.com; Path=/admin; Expires=Sun, 19 Mar 2025 15:03:26 GMT; Secure; HttpOnly; SameSite=strict"
|
||||
```
|
||||
|
||||
#### `toJSON(): CookieInit`
|
||||
|
||||
Converts the cookie to a plain object suitable for JSON serialization.
|
||||
|
||||
```ts title="cookie-json.ts" icon="/icons/typescript.svg"
|
||||
const cookie = new Bun.Cookie("session", "abc123", {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
});
|
||||
|
||||
const json = cookie.toJSON();
|
||||
// => {
|
||||
// name: "session",
|
||||
// value: "abc123",
|
||||
// path: "/",
|
||||
// secure: true,
|
||||
// httpOnly: true,
|
||||
// sameSite: "lax",
|
||||
// partitioned: false
|
||||
// }
|
||||
|
||||
// Works with JSON.stringify
|
||||
const jsonString = JSON.stringify(cookie);
|
||||
```
|
||||
|
||||
### Static methods
|
||||
|
||||
#### `Cookie.parse(cookieString: string): Cookie`
|
||||
|
||||
Parses a cookie string into a `Cookie` instance.
|
||||
|
||||
```ts title="parse-cookie.ts" icon="/icons/typescript.svg"
|
||||
const cookie = Bun.Cookie.parse("name=value; Path=/; Secure; SameSite=Lax");
|
||||
|
||||
console.log(cookie.name); // "name"
|
||||
console.log(cookie.value); // "value"
|
||||
console.log(cookie.path); // "/"
|
||||
console.log(cookie.secure); // true
|
||||
console.log(cookie.sameSite); // "lax"
|
||||
```
|
||||
|
||||
#### `Cookie.from(name: string, value: string, options?: CookieInit): Cookie`
|
||||
|
||||
Factory method to create a cookie.
|
||||
|
||||
```ts title="cookie-from.ts" icon="/icons/typescript.svg"
|
||||
const cookie = Bun.Cookie.from("session", "abc123", {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
maxAge: 3600,
|
||||
});
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
```ts title="types.ts" icon="/icons/typescript.svg"
|
||||
interface CookieInit {
|
||||
name?: string;
|
||||
value?: string;
|
||||
domain?: string;
|
||||
/** Defaults to '/'. To allow the browser to set the path, use an empty string. */
|
||||
path?: string;
|
||||
expires?: number | Date | string;
|
||||
secure?: boolean;
|
||||
/** Defaults to `lax`. */
|
||||
sameSite?: CookieSameSite;
|
||||
httpOnly?: boolean;
|
||||
partitioned?: boolean;
|
||||
maxAge?: number;
|
||||
}
|
||||
|
||||
interface CookieStoreDeleteOptions {
|
||||
name: string;
|
||||
domain?: string | null;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
interface CookieStoreGetOptions {
|
||||
name?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
type CookieSameSite = "strict" | "lax" | "none";
|
||||
|
||||
class Cookie {
|
||||
constructor(name: string, value: string, options?: CookieInit);
|
||||
constructor(cookieString: string);
|
||||
constructor(cookieObject?: CookieInit);
|
||||
|
||||
readonly name: string;
|
||||
value: string;
|
||||
domain?: string;
|
||||
path: string;
|
||||
expires?: Date;
|
||||
secure: boolean;
|
||||
sameSite: CookieSameSite;
|
||||
partitioned: boolean;
|
||||
maxAge?: number;
|
||||
httpOnly: boolean;
|
||||
|
||||
isExpired(): boolean;
|
||||
|
||||
serialize(): string;
|
||||
toString(): string;
|
||||
toJSON(): CookieInit;
|
||||
|
||||
static parse(cookieString: string): Cookie;
|
||||
static from(name: string, value: string, options?: CookieInit): Cookie;
|
||||
}
|
||||
|
||||
class CookieMap implements Iterable<[string, string]> {
|
||||
constructor(init?: string[][] | Record<string, string> | string);
|
||||
|
||||
get(name: string): string | null;
|
||||
|
||||
toSetCookieHeaders(): string[];
|
||||
|
||||
has(name: string): boolean;
|
||||
set(name: string, value: string, options?: CookieInit): void;
|
||||
set(options: CookieInit): void;
|
||||
delete(name: string): void;
|
||||
delete(options: CookieStoreDeleteOptions): void;
|
||||
delete(name: string, options: Omit<CookieStoreDeleteOptions, "name">): void;
|
||||
toJSON(): Record<string, string>;
|
||||
|
||||
readonly size: number;
|
||||
|
||||
entries(): IterableIterator<[string, string]>;
|
||||
keys(): IterableIterator<string>;
|
||||
values(): IterableIterator<string>;
|
||||
forEach(callback: (value: string, key: string, map: CookieMap) => void): void;
|
||||
[Symbol.iterator](): IterableIterator<[string, string]>;
|
||||
}
|
||||
```
|
||||
@@ -1,26 +1,30 @@
|
||||
---
|
||||
name: Debugging
|
||||
title: "Debugging"
|
||||
description: "Debug your Bun code with an interactive debugger using WebKit Inspector Protocol"
|
||||
---
|
||||
|
||||
Bun speaks the [WebKit Inspector Protocol](https://github.com/oven-sh/bun/blob/main/packages/bun-inspector-protocol/src/protocol/jsc/index.d.ts), so you can debug your code with an interactive debugger. For demonstration purposes, consider the following simple web server.
|
||||
|
||||
## Debugging JavaScript and TypeScript
|
||||
|
||||
```ts#server.ts
|
||||
```typescript icon="/icons/typescript.svg" title="server.ts"
|
||||
Bun.serve({
|
||||
fetch(req){
|
||||
fetch(req) {
|
||||
console.log(req.url);
|
||||
return new Response("Hello, world!");
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### `--inspect`
|
||||
|
||||
To enable debugging when running code with Bun, use the `--inspect` flag. This automatically starts a WebSocket server on an available port that can be used to introspect the running Bun process.
|
||||
|
||||
```sh
|
||||
$ bun --inspect server.ts
|
||||
```sh icon="terminal" title="terminal"
|
||||
bun --inspect server.ts
|
||||
```
|
||||
|
||||
```txt id="terminal"
|
||||
------------------ Bun Inspector ------------------
|
||||
Listening at:
|
||||
ws://localhost:6499/0tqxs9exrgrm
|
||||
@@ -42,12 +46,14 @@ The `--inspect-wait` flag behaves identically to `--inspect`, except the code wi
|
||||
|
||||
Regardless of which flag you use, you can optionally specify a port number, URL prefix, or both.
|
||||
|
||||
```sh
|
||||
$ bun --inspect=4000 server.ts
|
||||
$ bun --inspect=localhost:4000 server.ts
|
||||
$ bun --inspect=localhost:4000/prefix server.ts
|
||||
```sh icon="terminal" title="terminal"
|
||||
bun --inspect=4000 server.ts
|
||||
bun --inspect=localhost:4000 server.ts
|
||||
bun --inspect=localhost:4000/prefix server.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debuggers
|
||||
|
||||
Various debugging tools can connect to this server to provide an interactive debugging experience.
|
||||
@@ -58,42 +64,60 @@ Bun hosts a web-based debugger at [debug.bun.sh](https://debug.bun.sh). It is a
|
||||
|
||||
Open the provided `debug.bun.sh` URL in your browser to start a debugging session. From this interface, you'll be able to view the source code of the running file, view and set breakpoints, and execute code with the built-in console.
|
||||
|
||||
{% image src="https://github.com/oven-sh/bun/assets/3084745/e6a976a8-80cc-4394-8925-539025cc025d" alt="Screenshot of Bun debugger, Console tab" /%}
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
Let's set a breakpoint. Navigate to the Sources tab; you should see the code from earlier. Click on the line number `3` to set a breakpoint on our `console.log(req.url)` statement.
|
||||
|
||||
{% image src="https://github.com/oven-sh/bun/assets/3084745/3b69c7e9-25ff-4f9d-acc4-caa736862935" alt="screenshot of Bun debugger" /%}
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
Then visit [`http://localhost:3000`](http://localhost:3000) in your web browser. This will send an HTTP request to our `localhost` web server. It will seem like the page isn't loading. Why? Because the program has paused execution at the breakpoint we set earlier.
|
||||
|
||||
Note how the UI has changed.
|
||||
|
||||
{% image src="https://github.com/oven-sh/bun/assets/3084745/8b565e58-5445-4061-9bc4-f41090dfe769" alt="screenshot of Bun debugger" /%}
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
At this point there's a lot we can do to introspect the current execution environment. We can use the console at the bottom to run arbitrary code in the context of the program, with full access to the variables in scope at our breakpoint.
|
||||
|
||||
{% image src="https://github.com/oven-sh/bun/assets/3084745/f4312b76-48ba-4a7d-b3b6-6205968ac681" /%}
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
On the right side of the Sources pane, we can see all local variables currently in scope, and drill down to see their properties and methods. Here, we're inspecting the `req` variable.
|
||||
|
||||
{% image src="https://github.com/oven-sh/bun/assets/3084745/63d7f843-5180-489c-aa94-87c486e68646" /%}
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
In the upper left of the Sources pane, we can control the execution of the program.
|
||||
|
||||
{% image src="https://github.com/oven-sh/bun/assets/3084745/41b76deb-7371-4461-9d5d-81b5a6d2f7a4" /%}
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
Here's a cheat sheet explaining the functions of the control flow buttons.
|
||||
|
||||
- _Continue script execution_ — continue running the program until the next breakpoint or exception.
|
||||
- _Step over_ — The program will continue to the next line.
|
||||
- _Step into_ — If the current statement contains a function call, the debugger will "step into" the called function.
|
||||
- _Step out_ — If the current statement is a function call, the debugger will finish executing the call, then "step out" of the function to the location where it was called.
|
||||
- _Continue script execution_ — continue running the program until the next breakpoint or exception.
|
||||
- _Step over_ — The program will continue to the next line.
|
||||
- _Step into_ — If the current statement contains a function call, the debugger will "step into" the called function.
|
||||
- _Step out_ — If the current statement is a function call, the debugger will finish executing the call, then "step out" of the function to the location where it was called.
|
||||
|
||||
{% image src="https://github-production-user-asset-6210df.s3.amazonaws.com/3084745/261510346-6a94441c-75d3-413a-99a7-efa62365f83d.png" /%}
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
### Visual Studio Code Debugger
|
||||
|
||||
Experimental support for debugging Bun scripts is available in Visual Studio Code. To use it, you'll need to install the [Bun VSCode extension](https://bun.com/guides/runtime/vscode-debugger).
|
||||
Experimental support for debugging Bun scripts is available in Visual Studio Code. To use it, you'll need to install the [Bun VSCode extension](/guides/runtime/vscode-debugger).
|
||||
|
||||
---
|
||||
|
||||
## Debugging Network Requests
|
||||
|
||||
@@ -107,9 +131,9 @@ The `BUN_CONFIG_VERBOSE_FETCH` environment variable lets you log network request
|
||||
|
||||
### Print fetch & node:http requests as curl commands
|
||||
|
||||
Bun also supports printing `fetch()` and `node:http` network requests as `curl` commands by setting the environment variable `BUN_CONFIG_VERBOSE_FETCH` to `curl`.
|
||||
Bun also supports printing `fetch()` and `node:http` network requests as `curl` commands by setting the environment variable `BUN_CONFIG_VERBOSE_FETCH` to `curl`. This prints the `fetch` request as a single-line `curl` command to let you copy-paste into your terminal to replicate the request.
|
||||
|
||||
```ts
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
process.env.BUN_CONFIG_VERBOSE_FETCH = "curl";
|
||||
|
||||
await fetch("https://example.com", {
|
||||
@@ -121,14 +145,12 @@ await fetch("https://example.com", {
|
||||
});
|
||||
```
|
||||
|
||||
This prints the `fetch` request as a single-line `curl` command to let you copy-paste into your terminal to replicate the request.
|
||||
|
||||
```sh
|
||||
[fetch] $ curl --http1.1 "https://example.com/" -X POST -H "content-type: application/json" -H "Connection: keep-alive" -H "User-Agent: Bun/$BUN_LATEST_VERSION" -H "Accept: */*" -H "Host: example.com" -H "Accept-Encoding: gzip, deflate, br" --compressed -H "Content-Length: 13" --data-raw "{\"foo\":\"bar\"}"
|
||||
```txt
|
||||
[fetch] $ curl --http1.1 "https://example.com/" -X POST -H "content-type: application/json" -H "Connection: keep-alive" -H "User-Agent: Bun/1.3.3" -H "Accept: */*" -H "Host: example.com" -H "Accept-Encoding: gzip, deflate, br" --compressed -H "Content-Length: 13" --data-raw "{\"foo\":\"bar\"}"
|
||||
[fetch] > HTTP/1.1 POST https://example.com/
|
||||
[fetch] > content-type: application/json
|
||||
[fetch] > Connection: keep-alive
|
||||
[fetch] > User-Agent: Bun/$BUN_LATEST_VERSION
|
||||
[fetch] > User-Agent: Bun/1.3.3
|
||||
[fetch] > Accept: */*
|
||||
[fetch] > Host: example.com
|
||||
[fetch] > Accept-Encoding: gzip, deflate, br
|
||||
@@ -152,7 +174,7 @@ The `BUN_CONFIG_VERBOSE_FETCH` environment variable is supported in both `fetch(
|
||||
|
||||
To print without the `curl` command, set `BUN_CONFIG_VERBOSE_FETCH` to `true`.
|
||||
|
||||
```ts
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
process.env.BUN_CONFIG_VERBOSE_FETCH = "true";
|
||||
|
||||
await fetch("https://example.com", {
|
||||
@@ -164,13 +186,11 @@ await fetch("https://example.com", {
|
||||
});
|
||||
```
|
||||
|
||||
This prints the following to the console:
|
||||
|
||||
```sh
|
||||
```txt
|
||||
[fetch] > HTTP/1.1 POST https://example.com/
|
||||
[fetch] > content-type: application/json
|
||||
[fetch] > Connection: keep-alive
|
||||
[fetch] > User-Agent: Bun/$BUN_LATEST_VERSION
|
||||
[fetch] > User-Agent: Bun/1.3.3
|
||||
[fetch] > Accept: */*
|
||||
[fetch] > Host: example.com
|
||||
[fetch] > Accept-Encoding: gzip, deflate, br
|
||||
@@ -188,20 +208,12 @@ This prints the following to the console:
|
||||
[fetch] < Content-Length: 1256
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stacktraces & sourcemaps
|
||||
|
||||
Bun transpiles every file, which sounds like it would mean that the stack traces you see in the console would unhelpfully point to the transpiled output. To address this, Bun automatically generates and serves sourcemapped files for every file it transpiles. When you see a stack trace in the console, you can click on the file path and be taken to the original source code, even though it was written in TypeScript or JSX, or has some other transformation applied.
|
||||
|
||||
<!-- TODO: uncomment once v1.1.13 regression is fixed (cc @paperclover) -->
|
||||
<!-- In Bun, each `Error` object gets four additional properties:
|
||||
|
||||
- `line` — the source-mapped line number. This number points to the input source code, not the transpiled output.
|
||||
- `column` — the source-mapped column number. This number points to the input source code, not the transpiled output.
|
||||
- `originalColumn` — the column number pointing to transpiled source code, without sourcemaps. This number comes from JavaScriptCore.
|
||||
- `originalLine` — the line number pointing to transpiled source code, without sourcemaps. This number comes from JavaScriptCore.
|
||||
|
||||
These properties are populated lazily when `error.stack` is accessed. -->
|
||||
|
||||
Bun automatically loads sourcemaps both at runtime when transpiling files on-demand, and when using `bun build` to precompile files ahead of time.
|
||||
|
||||
### Syntax-highlighted source code preview
|
||||
@@ -216,7 +228,7 @@ console.log(Bun.inspect(err, { colors: true }));
|
||||
|
||||
This prints a syntax-highlighted preview of the source code where the error occurred, along with the error message and stack trace.
|
||||
|
||||
```js
|
||||
```ts icon="file-code"
|
||||
1 | // Create an error
|
||||
2 | const err = new Error("Something went wrong");
|
||||
^
|
||||
@@ -234,7 +246,7 @@ That's why when you log `error.stack` in Bun, the formatting of `error.stack` is
|
||||
|
||||
Bun implements the [V8 Stack Trace API](https://v8.dev/docs/stack-trace-api), which is a set of functions that allow you to manipulate stack traces.
|
||||
|
||||
##### Error.prepareStackTrace
|
||||
##### `Error.prepareStackTrace`
|
||||
|
||||
The `Error.prepareStackTrace` function is a global function that lets you customize the stack trace output. This function is called with the error object and an array of `CallSite` objects and lets you return a custom stack trace.
|
||||
|
||||
@@ -275,7 +287,7 @@ The `CallSite` object has the following methods:
|
||||
|
||||
In some cases, the `Function` object may have already been garbage collected, so some of these methods may return `undefined`.
|
||||
|
||||
##### Error.captureStackTrace(error, startFn)
|
||||
##### `Error.captureStackTrace(error, startFn)`
|
||||
|
||||
The `Error.captureStackTrace` function lets you capture a stack trace at a specific point in your code, rather than at the point where the error was thrown.
|
||||
|
||||
@@ -283,7 +295,7 @@ This can be helpful when you have callbacks or asynchronous code that makes it d
|
||||
|
||||
For example, the below code will make `err.stack` point to the code calling `fn()`, even though the error was thrown at `myInner`.
|
||||
|
||||
```ts
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const fn = () => {
|
||||
function myInner() {
|
||||
throw err;
|
||||
@@ -304,9 +316,7 @@ const fn = () => {
|
||||
fn();
|
||||
```
|
||||
|
||||
This logs the following:
|
||||
|
||||
```sh
|
||||
```txt
|
||||
Error: here!
|
||||
at myInner (file.js:4:15)
|
||||
at fn (file.js:8:5)
|
||||
@@ -1,253 +0,0 @@
|
||||
Bun reads your `.env` files automatically and provides idiomatic ways to read and write your environment variables programmatically. Plus, some aspects of Bun's runtime behavior can be configured with Bun-specific environment variables.
|
||||
|
||||
## Setting environment variables
|
||||
|
||||
Bun reads the following files automatically (listed in order of increasing precedence).
|
||||
|
||||
- `.env`
|
||||
- `.env.production`, `.env.development`, `.env.test` (depending on value of `NODE_ENV`)
|
||||
- `.env.local`
|
||||
|
||||
{% callout %}
|
||||
**Note:** When `NODE_ENV=test`, `.env.local` is **not** loaded. This ensures consistent test environments across different executions by preventing local overrides during testing. This behavior matches popular frameworks like [Next.js](https://nextjs.org/docs/pages/guides/environment-variables#test-environment-variables) and [Create React App](https://create-react-app.dev/docs/adding-custom-environment-variables/#what-other-env-files-can-be-used).
|
||||
{% /callout %}
|
||||
|
||||
```txt#.env
|
||||
FOO=hello
|
||||
BAR=world
|
||||
```
|
||||
|
||||
Variables can also be set via the command line.
|
||||
|
||||
{% codetabs %}
|
||||
|
||||
```sh#Linux/macOS
|
||||
$ FOO=helloworld bun run dev
|
||||
```
|
||||
|
||||
```sh#Windows
|
||||
# Using CMD
|
||||
$ set FOO=helloworld && bun run dev
|
||||
|
||||
# Using PowerShell
|
||||
$ $env:FOO="helloworld"; bun run dev
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
{% details summary="Cross-platform solution with Windows" %}
|
||||
|
||||
For a cross-platform solution, you can use [bun shell](https://bun.com/docs/runtime/shell). For example, the `bun exec` command.
|
||||
|
||||
```sh
|
||||
$ bun exec 'FOO=helloworld bun run dev'
|
||||
```
|
||||
|
||||
On Windows, `package.json` scripts called with `bun run` will automatically use the **bun shell**, making the following also cross-platform.
|
||||
|
||||
```json#package.json
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development bun --watch app.ts",
|
||||
},
|
||||
```
|
||||
|
||||
{% /details %}
|
||||
|
||||
Or programmatically by assigning a property to `process.env`.
|
||||
|
||||
```ts
|
||||
process.env.FOO = "hello";
|
||||
```
|
||||
|
||||
### Manually specifying `.env` files
|
||||
|
||||
Bun supports `--env-file` to override which specific `.env` file to load. You can use `--env-file` when running scripts in bun's runtime, or when running package.json scripts.
|
||||
|
||||
```sh
|
||||
$ bun --env-file=.env.1 src/index.ts
|
||||
|
||||
$ bun --env-file=.env.abc --env-file=.env.def run build
|
||||
```
|
||||
|
||||
### Quotation marks
|
||||
|
||||
Bun supports double quotes, single quotes, and template literal backticks:
|
||||
|
||||
```txt#.env
|
||||
FOO='hello'
|
||||
FOO="hello"
|
||||
FOO=`hello`
|
||||
```
|
||||
|
||||
### Expansion
|
||||
|
||||
Environment variables are automatically _expanded_. This means you can reference previously-defined variables in your environment variables.
|
||||
|
||||
```txt#.env
|
||||
FOO=world
|
||||
BAR=hello$FOO
|
||||
```
|
||||
|
||||
```ts
|
||||
process.env.BAR; // => "helloworld"
|
||||
```
|
||||
|
||||
This is useful for constructing connection strings or other compound values.
|
||||
|
||||
```txt#.env
|
||||
DB_USER=postgres
|
||||
DB_PASSWORD=secret
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_URL=postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME
|
||||
```
|
||||
|
||||
This can be disabled by escaping the `$` with a backslash.
|
||||
|
||||
```txt#.env
|
||||
FOO=world
|
||||
BAR=hello\$FOO
|
||||
```
|
||||
|
||||
```ts
|
||||
process.env.BAR; // => "hello$FOO"
|
||||
```
|
||||
|
||||
### `dotenv`
|
||||
|
||||
Generally speaking, you won't need `dotenv` or `dotenv-expand` anymore, because Bun reads `.env` files automatically.
|
||||
|
||||
## Reading environment variables
|
||||
|
||||
The current environment variables can be accessed via `process.env`.
|
||||
|
||||
```ts
|
||||
process.env.API_TOKEN; // => "secret"
|
||||
```
|
||||
|
||||
Bun also exposes these variables via `Bun.env` and `import.meta.env`, which is a simple alias of `process.env`.
|
||||
|
||||
```ts
|
||||
Bun.env.API_TOKEN; // => "secret"
|
||||
import.meta.env.API_TOKEN; // => "secret"
|
||||
```
|
||||
|
||||
To print all currently-set environment variables to the command line, run `bun --print process.env`. This is useful for debugging.
|
||||
|
||||
```sh
|
||||
$ bun --print process.env
|
||||
BAZ=stuff
|
||||
FOOBAR=aaaaaa
|
||||
<lots more lines>
|
||||
```
|
||||
|
||||
## TypeScript
|
||||
|
||||
In TypeScript, all properties of `process.env` are typed as `string | undefined`.
|
||||
|
||||
```ts
|
||||
Bun.env.whatever;
|
||||
// string | undefined
|
||||
```
|
||||
|
||||
To get autocompletion and tell TypeScript to treat a variable as a non-optional string, we'll use [interface merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces).
|
||||
|
||||
```ts
|
||||
declare module "bun" {
|
||||
interface Env {
|
||||
AWESOME: string;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Add this line to any file in your project. It will globally add the `AWESOME` property to `process.env` and `Bun.env`.
|
||||
|
||||
```ts
|
||||
process.env.AWESOME; // => string
|
||||
```
|
||||
|
||||
## Configuring Bun
|
||||
|
||||
These environment variables are read by Bun and configure aspects of its behavior.
|
||||
|
||||
{% table %}
|
||||
|
||||
- Name
|
||||
- Description
|
||||
|
||||
---
|
||||
|
||||
- `NODE_TLS_REJECT_UNAUTHORIZED`
|
||||
- `NODE_TLS_REJECT_UNAUTHORIZED=0` disables SSL certificate validation. This is useful for testing and debugging, but you should be very hesitant to use this in production. Note: This environment variable was originally introduced by Node.js and we kept the name for compatibility.
|
||||
|
||||
---
|
||||
|
||||
- `BUN_CONFIG_VERBOSE_FETCH`
|
||||
- If `BUN_CONFIG_VERBOSE_FETCH=curl`, then fetch requests will log the url, method, request headers and response headers to the console. This is useful for debugging network requests. This also works with `node:http`. `BUN_CONFIG_VERBOSE_FETCH=1` is equivalent to `BUN_CONFIG_VERBOSE_FETCH=curl` except without the `curl` output.
|
||||
|
||||
---
|
||||
|
||||
- `BUN_RUNTIME_TRANSPILER_CACHE_PATH`
|
||||
- The runtime transpiler caches the transpiled output of source files larger than 50 kb. This makes CLIs using Bun load faster. If `BUN_RUNTIME_TRANSPILER_CACHE_PATH` is set, then the runtime transpiler will cache transpiled output to the specified directory. If `BUN_RUNTIME_TRANSPILER_CACHE_PATH` is set to an empty string or the string `"0"`, then the runtime transpiler will not cache transpiled output. If `BUN_RUNTIME_TRANSPILER_CACHE_PATH` is unset, then the runtime transpiler will cache transpiled output to the platform-specific cache directory.
|
||||
|
||||
---
|
||||
|
||||
- `TMPDIR`
|
||||
- Bun occasionally requires a directory to store intermediate assets during bundling or other operations. If unset, defaults to the platform-specific temporary directory: `/tmp` on Linux, `/private/tmp` on macOS.
|
||||
|
||||
---
|
||||
|
||||
- `NO_COLOR`
|
||||
- If `NO_COLOR=1`, then ANSI color output is [disabled](https://no-color.org/).
|
||||
|
||||
---
|
||||
|
||||
- `FORCE_COLOR`
|
||||
- If `FORCE_COLOR=1`, then ANSI color output is force enabled, even if `NO_COLOR` is set.
|
||||
|
||||
---
|
||||
|
||||
- `BUN_CONFIG_MAX_HTTP_REQUESTS`
|
||||
- Control the maximum number of concurrent HTTP requests sent by fetch and `bun install`. Defaults to `256`. If you are running into rate limits or connection issues, you can reduce this number.
|
||||
|
||||
---
|
||||
|
||||
- `BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD`
|
||||
- If `BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD=true`, then `bun --watch` will not clear the console on reload
|
||||
|
||||
---
|
||||
|
||||
- `DO_NOT_TRACK`
|
||||
- Disable uploading crash reports to `bun.report` on crash. On macOS & Windows, crash report uploads are enabled by default. Otherwise, telemetry is not sent yet as of May 21st, 2024, but we are planning to add telemetry in the coming weeks. If `DO_NOT_TRACK=1`, then auto-uploading crash reports and telemetry are both [disabled](https://do-not-track.dev/).
|
||||
|
||||
---
|
||||
|
||||
- `BUN_OPTIONS`
|
||||
- Prepends command-line arguments to any Bun execution. For example, `BUN_OPTIONS="--hot"` makes `bun run dev` behave like `bun --hot run dev`.
|
||||
|
||||
{% /table %}
|
||||
|
||||
## Runtime transpiler caching
|
||||
|
||||
For files larger than 50 KB, Bun caches transpiled output into `$BUN_RUNTIME_TRANSPILER_CACHE_PATH` or the platform-specific cache directory. This makes CLIs using Bun load faster.
|
||||
|
||||
This transpiler cache is global and shared across all projects. It is safe to delete the cache at any time. It is a content-addressable cache, so it will never contain duplicate entries. It is also safe to delete the cache while a Bun process is running.
|
||||
|
||||
It is recommended to disable this cache when using ephemeral filesystems like Docker. Bun's Docker images automatically disable this cache.
|
||||
|
||||
### Disable the runtime transpiler cache
|
||||
|
||||
To disable the runtime transpiler cache, set `BUN_RUNTIME_TRANSPILER_CACHE_PATH` to an empty string or the string `"0"`.
|
||||
|
||||
```sh
|
||||
BUN_RUNTIME_TRANSPILER_CACHE_PATH=0 bun run dev
|
||||
```
|
||||
|
||||
### What does it cache?
|
||||
|
||||
It caches:
|
||||
|
||||
- The transpiled output of source files larger than 50 KB.
|
||||
- The sourcemap for the transpiled output of the file
|
||||
|
||||
The file extension `.pile` is used for these cached files.
|
||||
231
docs/runtime/environment-variables.mdx
Normal file
231
docs/runtime/environment-variables.mdx
Normal file
@@ -0,0 +1,231 @@
|
||||
---
|
||||
title: "Environment Variables"
|
||||
description: "Read and configure environment variables in Bun, including automatic .env file support"
|
||||
---
|
||||
|
||||
Bun reads your `.env` files automatically and provides idiomatic ways to read and write your environment variables programmatically. Plus, some aspects of Bun's runtime behavior can be configured with Bun-specific environment variables.
|
||||
|
||||
## Setting environment variables
|
||||
|
||||
Bun reads the following files automatically (listed in order of increasing precedence).
|
||||
|
||||
- `.env`
|
||||
- `.env.production`, `.env.development`, `.env.test` (depending on value of `NODE_ENV`)
|
||||
- `.env.local`
|
||||
|
||||
```ini .env icon="settings"
|
||||
FOO=hello
|
||||
BAR=world
|
||||
```
|
||||
|
||||
Variables can also be set via the command line.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```sh Linux/macOS icon="terminal"
|
||||
FOO=helloworld bun run dev
|
||||
```
|
||||
|
||||
```sh Windows icon="windows"
|
||||
# Using CMD
|
||||
set FOO=helloworld && bun run dev
|
||||
|
||||
# Using PowerShell
|
||||
$env:FOO="helloworld"; bun run dev
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<Accordion title="Cross-platform solution with Windows">
|
||||
|
||||
For a cross-platform solution, you can use [bun shell](/runtime/shell). For example, the `bun exec` command.
|
||||
|
||||
```sh
|
||||
bun exec 'FOO=helloworld bun run dev'
|
||||
```
|
||||
|
||||
On Windows, `package.json` scripts called with `bun run` will automatically use the **bun shell**, making the following also cross-platform.
|
||||
|
||||
```json package.json icon="file-json"
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development bun --watch app.ts",
|
||||
},
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
Or programmatically by assigning a property to `process.env`.
|
||||
|
||||
```ts
|
||||
process.env.FOO = "hello";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manually specifying `.env` files
|
||||
|
||||
Bun supports `--env-file` to override which specific `.env` file to load. You can use `--env-file` when running scripts in bun's runtime, or when running package.json scripts.
|
||||
|
||||
```sh
|
||||
bun --env-file=.env.1 src/index.ts
|
||||
|
||||
bun --env-file=.env.abc --env-file=.env.def run build
|
||||
```
|
||||
|
||||
## Disabling automatic `.env` loading
|
||||
|
||||
Use `--no-env-file` to disable Bun's automatic `.env` file loading. This is useful in production environments or CI/CD pipelines where you want to rely solely on system environment variables.
|
||||
|
||||
```sh
|
||||
bun run --no-env-file index.ts
|
||||
```
|
||||
|
||||
This can also be configured in `bunfig.toml`:
|
||||
|
||||
```toml bunfig.toml icon="settings"
|
||||
# Disable loading .env files
|
||||
env = false
|
||||
```
|
||||
|
||||
Explicitly provided environment files via `--env-file` will still be loaded even when default loading is disabled.
|
||||
|
||||
---
|
||||
|
||||
## Quotation marks
|
||||
|
||||
Bun supports double quotes, single quotes, and template literal backticks:
|
||||
|
||||
```ini .env icon="settings"
|
||||
FOO='hello'
|
||||
FOO="hello"
|
||||
FOO=`hello`
|
||||
```
|
||||
|
||||
### Expansion
|
||||
|
||||
Environment variables are automatically _expanded_. This means you can reference previously-defined variables in your environment variables.
|
||||
|
||||
```ini .env icon="settings"
|
||||
FOO=world
|
||||
BAR=hello$FOO
|
||||
```
|
||||
|
||||
```ts
|
||||
process.env.BAR; // => "helloworld"
|
||||
```
|
||||
|
||||
This is useful for constructing connection strings or other compound values.
|
||||
|
||||
```ini .env icon="settings"
|
||||
DB_USER=postgres
|
||||
DB_PASSWORD=secret
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_URL=postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME
|
||||
```
|
||||
|
||||
This can be disabled by escaping the `$` with a backslash.
|
||||
|
||||
```ini .env icon="settings"
|
||||
FOO=world
|
||||
BAR=hello\$FOO
|
||||
```
|
||||
|
||||
```ts
|
||||
process.env.BAR; // => "hello$FOO"
|
||||
```
|
||||
|
||||
### `dotenv`
|
||||
|
||||
Generally speaking, you won't need `dotenv` or `dotenv-expand` anymore, because Bun reads `.env` files automatically.
|
||||
|
||||
## Reading environment variables
|
||||
|
||||
The current environment variables can be accessed via `process.env`.
|
||||
|
||||
```ts
|
||||
process.env.API_TOKEN; // => "secret"
|
||||
```
|
||||
|
||||
Bun also exposes these variables via `Bun.env` and `import.meta.env`, which is a simple alias of `process.env`.
|
||||
|
||||
```ts
|
||||
Bun.env.API_TOKEN; // => "secret"
|
||||
import.meta.env.API_TOKEN; // => "secret"
|
||||
```
|
||||
|
||||
To print all currently-set environment variables to the command line, run `bun --print process.env`. This is useful for debugging.
|
||||
|
||||
```sh
|
||||
bun --print process.env
|
||||
BAZ=stuff
|
||||
FOOBAR=aaaaaa
|
||||
<lots more lines>
|
||||
```
|
||||
|
||||
## TypeScript
|
||||
|
||||
In TypeScript, all properties of `process.env` are typed as `string | undefined`.
|
||||
|
||||
```ts
|
||||
Bun.env.whatever;
|
||||
// string | undefined
|
||||
```
|
||||
|
||||
To get autocompletion and tell TypeScript to treat a variable as a non-optional string, we'll use [interface merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces).
|
||||
|
||||
```ts
|
||||
declare module "bun" {
|
||||
interface Env {
|
||||
AWESOME: string;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Add this line to any file in your project. It will globally add the `AWESOME` property to `process.env` and `Bun.env`.
|
||||
|
||||
```ts
|
||||
process.env.AWESOME; // => string
|
||||
```
|
||||
|
||||
## Configuring Bun
|
||||
|
||||
These environment variables are read by Bun and configure aspects of its behavior.
|
||||
|
||||
| Name | Description |
|
||||
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `NODE_TLS_REJECT_UNAUTHORIZED` | `NODE_TLS_REJECT_UNAUTHORIZED=0` disables SSL certificate validation. This is useful for testing and debugging, but you should be very hesitant to use this in production. Note: This environment variable was originally introduced by Node.js and we kept the name for compatibility. |
|
||||
| `BUN_CONFIG_VERBOSE_FETCH` | If `BUN_CONFIG_VERBOSE_FETCH=curl`, then fetch requests will log the url, method, request headers and response headers to the console. This is useful for debugging network requests. This also works with `node:http`. `BUN_CONFIG_VERBOSE_FETCH=1` is equivalent to `BUN_CONFIG_VERBOSE_FETCH=curl` except without the `curl` output. |
|
||||
| `BUN_RUNTIME_TRANSPILER_CACHE_PATH` | The runtime transpiler caches the transpiled output of source files larger than 50 kb. This makes CLIs using Bun load faster. If `BUN_RUNTIME_TRANSPILER_CACHE_PATH` is set, then the runtime transpiler will cache transpiled output to the specified directory. If `BUN_RUNTIME_TRANSPILER_CACHE_PATH` is set to an empty string or the string `"0"`, then the runtime transpiler will not cache transpiled output. If `BUN_RUNTIME_TRANSPILER_CACHE_PATH` is unset, then the runtime transpiler will cache transpiled output to the platform-specific cache directory. |
|
||||
| `TMPDIR` | Bun occasionally requires a directory to store intermediate assets during bundling or other operations. If unset, defaults to the platform-specific temporary directory: `/tmp` on Linux, `/private/tmp` on macOS. |
|
||||
| `NO_COLOR` | If `NO_COLOR=1`, then ANSI color output is [disabled](https://no-color.org/). |
|
||||
| `FORCE_COLOR` | If `FORCE_COLOR=1`, then ANSI color output is force enabled, even if `NO_COLOR` is set. |
|
||||
| `BUN_CONFIG_MAX_HTTP_REQUESTS` | Control the maximum number of concurrent HTTP requests sent by fetch and `bun install`. Defaults to `256`. If you are running into rate limits or connection issues, you can reduce this number. |
|
||||
| `BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD` | If `BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD=true`, then `bun --watch` will not clear the console on reload |
|
||||
| `DO_NOT_TRACK` | Disable uploading crash reports to `bun.report` on crash. On macOS & Windows, crash report uploads are enabled by default. Otherwise, telemetry is not sent yet as of May 21st, 2024, but we are planning to add telemetry in the coming weeks. If `DO_NOT_TRACK=1`, then auto-uploading crash reports and telemetry are both [disabled](https://do-not-track.dev/). |
|
||||
| `BUN_OPTIONS` | Prepends command-line arguments to any Bun execution. For example, `BUN_OPTIONS="--hot"` makes `bun run dev` behave like `bun --hot run dev` |
|
||||
|
||||
## Runtime transpiler caching
|
||||
|
||||
For files larger than 50 KB, Bun caches transpiled output into `$BUN_RUNTIME_TRANSPILER_CACHE_PATH` or the platform-specific cache directory. This makes CLIs using Bun load faster.
|
||||
|
||||
This transpiler cache is global and shared across all projects. It is safe to delete the cache at any time. It is a content-addressable cache, so it will never contain duplicate entries. It is also safe to delete the cache while a Bun process is running.
|
||||
|
||||
It is recommended to disable this cache when using ephemeral filesystems like Docker. Bun's Docker images automatically disable this cache.
|
||||
|
||||
### Disable the runtime transpiler cache
|
||||
|
||||
To disable the runtime transpiler cache, set `BUN_RUNTIME_TRANSPILER_CACHE_PATH` to an empty string or the string `"0"`.
|
||||
|
||||
```sh
|
||||
BUN_RUNTIME_TRANSPILER_CACHE_PATH=0 bun run dev
|
||||
```
|
||||
|
||||
### What does it cache?
|
||||
|
||||
It caches:
|
||||
|
||||
- The transpiled output of source files larger than 50 KB.
|
||||
- The sourcemap for the transpiled output of the file
|
||||
|
||||
The file extension `.pile` is used for these cached files.
|
||||
565
docs/runtime/ffi.mdx
Normal file
565
docs/runtime/ffi.mdx
Normal file
@@ -0,0 +1,565 @@
|
||||
---
|
||||
title: FFI
|
||||
description: Use Bun's FFI module to efficiently call native libraries from JavaScript
|
||||
---
|
||||
|
||||
<Warning>
|
||||
`bun:ffi` is **experimental**, with known bugs and limitations, and should not be relied on in production. The most
|
||||
stable way to interact with native code from Bun is to write a [Node-API module](/runtime/node-api).
|
||||
</Warning>
|
||||
|
||||
Use the built-in `bun:ffi` module to efficiently call native libraries from JavaScript. It works with languages that support the C ABI (Zig, Rust, C/C++, C#, Nim, Kotlin, etc).
|
||||
|
||||
---
|
||||
|
||||
## dlopen usage (`bun:ffi`)
|
||||
|
||||
To print the version number of `sqlite3`:
|
||||
|
||||
```ts
|
||||
import { dlopen, FFIType, suffix } from "bun:ffi";
|
||||
|
||||
// `suffix` is either "dylib", "so", or "dll" depending on the platform
|
||||
// you don't have to use "suffix", it's just there for convenience
|
||||
const path = `libsqlite3.${suffix}`;
|
||||
|
||||
const {
|
||||
symbols: {
|
||||
sqlite3_libversion, // the function to call
|
||||
},
|
||||
} = dlopen(
|
||||
path, // a library name or file path
|
||||
{
|
||||
sqlite3_libversion: {
|
||||
// no arguments, returns a string
|
||||
args: [],
|
||||
returns: FFIType.cstring,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
console.log(`SQLite 3 version: ${sqlite3_libversion()}`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance
|
||||
|
||||
According to [our benchmark](https://github.com/oven-sh/bun/tree/main/bench/ffi), `bun:ffi` is roughly 2-6x faster than Node.js FFI via `Node-API`.
|
||||
|
||||
<Image src="/images/ffi.png" height="400" />
|
||||
|
||||
Bun generates & just-in-time compiles C bindings that efficiently convert values between JavaScript types and native types. To compile C, Bun embeds [TinyCC](https://github.com/TinyCC/tinycc), a small and fast C compiler.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Zig
|
||||
|
||||
```zig add.zig icon="file-code"
|
||||
pub export fn add(a: i32, b: i32) i32 {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
To compile:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
zig build-lib add.zig -dynamic -OReleaseFast
|
||||
```
|
||||
|
||||
Pass a path to the shared library and a map of symbols to import into `dlopen`:
|
||||
|
||||
```ts
|
||||
import { dlopen, FFIType, suffix } from "bun:ffi";
|
||||
const { i32 } = FFIType;
|
||||
|
||||
const path = `libadd.${suffix}`;
|
||||
|
||||
const lib = dlopen(path, {
|
||||
add: {
|
||||
args: [i32, i32],
|
||||
returns: i32,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(lib.symbols.add(1, 2));
|
||||
```
|
||||
|
||||
### Rust
|
||||
|
||||
```rust
|
||||
// add.rs
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add(a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
```
|
||||
|
||||
To compile:
|
||||
|
||||
```bash
|
||||
rustc --crate-type cdylib add.rs
|
||||
```
|
||||
|
||||
### C++
|
||||
|
||||
```c
|
||||
#include <cstdint>
|
||||
|
||||
extern "C" int32_t add(int32_t a, int32_t b) {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
To compile:
|
||||
|
||||
```bash
|
||||
zig build-lib add.cpp -dynamic -lc -lc++
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FFI types
|
||||
|
||||
The following `FFIType` values are supported.
|
||||
|
||||
| `FFIType` | C Type | Aliases |
|
||||
| ---------- | -------------- | --------------------------- |
|
||||
| buffer | `char*` | |
|
||||
| cstring | `char*` | |
|
||||
| function | `(void*)(*)()` | `fn`, `callback` |
|
||||
| ptr | `void*` | `pointer`, `void*`, `char*` |
|
||||
| i8 | `int8_t` | `int8_t` |
|
||||
| i16 | `int16_t` | `int16_t` |
|
||||
| i32 | `int32_t` | `int32_t`, `int` |
|
||||
| i64 | `int64_t` | `int64_t` |
|
||||
| i64_fast | `int64_t` | |
|
||||
| u8 | `uint8_t` | `uint8_t` |
|
||||
| u16 | `uint16_t` | `uint16_t` |
|
||||
| u32 | `uint32_t` | `uint32_t` |
|
||||
| u64 | `uint64_t` | `uint64_t` |
|
||||
| u64_fast | `uint64_t` | |
|
||||
| f32 | `float` | `float` |
|
||||
| f64 | `double` | `double` |
|
||||
| bool | `bool` | |
|
||||
| char | `char` | |
|
||||
| napi_env | `napi_env` | |
|
||||
| napi_value | `napi_value` | |
|
||||
|
||||
Note: `buffer` arguments must be a `TypedArray` or `DataView`.
|
||||
|
||||
---
|
||||
|
||||
## Strings
|
||||
|
||||
JavaScript strings and C-like strings are different, and that complicates using strings with native libraries.
|
||||
|
||||
<Accordion title="How are JavaScript strings and C strings different?">
|
||||
JavaScript strings:
|
||||
|
||||
- UTF16 (2 bytes per letter) or potentially latin1, depending on the JavaScript engine & what characters are used
|
||||
- `length` stored separately
|
||||
- Immutable
|
||||
|
||||
C strings:
|
||||
|
||||
- UTF8 (1 byte per letter), usually
|
||||
- The length is not stored. Instead, the string is null-terminated which means the length is the index of the first `\0` it finds
|
||||
- Mutable
|
||||
|
||||
</Accordion>
|
||||
|
||||
To solve this, `bun:ffi` exports `CString` which extends JavaScript's built-in `String` to support null-terminated strings and add a few extras:
|
||||
|
||||
```ts
|
||||
class CString extends String {
|
||||
/**
|
||||
* Given a `ptr`, this will automatically search for the closing `\0` character and transcode from UTF-8 to UTF-16 if necessary.
|
||||
*/
|
||||
constructor(ptr: number, byteOffset?: number, byteLength?: number): string;
|
||||
|
||||
/**
|
||||
* The ptr to the C string
|
||||
*
|
||||
* This `CString` instance is a clone of the string, so it
|
||||
* is safe to continue using this instance after the `ptr` has been
|
||||
* freed.
|
||||
*/
|
||||
ptr: number;
|
||||
byteOffset?: number;
|
||||
byteLength?: number;
|
||||
}
|
||||
```
|
||||
|
||||
To convert from a null-terminated string pointer to a JavaScript string:
|
||||
|
||||
```ts
|
||||
const myString = new CString(ptr);
|
||||
```
|
||||
|
||||
To convert from a pointer with a known length to a JavaScript string:
|
||||
|
||||
```ts
|
||||
const myString = new CString(ptr, 0, byteLength);
|
||||
```
|
||||
|
||||
The `new CString()` constructor clones the C string, so it is safe to continue using `myString` after `ptr` has been freed.
|
||||
|
||||
```ts
|
||||
my_library_free(myString.ptr);
|
||||
|
||||
// this is safe because myString is a clone
|
||||
console.log(myString);
|
||||
```
|
||||
|
||||
When used in `returns`, `FFIType.cstring` coerces the pointer to a JavaScript `string`. When used in `args`, `FFIType.cstring` is identical to `ptr`.
|
||||
|
||||
---
|
||||
|
||||
## Function pointers
|
||||
|
||||
<Note>Async functions are not yet supported</Note>
|
||||
|
||||
To call a function pointer from JavaScript, use `CFunction`. This is useful if using Node-API (napi) with Bun, and you've already loaded some symbols.
|
||||
|
||||
```ts
|
||||
import { CFunction } from "bun:ffi";
|
||||
|
||||
let myNativeLibraryGetVersion = /* somehow, you got this pointer */
|
||||
|
||||
const getVersion = new CFunction({
|
||||
returns: "cstring",
|
||||
args: [],
|
||||
ptr: myNativeLibraryGetVersion,
|
||||
});
|
||||
getVersion();
|
||||
```
|
||||
|
||||
If you have multiple function pointers, you can define them all at once with `linkSymbols`:
|
||||
|
||||
```ts
|
||||
import { linkSymbols } from "bun:ffi";
|
||||
|
||||
// getVersionPtrs defined elsewhere
|
||||
const [majorPtr, minorPtr, patchPtr] = getVersionPtrs();
|
||||
|
||||
const lib = linkSymbols({
|
||||
// Unlike with dlopen(), the names here can be whatever you want
|
||||
getMajor: {
|
||||
returns: "cstring",
|
||||
args: [],
|
||||
|
||||
// Since this doesn't use dlsym(), you have to provide a valid ptr
|
||||
// That ptr could be a number or a bigint
|
||||
// An invalid pointer will crash your program.
|
||||
ptr: majorPtr,
|
||||
},
|
||||
getMinor: {
|
||||
returns: "cstring",
|
||||
args: [],
|
||||
ptr: minorPtr,
|
||||
},
|
||||
getPatch: {
|
||||
returns: "cstring",
|
||||
args: [],
|
||||
ptr: patchPtr,
|
||||
},
|
||||
});
|
||||
|
||||
const [major, minor, patch] = [lib.symbols.getMajor(), lib.symbols.getMinor(), lib.symbols.getPatch()];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Callbacks
|
||||
|
||||
Use `JSCallback` to create JavaScript callback functions that can be passed to C/FFI functions. The C/FFI function can call into the JavaScript/TypeScript code. This is useful for asynchronous code or whenever you want to call into JavaScript code from C.
|
||||
|
||||
```ts
|
||||
import { dlopen, JSCallback, ptr, CString } from "bun:ffi";
|
||||
|
||||
const {
|
||||
symbols: { search },
|
||||
close,
|
||||
} = dlopen("libmylib", {
|
||||
search: {
|
||||
returns: "usize",
|
||||
args: ["cstring", "callback"],
|
||||
},
|
||||
});
|
||||
|
||||
const searchIterator = new JSCallback((ptr, length) => /hello/.test(new CString(ptr, length)), {
|
||||
returns: "bool",
|
||||
args: ["ptr", "usize"],
|
||||
});
|
||||
|
||||
const str = Buffer.from("wwutwutwutwutwutwutwutwutwutwutut\0", "utf8");
|
||||
if (search(ptr(str), searchIterator)) {
|
||||
// found a match!
|
||||
}
|
||||
|
||||
// Sometime later:
|
||||
setTimeout(() => {
|
||||
searchIterator.close();
|
||||
close();
|
||||
}, 5000);
|
||||
```
|
||||
|
||||
When you're done with a JSCallback, you should call `close()` to free the memory.
|
||||
|
||||
### Experimental thread-safe callbacks
|
||||
|
||||
`JSCallback` has experimental support for thread-safe callbacks. This will be needed if you pass a callback function into a different thread from its instantiation context. You can enable it with the optional `threadsafe` parameter.
|
||||
|
||||
Currently, thread-safe callbacks work best when run from another thread that is running JavaScript code, i.e. a [`Worker`](/runtime/workers). A future version of Bun will enable them to be called from any thread (such as new threads spawned by your native library that Bun is not aware of).
|
||||
|
||||
```ts
|
||||
const searchIterator = new JSCallback((ptr, length) => /hello/.test(new CString(ptr, length)), {
|
||||
returns: "bool",
|
||||
args: ["ptr", "usize"],
|
||||
threadsafe: true, // Optional. Defaults to `false`
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
**⚡️ Performance tip** — For a slight performance boost, directly pass `JSCallback.prototype.ptr` instead of the `JSCallback` object:
|
||||
|
||||
```ts
|
||||
const onResolve = new JSCallback(arg => arg === 42, {
|
||||
returns: "bool",
|
||||
args: ["i32"],
|
||||
});
|
||||
const setOnResolve = new CFunction({
|
||||
returns: "bool",
|
||||
args: ["function"],
|
||||
ptr: myNativeLibrarySetOnResolve,
|
||||
});
|
||||
|
||||
// This code runs slightly faster:
|
||||
setOnResolve(onResolve.ptr);
|
||||
|
||||
// Compared to this:
|
||||
setOnResolve(onResolve);
|
||||
```
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Pointers
|
||||
|
||||
Bun represents [pointers](<https://en.wikipedia.org/wiki/Pointer_(computer_programming)>) as a `number` in JavaScript.
|
||||
|
||||
<Accordion title="How does a 64 bit pointer fit in a JavaScript number?">
|
||||
|
||||
64-bit processors support up to [52 bits of addressable space](https://en.wikipedia.org/wiki/64-bit_computing#Limits_of_processors). [JavaScript numbers](https://en.wikipedia.org/wiki/Double-precision_floating-point_format#IEEE_754_double-precision_binary_floating-point_format:_binary64) support 53 bits of usable space, so that leaves us with about 11 bits of extra space.
|
||||
|
||||
**Why not `BigInt`?** `BigInt` is slower. JavaScript engines allocate a separate `BigInt` which means they can't fit into a regular JavaScript value. If you pass a `BigInt` to a function, it will be converted to a `number`
|
||||
|
||||
</Accordion>
|
||||
|
||||
To convert from a `TypedArray` to a pointer:
|
||||
|
||||
```ts
|
||||
import { ptr } from "bun:ffi";
|
||||
let myTypedArray = new Uint8Array(32);
|
||||
const myPtr = ptr(myTypedArray);
|
||||
```
|
||||
|
||||
To convert from a pointer to an `ArrayBuffer`:
|
||||
|
||||
```ts
|
||||
import { ptr, toArrayBuffer } from "bun:ffi";
|
||||
let myTypedArray = new Uint8Array(32);
|
||||
const myPtr = ptr(myTypedArray);
|
||||
|
||||
// toArrayBuffer accepts a `byteOffset` and `byteLength`
|
||||
// if `byteLength` is not provided, it is assumed to be a null-terminated pointer
|
||||
myTypedArray = new Uint8Array(toArrayBuffer(myPtr, 0, 32), 0, 32);
|
||||
```
|
||||
|
||||
To read data from a pointer, you have two options. For long-lived pointers, use a `DataView`:
|
||||
|
||||
```ts
|
||||
import { toArrayBuffer } from "bun:ffi";
|
||||
let myDataView = new DataView(toArrayBuffer(myPtr, 0, 32));
|
||||
|
||||
console.log(
|
||||
myDataView.getUint8(0, true),
|
||||
myDataView.getUint8(1, true),
|
||||
myDataView.getUint8(2, true),
|
||||
myDataView.getUint8(3, true),
|
||||
);
|
||||
```
|
||||
|
||||
For short-lived pointers, use `read`:
|
||||
|
||||
```ts
|
||||
import { read } from "bun:ffi";
|
||||
|
||||
console.log(
|
||||
// ptr, byteOffset
|
||||
read.u8(myPtr, 0),
|
||||
read.u8(myPtr, 1),
|
||||
read.u8(myPtr, 2),
|
||||
read.u8(myPtr, 3),
|
||||
);
|
||||
```
|
||||
|
||||
The `read` function behaves similarly to `DataView`, but it's usually faster because it doesn't need to create a `DataView` or `ArrayBuffer`.
|
||||
|
||||
| `FFIType` | `read` function |
|
||||
| --------- | --------------- |
|
||||
| ptr | `read.ptr` |
|
||||
| i8 | `read.i8` |
|
||||
| i16 | `read.i16` |
|
||||
| i32 | `read.i32` |
|
||||
| i64 | `read.i64` |
|
||||
| u8 | `read.u8` |
|
||||
| u16 | `read.u16` |
|
||||
| u32 | `read.u32` |
|
||||
| u64 | `read.u64` |
|
||||
| f32 | `read.f32` |
|
||||
| f64 | `read.f64` |
|
||||
|
||||
### Memory management
|
||||
|
||||
`bun:ffi` does not manage memory for you. You must free the memory when you're done with it.
|
||||
|
||||
#### From JavaScript
|
||||
|
||||
If you want to track when a `TypedArray` is no longer in use from JavaScript, you can use a [FinalizationRegistry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry).
|
||||
|
||||
#### From C, Rust, Zig, etc
|
||||
|
||||
If you want to track when a `TypedArray` is no longer in use from C or FFI, you can pass a callback and an optional context pointer to `toArrayBuffer` or `toBuffer`. This function is called at some point later, once the garbage collector frees the underlying `ArrayBuffer` JavaScript object.
|
||||
|
||||
The expected signature is the same as in [JavaScriptCore's C API](https://developer.apple.com/documentation/javascriptcore/jstypedarraybytesdeallocator?language=objc):
|
||||
|
||||
```c
|
||||
typedef void (*JSTypedArrayBytesDeallocator)(void *bytes, void *deallocatorContext);
|
||||
```
|
||||
|
||||
```ts
|
||||
import { toArrayBuffer } from "bun:ffi";
|
||||
|
||||
// with a deallocatorContext:
|
||||
toArrayBuffer(
|
||||
bytes,
|
||||
byteOffset,
|
||||
|
||||
byteLength,
|
||||
|
||||
// this is an optional pointer to a callback
|
||||
deallocatorContext,
|
||||
|
||||
// this is a pointer to a function
|
||||
jsTypedArrayBytesDeallocator,
|
||||
);
|
||||
|
||||
// without a deallocatorContext:
|
||||
toArrayBuffer(
|
||||
bytes,
|
||||
byteOffset,
|
||||
|
||||
byteLength,
|
||||
|
||||
// this is a pointer to a function
|
||||
jsTypedArrayBytesDeallocator,
|
||||
);
|
||||
```
|
||||
|
||||
### Memory safety
|
||||
|
||||
Using raw pointers outside of FFI is extremely not recommended. A future version of Bun may add a CLI flag to disable `bun:ffi`.
|
||||
|
||||
### Pointer alignment
|
||||
|
||||
If an API expects a pointer sized to something other than `char` or `u8`, make sure the `TypedArray` is also that size. A `u64*` is not exactly the same as `[8]u8*` due to alignment.
|
||||
|
||||
### Passing a pointer
|
||||
|
||||
Where FFI functions expect a pointer, pass a `TypedArray` of equivalent size:
|
||||
|
||||
```ts
|
||||
import { dlopen, FFIType } from "bun:ffi";
|
||||
|
||||
const {
|
||||
symbols: { encode_png },
|
||||
} = dlopen(myLibraryPath, {
|
||||
encode_png: {
|
||||
// FFIType's can be specified as strings too
|
||||
args: ["ptr", "u32", "u32"],
|
||||
returns: FFIType.ptr,
|
||||
},
|
||||
});
|
||||
|
||||
const pixels = new Uint8ClampedArray(128 * 128 * 4);
|
||||
pixels.fill(254);
|
||||
pixels.subarray(0, 32 * 32 * 2).fill(0);
|
||||
|
||||
const out = encode_png(
|
||||
// pixels will be passed as a pointer
|
||||
pixels,
|
||||
|
||||
128,
|
||||
128,
|
||||
);
|
||||
```
|
||||
|
||||
The [auto-generated wrapper](https://github.com/oven-sh/bun/blob/6a65631cbdcae75bfa1e64323a6ad613a922cd1a/src/bun.js/ffi.exports.js#L180-L182) converts the pointer to a `TypedArray`.
|
||||
|
||||
<Accordion title="Hardmode">
|
||||
|
||||
If you don't want the automatic conversion or you want a pointer to a specific byte offset within the `TypedArray`, you can also directly get the pointer to the `TypedArray`:
|
||||
|
||||
```ts
|
||||
import { dlopen, FFIType, ptr } from "bun:ffi";
|
||||
|
||||
const {
|
||||
symbols: { encode_png },
|
||||
} = dlopen(myLibraryPath, {
|
||||
encode_png: {
|
||||
// FFIType's can be specified as strings too
|
||||
args: ["ptr", "u32", "u32"],
|
||||
returns: FFIType.ptr,
|
||||
},
|
||||
});
|
||||
|
||||
const pixels = new Uint8ClampedArray(128 * 128 * 4);
|
||||
pixels.fill(254);
|
||||
|
||||
// this returns a number! not a BigInt!
|
||||
const myPtr = ptr(pixels);
|
||||
|
||||
const out = encode_png(
|
||||
myPtr,
|
||||
|
||||
// dimensions:
|
||||
128,
|
||||
128,
|
||||
);
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
### Reading pointers
|
||||
|
||||
```ts
|
||||
const out = encode_png(
|
||||
// pixels will be passed as a pointer
|
||||
pixels,
|
||||
|
||||
// dimensions:
|
||||
128,
|
||||
128,
|
||||
);
|
||||
|
||||
// assuming it is 0-terminated, it can be read like this:
|
||||
let png = new Uint8Array(toArrayBuffer(out));
|
||||
|
||||
// save it to disk:
|
||||
await Bun.write("out.png", png);
|
||||
```
|
||||
306
docs/runtime/file-io.mdx
Normal file
306
docs/runtime/file-io.mdx
Normal file
@@ -0,0 +1,306 @@
|
||||
---
|
||||
title: File I/O
|
||||
description: Bun provides a set of optimized APIs for reading and writing files.
|
||||
---
|
||||
|
||||
<Note>
|
||||
|
||||
The `Bun.file` and `Bun.write` APIs documented on this page are heavily optimized and represent the recommended way to perform file-system tasks using Bun. For operations that are not yet available with `Bun.file`, such as `mkdir` or `readdir`, you can use Bun's [nearly complete](/runtime/nodejs-compat#node-fs) implementation of the [`node:fs`](https://nodejs.org/api/fs.html) module.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Reading files (`Bun.file()`)
|
||||
|
||||
`Bun.file(path): BunFile`
|
||||
|
||||
Create a `BunFile` instance with the `Bun.file(path)` function. A `BunFile` represents a lazily-loaded file; initializing it does not actually read the file from disk.
|
||||
|
||||
```ts
|
||||
const foo = Bun.file("foo.txt"); // relative to cwd
|
||||
foo.size; // number of bytes
|
||||
foo.type; // MIME type
|
||||
```
|
||||
|
||||
The reference conforms to the [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) interface, so the contents can be read in various formats.
|
||||
|
||||
```ts
|
||||
const foo = Bun.file("foo.txt");
|
||||
|
||||
await foo.text(); // contents as a string
|
||||
await foo.json(); // contents as a JSON object
|
||||
await foo.stream(); // contents as ReadableStream
|
||||
await foo.arrayBuffer(); // contents as ArrayBuffer
|
||||
await foo.bytes(); // contents as Uint8Array
|
||||
```
|
||||
|
||||
File references can also be created using numerical [file descriptors](https://en.wikipedia.org/wiki/File_descriptor) or `file://` URLs.
|
||||
|
||||
```ts
|
||||
Bun.file(1234);
|
||||
Bun.file(new URL(import.meta.url)); // reference to the current file
|
||||
```
|
||||
|
||||
A `BunFile` can point to a location on disk where a file does not exist.
|
||||
|
||||
```ts
|
||||
const notreal = Bun.file("notreal.txt");
|
||||
notreal.size; // 0
|
||||
notreal.type; // "text/plain;charset=utf-8"
|
||||
const exists = await notreal.exists(); // false
|
||||
```
|
||||
|
||||
The default MIME type is `text/plain;charset=utf-8`, but it can be overridden by passing a second argument to `Bun.file`.
|
||||
|
||||
```ts
|
||||
const notreal = Bun.file("notreal.json", { type: "application/json" });
|
||||
notreal.type; // => "application/json;charset=utf-8"
|
||||
```
|
||||
|
||||
For convenience, Bun exposes `stdin`, `stdout` and `stderr` as instances of `BunFile`.
|
||||
|
||||
```ts
|
||||
Bun.stdin; // readonly
|
||||
Bun.stdout;
|
||||
Bun.stderr;
|
||||
```
|
||||
|
||||
### Deleting files (`file.delete()`)
|
||||
|
||||
You can delete a file by calling the `.delete()` function.
|
||||
|
||||
```ts
|
||||
await Bun.file("logs.json").delete();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Writing files (`Bun.write()`)
|
||||
|
||||
`Bun.write(destination, data): Promise<number>`
|
||||
|
||||
The `Bun.write` function is a multi-tool for writing payloads of all kinds to disk.
|
||||
|
||||
The first argument is the `destination` which can have any of the following types:
|
||||
|
||||
- `string`: A path to a location on the file system. Use the `"path"` module to manipulate paths.
|
||||
- `URL`: A `file://` descriptor.
|
||||
- `BunFile`: A file reference.
|
||||
|
||||
The second argument is the data to be written. It can be any of the following:
|
||||
|
||||
- `string`
|
||||
- `Blob` (including `BunFile`)
|
||||
- `ArrayBuffer` or `SharedArrayBuffer`
|
||||
- `TypedArray` (`Uint8Array`, et. al.)
|
||||
- `Response`
|
||||
|
||||
All possible permutations are handled using the fastest available system calls on the current platform.
|
||||
|
||||
<Accordion title="See syscalls">
|
||||
|
||||
| Output | Input | System call | Platform |
|
||||
| -------------------- | -------------- | ----------------------------- | -------- |
|
||||
| file | file | copy_file_range | Linux |
|
||||
| file | pipe | sendfile | Linux |
|
||||
| pipe | pipe | splice | Linux |
|
||||
| terminal | file | sendfile | Linux |
|
||||
| terminal | terminal | sendfile | Linux |
|
||||
| socket | file or pipe | sendfile (if http, not https) | Linux |
|
||||
| file (doesn't exist) | file (path) | clonefile | macOS |
|
||||
| file (exists) | file | fcopyfile | macOS |
|
||||
| file | Blob or string | write | macOS |
|
||||
| file | Blob or string | write | Linux |
|
||||
|
||||
</Accordion>
|
||||
|
||||
To write a string to disk:
|
||||
|
||||
```ts
|
||||
const data = `It was the best of times, it was the worst of times.`;
|
||||
await Bun.write("output.txt", data);
|
||||
```
|
||||
|
||||
To copy a file to another location on disk:
|
||||
|
||||
```ts
|
||||
const input = Bun.file("input.txt");
|
||||
const output = Bun.file("output.txt"); // doesn't exist yet!
|
||||
await Bun.write(output, input);
|
||||
```
|
||||
|
||||
To write a byte array to disk:
|
||||
|
||||
```ts
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode("datadatadata"); // Uint8Array
|
||||
await Bun.write("output.txt", data);
|
||||
```
|
||||
|
||||
To write a file to `stdout`:
|
||||
|
||||
```ts
|
||||
const input = Bun.file("input.txt");
|
||||
await Bun.write(Bun.stdout, input);
|
||||
```
|
||||
|
||||
To write the body of an HTTP response to disk:
|
||||
|
||||
```ts
|
||||
const response = await fetch("https://bun.com");
|
||||
await Bun.write("index.html", response);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Incremental writing with `FileSink`
|
||||
|
||||
Bun provides a native incremental file writing API called `FileSink`. To retrieve a `FileSink` instance from a `BunFile`:
|
||||
|
||||
```ts
|
||||
const file = Bun.file("output.txt");
|
||||
const writer = file.writer();
|
||||
```
|
||||
|
||||
To incrementally write to the file, call `.write()`.
|
||||
|
||||
```ts
|
||||
const file = Bun.file("output.txt");
|
||||
const writer = file.writer();
|
||||
|
||||
writer.write("it was the best of times\n");
|
||||
writer.write("it was the worst of times\n");
|
||||
```
|
||||
|
||||
These chunks will be buffered internally. To flush the buffer to disk, use `.flush()`. This returns the number of flushed bytes.
|
||||
|
||||
```ts
|
||||
writer.flush(); // write buffer to disk
|
||||
```
|
||||
|
||||
The buffer will also auto-flush when the `FileSink`'s _high water mark_ is reached; that is, when its internal buffer is full. This value can be configured.
|
||||
|
||||
```ts
|
||||
const file = Bun.file("output.txt");
|
||||
const writer = file.writer({ highWaterMark: 1024 * 1024 }); // 1MB
|
||||
```
|
||||
|
||||
To flush the buffer and close the file:
|
||||
|
||||
```ts
|
||||
writer.end();
|
||||
```
|
||||
|
||||
Note that, by default, the `bun` process will stay alive until this `FileSink` is explicitly closed with `.end()`. To opt out of this behavior, you can "unref" the instance.
|
||||
|
||||
```ts
|
||||
writer.unref();
|
||||
|
||||
// to "re-ref" it later
|
||||
writer.ref();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Directories
|
||||
|
||||
Bun's implementation of `node:fs` is fast, and we haven't implemented a Bun-specific API for reading directories just yet. For now, you should use `node:fs` for working with directories in Bun.
|
||||
|
||||
### Reading directories (readdir)
|
||||
|
||||
To read a directory in Bun, use `readdir` from `node:fs`.
|
||||
|
||||
```ts
|
||||
import { readdir } from "node:fs/promises";
|
||||
|
||||
// read all the files in the current directory
|
||||
const files = await readdir(import.meta.dir);
|
||||
```
|
||||
|
||||
#### Reading directories recursively
|
||||
|
||||
To recursively read a directory in Bun, use `readdir` with `recursive: true`.
|
||||
|
||||
```ts
|
||||
import { readdir } from "node:fs/promises";
|
||||
|
||||
// read all the files in the current directory, recursively
|
||||
const files = await readdir("../", { recursive: true });
|
||||
```
|
||||
|
||||
### Creating directories (mkdir)
|
||||
|
||||
To recursively create a directory, use `mkdir` in `node:fs`:
|
||||
|
||||
```ts
|
||||
import { mkdir } from "node:fs/promises";
|
||||
|
||||
await mkdir("path/to/dir", { recursive: true });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benchmarks
|
||||
|
||||
The following is a 3-line implementation of the Linux `cat` command.
|
||||
|
||||
```ts cat.ts icon="/icons/typescript.svg"
|
||||
// Usage
|
||||
// bun ./cat.ts ./path-to-file
|
||||
|
||||
import { resolve } from "path";
|
||||
|
||||
const path = resolve(process.argv.at(-1));
|
||||
await Bun.write(Bun.stdout, Bun.file(path));
|
||||
```
|
||||
|
||||
To run the file:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun ./cat.ts ./path-to-file
|
||||
```
|
||||
|
||||
It runs 2x faster than GNU `cat` for large files on Linux.
|
||||
|
||||
<Frame></Frame>
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
```ts
|
||||
interface Bun {
|
||||
stdin: BunFile;
|
||||
stdout: BunFile;
|
||||
stderr: BunFile;
|
||||
|
||||
file(path: string | number | URL, options?: { type?: string }): BunFile;
|
||||
|
||||
write(
|
||||
destination: string | number | BunFile | URL,
|
||||
input: string | Blob | ArrayBuffer | SharedArrayBuffer | TypedArray | Response,
|
||||
): Promise<number>;
|
||||
}
|
||||
|
||||
interface BunFile {
|
||||
readonly size: number;
|
||||
readonly type: string;
|
||||
|
||||
text(): Promise<string>;
|
||||
stream(): ReadableStream;
|
||||
arrayBuffer(): Promise<ArrayBuffer>;
|
||||
json(): Promise<any>;
|
||||
writer(params: { highWaterMark?: number }): FileSink;
|
||||
exists(): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface FileSink {
|
||||
write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
|
||||
flush(): number | Promise<number>;
|
||||
end(error?: Error): number | Promise<number>;
|
||||
start(options?: { highWaterMark?: number }): void;
|
||||
ref(): void;
|
||||
unref(): void;
|
||||
}
|
||||
```
|
||||
118
docs/runtime/file-system-router.mdx
Normal file
118
docs/runtime/file-system-router.mdx
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: File System Router
|
||||
description: Bun provides a fast API for resolving routes against file-system paths
|
||||
---
|
||||
|
||||
This API is primarily intended for library authors. At the moment only Next.js-style file-system routing is supported, but other styles may be added in the future.
|
||||
|
||||
## Next.js-style
|
||||
|
||||
The `FileSystemRouter` class can resolve routes against a `pages` directory. (The Next.js 13 `app` directory is not yet supported.) Consider the following `pages` directory:
|
||||
|
||||
```txt
|
||||
pages
|
||||
├── index.tsx
|
||||
├── settings.tsx
|
||||
├── blog
|
||||
│ ├── [slug].tsx
|
||||
│ └── index.tsx
|
||||
└── [[...catchall]].tsx
|
||||
```
|
||||
|
||||
The `FileSystemRouter` can be used to resolve routes against this directory:
|
||||
|
||||
```ts router.ts
|
||||
const router = new Bun.FileSystemRouter({
|
||||
style: "nextjs",
|
||||
dir: "./pages",
|
||||
origin: "https://mydomain.com",
|
||||
assetPrefix: "_next/static/"
|
||||
});
|
||||
|
||||
router.match("/");
|
||||
|
||||
// =>
|
||||
{
|
||||
filePath: "/path/to/pages/index.tsx",
|
||||
kind: "exact",
|
||||
name: "/",
|
||||
pathname: "/",
|
||||
src: "https://mydomain.com/_next/static/pages/index.tsx"
|
||||
}
|
||||
```
|
||||
|
||||
Query parameters will be parsed and returned in the `query` property.
|
||||
|
||||
```ts
|
||||
router.match("/settings?foo=bar");
|
||||
|
||||
// =>
|
||||
{
|
||||
filePath: "/Users/colinmcd94/Documents/bun/fun/pages/settings.tsx",
|
||||
kind: "dynamic",
|
||||
name: "/settings",
|
||||
pathname: "/settings?foo=bar",
|
||||
src: "https://mydomain.com/_next/static/pages/settings.tsx",
|
||||
query: {
|
||||
foo: "bar"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The router will automatically parse URL parameters and return them in the `params` property:
|
||||
|
||||
```ts
|
||||
router.match("/blog/my-cool-post");
|
||||
|
||||
// =>
|
||||
{
|
||||
filePath: "/Users/colinmcd94/Documents/bun/fun/pages/blog/[slug].tsx",
|
||||
kind: "dynamic",
|
||||
name: "/blog/[slug]",
|
||||
pathname: "/blog/my-cool-post",
|
||||
src: "https://mydomain.com/_next/static/pages/blog/[slug].tsx",
|
||||
params: {
|
||||
slug: "my-cool-post"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `.match()` method also accepts `Request` and `Response` objects. The `url` property will be used to resolve the route.
|
||||
|
||||
```ts
|
||||
router.match(new Request("https://example.com/blog/my-cool-post"));
|
||||
```
|
||||
|
||||
The router will read the directory contents on initialization. To re-scan the files, use the `.reload()` method.
|
||||
|
||||
```ts
|
||||
router.reload();
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
```ts
|
||||
interface Bun {
|
||||
class FileSystemRouter {
|
||||
constructor(params: {
|
||||
dir: string;
|
||||
style: "nextjs";
|
||||
origin?: string;
|
||||
assetPrefix?: string;
|
||||
fileExtensions?: string[];
|
||||
});
|
||||
|
||||
reload(): void;
|
||||
|
||||
match(path: string | Request | Response): {
|
||||
filePath: string;
|
||||
kind: "exact" | "catch-all" | "optional-catch-all" | "dynamic";
|
||||
name: string;
|
||||
pathname: string;
|
||||
src: string;
|
||||
params?: Record<string, string>;
|
||||
query?: Record<string, string>;
|
||||
} | null
|
||||
}
|
||||
}
|
||||
```
|
||||
435
docs/runtime/file-types.mdx
Normal file
435
docs/runtime/file-types.mdx
Normal file
@@ -0,0 +1,435 @@
|
||||
---
|
||||
title: "File Types"
|
||||
description: "File types and loaders supported by Bun's bundler and runtime"
|
||||
---
|
||||
|
||||
The Bun bundler implements a set of default loaders out of the box. As a rule of thumb, the bundler and the runtime both support the same set of file types out of the box.
|
||||
|
||||
`.js` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` `.jsx` `.css` `.json` `.jsonc` `.toml` `.yaml` `.yml` `.txt` `.wasm` `.node` `.html` `.sh`
|
||||
|
||||
Bun uses the file extension to determine which built-in _loader_ should be used to parse the file. Every loader has a name, such as `js`, `tsx`, or `json`. These names are used when building [plugins](/bundler/plugins) that extend Bun with custom loaders.
|
||||
|
||||
You can explicitly specify which loader to use using the `'type'` import attribute.
|
||||
|
||||
```ts
|
||||
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**. Default for `.cjs` and `.mjs`.
|
||||
|
||||
Parses the code and applies a set of default transforms like dead-code elimination and tree shaking. Note that Bun does not attempt to down-convert syntax at the moment.
|
||||
|
||||
### `jsx`
|
||||
|
||||
**JavaScript + JSX.**. Default for `.js` and `.jsx`.
|
||||
|
||||
Same as the `js` loader, but JSX syntax is supported. By default, JSX is down-converted to plain JavaScript; the details of how this is done depends on the `jsx*` compiler options in your `tsconfig.json`. Refer to the TypeScript documentation [on JSX](https://www.typescriptlang.org/docs/handbook/jsx.html) for more information.
|
||||
|
||||
### `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.
|
||||
|
||||
```ts
|
||||
import pkg from "./package.json";
|
||||
pkg.name; // => "my-package"
|
||||
```
|
||||
|
||||
During bundling, the parsed JSON is inlined into the bundle as a JavaScript object.
|
||||
|
||||
```ts
|
||||
var pkg = {
|
||||
name: "my-package",
|
||||
// ... other fields
|
||||
};
|
||||
pkg.name;
|
||||
```
|
||||
|
||||
If a `.json` file is passed as an entrypoint to the bundler, it will be converted to a `.js` module that `export default`s the parsed object.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```json Input
|
||||
{
|
||||
"name": "John Doe",
|
||||
"age": 35,
|
||||
"email": "johndoe@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
```ts Output
|
||||
export default {
|
||||
name: "John Doe",
|
||||
age: 35,
|
||||
email: "johndoe@example.com",
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### `jsonc`
|
||||
|
||||
**JSON with Comments loader**. Default for `.jsonc`.
|
||||
|
||||
JSONC (JSON with Comments) files can be directly imported. Bun will parse them, stripping out comments and trailing commas.
|
||||
|
||||
```ts
|
||||
import config from "./config.jsonc";
|
||||
console.log(config);
|
||||
```
|
||||
|
||||
During bundling, the parsed JSONC is inlined into the bundle as a JavaScript object, identical to the `json` loader.
|
||||
|
||||
```ts
|
||||
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 will parse them with its fast native TOML parser.
|
||||
|
||||
```ts
|
||||
import config from "./bunfig.toml";
|
||||
config.logLevel; // => "debug"
|
||||
|
||||
// via import attribute:
|
||||
// import myCustomTOML from './my.config' with {type: "toml"};
|
||||
```
|
||||
|
||||
During bundling, the parsed TOML is inlined into the bundle as a JavaScript object.
|
||||
|
||||
```ts
|
||||
var config = {
|
||||
logLevel: "debug",
|
||||
// ...other fields
|
||||
};
|
||||
config.logLevel;
|
||||
```
|
||||
|
||||
If a `.toml` file is passed as an entrypoint, it will be converted to a `.js` module that `export default`s the parsed object.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```toml Input
|
||||
name = "John Doe"
|
||||
age = 35
|
||||
email = "johndoe@example.com"
|
||||
```
|
||||
|
||||
```ts Output
|
||||
export default {
|
||||
name: "John Doe",
|
||||
age: 35,
|
||||
email: "johndoe@example.com",
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### `yaml`
|
||||
|
||||
**YAML loader**. Default for `.yaml` and `.yml`.
|
||||
|
||||
YAML files can be directly imported. Bun will parse them with its fast native YAML parser.
|
||||
|
||||
```ts
|
||||
import config from "./config.yaml";
|
||||
console.log(config);
|
||||
|
||||
// via import attribute:
|
||||
import data from "./data.txt" with { type: "yaml" };
|
||||
```
|
||||
|
||||
During bundling, the parsed YAML is inlined into the bundle as a JavaScript object.
|
||||
|
||||
```ts
|
||||
var config = {
|
||||
name: "my-app",
|
||||
version: "1.0.0",
|
||||
// ...other fields
|
||||
};
|
||||
```
|
||||
|
||||
If a `.yaml` or `.yml` file is passed as an entrypoint, it will be converted to a `.js` module that `export default`s the parsed object.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```yaml Input
|
||||
name: John Doe
|
||||
age: 35
|
||||
email: johndoe@example.com
|
||||
```
|
||||
|
||||
```ts Output
|
||||
export default {
|
||||
name: "John Doe",
|
||||
age: 35,
|
||||
email: "johndoe@example.com",
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### `text`
|
||||
|
||||
**Text loader**. Default for `.txt`.
|
||||
|
||||
The contents of the text file are read and inlined into the bundle as a string.
|
||||
Text files can be directly imported. The file is read and returned as a string.
|
||||
|
||||
```ts
|
||||
import contents from "./file.txt";
|
||||
console.log(contents); // => "Hello, world!"
|
||||
|
||||
// To import an html file as text
|
||||
// The "type' attribute can be used to override the default loader.
|
||||
import html from "./index.html" with { type: "text" };
|
||||
```
|
||||
|
||||
When referenced during a build, the contents are inlined into the bundle as a string.
|
||||
|
||||
```ts
|
||||
var contents = `Hello, world!`;
|
||||
console.log(contents);
|
||||
```
|
||||
|
||||
If a `.txt` file is passed as an entrypoint, it will be converted to a `.js` module that `export default`s the file contents.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```txt Input
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
```ts Output
|
||||
export default "Hello, world!";
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### `napi`
|
||||
|
||||
**Native addon loader**. Default for `.node`.
|
||||
|
||||
In the runtime, native addons can be directly imported.
|
||||
|
||||
```ts
|
||||
import addon from "./addon.node";
|
||||
console.log(addon);
|
||||
```
|
||||
|
||||
In the bundler, `.node` files are handled using the [`file`](#file) loader.
|
||||
|
||||
### `sqlite`
|
||||
|
||||
**SQLite loader**. `with { "type": "sqlite" }` import attribute
|
||||
|
||||
In the runtime and bundler, SQLite databases can be directly imported. This will load the database using [`bun:sqlite`](/runtime/sqlite).
|
||||
|
||||
```ts
|
||||
import db from "./my.db" with { type: "sqlite" };
|
||||
```
|
||||
|
||||
This is only supported when the `target` is `bun`.
|
||||
|
||||
By default, the database is external to the bundle (so that you can potentially use a database loaded elsewhere), so the database file on-disk won't be bundled into the final output.
|
||||
|
||||
You can change this behavior with the `"embed"` attribute:
|
||||
|
||||
```ts
|
||||
// embed the database into the bundle
|
||||
import db from "./my.db" with { type: "sqlite", embed: "true" };
|
||||
```
|
||||
|
||||
When using a [standalone executable](/bundler/executables), the database is embedded into the single-file executable.
|
||||
|
||||
Otherwise, the database to embed is copied into the `outdir` with a hashed filename.
|
||||
|
||||
### `html`
|
||||
|
||||
The html loader processes HTML files and bundles any referenced assets. It will:
|
||||
|
||||
- Bundle and hash referenced JavaScript files (`<script src="...">`)
|
||||
- Bundle and hash referenced CSS files (`<link rel="stylesheet" href="...">`)
|
||||
- Hash referenced images (`<img src="...">`)
|
||||
- Preserve external URLs (by default, anything starting with `http://` or `https://`)
|
||||
|
||||
For example, given this HTML file:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```html src/index.html
|
||||
<!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>
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
It will output a new HTML file with the bundled assets:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```html dist/output.html
|
||||
<!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>
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
Under the hood, it uses [`lol-html`](https://github.com/cloudflare/lol-html) to extract script and link tags as entrypoints, and other assets as external.
|
||||
|
||||
Currently, the list of selectors is:
|
||||
|
||||
- `audio[src]`
|
||||
- `iframe[src]`
|
||||
- `img[src]`
|
||||
- `img[srcset]`
|
||||
- `link:not([rel~='stylesheet']):not([rel~='modulepreload']):not([rel~='manifest']):not([rel~='icon']):not([rel~='apple-touch-icon'])[href]`
|
||||
- `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]`
|
||||
|
||||
<Note>
|
||||
|
||||
**HTML Loader Behavior in Different Contexts**
|
||||
|
||||
The `html` loader behaves differently depending on how it's used:
|
||||
|
||||
1. **Static Build:** When you run `bun build ./index.html`, Bun produces a static site with all assets bundled and hashed.
|
||||
|
||||
2. **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.
|
||||
|
||||
3. **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 efficiently serve pre-bundled assets in production.
|
||||
|
||||
</Note>
|
||||
|
||||
### `css`
|
||||
|
||||
**CSS loader**. Default for `.css`.
|
||||
|
||||
CSS files can be directly imported. This is primarily useful for [full-stack applications](/bundler/html-static) where CSS is bundled alongside HTML.
|
||||
|
||||
```ts
|
||||
import "./styles.css";
|
||||
```
|
||||
|
||||
There isn't any value returned from the import, it's only used for side effects.
|
||||
|
||||
### `sh` loader
|
||||
|
||||
**Bun Shell loader**. Default for `.sh` files
|
||||
|
||||
This loader is used to parse [Bun Shell](/runtime/shell) scripts. It's only supported when starting Bun itself, so it's not available in the bundler or in the runtime.
|
||||
|
||||
```sh
|
||||
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.
|
||||
|
||||
```ts logo.ts
|
||||
import logo from "./logo.svg";
|
||||
console.log(logo);
|
||||
```
|
||||
|
||||
_In the runtime_, Bun checks that the `logo.svg` file exists and converts it to an absolute path to the location of `logo.svg` on disk.
|
||||
|
||||
```bash
|
||||
bun run logo.ts
|
||||
/path/to/project/logo.svg
|
||||
```
|
||||
|
||||
_In the bundler_, things are slightly different. The file is copied into `outdir` as-is, and the import is resolved as a relative path pointing to the copied file.
|
||||
|
||||
```ts Output
|
||||
var logo = "./logo.svg";
|
||||
console.log(logo);
|
||||
```
|
||||
|
||||
If a value is specified for `publicPath`, the import will use 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 location and file name of the copied file is determined by the value of [`naming.asset`](/bundler#naming).
|
||||
</Note>
|
||||
This loader is copied into the `outdir` as-is. The name of the copied file is determined using the value of
|
||||
`naming.asset`.
|
||||
|
||||
<Accordion title="Fixing TypeScript import errors">
|
||||
If you're using TypeScript, you may get an error like this:
|
||||
|
||||
```ts
|
||||
// TypeScript error
|
||||
// Cannot find module './logo.svg' or its corresponding type declarations.
|
||||
```
|
||||
|
||||
This can be fixed by creating `*.d.ts` file anywhere in your project (any name will work) with the following contents:
|
||||
|
||||
```ts
|
||||
declare module "*.svg" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
```
|
||||
|
||||
This tells TypeScript that any default imports from `.svg` should be treated as a string.
|
||||
|
||||
</Accordion>
|
||||
181
docs/runtime/glob.mdx
Normal file
181
docs/runtime/glob.mdx
Normal file
@@ -0,0 +1,181 @@
|
||||
---
|
||||
title: Glob
|
||||
description: Use Bun's fast native implementation of file globbing
|
||||
---
|
||||
|
||||
## Quickstart
|
||||
|
||||
**Scan a directory for files matching `*.ts`**:
|
||||
|
||||
```ts
|
||||
import { Glob } from "bun";
|
||||
|
||||
const glob = new Glob("**/*.ts");
|
||||
|
||||
// Scans the current working directory and each of its sub-directories recursively
|
||||
for await (const file of glob.scan(".")) {
|
||||
console.log(file); // => "index.ts"
|
||||
}
|
||||
```
|
||||
|
||||
**Match a string against a glob pattern**:
|
||||
|
||||
```ts
|
||||
import { Glob } from "bun";
|
||||
|
||||
const glob = new Glob("*.ts");
|
||||
|
||||
glob.match("index.ts"); // => true
|
||||
glob.match("index.js"); // => false
|
||||
```
|
||||
|
||||
`Glob` is a class which implements the following interface:
|
||||
|
||||
```ts
|
||||
class Glob {
|
||||
scan(root: string | ScanOptions): AsyncIterable<string>;
|
||||
scanSync(root: string | ScanOptions): Iterable<string>;
|
||||
|
||||
match(path: string): boolean;
|
||||
}
|
||||
|
||||
interface ScanOptions {
|
||||
/**
|
||||
* The root directory to start matching from. Defaults to `process.cwd()`
|
||||
*/
|
||||
cwd?: string;
|
||||
|
||||
/**
|
||||
* Allow patterns to match entries that begin with a period (`.`).
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
dot?: boolean;
|
||||
|
||||
/**
|
||||
* Return the absolute path for entries.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
absolute?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether to traverse descendants of symbolic link directories.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
followSymlinks?: boolean;
|
||||
|
||||
/**
|
||||
* Throw an error when symbolic link is broken
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
throwErrorOnBrokenSymlink?: boolean;
|
||||
|
||||
/**
|
||||
* Return only files.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
onlyFiles?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
## Supported Glob Patterns
|
||||
|
||||
Bun supports the following glob patterns:
|
||||
|
||||
### `?` - Match any single character
|
||||
|
||||
```ts
|
||||
const glob = new Glob("???.ts");
|
||||
glob.match("foo.ts"); // => true
|
||||
glob.match("foobar.ts"); // => false
|
||||
```
|
||||
|
||||
### `*` - Matches zero or more characters, except for path separators (`/` or `\`)
|
||||
|
||||
```ts
|
||||
const glob = new Glob("*.ts");
|
||||
glob.match("index.ts"); // => true
|
||||
glob.match("src/index.ts"); // => false
|
||||
```
|
||||
|
||||
### `**` - Match any number of characters including `/`
|
||||
|
||||
```ts
|
||||
const glob = new Glob("**/*.ts");
|
||||
glob.match("index.ts"); // => true
|
||||
glob.match("src/index.ts"); // => true
|
||||
glob.match("src/index.js"); // => false
|
||||
```
|
||||
|
||||
### `[ab]` - Matches one of the characters contained in the brackets, as well as character ranges
|
||||
|
||||
```ts
|
||||
const glob = new Glob("ba[rz].ts");
|
||||
glob.match("bar.ts"); // => true
|
||||
glob.match("baz.ts"); // => true
|
||||
glob.match("bat.ts"); // => false
|
||||
```
|
||||
|
||||
You can use character ranges (e.g `[0-9]`, `[a-z]`) as well as the negation operators `^` or `!` to match anything _except_ the characters contained within the braces (e.g `[^ab]`, `[!a-z]`)
|
||||
|
||||
```ts
|
||||
const glob = new Glob("ba[a-z][0-9][^4-9].ts");
|
||||
glob.match("bar01.ts"); // => true
|
||||
glob.match("baz83.ts"); // => true
|
||||
glob.match("bat22.ts"); // => true
|
||||
glob.match("bat24.ts"); // => false
|
||||
glob.match("ba0a8.ts"); // => false
|
||||
```
|
||||
|
||||
### `{a,b,c}` - Match any of the given patterns
|
||||
|
||||
```ts
|
||||
const glob = new Glob("{a,b,c}.ts");
|
||||
glob.match("a.ts"); // => true
|
||||
glob.match("b.ts"); // => true
|
||||
glob.match("c.ts"); // => true
|
||||
glob.match("d.ts"); // => false
|
||||
```
|
||||
|
||||
These match patterns can be deeply nested (up to 10 levels), and contain any of the wildcards from above.
|
||||
|
||||
### `!` - Negates the result at the start of a pattern
|
||||
|
||||
```ts
|
||||
const glob = new Glob("!index.ts");
|
||||
glob.match("index.ts"); // => false
|
||||
glob.match("foo.ts"); // => true
|
||||
```
|
||||
|
||||
### `\` - Escapes any of the special characters above
|
||||
|
||||
```ts
|
||||
const glob = new Glob("\\!index.ts");
|
||||
glob.match("!index.ts"); // => true
|
||||
glob.match("index.ts"); // => false
|
||||
```
|
||||
|
||||
## Node.js `fs.glob()` compatibility
|
||||
|
||||
Bun also implements Node.js's `fs.glob()` functions with additional features:
|
||||
|
||||
```ts
|
||||
import { glob, globSync, promises } from "node:fs";
|
||||
|
||||
// Array of patterns
|
||||
const files = await promises.glob(["**/*.ts", "**/*.js"]);
|
||||
|
||||
// Exclude patterns
|
||||
const filtered = await promises.glob("**/*", {
|
||||
exclude: ["node_modules/**", "*.test.*"],
|
||||
});
|
||||
```
|
||||
|
||||
All three functions (`fs.glob()`, `fs.globSync()`, `fs.promises.glob()`) support:
|
||||
|
||||
- Array of patterns as the first argument
|
||||
- `exclude` option to filter results
|
||||
72
docs/runtime/globals.mdx
Normal file
72
docs/runtime/globals.mdx
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: Globals
|
||||
description: Use Bun's global objects
|
||||
---
|
||||
|
||||
Bun implements the following globals.
|
||||
|
||||
| Global | Source | Notes |
|
||||
| ----------------------------------------------------------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) | Web | |
|
||||
| [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) | Web | |
|
||||
| [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert) | Web | Intended for command-line tools |
|
||||
| [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) | Web | |
|
||||
| [`Buffer`](https://nodejs.org/api/buffer.html#class-buffer) | Node.js | See [Node.js > `Buffer`](/runtime/nodejs-compat#node-buffer) |
|
||||
| `Bun` | Bun | Subject to change as additional APIs are added |
|
||||
| [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy) | Web | |
|
||||
| [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) | Web | Intended for command-line tools |
|
||||
| [`__dirname`](https://nodejs.org/api/globals.html#__dirname) | Node.js | |
|
||||
| [`__filename`](https://nodejs.org/api/globals.html#__filename) | Node.js | |
|
||||
| [`atob()`](https://developer.mozilla.org/en-US/docs/Web/API/atob) | Web | |
|
||||
| [`btoa()`](https://developer.mozilla.org/en-US/docs/Web/API/btoa) | Web | |
|
||||
| `BuildMessage` | Bun | |
|
||||
| [`clearImmediate()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearImmediate) | Web | |
|
||||
| [`clearInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearInterval) | Web | |
|
||||
| [`clearTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearTimeout) | Web | |
|
||||
| [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console) | Web | |
|
||||
| [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) | Web | |
|
||||
| [`Crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto) | Web | |
|
||||
| [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/crypto) | Web | |
|
||||
| [`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey) | Web | |
|
||||
| [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) | Web | |
|
||||
| [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) | Web | Also [`ErrorEvent`](https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent) [`CloseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent). |
|
||||
| [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) | Web | |
|
||||
| [`exports`](https://nodejs.org/api/globals.html#exports) | Node.js | |
|
||||
| [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) | Web | |
|
||||
| [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) | Web | |
|
||||
| [`global`](https://nodejs.org/api/globals.html#global) | Node.js | See [Node.js > `global`](/runtime/nodejs-compat#global). |
|
||||
| [`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) | Cross-platform | Aliases to `global` |
|
||||
| [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) | Web | |
|
||||
| [`HTMLRewriter`](/runtime/html-rewriter) | Cloudflare | |
|
||||
| [`JSON`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) | Web | |
|
||||
| [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent) | Web | |
|
||||
| [`module`](https://nodejs.org/api/globals.html#module) | Node.js | |
|
||||
| [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/performance) | Web | |
|
||||
| [`process`](https://nodejs.org/api/process.html) | Node.js | See [Node.js > `process`](/runtime/nodejs-compat#node-process) |
|
||||
| [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) | Web | Intended for command-line tools |
|
||||
| [`queueMicrotask()`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) | Web | |
|
||||
| [`ReadableByteStreamController`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableByteStreamController) | Web | |
|
||||
| [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) | Web | |
|
||||
| [`ReadableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultController) | Web | |
|
||||
| [`ReadableStreamDefaultReader`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader) | Web | |
|
||||
| [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError) | Web | |
|
||||
| [`require()`](https://nodejs.org/api/globals.html#require) | Node.js | |
|
||||
| `ResolveMessage` | Bun | |
|
||||
| [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) | Web | |
|
||||
| [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) | Web | |
|
||||
| [`setImmediate()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate) | Web | |
|
||||
| [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setInterval) | Web | |
|
||||
| [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout) | Web | |
|
||||
| [`ShadowRealm`](https://github.com/tc39/proposal-shadowrealm) | Web | Stage 3 proposal |
|
||||
| [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) | Web | |
|
||||
| [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) | Web | |
|
||||
| [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) | Web | |
|
||||
| [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) | Web | |
|
||||
| [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) | Web | |
|
||||
| [`TransformStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStreamDefaultController) | Web | |
|
||||
| [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) | Web | |
|
||||
| [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) | Web | |
|
||||
| [`WebAssembly`](https://nodejs.org/api/globals.html#webassembly) | Web | |
|
||||
| [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) | Web | |
|
||||
| [`WritableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultController) | Web | |
|
||||
| [`WritableStreamDefaultWriter`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultWriter) | Web | |
|
||||
315
docs/runtime/hashing.mdx
Normal file
315
docs/runtime/hashing.mdx
Normal file
@@ -0,0 +1,315 @@
|
||||
---
|
||||
title: Hashing
|
||||
description: Bun provides a set of utility functions for hashing and verifying passwords with various cryptographically secure algorithms
|
||||
---
|
||||
|
||||
<Note>
|
||||
Bun implements the `createHash` and `createHmac` functions from [`node:crypto`](https://nodejs.org/api/crypto.html) in
|
||||
addition to the Bun-native APIs documented below.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## `Bun.password`
|
||||
|
||||
`Bun.password` is a collection of utility functions for hashing and verifying passwords with various cryptographically secure algorithms.
|
||||
|
||||
```ts
|
||||
const password = "super-secure-pa$$word";
|
||||
|
||||
const hash = await Bun.password.hash(password);
|
||||
// => $argon2id$v=19$m=65536,t=2,p=1$tFq+9AVr1bfPxQdh6E8DQRhEXg/M/SqYCNu6gVdRRNs$GzJ8PuBi+K+BVojzPfS5mjnC8OpLGtv8KJqF99eP6a4
|
||||
|
||||
const isMatch = await Bun.password.verify(password, hash);
|
||||
// => true
|
||||
```
|
||||
|
||||
The second argument to `Bun.password.hash` accepts a params object that lets you pick and configure the hashing algorithm.
|
||||
|
||||
```ts
|
||||
const password = "super-secure-pa$$word";
|
||||
|
||||
// use argon2 (default)
|
||||
const argonHash = await Bun.password.hash(password, {
|
||||
algorithm: "argon2id", // "argon2id" | "argon2i" | "argon2d"
|
||||
memoryCost: 4, // memory usage in kibibytes
|
||||
timeCost: 3, // the number of iterations
|
||||
});
|
||||
|
||||
// use bcrypt
|
||||
const bcryptHash = await Bun.password.hash(password, {
|
||||
algorithm: "bcrypt",
|
||||
cost: 4, // number between 4-31
|
||||
});
|
||||
```
|
||||
|
||||
The algorithm used to create the hash is stored in the hash itself. When using `bcrypt`, the returned hash is encoded in [Modular Crypt Format](https://passlib.readthedocs.io/en/stable/modular_crypt_format.html) for compatibility with most existing `bcrypt` implementations; with `argon2` the result is encoded in the newer [PHC format](https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md).
|
||||
|
||||
The `verify` function automatically detects the algorithm based on the input hash and use the correct verification method. It can correctly infer the algorithm from both PHC- or MCF-encoded hashes.
|
||||
|
||||
```ts
|
||||
const password = "super-secure-pa$$word";
|
||||
|
||||
const hash = await Bun.password.hash(password, {
|
||||
/* config */
|
||||
});
|
||||
|
||||
const isMatch = await Bun.password.verify(password, hash);
|
||||
// => true
|
||||
```
|
||||
|
||||
Synchronous versions of all functions are also available. Keep in mind that these functions are computationally expensive, so using a blocking API may degrade application performance.
|
||||
|
||||
```ts
|
||||
const password = "super-secure-pa$$word";
|
||||
|
||||
const hash = Bun.password.hashSync(password, {
|
||||
/* config */
|
||||
});
|
||||
|
||||
const isMatch = Bun.password.verifySync(password, hash);
|
||||
// => true
|
||||
```
|
||||
|
||||
### Salt
|
||||
|
||||
When you use `Bun.password.hash`, a salt is automatically generated and included in the hash.
|
||||
|
||||
### bcrypt - Modular Crypt Format
|
||||
|
||||
In the following [Modular Crypt Format](https://passlib.readthedocs.io/en/stable/modular_crypt_format.html) hash (used by `bcrypt`):
|
||||
|
||||
Input:
|
||||
|
||||
```ts
|
||||
await Bun.password.hash("hello", {
|
||||
algorithm: "bcrypt",
|
||||
});
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```sh
|
||||
$2b$10$Lyj9kHYZtiyfxh2G60TEfeqs7xkkGiEFFDi3iJGc50ZG/XJ1sxIFi;
|
||||
```
|
||||
|
||||
The format is composed of:
|
||||
|
||||
- `bcrypt`: `$2b`
|
||||
- `rounds`: `$10` - rounds (log10 of the actual number of rounds)
|
||||
- `salt`: `$Lyj9kHYZtiyfxh2G60TEfeqs7xkkGiEFFDi3iJGc50ZG/XJ1sxIFi`
|
||||
- `hash`: `$GzJ8PuBi+K+BVojzPfS5mjnC8OpLGtv8KJqF99eP6a4`
|
||||
|
||||
By default, the bcrypt library truncates passwords longer than 72 bytes. In Bun, if you pass `Bun.password.hash` a password longer than 72 bytes and use the `bcrypt` algorithm, the password will be hashed via SHA-512 before being passed to bcrypt.
|
||||
|
||||
```ts
|
||||
await Bun.password.hash("hello".repeat(100), {
|
||||
algorithm: "bcrypt",
|
||||
});
|
||||
```
|
||||
|
||||
So instead of sending bcrypt a 500-byte password silently truncated to 72 bytes, Bun will hash the password using SHA-512 and send the hashed password to bcrypt (only if it exceeds 72 bytes). This is a more secure default behavior.
|
||||
|
||||
### argon2 - PHC format
|
||||
|
||||
In the following [PHC format](https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md) hash (used by `argon2`):
|
||||
|
||||
Input:
|
||||
|
||||
```ts
|
||||
await Bun.password.hash("hello", {
|
||||
algorithm: "argon2id",
|
||||
});
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```sh
|
||||
$argon2id$v=19$m=65536,t=2,p=1$xXnlSvPh4ym5KYmxKAuuHVlDvy2QGHBNuI6bJJrRDOs$2YY6M48XmHn+s5NoBaL+ficzXajq2Yj8wut3r0vnrwI
|
||||
```
|
||||
|
||||
The format is composed of:
|
||||
|
||||
- `algorithm`: `$argon2id`
|
||||
- `version`: `$v=19`
|
||||
- `memory cost`: `65536`
|
||||
- `iterations`: `t=2`
|
||||
- `parallelism`: `p=1`
|
||||
- `salt`: `$xXnlSvPh4ym5KYmxKAuuHVlDvy2QGHBNuI6bJJrRDOs`
|
||||
- `hash`: `$2YY6M48XmHn+s5NoBaL+ficzXajq2Yj8wut3r0vnrwI`
|
||||
|
||||
---
|
||||
|
||||
## `Bun.hash`
|
||||
|
||||
`Bun.hash` is a collection of utilities for _non-cryptographic_ hashing. Non-cryptographic hashing algorithms are optimized for speed of computation over collision-resistance or security.
|
||||
|
||||
The standard `Bun.hash` functions uses [Wyhash](https://github.com/wangyi-fudan/wyhash) to generate a 64-bit hash from an input of arbitrary size.
|
||||
|
||||
```ts
|
||||
Bun.hash("some data here");
|
||||
// 11562320457524636935n
|
||||
```
|
||||
|
||||
The input can be a string, `TypedArray`, `DataView`, `ArrayBuffer`, or `SharedArrayBuffer`.
|
||||
|
||||
```ts
|
||||
const arr = new Uint8Array([1, 2, 3, 4]);
|
||||
|
||||
Bun.hash("some data here");
|
||||
Bun.hash(arr);
|
||||
Bun.hash(arr.buffer);
|
||||
Bun.hash(new DataView(arr.buffer));
|
||||
```
|
||||
|
||||
Optionally, an integer seed can be specified as the second parameter. For 64-bit hashes seeds above `Number.MAX_SAFE_INTEGER` should be given as BigInt to avoid loss of precision.
|
||||
|
||||
```ts
|
||||
Bun.hash("some data here", 1234);
|
||||
// 15724820720172937558n
|
||||
```
|
||||
|
||||
Additional hashing algorithms are available as properties on `Bun.hash`. The API is the same for each, only changing the return type from number for 32-bit hashes to bigint for 64-bit hashes.
|
||||
|
||||
```ts
|
||||
Bun.hash.wyhash("data", 1234); // equivalent to Bun.hash()
|
||||
Bun.hash.crc32("data", 1234);
|
||||
Bun.hash.adler32("data", 1234);
|
||||
Bun.hash.cityHash32("data", 1234);
|
||||
Bun.hash.cityHash64("data", 1234);
|
||||
Bun.hash.xxHash32("data", 1234);
|
||||
Bun.hash.xxHash64("data", 1234);
|
||||
Bun.hash.xxHash3("data", 1234);
|
||||
Bun.hash.murmur32v3("data", 1234);
|
||||
Bun.hash.murmur32v2("data", 1234);
|
||||
Bun.hash.murmur64v2("data", 1234);
|
||||
Bun.hash.rapidhash("data", 1234);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `Bun.CryptoHasher`
|
||||
|
||||
`Bun.CryptoHasher` is a general-purpose utility class that lets you incrementally compute a hash of string or binary data using a range of cryptographic hash algorithms. The following algorithms are supported:
|
||||
|
||||
- `"blake2b256"`
|
||||
- `"blake2b512"`
|
||||
- `"md4"`
|
||||
- `"md5"`
|
||||
- `"ripemd160"`
|
||||
- `"sha1"`
|
||||
- `"sha224"`
|
||||
- `"sha256"`
|
||||
- `"sha384"`
|
||||
- `"sha512"`
|
||||
- `"sha512-224"`
|
||||
- `"sha512-256"`
|
||||
- `"sha3-224"`
|
||||
- `"sha3-256"`
|
||||
- `"sha3-384"`
|
||||
- `"sha3-512"`
|
||||
- `"shake128"`
|
||||
- `"shake256"`
|
||||
|
||||
```ts
|
||||
const hasher = new Bun.CryptoHasher("sha256");
|
||||
hasher.update("hello world");
|
||||
hasher.digest();
|
||||
// Uint8Array(32) [ <byte>, <byte>, ... ]
|
||||
```
|
||||
|
||||
Once initialized, data can be incrementally fed to to the hasher using `.update()`. This method accepts `string`, `TypedArray`, and `ArrayBuffer`.
|
||||
|
||||
```ts
|
||||
const hasher = new Bun.CryptoHasher("sha256");
|
||||
|
||||
hasher.update("hello world");
|
||||
hasher.update(new Uint8Array([1, 2, 3]));
|
||||
hasher.update(new ArrayBuffer(10));
|
||||
```
|
||||
|
||||
If a `string` is passed, an optional second parameter can be used to specify the encoding (default `'utf-8'`). The following encodings are supported:
|
||||
|
||||
| Category | Encodings |
|
||||
| -------------------------- | ------------------------------------------- |
|
||||
| Binary encodings | `"base64"` `"base64url"` `"hex"` `"binary"` |
|
||||
| Character encodings | `"utf8"` `"utf-8"` `"utf16le"` `"latin1"` |
|
||||
| Legacy character encodings | `"ascii"` `"binary"` `"ucs2"` `"ucs-2"` |
|
||||
|
||||
```ts
|
||||
hasher.update("hello world"); // defaults to utf8
|
||||
hasher.update("hello world", "hex");
|
||||
hasher.update("hello world", "base64");
|
||||
hasher.update("hello world", "latin1");
|
||||
```
|
||||
|
||||
After the data has been feed into the hasher, a final hash can be computed using `.digest()`. By default, this method returns a `Uint8Array` containing the hash.
|
||||
|
||||
```ts
|
||||
const hasher = new Bun.CryptoHasher("sha256");
|
||||
hasher.update("hello world");
|
||||
|
||||
hasher.digest();
|
||||
// => Uint8Array(32) [ 185, 77, 39, 185, 147, ... ]
|
||||
```
|
||||
|
||||
The `.digest()` method can optionally return the hash as a string. To do so, specify an encoding:
|
||||
|
||||
```ts
|
||||
hasher.digest("base64");
|
||||
// => "uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="
|
||||
|
||||
hasher.digest("hex");
|
||||
// => "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
|
||||
```
|
||||
|
||||
Alternatively, the method can write the hash into a pre-existing `TypedArray` instance. This may be desirable in some performance-sensitive applications.
|
||||
|
||||
```ts
|
||||
const arr = new Uint8Array(32);
|
||||
|
||||
hasher.digest(arr);
|
||||
|
||||
console.log(arr);
|
||||
// => Uint8Array(32) [ 185, 77, 39, 185, 147, ... ]
|
||||
```
|
||||
|
||||
### HMAC in `Bun.CryptoHasher`
|
||||
|
||||
`Bun.CryptoHasher` can be used to compute HMAC digests. To do so, pass the key to the constructor.
|
||||
|
||||
```ts
|
||||
const hasher = new Bun.CryptoHasher("sha256", "secret-key");
|
||||
hasher.update("hello world");
|
||||
console.log(hasher.digest("hex"));
|
||||
// => "095d5a21fe6d0646db223fdf3de6436bb8dfb2fab0b51677ecf6441fcf5f2a67"
|
||||
```
|
||||
|
||||
When using HMAC, a more limited set of algorithms are supported:
|
||||
|
||||
- `"blake2b512"`
|
||||
- `"md5"`
|
||||
- `"sha1"`
|
||||
- `"sha224"`
|
||||
- `"sha256"`
|
||||
- `"sha384"`
|
||||
- `"sha512-224"`
|
||||
- `"sha512-256"`
|
||||
- `"sha512"`
|
||||
|
||||
Unlike the non-HMAC `Bun.CryptoHasher`, the HMAC `Bun.CryptoHasher` instance is not reset after `.digest()` is called, and attempting to use the same instance again will throw an error.
|
||||
|
||||
Other methods like `.copy()` and `.update()` are supported (as long as it's before `.digest()`), but methods like `.digest()` that finalize the hasher are not.
|
||||
|
||||
```ts
|
||||
const hasher = new Bun.CryptoHasher("sha256", "secret-key");
|
||||
hasher.update("hello world");
|
||||
|
||||
const copy = hasher.copy();
|
||||
copy.update("!");
|
||||
console.log(copy.digest("hex"));
|
||||
// => "3840176c3d8923f59ac402b7550404b28ab11cb0ef1fa199130a5c37864b5497"
|
||||
|
||||
console.log(hasher.digest("hex"));
|
||||
// => "095d5a21fe6d0646db223fdf3de6436bb8dfb2fab0b51677ecf6441fcf5f2a67"
|
||||
```
|
||||
333
docs/runtime/html-rewriter.mdx
Normal file
333
docs/runtime/html-rewriter.mdx
Normal file
@@ -0,0 +1,333 @@
|
||||
---
|
||||
title: HTMLRewriter
|
||||
description: Use Bun's HTMLRewriter to transform HTML documents with CSS selectors
|
||||
---
|
||||
|
||||
HTMLRewriter lets you use CSS selectors to transform HTML documents. It works with `Request`, `Response`, as well as `string`. Bun's implementation is based on Cloudflare's [lol-html](https://github.com/cloudflare/lol-html).
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
A common usecase is rewriting URLs in HTML content. Here's an example that rewrites image sources and link URLs to use a CDN domain:
|
||||
|
||||
```ts
|
||||
// Replace all images with a rickroll
|
||||
const rewriter = new HTMLRewriter().on("img", {
|
||||
element(img) {
|
||||
// Famous rickroll video thumbnail
|
||||
img.setAttribute("src", "https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg");
|
||||
|
||||
// Wrap the image in a link to the video
|
||||
img.before('<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">', {
|
||||
html: true,
|
||||
});
|
||||
img.after("</a>", { html: true });
|
||||
|
||||
// Add some fun alt text
|
||||
img.setAttribute("alt", "Definitely not a rickroll");
|
||||
},
|
||||
});
|
||||
|
||||
// An example HTML document
|
||||
const html = `
|
||||
<html>
|
||||
<body>
|
||||
<img src="/cat.jpg">
|
||||
<img src="dog.png">
|
||||
<img src="https://example.com/bird.webp">
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const result = rewriter.transform(html);
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
This replaces all images with a thumbnail of Rick Astley and wraps each `<img>` in a link, producing a diff like this:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<img src="/cat.jpg" /> <!-- [!code --] -->
|
||||
<img src="dog.png" /> <!-- [!code --] -->
|
||||
<img src="https://example.com/bird.webp" /> <!-- [!code --] -->
|
||||
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank"> <!-- [!code ++] -->
|
||||
<img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" /> <!-- [!code ++] -->
|
||||
</a> <!-- [!code ++] -->
|
||||
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank"> <!-- [!code ++] -->
|
||||
<img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" /> <!-- [!code ++] -->
|
||||
</a> <!-- [!code ++] -->
|
||||
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank"> <!-- [!code ++] -->
|
||||
<img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" /> <!-- [!code ++] -->
|
||||
</a> <!-- [!code ++] -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Now every image on the page will be replaced with a thumbnail of Rick Astley, and clicking any image will lead to [a very famous video](https://www.youtube.com/watch?v=dQw4w9WgXcQ).
|
||||
|
||||
### Input types
|
||||
|
||||
HTMLRewriter can transform HTML from various sources. The input is automatically handled based on its type:
|
||||
|
||||
```ts
|
||||
// From Response
|
||||
rewriter.transform(new Response("<div>content</div>"));
|
||||
|
||||
// From string
|
||||
rewriter.transform("<div>content</div>");
|
||||
|
||||
// From ArrayBuffer
|
||||
rewriter.transform(new TextEncoder().encode("<div>content</div>").buffer);
|
||||
|
||||
// From Blob
|
||||
rewriter.transform(new Blob(["<div>content</div>"]));
|
||||
|
||||
// From File
|
||||
rewriter.transform(Bun.file("index.html"));
|
||||
```
|
||||
|
||||
Note that Cloudflare Workers implementation of HTMLRewriter only supports `Response` objects.
|
||||
|
||||
### Element Handlers
|
||||
|
||||
The `on(selector, handlers)` method allows you to register handlers for HTML elements that match a CSS selector. The handlers are called for each matching element during parsing:
|
||||
|
||||
```ts
|
||||
rewriter.on("div.content", {
|
||||
// Handle elements
|
||||
element(element) {
|
||||
element.setAttribute("class", "new-content");
|
||||
element.append("<p>New content</p>", { html: true });
|
||||
},
|
||||
// Handle text nodes
|
||||
text(text) {
|
||||
text.replace("new text");
|
||||
},
|
||||
// Handle comments
|
||||
comments(comment) {
|
||||
comment.remove();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The handlers can be asynchronous and return a Promise. Note that async operations will block the transformation until they complete:
|
||||
|
||||
```ts
|
||||
rewriter.on("div", {
|
||||
async element(element) {
|
||||
await Bun.sleep(1000);
|
||||
element.setInnerContent("<span>replace</span>", { html: true });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### CSS Selector Support
|
||||
|
||||
The `on()` method supports a wide range of CSS selectors:
|
||||
|
||||
```ts
|
||||
// Tag selectors
|
||||
rewriter.on("p", handler);
|
||||
|
||||
// Class selectors
|
||||
rewriter.on("p.red", handler);
|
||||
|
||||
// ID selectors
|
||||
rewriter.on("h1#header", handler);
|
||||
|
||||
// Attribute selectors
|
||||
rewriter.on("p[data-test]", handler); // Has attribute
|
||||
rewriter.on('p[data-test="one"]', handler); // Exact match
|
||||
rewriter.on('p[data-test="one" i]', handler); // Case-insensitive
|
||||
rewriter.on('p[data-test="one" s]', handler); // Case-sensitive
|
||||
rewriter.on('p[data-test~="two"]', handler); // Word match
|
||||
rewriter.on('p[data-test^="a"]', handler); // Starts with
|
||||
rewriter.on('p[data-test$="1"]', handler); // Ends with
|
||||
rewriter.on('p[data-test*="b"]', handler); // Contains
|
||||
rewriter.on('p[data-test|="a"]', handler); // Dash-separated
|
||||
|
||||
// Combinators
|
||||
rewriter.on("div span", handler); // Descendant
|
||||
rewriter.on("div > span", handler); // Direct child
|
||||
|
||||
// Pseudo-classes
|
||||
rewriter.on("p:nth-child(2)", handler);
|
||||
rewriter.on("p:first-child", handler);
|
||||
rewriter.on("p:nth-of-type(2)", handler);
|
||||
rewriter.on("p:first-of-type", handler);
|
||||
rewriter.on("p:not(:first-child)", handler);
|
||||
|
||||
// Universal selector
|
||||
rewriter.on("*", handler);
|
||||
```
|
||||
|
||||
### Element Operations
|
||||
|
||||
Elements provide various methods for manipulation. All modification methods return the element instance for chaining:
|
||||
|
||||
```ts
|
||||
rewriter.on("div", {
|
||||
element(el) {
|
||||
// Attributes
|
||||
el.setAttribute("class", "new-class").setAttribute("data-id", "123");
|
||||
|
||||
const classAttr = el.getAttribute("class"); // "new-class"
|
||||
const hasId = el.hasAttribute("id"); // boolean
|
||||
el.removeAttribute("class");
|
||||
|
||||
// Content manipulation
|
||||
el.setInnerContent("New content"); // Escapes HTML by default
|
||||
el.setInnerContent("<p>HTML content</p>", { html: true }); // Parses HTML
|
||||
el.setInnerContent(""); // Clear content
|
||||
|
||||
// Position manipulation
|
||||
el.before("Content before").after("Content after").prepend("First child").append("Last child");
|
||||
|
||||
// HTML content insertion
|
||||
el.before("<span>before</span>", { html: true })
|
||||
.after("<span>after</span>", { html: true })
|
||||
.prepend("<span>first</span>", { html: true })
|
||||
.append("<span>last</span>", { html: true });
|
||||
|
||||
// Removal
|
||||
el.remove(); // Remove element and contents
|
||||
el.removeAndKeepContent(); // Remove only the element tags
|
||||
|
||||
// Properties
|
||||
console.log(el.tagName); // Lowercase tag name
|
||||
console.log(el.namespaceURI); // Element's namespace URI
|
||||
console.log(el.selfClosing); // Whether element is self-closing (e.g. <div />)
|
||||
console.log(el.canHaveContent); // Whether element can contain content (false for void elements like <br>)
|
||||
console.log(el.removed); // Whether element was removed
|
||||
|
||||
// Attributes iteration
|
||||
for (const [name, value] of el.attributes) {
|
||||
console.log(name, value);
|
||||
}
|
||||
|
||||
// End tag handling
|
||||
el.onEndTag(endTag => {
|
||||
endTag.before("Before end tag");
|
||||
endTag.after("After end tag");
|
||||
endTag.remove(); // Remove the end tag
|
||||
console.log(endTag.name); // Tag name in lowercase
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Text Operations
|
||||
|
||||
Text handlers provide methods for text manipulation. Text chunks represent portions of text content and provide information about their position in the text node:
|
||||
|
||||
```ts
|
||||
rewriter.on("p", {
|
||||
text(text) {
|
||||
// Content
|
||||
console.log(text.text); // Text content
|
||||
console.log(text.lastInTextNode); // Whether this is the last chunk
|
||||
console.log(text.removed); // Whether text was removed
|
||||
|
||||
// Manipulation
|
||||
text.before("Before text").after("After text").replace("New text").remove();
|
||||
|
||||
// HTML content insertion
|
||||
text
|
||||
.before("<span>before</span>", { html: true })
|
||||
.after("<span>after</span>", { html: true })
|
||||
.replace("<span>replace</span>", { html: true });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Comment Operations
|
||||
|
||||
Comment handlers allow comment manipulation with similar methods to text nodes:
|
||||
|
||||
```ts
|
||||
rewriter.on("*", {
|
||||
comments(comment) {
|
||||
// Content
|
||||
console.log(comment.text); // Comment text
|
||||
comment.text = "New comment text"; // Set comment text
|
||||
console.log(comment.removed); // Whether comment was removed
|
||||
|
||||
// Manipulation
|
||||
comment.before("Before comment").after("After comment").replace("New comment").remove();
|
||||
|
||||
// HTML content insertion
|
||||
comment
|
||||
.before("<span>before</span>", { html: true })
|
||||
.after("<span>after</span>", { html: true })
|
||||
.replace("<span>replace</span>", { html: true });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Document Handlers
|
||||
|
||||
The `onDocument(handlers)` method allows you to handle document-level events. These handlers are called for events that occur at the document level rather than within specific elements:
|
||||
|
||||
```ts
|
||||
rewriter.onDocument({
|
||||
// Handle doctype
|
||||
doctype(doctype) {
|
||||
console.log(doctype.name); // "html"
|
||||
console.log(doctype.publicId); // public identifier if present
|
||||
console.log(doctype.systemId); // system identifier if present
|
||||
},
|
||||
// Handle text nodes
|
||||
text(text) {
|
||||
console.log(text.text);
|
||||
},
|
||||
// Handle comments
|
||||
comments(comment) {
|
||||
console.log(comment.text);
|
||||
},
|
||||
// Handle document end
|
||||
end(end) {
|
||||
end.append("<!-- Footer -->", { html: true });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Response Handling
|
||||
|
||||
When transforming a Response:
|
||||
|
||||
- The status code, headers, and other response properties are preserved
|
||||
- The body is transformed while maintaining streaming capabilities
|
||||
- Content-encoding (like gzip) is handled automatically
|
||||
- The original response body is marked as used after transformation
|
||||
- Headers are cloned to the new response
|
||||
|
||||
## Error Handling
|
||||
|
||||
HTMLRewriter operations can throw errors in several cases:
|
||||
|
||||
- Invalid selector syntax in `on()` method
|
||||
- Invalid HTML content in transformation methods
|
||||
- Stream errors when processing Response bodies
|
||||
- Memory allocation failures
|
||||
- Invalid input types (e.g., passing Symbol)
|
||||
- Body already used errors
|
||||
|
||||
Errors should be caught and handled appropriately:
|
||||
|
||||
```ts
|
||||
try {
|
||||
const result = rewriter.transform(input);
|
||||
// Process result
|
||||
} catch (error) {
|
||||
console.error("HTMLRewriter error:", error);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
You can also read the [Cloudflare documentation](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/), which this API is intended to be compatible with.
|
||||
79
docs/runtime/http/cookies.mdx
Normal file
79
docs/runtime/http/cookies.mdx
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: Cookies
|
||||
description: Work with cookies in HTTP requests and responses using Bun's built-in Cookie API.
|
||||
---
|
||||
|
||||
Bun provides a built-in API for working with cookies in HTTP requests and responses. The `BunRequest` object includes a `cookies` property that provides a `CookieMap` for easily accessing and manipulating cookies. When using `routes`, `Bun.serve()` automatically tracks `request.cookies.set` and applies them to the response.
|
||||
|
||||
## Reading cookies
|
||||
|
||||
Read cookies from incoming requests using the `cookies` property on the `BunRequest` object:
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/profile": req => {
|
||||
// Access cookies from the request
|
||||
const userId = req.cookies.get("user_id");
|
||||
const theme = req.cookies.get("theme") || "light";
|
||||
|
||||
return Response.json({
|
||||
userId,
|
||||
theme,
|
||||
message: "Profile page",
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Setting cookies
|
||||
|
||||
To set cookies, use the `set` method on the `CookieMap` from the `BunRequest` object.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/login": req => {
|
||||
const cookies = req.cookies;
|
||||
|
||||
// Set a cookie with various options
|
||||
cookies.set("user_id", "12345", {
|
||||
maxAge: 60 * 60 * 24 * 7, // 1 week
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
path: "/",
|
||||
});
|
||||
|
||||
// Add a theme preference cookie
|
||||
cookies.set("theme", "dark");
|
||||
|
||||
// Modified cookies from the request are automatically applied to the response
|
||||
return new Response("Login successful");
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
`Bun.serve()` automatically tracks modified cookies from the request and applies them to the response.
|
||||
|
||||
## Deleting cookies
|
||||
|
||||
To delete a cookie, use the `delete` method on the `request.cookies` (`CookieMap`) object:
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/logout": req => {
|
||||
// Delete the user_id cookie
|
||||
req.cookies.delete("user_id", {
|
||||
path: "/",
|
||||
});
|
||||
|
||||
return new Response("Logged out successfully");
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Deleted cookies become a `Set-Cookie` header on the response with the `maxAge` set to `0` and an empty `value`.
|
||||
40
docs/runtime/http/error-handling.mdx
Normal file
40
docs/runtime/http/error-handling.mdx
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Error Handling
|
||||
description: Learn how to handle errors in Bun's development server
|
||||
---
|
||||
|
||||
To activate development mode, set `development: true`.
|
||||
|
||||
```ts title="server.ts" icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
development: true, // [!code ++]
|
||||
fetch(req) {
|
||||
throw new Error("woops!");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
In development mode, Bun will surface errors in-browser with a built-in error page.
|
||||
|
||||
<Frame></Frame>
|
||||
|
||||
### `error` callback
|
||||
|
||||
To handle server-side errors, implement an `error` handler. This function should return a `Response` to serve to the client when an error occurs. This response will supersede Bun's default error page in `development` mode.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
fetch(req) {
|
||||
throw new Error("woops!");
|
||||
},
|
||||
error(error) {
|
||||
return new Response(`<pre>${error}\n${error.stack}</pre>`, {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Info>[Learn more about debugging in Bun](/runtime/debugger)</Info>
|
||||
36
docs/runtime/http/metrics.mdx
Normal file
36
docs/runtime/http/metrics.mdx
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: Metrics
|
||||
description: Monitor server activity with built-in metrics
|
||||
---
|
||||
|
||||
### `server.pendingRequests` and `server.pendingWebSockets`
|
||||
|
||||
Monitor server activity with built-in counters:
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
fetch(req, server) {
|
||||
return new Response(
|
||||
`Active requests: ${server.pendingRequests}\n` + `Active WebSockets: ${server.pendingWebSockets}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### `server.subscriberCount(topic)`
|
||||
|
||||
Get count of subscribers for a WebSocket topic:
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
fetch(req, server) {
|
||||
const chatUsers = server.subscriberCount("chat");
|
||||
return new Response(`${chatUsers} users in chat`);
|
||||
},
|
||||
websocket: {
|
||||
message(ws) {
|
||||
ws.subscribe("chat");
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
289
docs/runtime/http/routing.mdx
Normal file
289
docs/runtime/http/routing.mdx
Normal file
@@ -0,0 +1,289 @@
|
||||
---
|
||||
title: Routing
|
||||
description: Define routes in `Bun.serve` using static paths, parameters, and wildcards
|
||||
---
|
||||
|
||||
You can add routes to `Bun.serve()` by using the `routes` property (for static paths, parameters, and wildcards) or by handling unmatched requests with the [`fetch`](#fetch) method.
|
||||
|
||||
`Bun.serve()`'s router builds on top uWebSocket's [tree-based approach](https://github.com/oven-sh/bun/blob/0d1a00fa0f7830f8ecd99c027fce8096c9d459b6/packages/bun-uws/src/HttpRouter.h#L57-L64) to add [SIMD-accelerated route parameter decoding](https://github.com/oven-sh/bun/blob/main/src/bun.js/bindings/decodeURIComponentSIMD.cpp#L21-L271) and [JavaScriptCore structure caching](https://github.com/oven-sh/bun/blob/main/src/bun.js/bindings/ServerRouteList.cpp#L100-L101) to push the performance limits of what modern hardware allows.
|
||||
|
||||
## Basic Setup
|
||||
|
||||
```ts title="server.ts" icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/": () => new Response("Home"),
|
||||
"/api": () => Response.json({ success: true }),
|
||||
"/users": async () => Response.json({ users: [] }),
|
||||
},
|
||||
fetch() {
|
||||
return new Response("Unmatched route");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Routes in `Bun.serve()` receive a `BunRequest` (which extends [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)) and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) or `Promise<Response>`. This makes it easier to use the same code for both sending & receiving HTTP requests.
|
||||
|
||||
```ts
|
||||
// Simplified for brevity
|
||||
interface BunRequest<T extends string> extends Request {
|
||||
params: Record<T, string>;
|
||||
readonly cookies: CookieMap;
|
||||
}
|
||||
```
|
||||
|
||||
## Asynchronous Routes
|
||||
|
||||
### Async/await
|
||||
|
||||
You can use async/await in route handlers to return a `Promise<Response>`.
|
||||
|
||||
```ts
|
||||
import { sql, serve } from "bun";
|
||||
|
||||
serve({
|
||||
port: 3001,
|
||||
routes: {
|
||||
"/api/version": async () => {
|
||||
const [version] = await sql`SELECT version()`;
|
||||
return Response.json(version);
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Promise
|
||||
|
||||
You can also return a `Promise<Response>` from a route handler.
|
||||
|
||||
```ts
|
||||
import { sql, serve } from "bun";
|
||||
|
||||
serve({
|
||||
routes: {
|
||||
"/api/version": () => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(async () => {
|
||||
const [version] = await sql`SELECT version()`;
|
||||
resolve(Response.json(version));
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Route precedence
|
||||
|
||||
Routes are matched in order of specificity:
|
||||
|
||||
1. Exact routes (`/users/all`)
|
||||
2. Parameter routes (`/users/:id`)
|
||||
3. Wildcard routes (`/users/*`)
|
||||
4. Global catch-all (`/*`)
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
routes: {
|
||||
// Most specific first
|
||||
"/api/users/me": () => new Response("Current user"),
|
||||
"/api/users/:id": req => new Response(`User ${req.params.id}`),
|
||||
"/api/*": () => new Response("API catch-all"),
|
||||
"/*": () => new Response("Global catch-all"),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Type-safe route parameters
|
||||
|
||||
TypeScript parses route parameters when passed as a string literal, so that your editor will show autocomplete when accessing `request.params`.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import type { BunRequest } from "bun";
|
||||
|
||||
Bun.serve({
|
||||
routes: {
|
||||
// TypeScript knows the shape of params when passed as a string literal
|
||||
"/orgs/:orgId/repos/:repoId": req => {
|
||||
const { orgId, repoId } = req.params;
|
||||
return Response.json({ orgId, repoId });
|
||||
},
|
||||
|
||||
"/orgs/:orgId/repos/:repoId/settings": (
|
||||
// optional: you can explicitly pass a type to BunRequest:
|
||||
req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
|
||||
) => {
|
||||
const { orgId, repoId } = req.params;
|
||||
return Response.json({ orgId, repoId });
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Percent-encoded route parameter values are automatically decoded. Unicode characters are supported. Invalid unicode is replaced with the unicode replacement character `&0xFFFD;`.
|
||||
|
||||
### Static responses
|
||||
|
||||
Routes can also be `Response` objects (without the handler function). Bun.serve() optimizes it for zero-allocation dispatch - perfect for health checks, redirects, and fixed content:
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
routes: {
|
||||
// Health checks
|
||||
"/health": new Response("OK"),
|
||||
"/ready": new Response("Ready", {
|
||||
headers: {
|
||||
// Pass custom headers
|
||||
"X-Ready": "1",
|
||||
},
|
||||
}),
|
||||
|
||||
// Redirects
|
||||
"/blog": Response.redirect("https://bun.com/blog"),
|
||||
|
||||
// API responses
|
||||
"/api/config": Response.json({
|
||||
version: "1.0.0",
|
||||
env: "production",
|
||||
}),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Static responses do not allocate additional memory after initialization. You can generally expect at least a 15% performance improvement over manually returning a `Response` object.
|
||||
|
||||
Static route responses are cached for the lifetime of the server object. To reload static routes, call `server.reload(options)`.
|
||||
|
||||
### File Responses vs Static Responses
|
||||
|
||||
When serving files in routes, there are two distinct behaviors depending on whether you buffer the file content or serve it directly:
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
routes: {
|
||||
// Static route - content is buffered in memory at startup
|
||||
"/logo.png": new Response(await Bun.file("./logo.png").bytes()),
|
||||
|
||||
// File route - content is read from filesystem on each request
|
||||
"/download.zip": new Response(Bun.file("./download.zip")),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**Static routes** (`new Response(await file.bytes())`) buffer content in memory at startup:
|
||||
|
||||
- **Zero filesystem I/O** during requests - content served entirely from memory
|
||||
- **ETag support** - Automatically generates and validates ETags for caching
|
||||
- **If-None-Match** - Returns `304 Not Modified` when client ETag matches
|
||||
- **No 404 handling** - Missing files cause startup errors, not runtime 404s
|
||||
- **Memory usage** - Full file content stored in RAM
|
||||
- **Best for**: Small static assets, API responses, frequently accessed files
|
||||
|
||||
**File routes** (`new Response(Bun.file(path))`) read from filesystem per request:
|
||||
|
||||
- **Filesystem reads** on each request - checks file existence and reads content
|
||||
- **Built-in 404 handling** - Returns `404 Not Found` if file doesn't exist or becomes inaccessible
|
||||
- **Last-Modified support** - Uses file modification time for `If-Modified-Since` headers
|
||||
- **If-Modified-Since** - Returns `304 Not Modified` when file hasn't changed since client's cached version
|
||||
- **Range request support** - Automatically handles partial content requests with `Content-Range` headers
|
||||
- **Streaming transfers** - Uses buffered reader with backpressure handling for efficient memory usage
|
||||
- **Memory efficient** - Only buffers small chunks during transfer, not entire file
|
||||
- **Best for**: Large files, dynamic content, user uploads, files that change frequently
|
||||
|
||||
---
|
||||
|
||||
## Streaming files
|
||||
|
||||
To stream a file, return a `Response` object with a `BunFile` object as the body.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
fetch(req) {
|
||||
return new Response(Bun.file("./hello.txt"));
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Info>
|
||||
⚡️ **Speed** — Bun automatically uses the [`sendfile(2)`](https://man7.org/linux/man-pages/man2/sendfile.2.html)
|
||||
system call when possible, enabling zero-copy file transfers in the kernel—the fastest way to send files.
|
||||
</Info>
|
||||
|
||||
You can send part of a file using the [`slice(start, end)`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice) method on the `Bun.file` object. This automatically sets the `Content-Range` and `Content-Length` headers on the `Response` object.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
fetch(req) {
|
||||
// parse `Range` header
|
||||
const [start = 0, end = Infinity] = req.headers
|
||||
.get("Range") // Range: bytes=0-100
|
||||
.split("=") // ["Range: bytes", "0-100"]
|
||||
.at(-1) // "0-100"
|
||||
.split("-") // ["0", "100"]
|
||||
.map(Number); // [0, 100]
|
||||
|
||||
// return a slice of the file
|
||||
const bigFile = Bun.file("./big-video.mp4");
|
||||
return new Response(bigFile.slice(start, end));
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `fetch` request handler
|
||||
|
||||
The `fetch` handler handles incoming requests that weren't matched by any route. It receives a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object and returns a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) or [`Promise<Response>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
fetch(req) {
|
||||
const url = new URL(req.url);
|
||||
if (url.pathname === "/") return new Response("Home page!");
|
||||
if (url.pathname === "/blog") return new Response("Blog!");
|
||||
return new Response("404!");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The `fetch` handler supports async/await:
|
||||
|
||||
```ts
|
||||
import { sleep, serve } from "bun";
|
||||
|
||||
serve({
|
||||
async fetch(req) {
|
||||
const start = performance.now();
|
||||
await sleep(10);
|
||||
const end = performance.now();
|
||||
return new Response(`Slept for ${end - start}ms`);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Promise-based responses are also supported:
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
fetch(req) {
|
||||
// Forward the request to another server.
|
||||
return fetch("https://example.com");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You can also access the `Server` object from the `fetch` handler. It's the second argument passed to the `fetch` function.
|
||||
|
||||
```ts
|
||||
// `server` is passed in as the second argument to `fetch`.
|
||||
const server = Bun.serve({
|
||||
fetch(req, server) {
|
||||
const ip = server.requestIP(req);
|
||||
return new Response(`Your IP is ${ip.address}`);
|
||||
},
|
||||
});
|
||||
```
|
||||
643
docs/runtime/http/server.mdx
Normal file
643
docs/runtime/http/server.mdx
Normal file
@@ -0,0 +1,643 @@
|
||||
---
|
||||
title: Server
|
||||
description: Use `Bun.serve` to start a high-performance HTTP server in Bun
|
||||
---
|
||||
|
||||
## Basic Setup
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
const server = Bun.serve({
|
||||
// `routes` requires Bun v1.2.3+
|
||||
routes: {
|
||||
// Static routes
|
||||
"/api/status": new Response("OK"),
|
||||
|
||||
// Dynamic routes
|
||||
"/users/:id": req => {
|
||||
return new Response(`Hello User ${req.params.id}!`);
|
||||
},
|
||||
|
||||
// Per-HTTP method handlers
|
||||
"/api/posts": {
|
||||
GET: () => new Response("List posts"),
|
||||
POST: async req => {
|
||||
const body = await req.json();
|
||||
return Response.json({ created: true, ...body });
|
||||
},
|
||||
},
|
||||
|
||||
// Wildcard route for all routes that start with "/api/" and aren't otherwise matched
|
||||
"/api/*": Response.json({ message: "Not found" }, { status: 404 }),
|
||||
|
||||
// Redirect from /blog/hello to /blog/hello/world
|
||||
"/blog/hello": Response.redirect("/blog/hello/world"),
|
||||
|
||||
// Serve a file by lazily loading it into memory
|
||||
"/favicon.ico": Bun.file("./favicon.ico"),
|
||||
},
|
||||
|
||||
// (optional) fallback for unmatched routes:
|
||||
// Required if Bun's version < 1.2.3
|
||||
fetch(req) {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Server running at ${server.url}`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTML imports
|
||||
|
||||
Bun supports importing HTML files directly into your server code, enabling full-stack applications with both server-side and client-side code. HTML imports work in two modes:
|
||||
|
||||
**Development (`bun --hot`):** Assets are bundled on-demand at runtime, enabling hot module replacement (HMR) for a fast, iterative development experience. When you change your frontend code, the browser automatically updates without a full page reload.
|
||||
|
||||
**Production (`bun build`):** When building with `bun build --target=bun`, the `import index from "./index.html"` statement resolves to a pre-built manifest object containing all bundled client assets. `Bun.serve` consumes this manifest to serve optimized assets with zero runtime bundling overhead. This is ideal for deploying to production.
|
||||
|
||||
```ts
|
||||
import myReactSinglePageApp from "./index.html";
|
||||
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/": myReactSinglePageApp,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
HTML imports don't just serve HTML — it's a full-featured frontend bundler, transpiler, and toolkit built using Bun's [bundler](/bundler), JavaScript transpiler and CSS parser. You can use this to build full-featured frontends with React, TypeScript, Tailwind CSS, and more.
|
||||
|
||||
For a complete guide on building full-stack applications with HTML imports, including detailed examples and best practices, see [/docs/bundler/fullstack](/bundler/fullstack).
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Changing the `port` and `hostname`
|
||||
|
||||
To configure which port and hostname the server will listen on, set `port` and `hostname` in the options object.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
port: 8080, // defaults to $BUN_PORT, $PORT, $NODE_PORT otherwise 3000 // [!code ++]
|
||||
hostname: "mydomain.com", // defaults to "0.0.0.0" // [!code ++]
|
||||
fetch(req) {
|
||||
return new Response("404!");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
To randomly select an available port, set `port` to `0`.
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
port: 0, // random port // [!code ++]
|
||||
fetch(req) {
|
||||
return new Response("404!");
|
||||
},
|
||||
});
|
||||
|
||||
// server.port is the randomly selected port
|
||||
console.log(server.port);
|
||||
```
|
||||
|
||||
You can view the chosen port by accessing the `port` property on the server object, or by accessing the `url` property.
|
||||
|
||||
```ts
|
||||
console.log(server.port); // 3000
|
||||
console.log(server.url); // http://localhost:3000
|
||||
```
|
||||
|
||||
### Configuring a default port
|
||||
|
||||
Bun supports several options and environment variables to configure the default port. The default port is used when the `port` option is not set.
|
||||
|
||||
- `--port` CLI flag
|
||||
|
||||
```sh
|
||||
bun --port=4002 server.ts
|
||||
```
|
||||
|
||||
- `BUN_PORT` environment variable
|
||||
|
||||
```sh
|
||||
BUN_PORT=4002 bun server.ts
|
||||
```
|
||||
|
||||
- `PORT` environment variable
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
PORT=4002 bun server.ts
|
||||
```
|
||||
|
||||
- `NODE_PORT` environment variable
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
NODE_PORT=4002 bun server.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Unix domain sockets
|
||||
|
||||
To listen on a [unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket), pass the `unix` option with the path to the socket.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
unix: "/tmp/my-socket.sock", // path to socket
|
||||
fetch(req) {
|
||||
return new Response(`404!`);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Abstract namespace sockets
|
||||
|
||||
Bun supports Linux abstract namespace sockets. To use an abstract namespace socket, prefix the `unix` path with a null byte.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
unix: "\0my-abstract-socket", // abstract namespace socket
|
||||
fetch(req) {
|
||||
return new Response(`404!`);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Unlike unix domain sockets, abstract namespace sockets are not bound to the filesystem and are automatically removed when the last reference to the socket is closed.
|
||||
|
||||
---
|
||||
|
||||
## idleTimeout
|
||||
|
||||
To configure the idle timeout, set the `idleTimeout` field in Bun.serve.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
// 10 seconds:
|
||||
idleTimeout: 10,
|
||||
|
||||
fetch(req) {
|
||||
return new Response("Bun!");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This is the maximum amount of time a connection is allowed to be idle before the server closes it. A connection is idling if there is no data sent or received.
|
||||
|
||||
---
|
||||
|
||||
## export default syntax
|
||||
|
||||
Thus far, the examples on this page have used the explicit `Bun.serve` API. Bun also supports an alternate syntax.
|
||||
|
||||
```ts server.ts
|
||||
import { type Serve } from "bun";
|
||||
|
||||
export default {
|
||||
fetch(req) {
|
||||
return new Response("Bun!");
|
||||
},
|
||||
} satisfies Serve;
|
||||
```
|
||||
|
||||
Instead of passing the server options into `Bun.serve`, `export default` it. This file can be executed as-is; when Bun sees a file with a `default` export containing a `fetch` handler, it passes it into `Bun.serve` under the hood.
|
||||
|
||||
---
|
||||
|
||||
## Hot Route Reloading
|
||||
|
||||
Update routes without server restarts using `server.reload()`:
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
routes: {
|
||||
"/api/version": () => Response.json({ version: "1.0.0" }),
|
||||
},
|
||||
});
|
||||
|
||||
// Deploy new routes without downtime
|
||||
server.reload({
|
||||
routes: {
|
||||
"/api/version": () => Response.json({ version: "2.0.0" }),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Server Lifecycle Methods
|
||||
|
||||
### `server.stop()`
|
||||
|
||||
To stop the server from accepting new connections:
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
fetch(req) {
|
||||
return new Response("Hello!");
|
||||
},
|
||||
});
|
||||
|
||||
// Gracefully stop the server (waits for in-flight requests)
|
||||
await server.stop();
|
||||
|
||||
// Force stop and close all active connections
|
||||
await server.stop(true);
|
||||
```
|
||||
|
||||
By default, `stop()` allows in-flight requests and WebSocket connections to complete. Pass `true` to immediately terminate all connections.
|
||||
|
||||
### `server.ref()` and `server.unref()`
|
||||
|
||||
Control whether the server keeps the Bun process alive:
|
||||
|
||||
```ts
|
||||
// Don't keep process alive if server is the only thing running
|
||||
server.unref();
|
||||
|
||||
// Restore default behavior - keep process alive
|
||||
server.ref();
|
||||
```
|
||||
|
||||
### `server.reload()`
|
||||
|
||||
Update the server's handlers without restarting:
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
routes: {
|
||||
"/api/version": Response.json({ version: "v1" }),
|
||||
},
|
||||
fetch(req) {
|
||||
return new Response("v1");
|
||||
},
|
||||
});
|
||||
|
||||
// Update to new handler
|
||||
server.reload({
|
||||
routes: {
|
||||
"/api/version": Response.json({ version: "v2" }),
|
||||
},
|
||||
fetch(req) {
|
||||
return new Response("v2");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This is useful for development and hot reloading. Only `fetch`, `error`, and `routes` can be updated.
|
||||
|
||||
---
|
||||
|
||||
## Per-Request Controls
|
||||
|
||||
### `server.timeout(Request, seconds)`
|
||||
|
||||
Set a custom idle timeout for individual requests:
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
async fetch(req, server) {
|
||||
// Set 60 second timeout for this request
|
||||
server.timeout(req, 60);
|
||||
|
||||
// If they take longer than 60 seconds to send the body, the request will be aborted
|
||||
await req.text();
|
||||
|
||||
return new Response("Done!");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Pass `0` to disable the timeout for a request.
|
||||
|
||||
### `server.requestIP(Request)`
|
||||
|
||||
Get client IP and port information:
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
fetch(req, server) {
|
||||
const address = server.requestIP(req);
|
||||
if (address) {
|
||||
return new Response(`Client IP: ${address.address}, Port: ${address.port}`);
|
||||
}
|
||||
return new Response("Unknown client");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Returns `null` for closed requests or Unix domain sockets.
|
||||
|
||||
---
|
||||
|
||||
## Server Metrics
|
||||
|
||||
### `server.pendingRequests` and `server.pendingWebSockets`
|
||||
|
||||
Monitor server activity with built-in counters:
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
fetch(req, server) {
|
||||
return new Response(
|
||||
`Active requests: ${server.pendingRequests}\n` + `Active WebSockets: ${server.pendingWebSockets}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### `server.subscriberCount(topic)`
|
||||
|
||||
Get count of subscribers for a WebSocket topic:
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
fetch(req, server) {
|
||||
const chatUsers = server.subscriberCount("chat");
|
||||
return new Response(`${chatUsers} users in chat`);
|
||||
},
|
||||
websocket: {
|
||||
message(ws) {
|
||||
ws.subscribe("chat");
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Below are Bun and Node.js implementations of a simple HTTP server that responds `Bun!` to each incoming `Request`.
|
||||
|
||||
```ts Bun
|
||||
Bun.serve({
|
||||
fetch(req: Request) {
|
||||
return new Response("Bun!");
|
||||
},
|
||||
port: 3000,
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
require("http")
|
||||
.createServer((req, res) => res.end("Bun!"))
|
||||
.listen(8080);
|
||||
```
|
||||
|
||||
The `Bun.serve` server can handle roughly 2.5x more requests per second than Node.js on Linux.
|
||||
|
||||
| Runtime | Requests per second |
|
||||
| ------- | ------------------- |
|
||||
| Node 16 | ~64,000 |
|
||||
| Bun | ~160,000 |
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
---
|
||||
|
||||
## Practical example: REST API
|
||||
|
||||
Here's a basic database-backed REST API using Bun's router with zero dependencies:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```ts server.ts expandable icon="file-code"
|
||||
import type { Post } from "./types.ts";
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const db = new Database("posts.db");
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
Bun.serve({
|
||||
routes: {
|
||||
// List posts
|
||||
"/api/posts": {
|
||||
GET: () => {
|
||||
const posts = db.query("SELECT * FROM posts").all();
|
||||
return Response.json(posts);
|
||||
},
|
||||
|
||||
// Create post
|
||||
POST: async req => {
|
||||
const post: Omit<Post, "id" | "created_at"> = await req.json();
|
||||
const id = crypto.randomUUID();
|
||||
|
||||
db.query(
|
||||
`INSERT INTO posts (id, title, content, created_at)
|
||||
VALUES (?, ?, ?, ?)`,
|
||||
).run(id, post.title, post.content, new Date().toISOString());
|
||||
|
||||
return Response.json({ id, ...post }, { status: 201 });
|
||||
},
|
||||
},
|
||||
|
||||
// Get post by ID
|
||||
"/api/posts/:id": req => {
|
||||
const post = db.query("SELECT * FROM posts WHERE id = ?").get(req.params.id);
|
||||
|
||||
if (!post) {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
|
||||
return Response.json(post);
|
||||
},
|
||||
},
|
||||
|
||||
error(error) {
|
||||
console.error(error);
|
||||
return new Response("Internal Server Error", { status: 500 });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts types.ts icon="/icons/typescript.svg"
|
||||
export interface Post {
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
created_at: string;
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
```ts expandable See TypeScript Definitions
|
||||
interface Server extends Disposable {
|
||||
/**
|
||||
* Stop the server from accepting new connections.
|
||||
* @param closeActiveConnections If true, immediately terminates all connections
|
||||
* @returns Promise that resolves when the server has stopped
|
||||
*/
|
||||
stop(closeActiveConnections?: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* Update handlers without restarting the server.
|
||||
* Only fetch and error handlers can be updated.
|
||||
*/
|
||||
reload(options: Serve): void;
|
||||
|
||||
/**
|
||||
* Make a request to the running server.
|
||||
* Useful for testing or internal routing.
|
||||
*/
|
||||
fetch(request: Request | string): Response | Promise<Response>;
|
||||
|
||||
/**
|
||||
* Upgrade an HTTP request to a WebSocket connection.
|
||||
* @returns true if upgrade successful, false if failed
|
||||
*/
|
||||
upgrade<T = undefined>(
|
||||
request: Request,
|
||||
options?: {
|
||||
headers?: Bun.HeadersInit;
|
||||
data?: T;
|
||||
},
|
||||
): boolean;
|
||||
|
||||
/**
|
||||
* Publish a message to all WebSocket clients subscribed to a topic.
|
||||
* @returns Bytes sent, 0 if dropped, -1 if backpressure applied
|
||||
*/
|
||||
publish(
|
||||
topic: string,
|
||||
data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer,
|
||||
compress?: boolean,
|
||||
): ServerWebSocketSendStatus;
|
||||
|
||||
/**
|
||||
* Get count of WebSocket clients subscribed to a topic.
|
||||
*/
|
||||
subscriberCount(topic: string): number;
|
||||
|
||||
/**
|
||||
* Get client IP address and port.
|
||||
* @returns null for closed requests or Unix sockets
|
||||
*/
|
||||
requestIP(request: Request): SocketAddress | null;
|
||||
|
||||
/**
|
||||
* Set custom idle timeout for a request.
|
||||
* @param seconds Timeout in seconds, 0 to disable
|
||||
*/
|
||||
timeout(request: Request, seconds: number): void;
|
||||
|
||||
/**
|
||||
* Keep process alive while server is running.
|
||||
*/
|
||||
ref(): void;
|
||||
|
||||
/**
|
||||
* Allow process to exit if server is only thing running.
|
||||
*/
|
||||
unref(): void;
|
||||
|
||||
/** Number of in-flight HTTP requests */
|
||||
readonly pendingRequests: number;
|
||||
|
||||
/** Number of active WebSocket connections */
|
||||
readonly pendingWebSockets: number;
|
||||
|
||||
/** Server URL including protocol, hostname and port */
|
||||
readonly url: URL;
|
||||
|
||||
/** Port server is listening on */
|
||||
readonly port: number;
|
||||
|
||||
/** Hostname server is bound to */
|
||||
readonly hostname: string;
|
||||
|
||||
/** Whether server is in development mode */
|
||||
readonly development: boolean;
|
||||
|
||||
/** Server instance identifier */
|
||||
readonly id: string;
|
||||
}
|
||||
|
||||
interface WebSocketHandler<T = undefined> {
|
||||
/** Maximum WebSocket message size in bytes */
|
||||
maxPayloadLength?: number;
|
||||
|
||||
/** Bytes of queued messages before applying backpressure */
|
||||
backpressureLimit?: number;
|
||||
|
||||
/** Whether to close connection when backpressure limit hit */
|
||||
closeOnBackpressureLimit?: boolean;
|
||||
|
||||
/** Called when backpressure is relieved */
|
||||
drain?(ws: ServerWebSocket<T>): void | Promise<void>;
|
||||
|
||||
/** Seconds before idle timeout */
|
||||
idleTimeout?: number;
|
||||
|
||||
/** Enable per-message deflate compression */
|
||||
perMessageDeflate?:
|
||||
| boolean
|
||||
| {
|
||||
compress?: WebSocketCompressor | boolean;
|
||||
decompress?: WebSocketCompressor | boolean;
|
||||
};
|
||||
|
||||
/** Send ping frames to keep connection alive */
|
||||
sendPings?: boolean;
|
||||
|
||||
/** Whether server receives its own published messages */
|
||||
publishToSelf?: boolean;
|
||||
|
||||
/** Called when connection opened */
|
||||
open?(ws: ServerWebSocket<T>): void | Promise<void>;
|
||||
|
||||
/** Called when message received */
|
||||
message(ws: ServerWebSocket<T>, message: string | Buffer): void | Promise<void>;
|
||||
|
||||
/** Called when connection closed */
|
||||
close?(ws: ServerWebSocket<T>, code: number, reason: string): void | Promise<void>;
|
||||
|
||||
/** Called when ping frame received */
|
||||
ping?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;
|
||||
|
||||
/** Called when pong frame received */
|
||||
pong?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;
|
||||
}
|
||||
|
||||
interface TLSOptions {
|
||||
/** Certificate authority chain */
|
||||
ca?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;
|
||||
|
||||
/** Server certificate */
|
||||
cert?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;
|
||||
|
||||
/** Path to DH parameters file */
|
||||
dhParamsFile?: string;
|
||||
|
||||
/** Private key */
|
||||
key?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;
|
||||
|
||||
/** Reduce TLS memory usage */
|
||||
lowMemoryMode?: boolean;
|
||||
|
||||
/** Private key passphrase */
|
||||
passphrase?: string;
|
||||
|
||||
/** OpenSSL options flags */
|
||||
secureOptions?: number;
|
||||
|
||||
/** Server name for SNI */
|
||||
serverName?: string;
|
||||
}
|
||||
```
|
||||
101
docs/runtime/http/tls.mdx
Normal file
101
docs/runtime/http/tls.mdx
Normal file
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title: TLS
|
||||
description: Enable TLS in Bun.serve
|
||||
---
|
||||
|
||||
Bun supports TLS out of the box, powered by [BoringSSL](https://boringssl.googlesource.com/boringssl). Enable TLS by passing in a value for `key` and `cert`; both are required to enable TLS.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
tls: {
|
||||
key: Bun.file("./key.pem"), // [!code ++]
|
||||
cert: Bun.file("./cert.pem"), // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The `key` and `cert` fields expect the _contents_ of your TLS key and certificate, _not a path to it_. This can be a string, `BunFile`, `TypedArray`, or `Buffer`.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
tls: {
|
||||
key: Bun.file("./key.pem"), // BunFile
|
||||
key: fs.readFileSync("./key.pem"), // Buffer
|
||||
key: fs.readFileSync("./key.pem", "utf8"), // string
|
||||
key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")], // array of above
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Passphrase
|
||||
|
||||
If your private key is encrypted with a passphrase, provide a value for `passphrase` to decrypt it.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
tls: {
|
||||
key: Bun.file("./key.pem"),
|
||||
cert: Bun.file("./cert.pem"),
|
||||
passphrase: "my-secret-passphrase", // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### CA Certificates
|
||||
|
||||
Optionally, you can override the trusted CA certificates by passing a value for `ca`. By default, the server will trust the list of well-known CAs curated by Mozilla. When `ca` is specified, the Mozilla list is overwritten.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
tls: {
|
||||
key: Bun.file("./key.pem"), // path to TLS key
|
||||
cert: Bun.file("./cert.pem"), // path to TLS cert
|
||||
ca: Bun.file("./ca.pem"), // path to root CA certificate // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Diffie-Hellman
|
||||
|
||||
To override Diffie-Hellman parameters:
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
tls: {
|
||||
dhParamsFile: "/path/to/dhparams.pem", // path to Diffie Hellman parameters // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Server name indication (SNI)
|
||||
|
||||
To configure the server name indication (SNI) for the server, set the `serverName` field in the `tls` object.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
tls: {
|
||||
serverName: "my-server.com", // SNI // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
To allow multiple server names, pass an array of objects to `tls`, each with a `serverName` field.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
tls: [
|
||||
{
|
||||
key: Bun.file("./key1.pem"),
|
||||
cert: Bun.file("./cert1.pem"),
|
||||
serverName: "my-server1.com", // [!code ++]
|
||||
},
|
||||
{
|
||||
key: Bun.file("./key2.pem"),
|
||||
cert: Bun.file("./cert2.pem"),
|
||||
serverName: "my-server2.com", // [!code ++]
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
414
docs/runtime/http/websockets.mdx
Normal file
414
docs/runtime/http/websockets.mdx
Normal file
@@ -0,0 +1,414 @@
|
||||
---
|
||||
title: WebSockets
|
||||
description: Server-side WebSockets in Bun
|
||||
---
|
||||
|
||||
`Bun.serve()` supports server-side WebSockets, with on-the-fly compression, TLS support, and a Bun-native publish-subscribe API.
|
||||
|
||||
<Info>
|
||||
|
||||
**⚡️ 7x more throughput**
|
||||
|
||||
Bun's WebSockets are fast. For a [simple chatroom](https://github.com/oven-sh/bun/tree/main/bench/websocket-server/README.md) on Linux x64, Bun can handle 7x more requests per second than Node.js + [`"ws"`](https://github.com/websockets/ws).
|
||||
|
||||
| **Messages sent per second** | **Runtime** | **Clients** |
|
||||
| ---------------------------- | ------------------------------ | ----------- |
|
||||
| ~700,000 | (`Bun.serve`) Bun v0.2.1 (x64) | 16 |
|
||||
| ~100,000 | (`ws`) Node v18.10.0 (x64) | 16 |
|
||||
|
||||
Internally Bun's WebSocket implementation is built on [uWebSockets](https://github.com/uNetworking/uWebSockets).
|
||||
|
||||
</Info>
|
||||
|
||||
---
|
||||
|
||||
## Start a WebSocket server
|
||||
|
||||
Below is a simple WebSocket server built with `Bun.serve`, in which all incoming requests are [upgraded](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) to WebSocket connections in the `fetch` handler. The socket handlers are declared in the `websocket` parameter.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
fetch(req, server) {
|
||||
// upgrade the request to a WebSocket
|
||||
if (server.upgrade(req)) {
|
||||
return; // do not return a Response
|
||||
}
|
||||
return new Response("Upgrade failed", { status: 500 });
|
||||
},
|
||||
websocket: {}, // handlers
|
||||
});
|
||||
```
|
||||
|
||||
The following WebSocket event handlers are supported:
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
fetch(req, server) {}, // upgrade logic
|
||||
websocket: {
|
||||
message(ws, message) {}, // a message is received
|
||||
open(ws) {}, // a socket is opened
|
||||
close(ws, code, message) {}, // a socket is closed
|
||||
drain(ws) {}, // the socket is ready to receive more data
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Accordion title="An API designed for speed">
|
||||
|
||||
In Bun, handlers are declared once per server, instead of per socket.
|
||||
|
||||
`ServerWebSocket` expects you to pass a `WebSocketHandler` object to the `Bun.serve()` method which has methods for `open`, `message`, `close`, `drain`, and `error`. This is different than the client-side `WebSocket` class which extends `EventTarget` (onmessage, onopen, onclose),
|
||||
|
||||
Clients tend to not have many socket connections open so an event-based API makes sense.
|
||||
|
||||
But servers tend to have **many** socket connections open, which means:
|
||||
|
||||
- Time spent adding/removing event listeners for each connection adds up
|
||||
- Extra memory spent on storing references to callbacks function for each connection
|
||||
- Usually, people create new functions for each connection, which also means more memory
|
||||
|
||||
So, instead of using an event-based API, `ServerWebSocket` expects you to pass a single object with methods for each event in `Bun.serve()` and it is reused for each connection.
|
||||
|
||||
This leads to less memory usage and less time spent adding/removing event listeners.
|
||||
|
||||
</Accordion>
|
||||
|
||||
The first argument to each handler is the instance of `ServerWebSocket` handling the event. The `ServerWebSocket` class is a fast, Bun-native implementation of [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) with some additional features.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
fetch(req, server) {}, // upgrade logic
|
||||
websocket: {
|
||||
message(ws, message) {
|
||||
ws.send(message); // echo back the message
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Sending messages
|
||||
|
||||
Each `ServerWebSocket` instance has a `.send()` method for sending messages to the client. It supports a range of input types.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg" focus={4-6}
|
||||
Bun.serve({
|
||||
fetch(req, server) {}, // upgrade logic
|
||||
websocket: {
|
||||
message(ws, message) {
|
||||
ws.send("Hello world"); // string
|
||||
ws.send(response.arrayBuffer()); // ArrayBuffer
|
||||
ws.send(new Uint8Array([1, 2, 3])); // TypedArray | DataView
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Headers
|
||||
|
||||
Once the upgrade succeeds, Bun will send a `101 Switching Protocols` response per the [spec](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism). Additional `headers` can be attached to this `Response` in the call to `server.upgrade()`.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
fetch(req, server) {
|
||||
const sessionId = await generateSessionId();
|
||||
server.upgrade(req, {
|
||||
headers: { // [!code ++]
|
||||
"Set-Cookie": `SessionId=${sessionId}`, // [!code ++]
|
||||
}, // [!code ++]
|
||||
});
|
||||
},
|
||||
websocket: {}, // handlers
|
||||
});
|
||||
```
|
||||
|
||||
### Contextual data
|
||||
|
||||
Contextual `data` can be attached to a new WebSocket in the `.upgrade()` call. This data is made available on the `ws.data` property inside the WebSocket handlers.
|
||||
|
||||
To strongly type `ws.data`, add a `data` property to the `websocket` handler object. This types `ws.data` across all lifecycle hooks.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
type WebSocketData = {
|
||||
createdAt: number;
|
||||
channelId: string;
|
||||
authToken: string;
|
||||
};
|
||||
|
||||
Bun.serve({
|
||||
fetch(req, server) {
|
||||
const cookies = new Bun.CookieMap(req.headers.get("cookie")!);
|
||||
|
||||
server.upgrade(req, {
|
||||
// this object must conform to WebSocketData
|
||||
data: {
|
||||
createdAt: Date.now(),
|
||||
channelId: new URL(req.url).searchParams.get("channelId"),
|
||||
authToken: cookies.get("X-Token"),
|
||||
},
|
||||
});
|
||||
|
||||
return undefined;
|
||||
},
|
||||
websocket: {
|
||||
// TypeScript: specify the type of ws.data like this
|
||||
data: {} as WebSocketData,
|
||||
// handler called when a message is received
|
||||
async message(ws, message) {
|
||||
// ws.data is now properly typed as WebSocketData
|
||||
const user = getUserFromToken(ws.data.authToken);
|
||||
|
||||
await saveMessageToDatabase({
|
||||
channel: ws.data.channelId,
|
||||
message: String(message),
|
||||
userId: user.id,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Info>
|
||||
**Note:** Previously, you could specify the type of `ws.data` using a type parameter on `Bun.serve`, like `Bun.serve<MyData>({...})`. This pattern was removed due to [a limitation in TypeScript](https://github.com/microsoft/TypeScript/issues/26242) in favor of the `data` property shown above.
|
||||
</Info>
|
||||
|
||||
To connect to this server from the browser, create a new `WebSocket`.
|
||||
|
||||
```js browser.js icon="file-code"
|
||||
const socket = new WebSocket("ws://localhost:3000/chat");
|
||||
|
||||
socket.addEventListener("message", event => {
|
||||
console.log(event.data);
|
||||
});
|
||||
```
|
||||
|
||||
<Info>
|
||||
**Identifying users**
|
||||
|
||||
The cookies that are currently set on the page will be sent with the WebSocket upgrade request and available on `req.headers` in the `fetch` handler. Parse these cookies to determine the identity of the connecting user and set the value of `data` accordingly.
|
||||
|
||||
</Info>
|
||||
|
||||
### Pub/Sub
|
||||
|
||||
Bun's `ServerWebSocket` implementation implements a native publish-subscribe API for topic-based broadcasting. Individual sockets can `.subscribe()` to a topic (specified with a string identifier) and `.publish()` messages to all other subscribers to that topic (excluding itself). This topic-based broadcast API is similar to [MQTT](https://en.wikipedia.org/wiki/MQTT) and [Redis Pub/Sub](https://redis.io/topics/pubsub).
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const server = Bun.serve({
|
||||
fetch(req, server) {
|
||||
const url = new URL(req.url);
|
||||
if (url.pathname === "/chat") {
|
||||
console.log(`upgrade!`);
|
||||
const username = getUsernameFromReq(req);
|
||||
const success = server.upgrade(req, { data: { username } });
|
||||
return success ? undefined : new Response("WebSocket upgrade error", { status: 400 });
|
||||
}
|
||||
|
||||
return new Response("Hello world");
|
||||
},
|
||||
websocket: {
|
||||
// TypeScript: specify the type of ws.data like this
|
||||
data: {} as { username: string },
|
||||
open(ws) {
|
||||
const msg = `${ws.data.username} has entered the chat`;
|
||||
ws.subscribe("the-group-chat");
|
||||
server.publish("the-group-chat", msg);
|
||||
},
|
||||
message(ws, message) {
|
||||
// this is a group chat
|
||||
// so the server re-broadcasts incoming message to everyone
|
||||
server.publish("the-group-chat", `${ws.data.username}: ${message}`);
|
||||
|
||||
// inspect current subscriptions
|
||||
console.log(ws.subscriptions); // ["the-group-chat"]
|
||||
},
|
||||
close(ws) {
|
||||
const msg = `${ws.data.username} has left the chat`;
|
||||
ws.unsubscribe("the-group-chat");
|
||||
server.publish("the-group-chat", msg);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Listening on ${server.hostname}:${server.port}`);
|
||||
```
|
||||
|
||||
Calling `.publish(data)` will send the message to all subscribers of a topic _except_ the socket that called `.publish()`. To send a message to all subscribers of a topic, use the `.publish()` method on the `Server` instance.
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
websocket: {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
|
||||
// listen for some external event
|
||||
server.publish("the-group-chat", "Hello world");
|
||||
```
|
||||
|
||||
### Compression
|
||||
|
||||
Per-message [compression](https://websockets.readthedocs.io/en/stable/topics/compression.html) can be enabled with the `perMessageDeflate` parameter.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
websocket: {
|
||||
perMessageDeflate: true, // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Compression can be enabled for individual messages by passing a `boolean` as the second argument to `.send()`.
|
||||
|
||||
```ts
|
||||
ws.send("Hello world", true);
|
||||
```
|
||||
|
||||
For fine-grained control over compression characteristics, refer to the [Reference](#reference).
|
||||
|
||||
### Backpressure
|
||||
|
||||
The `.send(message)` method of `ServerWebSocket` returns a `number` indicating the result of the operation.
|
||||
|
||||
- `-1` — The message was enqueued but there is backpressure
|
||||
- `0` — The message was dropped due to a connection issue
|
||||
- `1+` — The number of bytes sent
|
||||
|
||||
This gives you better control over backpressure in your server.
|
||||
|
||||
### Timeouts and limits
|
||||
|
||||
By default, Bun will close a WebSocket connection if it is idle for 120 seconds. This can be configured with the `idleTimeout` parameter.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
fetch(req, server) {}, // upgrade logic
|
||||
websocket: {
|
||||
idleTimeout: 60, // 60 seconds // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Bun will also close a WebSocket connection if it receives a message that is larger than 16 MB. This can be configured with the `maxPayloadLength` parameter.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
fetch(req, server) {}, // upgrade logic
|
||||
websocket: {
|
||||
maxPayloadLength: 1024 * 1024, // 1 MB // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Connect to a `Websocket` server
|
||||
|
||||
Bun implements the `WebSocket` class. To create a WebSocket client that connects to a `ws://` or `wss://` server, create an instance of `WebSocket`, as you would in the browser.
|
||||
|
||||
```ts
|
||||
const socket = new WebSocket("ws://localhost:3000");
|
||||
|
||||
// With subprotocol negotiation
|
||||
const socket2 = new WebSocket("ws://localhost:3000", ["soap", "wamp"]);
|
||||
```
|
||||
|
||||
In browsers, the cookies that are currently set on the page will be sent with the WebSocket upgrade request. This is a standard feature of the `WebSocket` API.
|
||||
|
||||
For convenience, Bun lets you setting custom headers directly in the constructor. This is a Bun-specific extension of the `WebSocket` standard. _This will not work in browsers._
|
||||
|
||||
```ts
|
||||
const socket = new WebSocket("ws://localhost:3000", {
|
||||
headers: {
|
||||
/* custom headers */
|
||||
}, // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
To add event listeners to the socket:
|
||||
|
||||
```ts
|
||||
// message is received
|
||||
socket.addEventListener("message", event => {});
|
||||
|
||||
// socket opened
|
||||
socket.addEventListener("open", event => {});
|
||||
|
||||
// socket closed
|
||||
socket.addEventListener("close", event => {});
|
||||
|
||||
// error handler
|
||||
socket.addEventListener("error", event => {});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
```ts See Typescript Definitions expandable
|
||||
namespace Bun {
|
||||
export function serve(params: {
|
||||
fetch: (req: Request, server: Server) => Response | Promise<Response>;
|
||||
websocket?: {
|
||||
message: (ws: ServerWebSocket, message: string | ArrayBuffer | Uint8Array) => void;
|
||||
open?: (ws: ServerWebSocket) => void;
|
||||
close?: (ws: ServerWebSocket, code: number, reason: string) => void;
|
||||
error?: (ws: ServerWebSocket, error: Error) => void;
|
||||
drain?: (ws: ServerWebSocket) => void;
|
||||
|
||||
maxPayloadLength?: number; // default: 16 * 1024 * 1024 = 16 MB
|
||||
idleTimeout?: number; // default: 120 (seconds)
|
||||
backpressureLimit?: number; // default: 1024 * 1024 = 1 MB
|
||||
closeOnBackpressureLimit?: boolean; // default: false
|
||||
sendPings?: boolean; // default: true
|
||||
publishToSelf?: boolean; // default: false
|
||||
|
||||
perMessageDeflate?:
|
||||
| boolean
|
||||
| {
|
||||
compress?: boolean | Compressor;
|
||||
decompress?: boolean | Compressor;
|
||||
};
|
||||
};
|
||||
}): Server;
|
||||
}
|
||||
|
||||
type Compressor =
|
||||
| `"disable"`
|
||||
| `"shared"`
|
||||
| `"dedicated"`
|
||||
| `"3KB"`
|
||||
| `"4KB"`
|
||||
| `"8KB"`
|
||||
| `"16KB"`
|
||||
| `"32KB"`
|
||||
| `"64KB"`
|
||||
| `"128KB"`
|
||||
| `"256KB"`;
|
||||
|
||||
interface Server {
|
||||
pendingWebSockets: number;
|
||||
publish(topic: string, data: string | ArrayBufferView | ArrayBuffer, compress?: boolean): number;
|
||||
upgrade(
|
||||
req: Request,
|
||||
options?: {
|
||||
headers?: HeadersInit;
|
||||
data?: any;
|
||||
},
|
||||
): boolean;
|
||||
}
|
||||
|
||||
interface ServerWebSocket {
|
||||
readonly data: any;
|
||||
readonly readyState: number;
|
||||
readonly remoteAddress: string;
|
||||
readonly subscriptions: string[];
|
||||
send(message: string | ArrayBuffer | Uint8Array, compress?: boolean): number;
|
||||
close(code?: number, reason?: string): void;
|
||||
subscribe(topic: string): void;
|
||||
unsubscribe(topic: string): void;
|
||||
publish(topic: string, message: string | ArrayBuffer | Uint8Array): void;
|
||||
isSubscribed(topic: string): boolean;
|
||||
cork(cb: (ws: ServerWebSocket) => void): void;
|
||||
}
|
||||
```
|
||||
@@ -1,312 +0,0 @@
|
||||
Bun is a new JavaScript & TypeScript runtime designed to be a faster, leaner, and more modern drop-in replacement for Node.js.
|
||||
|
||||
## Speed
|
||||
|
||||
Bun is designed to start fast and run fast. Its transpiler and runtime are written in Zig, a modern, high-performance language. On Linux, this translates into startup times [4x faster](https://twitter.com/jarredsumner/status/1499225725492076544) than Node.js.
|
||||
|
||||
{% image src="/images/bun-run-speed.jpeg" caption="Bun vs Node.js vs Deno running Hello World" /%}
|
||||
|
||||
<!-- If no `node_modules` directory is found in the working directory or above, Bun will abandon Node.js-style module resolution in favor of the `Bun module resolution algorithm`. Under Bun-style module resolution, all packages are _auto-installed_ on the fly into a [global module cache](https://bun.com/docs/install/cache). For full details on this algorithm, refer to [Runtime > Modules](https://bun.com/docs/runtime/modules). -->
|
||||
|
||||
Performance sensitive APIs like `Buffer`, `fetch`, and `Response` are heavily profiled and optimized. Under the hood Bun uses the [JavaScriptCore engine](https://developer.apple.com/documentation/javascriptcore), which is developed by Apple for Safari. It starts and runs faster than V8, the engine used by Node.js and Chromium-based browsers.
|
||||
|
||||
## TypeScript
|
||||
|
||||
Bun natively supports TypeScript out of the box. All files are transpiled on the fly by Bun's fast native transpiler before being executed. Similar to other build tools, Bun does not perform typechecking; it simply removes type annotations from the file.
|
||||
|
||||
```bash
|
||||
$ bun index.js
|
||||
$ bun index.jsx
|
||||
$ bun index.ts
|
||||
$ bun index.tsx
|
||||
```
|
||||
|
||||
Some aspects of Bun's runtime behavior are affected by the contents of your `tsconfig.json` file. Refer to [Runtime > TypeScript](https://bun.com/docs/runtime/typescript) page for details.
|
||||
|
||||
<!-- Before execution, Bun internally transforms all source files to vanilla JavaScript using its fast native transpiler. The transpiler looks at the files extension to determine how to handle it. -->
|
||||
|
||||
<!--
|
||||
|
||||
every file before execution. Its transpiler can directly run TypeScript and JSX `{.js|.jsx|.ts|.tsx}` files directly. During execution, Bun internally transpiles all files (including `.js` files) to vanilla JavaScript with its fast native transpiler. -->
|
||||
|
||||
<!-- A loader determines how to map imports & file extensions to transforms and output. -->
|
||||
|
||||
<!-- Currently, Bun implements the following loaders: -->
|
||||
|
||||
<!-- {% table %}
|
||||
|
||||
- Extension
|
||||
- Transforms
|
||||
- Output (internal)
|
||||
|
||||
---
|
||||
|
||||
- `.js`
|
||||
- JSX + JavaScript
|
||||
- `.js`
|
||||
|
||||
---
|
||||
|
||||
- `.jsx`
|
||||
- JSX + JavaScript
|
||||
- `.js`
|
||||
|
||||
---
|
||||
|
||||
- `.ts`
|
||||
- TypeScript + JavaScript
|
||||
- `.js`
|
||||
|
||||
---
|
||||
|
||||
- `.tsx`
|
||||
- TypeScript + JSX + JavaScript
|
||||
- `.js`
|
||||
|
||||
---
|
||||
|
||||
- `.mjs`
|
||||
- JavaScript
|
||||
- `.js`
|
||||
|
||||
---
|
||||
|
||||
- `.cjs`
|
||||
- JavaScript
|
||||
- `.js`
|
||||
|
||||
---
|
||||
|
||||
- `.mts`
|
||||
- TypeScript
|
||||
- `.js`
|
||||
|
||||
---
|
||||
|
||||
- `.cts`
|
||||
- TypeScript
|
||||
- `.js`
|
||||
|
||||
|
||||
{% /table %} -->
|
||||
|
||||
## JSX
|
||||
|
||||
## JSON, TOML, and YAML
|
||||
|
||||
Source files can import `*.json`, `*.toml`, or `*.yaml` files to load their contents as plain JavaScript objects.
|
||||
|
||||
```ts
|
||||
import pkg from "./package.json";
|
||||
import bunfig from "./bunfig.toml";
|
||||
import config from "./config.yaml";
|
||||
```
|
||||
|
||||
See the [YAML API documentation](/docs/api/yaml) for more details on YAML support.
|
||||
|
||||
## WASI
|
||||
|
||||
{% callout %}
|
||||
🚧 **Experimental**
|
||||
{% /callout %}
|
||||
|
||||
Bun has experimental support for WASI, the [WebAssembly System Interface](https://github.com/WebAssembly/WASI). To run a `.wasm` binary with Bun:
|
||||
|
||||
```bash
|
||||
$ bun ./my-wasm-app.wasm
|
||||
# if the filename doesn't end with ".wasm"
|
||||
$ bun run ./my-wasm-app.whatever
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
|
||||
**Note** — WASI support is based on [wasi-js](https://github.com/sagemathinc/cowasm/tree/main/core/wasi-js). Currently, it only supports WASI binaries that use the `wasi_snapshot_preview1` or `wasi_unstable` APIs. Bun's implementation is not fully optimized for performance; this will become more of a priority as WASM grows in popularity.
|
||||
{% /callout %}
|
||||
|
||||
## Node.js compatibility
|
||||
|
||||
Long-term, Bun aims for complete Node.js compatibility. Most Node.js packages already work with Bun out of the box, but certain low-level APIs like `dgram` are still unimplemented. Track the current compatibility status at [Ecosystem > Node.js](https://bun.com/docs/runtime/nodejs-apis).
|
||||
|
||||
Bun implements the Node.js module resolution algorithm, so dependencies can still be managed with `package.json`, `node_modules`, and CommonJS-style imports.
|
||||
|
||||
{% callout %}
|
||||
**Note** — We recommend using Bun's [built-in package manager](https://bun.com/docs/cli/install) for a performance boost over other npm clients.
|
||||
{% /callout %}
|
||||
|
||||
## Web APIs
|
||||
|
||||
<!-- When prudent, Bun attempts to implement Web-standard APIs instead of introducing new APIs. Refer to [Runtime > Web APIs](https://bun.com/docs/web-apis) for a list of Web APIs that are available in Bun. -->
|
||||
|
||||
Some Web APIs aren't relevant in the context of a server-first runtime like Bun, such as the [DOM API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API#html_dom_api_interfaces) or [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API). Many others, though, are broadly useful outside of the browser context; when possible, Bun implements these Web-standard APIs instead of introducing new APIs.
|
||||
|
||||
The following Web APIs are partially or completely supported.
|
||||
|
||||
{% table %}
|
||||
|
||||
---
|
||||
|
||||
- HTTP
|
||||
- [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
|
||||
|
||||
---
|
||||
|
||||
- URLs
|
||||
- [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
|
||||
|
||||
---
|
||||
|
||||
- Streams
|
||||
- [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy) [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) and associated classes
|
||||
|
||||
---
|
||||
|
||||
- Blob
|
||||
- [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
|
||||
|
||||
---
|
||||
|
||||
- WebSockets
|
||||
- [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
|
||||
|
||||
---
|
||||
|
||||
- Encoding and decoding
|
||||
- [`atob`](https://developer.mozilla.org/en-US/docs/Web/API/atob) [`btoa`](https://developer.mozilla.org/en-US/docs/Web/API/btoa) [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder)
|
||||
|
||||
---
|
||||
|
||||
- Timeouts
|
||||
- [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout)
|
||||
|
||||
---
|
||||
|
||||
- Intervals
|
||||
- [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval)[`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)
|
||||
|
||||
---
|
||||
|
||||
- Crypto
|
||||
- [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto) [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto)
|
||||
[`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey)
|
||||
|
||||
---
|
||||
|
||||
- Debugging
|
||||
|
||||
- [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console) [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/Performance)
|
||||
|
||||
---
|
||||
|
||||
- Microtasks
|
||||
- [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask)
|
||||
|
||||
---
|
||||
|
||||
- Errors
|
||||
- [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError)
|
||||
|
||||
---
|
||||
|
||||
- User interaction
|
||||
- [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert) [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) (intended for interactive CLIs)
|
||||
|
||||
<!-- - Blocking. Prints the alert message to terminal and awaits `[ENTER]` before proceeding. -->
|
||||
<!-- - Blocking. Prints confirmation message and awaits `[y/N]` input from user. Returns `true` if user entered `y` or `Y`, `false` otherwise.
|
||||
- Blocking. Prints prompt message and awaits user input. Returns the user input as a string. -->
|
||||
|
||||
---
|
||||
|
||||
- Realms
|
||||
- [`ShadowRealm`](https://github.com/tc39/proposal-shadowrealm)
|
||||
|
||||
---
|
||||
|
||||
- Events
|
||||
- [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)
|
||||
[`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) [`ErrorEvent`](https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent) [`CloseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent)
|
||||
|
||||
---
|
||||
|
||||
{% /table %}
|
||||
|
||||
## Bun APIs
|
||||
|
||||
Bun exposes a set of Bun-specific APIs on the `Bun` global object and through a number of built-in modules. These APIs represent the canonical "Bun-native" way to perform some common development tasks. They are all heavily optimized for performance. Click the link in the left column to view the associated documentation.
|
||||
|
||||
{% table %}
|
||||
|
||||
- Topic
|
||||
- APIs
|
||||
|
||||
---
|
||||
|
||||
- [HTTP](https://bun.com/docs/api/http)
|
||||
- `Bun.serve`
|
||||
|
||||
---
|
||||
|
||||
- [File I/O](https://bun.com/docs/api/file-io)
|
||||
- `Bun.file` `Bun.write`
|
||||
|
||||
---
|
||||
|
||||
- [Processes](https://bun.com/docs/api/spawn)
|
||||
- `Bun.spawn` `Bun.spawnSync`
|
||||
|
||||
---
|
||||
|
||||
- [TCP](https://bun.com/docs/api/tcp)
|
||||
- `Bun.listen` `Bun.connect`
|
||||
|
||||
---
|
||||
|
||||
- [Transpiler](https://bun.com/docs/api/transpiler)
|
||||
- `Bun.Transpiler`
|
||||
|
||||
---
|
||||
|
||||
- [Routing](https://bun.com/docs/api/file-system-router)
|
||||
- `Bun.FileSystemRouter`
|
||||
|
||||
---
|
||||
|
||||
- [HTMLRewriter](https://bun.com/docs/api/html-rewriter)
|
||||
- `HTMLRewriter`
|
||||
|
||||
---
|
||||
|
||||
- [Utils](https://bun.com/docs/api/utils)
|
||||
- `Bun.peek` `Bun.which`
|
||||
|
||||
---
|
||||
|
||||
- [SQLite](https://bun.com/docs/api/sqlite)
|
||||
- `bun:sqlite`
|
||||
|
||||
---
|
||||
|
||||
- [FFI](https://bun.com/docs/api/ffi)
|
||||
- `bun:ffi`
|
||||
|
||||
---
|
||||
|
||||
- [DNS](https://bun.com/docs/api/dns)
|
||||
- `bun:dns`
|
||||
|
||||
---
|
||||
|
||||
- [Testing](https://bun.com/docs/api/test)
|
||||
- `bun:test`
|
||||
|
||||
---
|
||||
|
||||
- [Node-API](https://bun.com/docs/api/node-api)
|
||||
- `Node-API`
|
||||
|
||||
---
|
||||
|
||||
{% /table %}
|
||||
|
||||
## Plugins
|
||||
|
||||
Support for additional file types can be implemented with plugins. Refer to [Runtime > Plugins](https://bun.com/docs/bundler/plugins) for full documentation.
|
||||
223
docs/runtime/index.mdx
Normal file
223
docs/runtime/index.mdx
Normal file
@@ -0,0 +1,223 @@
|
||||
---
|
||||
title: Bun Runtime
|
||||
description: Execute JavaScript/TypeScript files, package.json scripts, and executable packages with Bun's fast runtime.
|
||||
---
|
||||
|
||||
import Run from "/snippets/cli/run.mdx";
|
||||
|
||||
The Bun Runtime is designed to start fast and run fast.
|
||||
|
||||
Under the hood, Bun uses the [JavaScriptCore engine](https://developer.apple.com/documentation/javascriptcore), which is developed by Apple for Safari. In most cases, the startup and running performance is faster than V8, the engine used by Node.js and Chromium-based browsers. Its transpiler and runtime are written in Zig, a modern, high-performance language. On Linux, this translates into startup times [4x faster](https://twitter.com/jarredsumner/status/1499225725492076544) than Node.js.
|
||||
|
||||
| Command | Time |
|
||||
| --------------- | -------- |
|
||||
| `bun hello.js` | `5.2ms` |
|
||||
| `node hello.js` | `25.1ms` |
|
||||
|
||||
This benchmark is based on running a simple Hello World script on Linux
|
||||
|
||||
## Run a file
|
||||
|
||||
Use `bun run` to execute a source file.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run index.js
|
||||
```
|
||||
|
||||
Bun supports TypeScript and JSX out of the box. Every file is transpiled on the fly by Bun's fast native transpiler before being executed.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run index.js
|
||||
bun run index.jsx
|
||||
bun run index.ts
|
||||
bun run index.tsx
|
||||
```
|
||||
|
||||
Alternatively, you can omit the `run` keyword and use the "naked" command; it behaves identically.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun index.tsx
|
||||
bun index.js
|
||||
```
|
||||
|
||||
### `--watch`
|
||||
|
||||
To run a file in watch mode, use the `--watch` flag.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun --watch run index.tsx
|
||||
```
|
||||
|
||||
<Note>
|
||||
When using `bun run`, put Bun flags like `--watch` immediately after `bun`.
|
||||
|
||||
```bash
|
||||
bun --watch run dev # ✔️ do this
|
||||
bun run dev --watch # ❌ don't do this
|
||||
```
|
||||
|
||||
Flags that occur at the end of the command will be ignored and passed through to the `"dev"` script itself.
|
||||
|
||||
</Note>
|
||||
|
||||
## Run a `package.json` script
|
||||
|
||||
<Note>
|
||||
Compare to `npm run <script>` or `yarn <script>`
|
||||
</Note>
|
||||
|
||||
```sh
|
||||
bun [bun flags] run <script> [script flags]
|
||||
```
|
||||
|
||||
Your `package.json` can define a number of named `"scripts"` that correspond to shell commands.
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
// ... other fields
|
||||
"scripts": {
|
||||
"clean": "rm -rf dist && echo 'Done.'",
|
||||
"dev": "bun server.ts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use `bun run <script>` to execute these scripts.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run clean
|
||||
rm -rf dist && echo 'Done.'
|
||||
```
|
||||
|
||||
```txt
|
||||
Cleaning...
|
||||
Done.
|
||||
```
|
||||
|
||||
Bun executes the script command in a subshell. On Linux & macOS, it checks for the following shells in order, using the first one it finds: `bash`, `sh`, `zsh`. On Windows, it uses [bun shell](/runtime/shell) to support bash-like syntax and many common commands.
|
||||
|
||||
<Note>⚡️ The startup time for `npm run` on Linux is roughly 170ms; with Bun it is `6ms`.</Note>
|
||||
|
||||
Scripts can also be run with the shorter command `bun <script>`, however if there is a built-in bun command with the same name, the built-in command takes precedence. In this case, use the more explicit `bun run <script>` command to execute your package script.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run dev
|
||||
```
|
||||
|
||||
To see a list of available scripts, run `bun run` without any arguments.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run
|
||||
```
|
||||
|
||||
```txt
|
||||
quickstart scripts:
|
||||
|
||||
bun run clean
|
||||
rm -rf dist && echo 'Done.'
|
||||
|
||||
bun run dev
|
||||
bun server.ts
|
||||
|
||||
2 scripts
|
||||
```
|
||||
|
||||
Bun respects lifecycle hooks. For instance, `bun run clean` will execute `preclean` and `postclean`, if defined. If the `pre<script>` fails, Bun will not execute the script itself.
|
||||
|
||||
### `--bun`
|
||||
|
||||
It's common for `package.json` scripts to reference locally-installed CLIs like `vite` or `next`. These CLIs are often JavaScript files marked with a [shebang](<https://en.wikipedia.org/wiki/Shebang_(Unix)>) to indicate that they should be executed with `node`.
|
||||
|
||||
```js cli.js icon="/icons/javascript.svg"
|
||||
#!/usr/bin/env node
|
||||
|
||||
// do stuff
|
||||
```
|
||||
|
||||
By default, Bun respects this shebang and executes the script with `node`. However, you can override this behavior with the `--bun` flag. For Node.js-based CLIs, this will run the CLI with Bun instead of Node.js.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run --bun vite
|
||||
```
|
||||
|
||||
### Filtering
|
||||
|
||||
In monorepos containing multiple packages, you can use the `--filter` argument to execute scripts in many packages at once.
|
||||
|
||||
Use `bun run --filter <name_pattern> <script>` to execute `<script>` in all packages whose name matches `<name_pattern>`.
|
||||
For example, if you have subdirectories containing packages named `foo`, `bar` and `baz`, running
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run --filter 'ba*' <script>
|
||||
```
|
||||
|
||||
will execute `<script>` in both `bar` and `baz`, but not in `foo`.
|
||||
|
||||
Find more details in the docs page for [filter](/pm/filter#running-scripts-with-filter).
|
||||
|
||||
## `bun run -` to pipe code from stdin
|
||||
|
||||
`bun run -` lets you read JavaScript, TypeScript, TSX, or JSX from stdin and execute it without writing to a temporary file first.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
echo "console.log('Hello')" | bun run -
|
||||
```
|
||||
|
||||
```txt
|
||||
Hello
|
||||
```
|
||||
|
||||
You can also use `bun run -` to redirect files into Bun. For example, to run a `.js` file as if it were a `.ts` file:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
echo "console.log!('This is TypeScript!' as any)" > secretly-typescript.js
|
||||
bun run - < secretly-typescript.js
|
||||
```
|
||||
|
||||
```txt
|
||||
This is TypeScript!
|
||||
```
|
||||
|
||||
For convenience, all code is treated as TypeScript with JSX support when using `bun run -`.
|
||||
|
||||
## `bun run --console-depth`
|
||||
|
||||
Control the depth of object inspection in console output with the `--console-depth` flag.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun --console-depth 5 run index.tsx
|
||||
```
|
||||
|
||||
This sets how deeply nested objects are displayed in `console.log()` output. The default depth is `2`. Higher values show more nested properties but may produce verbose output for complex objects.
|
||||
|
||||
```ts console.ts icon="/icons/typescript.svg"
|
||||
const nested = { a: { b: { c: { d: "deep" } } } };
|
||||
console.log(nested);
|
||||
// With --console-depth 2 (default): { a: { b: [Object] } }
|
||||
// With --console-depth 4: { a: { b: { c: { d: 'deep' } } } }
|
||||
```
|
||||
|
||||
## `bun run --smol`
|
||||
|
||||
In memory-constrained environments, use the `--smol` flag to reduce memory usage at a cost to performance.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun --smol run index.tsx
|
||||
```
|
||||
|
||||
This causes the garbage collector to run more frequently, which can slow down execution. However, it can be useful in environments with limited memory. Bun automatically adjusts the garbage collector's heap size based on the available memory (accounting for cgroups and other memory limits) with and without the `--smol` flag, so this is mostly useful for cases where you want to make the heap size grow more slowly.
|
||||
|
||||
## Resolution order
|
||||
|
||||
Absolute paths and paths starting with `./` or `.\\` are always executed as source files. Unless using `bun run`, running a file with an allowed extension will prefer the file over a package.json script.
|
||||
|
||||
When there is a package.json script and a file with the same name, `bun run` prioritizes the package.json script. The full resolution order is:
|
||||
|
||||
1. package.json scripts, eg `bun run build`
|
||||
2. Source files, eg `bun run src/main.js`
|
||||
3. Binaries from project packages, eg `bun add eslint && bun run eslint`
|
||||
4. (`bun run` only) System commands, eg `bun run ls`
|
||||
|
||||
---
|
||||
|
||||
<Run />
|
||||
@@ -1,385 +0,0 @@
|
||||
Bun supports `.jsx` and `.tsx` files out of the box. Bun's internal transpiler converts JSX syntax into vanilla JavaScript before execution.
|
||||
|
||||
```tsx#react.tsx
|
||||
function Component(props: {message: string}) {
|
||||
return (
|
||||
<body>
|
||||
<h1 style={{color: 'red'}}>{props.message}</h1>
|
||||
</body>
|
||||
);
|
||||
}
|
||||
|
||||
console.log(<Component message="Hello world!" />);
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Bun reads your `tsconfig.json` or `jsconfig.json` configuration files to determines how to perform the JSX transform internally. To avoid using either of these, the following options can also be defined in [`bunfig.toml`](https://bun.com/docs/runtime/bunfig).
|
||||
|
||||
The following compiler options are respected.
|
||||
|
||||
### [`jsx`](https://www.typescriptlang.org/tsconfig#jsx)
|
||||
|
||||
How JSX constructs are transformed into vanilla JavaScript internally. The table below lists the possible values of `jsx`, along with their transpilation of the following simple JSX component:
|
||||
|
||||
```tsx
|
||||
<Box width={5}>Hello</Box>
|
||||
```
|
||||
|
||||
{% table %}
|
||||
|
||||
- Compiler options
|
||||
- Transpiled output
|
||||
|
||||
---
|
||||
|
||||
- ```json
|
||||
{
|
||||
"jsx": "react"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
import { createElement } from "react";
|
||||
createElement("Box", { width: 5 }, "Hello");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```json
|
||||
{
|
||||
"jsx": "react-jsx"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
import { jsx } from "react/jsx-runtime";
|
||||
jsx("Box", { width: 5 }, "Hello");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```json
|
||||
{
|
||||
"jsx": "react-jsxdev"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
import { jsxDEV } from "react/jsx-dev-runtime";
|
||||
jsxDEV(
|
||||
"Box",
|
||||
{ width: 5, children: "Hello" },
|
||||
undefined,
|
||||
false,
|
||||
undefined,
|
||||
this,
|
||||
);
|
||||
```
|
||||
|
||||
The `jsxDEV` variable name is a convention used by React. The `DEV` suffix is a visible way to indicate that the code is intended for use in development. The development version of React is slower and includes additional validity checks & debugging tools.
|
||||
|
||||
---
|
||||
|
||||
- ```json
|
||||
{
|
||||
"jsx": "preserve"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
// JSX is not transpiled
|
||||
// "preserve" is not supported by Bun currently
|
||||
<Box width={5}>Hello</Box>
|
||||
```
|
||||
|
||||
{% /table %}
|
||||
|
||||
<!-- {% table %}
|
||||
|
||||
- `react`
|
||||
- `React.createElement("Box", {width: 5}, "Hello")`
|
||||
|
||||
---
|
||||
|
||||
- `react-jsx`
|
||||
- `jsx("Box", {width: 5}, "Hello")`
|
||||
|
||||
---
|
||||
|
||||
- `react-jsxdev`
|
||||
- `jsxDEV("Box", {width: 5}, "Hello", void 0, false)`
|
||||
|
||||
---
|
||||
|
||||
- `preserve`
|
||||
- `<Box width={5}>Hello</Box>` Left as-is; not yet supported by Bun.
|
||||
|
||||
{% /table %} -->
|
||||
|
||||
### [`jsxFactory`](https://www.typescriptlang.org/tsconfig#jsxFactory)
|
||||
|
||||
{% callout %}
|
||||
**Note** — Only applicable when `jsx` is `react`.
|
||||
{% /callout %}
|
||||
|
||||
The function name used to represent JSX constructs. Default value is `"createElement"`. This is useful for libraries like [Preact](https://preactjs.com/) that use a different function name (`"h"`).
|
||||
|
||||
{% table %}
|
||||
|
||||
- Compiler options
|
||||
- Transpiled output
|
||||
|
||||
---
|
||||
|
||||
- ```json
|
||||
{
|
||||
"jsx": "react",
|
||||
"jsxFactory": "h"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
import { h } from "react";
|
||||
h("Box", { width: 5 }, "Hello");
|
||||
```
|
||||
|
||||
{% /table %}
|
||||
|
||||
### [`jsxFragmentFactory`](https://www.typescriptlang.org/tsconfig#jsxFragmentFactory)
|
||||
|
||||
{% callout %}
|
||||
**Note** — Only applicable when `jsx` is `react`.
|
||||
{% /callout %}
|
||||
|
||||
The function name used to represent [JSX fragments](https://react.dev/reference/react/Fragment) such as `<>Hello</>`; only applicable when `jsx` is `react`. Default value is `"Fragment"`.
|
||||
|
||||
{% table %}
|
||||
|
||||
- Compiler options
|
||||
- Transpiled output
|
||||
|
||||
---
|
||||
|
||||
- ```json
|
||||
{
|
||||
"jsx": "react",
|
||||
"jsxFactory": "myjsx",
|
||||
"jsxFragmentFactory": "MyFragment"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
// input
|
||||
<>Hello</>;
|
||||
|
||||
// output
|
||||
import { myjsx, MyFragment } from "react";
|
||||
myjsx(MyFragment, null, "Hello");
|
||||
```
|
||||
|
||||
{% /table %}
|
||||
|
||||
### [`jsxImportSource`](https://www.typescriptlang.org/tsconfig#jsxImportSource)
|
||||
|
||||
{% callout %}
|
||||
**Note** — Only applicable when `jsx` is `react-jsx` or `react-jsxdev`.
|
||||
{% /callout %}
|
||||
|
||||
The module from which the component factory function (`createElement`, `jsx`, `jsxDEV`, etc) will be imported. Default value is `"react"`. This will typically be necessary when using a component library like Preact.
|
||||
|
||||
{% table %}
|
||||
|
||||
- Compiler options
|
||||
- Transpiled output
|
||||
|
||||
---
|
||||
|
||||
- ```jsonc
|
||||
{
|
||||
"jsx": "react",
|
||||
// jsxImportSource is not defined
|
||||
// default to "react"
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
import { jsx } from "react/jsx-runtime";
|
||||
jsx("Box", { width: 5, children: "Hello" });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```jsonc
|
||||
{
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact",
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
import { jsx } from "preact/jsx-runtime";
|
||||
jsx("Box", { width: 5, children: "Hello" });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```jsonc
|
||||
{
|
||||
"jsx": "react-jsxdev",
|
||||
"jsxImportSource": "preact",
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
// /jsx-runtime is automatically appended
|
||||
import { jsxDEV } from "preact/jsx-dev-runtime";
|
||||
jsxDEV(
|
||||
"Box",
|
||||
{ width: 5, children: "Hello" },
|
||||
undefined,
|
||||
false,
|
||||
undefined,
|
||||
this,
|
||||
);
|
||||
```
|
||||
|
||||
{% /table %}
|
||||
|
||||
### `jsxSideEffects`
|
||||
|
||||
By default, Bun marks JSX expressions as `/* @__PURE__ */` so they can be removed during bundling if they are unused (known as "dead code elimination" or "tree shaking"). Set `jsxSideEffects` to `true` to prevent this behavior.
|
||||
|
||||
{% table %}
|
||||
|
||||
- Compiler options
|
||||
- Transpiled output
|
||||
|
||||
---
|
||||
|
||||
- ```jsonc
|
||||
{
|
||||
"jsx": "react",
|
||||
// jsxSideEffects is false by default
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
// JSX expressions are marked as pure
|
||||
/* @__PURE__ */ React.createElement("div", null, "Hello");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```jsonc
|
||||
{
|
||||
"jsx": "react",
|
||||
"jsxSideEffects": true,
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
// JSX expressions are not marked as pure
|
||||
React.createElement("div", null, "Hello");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```jsonc
|
||||
{
|
||||
"jsx": "react-jsx",
|
||||
"jsxSideEffects": true,
|
||||
}
|
||||
```
|
||||
|
||||
- ```tsx
|
||||
// Automatic runtime also respects jsxSideEffects
|
||||
jsx("div", { children: "Hello" });
|
||||
```
|
||||
|
||||
{% /table %}
|
||||
|
||||
This option is also available as a CLI flag:
|
||||
|
||||
```bash
|
||||
$ bun build --jsx-side-effects
|
||||
```
|
||||
|
||||
### JSX pragma
|
||||
|
||||
All of these values can be set on a per-file basis using _pragmas_. A pragma is a special comment that sets a compiler option in a particular file.
|
||||
|
||||
{% table %}
|
||||
|
||||
- Pragma
|
||||
- Equivalent config
|
||||
|
||||
---
|
||||
|
||||
- ```ts
|
||||
// @jsx h
|
||||
```
|
||||
|
||||
- ```jsonc
|
||||
{
|
||||
"jsxFactory": "h",
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```ts
|
||||
// @jsxFrag MyFragment
|
||||
```
|
||||
- ```jsonc
|
||||
{
|
||||
"jsxFragmentFactory": "MyFragment",
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- ```ts
|
||||
// @jsxImportSource preact
|
||||
```
|
||||
- ```jsonc
|
||||
{
|
||||
"jsxImportSource": "preact",
|
||||
}
|
||||
```
|
||||
|
||||
{% /table %}
|
||||
|
||||
## Logging
|
||||
|
||||
Bun implements special logging for JSX to make debugging easier. Given the following file:
|
||||
|
||||
```tsx#index.tsx
|
||||
import { Stack, UserCard } from "./components";
|
||||
|
||||
console.log(
|
||||
<Stack>
|
||||
<UserCard name="Dom" bio="Street racer and Corona lover" />
|
||||
<UserCard name="Jakob" bio="Super spy and Dom's secret brother" />
|
||||
</Stack>
|
||||
);
|
||||
```
|
||||
|
||||
Bun will pretty-print the component tree when logged:
|
||||
|
||||
{% image src="https://github.com/oven-sh/bun/assets/3084745/d29db51d-6837-44e2-b8be-84fc1b9e9d97" / %}
|
||||
|
||||
## Prop punning
|
||||
|
||||
The Bun runtime also supports "prop punning" for JSX. This is a shorthand syntax useful for assigning a variable to a prop with the same name.
|
||||
|
||||
```tsx
|
||||
function Div(props: {className: string;}) {
|
||||
const {className} = props;
|
||||
|
||||
// without punning
|
||||
return <div className={className} />;
|
||||
// with punning
|
||||
return <div {className} />;
|
||||
}
|
||||
```
|
||||
115
docs/runtime/jsx.mdx
Normal file
115
docs/runtime/jsx.mdx
Normal file
@@ -0,0 +1,115 @@
|
||||
---
|
||||
title: "JSX"
|
||||
description: "Built-in JSX and TSX support in Bun with configurable transpilation options"
|
||||
---
|
||||
|
||||
Bun supports `.jsx` and `.tsx` files out of the box. Bun's internal transpiler converts JSX syntax into vanilla JavaScript before execution.
|
||||
|
||||
```ts react.tsx icon="/icons/typescript.svg"
|
||||
function Component(props: {message: string}) {
|
||||
return (
|
||||
<body>
|
||||
<h1 style={{color: 'red'}}>{props.message}</h1>
|
||||
</body>
|
||||
);
|
||||
}
|
||||
|
||||
console.log(<Component message="Hello world!" />);
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Bun reads your `tsconfig.json` or `jsconfig.json` configuration files to determines how to perform the JSX transform internally. To avoid using either of these, the following options can also be defined in [`bunfig.toml`](/runtime/bunfig).
|
||||
|
||||
The following compiler options are respected.
|
||||
|
||||
### [`jsx`](https://www.typescriptlang.org/tsconfig#jsx)
|
||||
|
||||
How JSX constructs are transformed into vanilla JavaScript internally. The table below lists the possible values of `jsx`, along with their transpilation of the following simple JSX component:
|
||||
|
||||
```tsx
|
||||
<Box width={5}>Hello</Box>
|
||||
```
|
||||
|
||||
| Compiler options | Transpiled output |
|
||||
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `json<br/>{<br/> "jsx": "react"<br/>}<br/>` | `tsx<br/>import { createElement } from "react";<br/>createElement("Box", { width: 5 }, "Hello");<br/>` |
|
||||
| `json<br/>{<br/> "jsx": "react-jsx"<br/>}<br/>` | `tsx<br/>import { jsx } from "react/jsx-runtime";<br/>jsx("Box", { width: 5 }, "Hello");<br/>` |
|
||||
| `json<br/>{<br/> "jsx": "react-jsxdev"<br/>}<br/>` | `tsx<br/>import { jsxDEV } from "react/jsx-dev-runtime";<br/>jsxDEV(<br/> "Box",<br/> { width: 5, children: "Hello" },<br/> undefined,<br/> false,<br/> undefined,<br/> this,<br/>);<br/>`<br/><br/>The `jsxDEV` variable name is a convention used by React. The `DEV` suffix is a visible way to indicate that the code is intended for use in development. The development version of React is slower and includes additional validity checks & debugging tools. |
|
||||
| `json<br/>{<br/> "jsx": "preserve"<br/>}<br/>` | `tsx<br/>// JSX is not transpiled<br/>// "preserve" is not supported by Bun currently<br/><Box width={5}>Hello</Box><br/>` |
|
||||
|
||||
### [`jsxFactory`](https://www.typescriptlang.org/tsconfig#jsxFactory)
|
||||
|
||||
<Note>**Note** — Only applicable when `jsx` is `react`.</Note>
|
||||
|
||||
The function name used to represent JSX constructs. Default value is `"createElement"`. This is useful for libraries like [Preact](https://preactjs.com/) that use a different function name (`"h"`).
|
||||
|
||||
| Compiler options | Transpiled output |
|
||||
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `json<br/>{<br/> "jsx": "react",<br/> "jsxFactory": "h"<br/>}<br/>` | `tsx<br/>import { h } from "react";<br/>h("Box", { width: 5 }, "Hello");<br/>` |
|
||||
|
||||
### [`jsxFragmentFactory`](https://www.typescriptlang.org/tsconfig#jsxFragmentFactory)
|
||||
|
||||
<Note>**Note** — Only applicable when `jsx` is `react`.</Note>
|
||||
|
||||
The function name used to represent [JSX fragments](https://react.dev/reference/react/Fragment) such as `<>Hello</>`; only applicable when `jsx` is `react`. Default value is `"Fragment"`.
|
||||
|
||||
| Compiler options | Transpiled output |
|
||||
| ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `json<br/>{<br/> "jsx": "react",<br/> "jsxFactory": "myjsx",<br/> "jsxFragmentFactory": "MyFragment"<br/>}<br/>` | `tsx<br/>// input<br/><>Hello</>;<br/><br/>// output<br/>import { myjsx, MyFragment } from "react";<br/>myjsx(MyFragment, null, "Hello");<br/>` |
|
||||
|
||||
### [`jsxImportSource`](https://www.typescriptlang.org/tsconfig#jsxImportSource)
|
||||
|
||||
<Note>**Note** — Only applicable when `jsx` is `react-jsx` or `react-jsxdev`.</Note>
|
||||
|
||||
The module from which the component factory function (`createElement`, `jsx`, `jsxDEV`, etc) will be imported. Default value is `"react"`. This will typically be necessary when using a component library like Preact.
|
||||
|
||||
| Compiler options | Transpiled output |
|
||||
| ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `jsonc<br/>{<br/> "jsx": "react",<br/> // jsxImportSource is not defined<br/> // default to "react"<br/>}<br/>` | `tsx<br/>import { jsx } from "react/jsx-runtime";<br/>jsx("Box", { width: 5, children: "Hello" });<br/>` |
|
||||
| `jsonc<br/>{<br/> "jsx": "react-jsx",<br/> "jsxImportSource": "preact",<br/>}<br/>` | `tsx<br/>import { jsx } from "preact/jsx-runtime";<br/>jsx("Box", { width: 5, children: "Hello" });<br/>` |
|
||||
| `jsonc<br/>{<br/> "jsx": "react-jsxdev",<br/> "jsxImportSource": "preact",<br/>}<br/>` | `tsx<br/>// /jsx-runtime is automatically appended<br/>import { jsxDEV } from "preact/jsx-dev-runtime";<br/>jsxDEV(<br/> "Box",<br/> { width: 5, children: "Hello" },<br/> undefined,<br/> false,<br/> undefined,<br/> this,<br/>);<br/>` |
|
||||
|
||||
### JSX pragma
|
||||
|
||||
All of these values can be set on a per-file basis using _pragmas_. A pragma is a special comment that sets a compiler option in a particular file.
|
||||
|
||||
| Pragma | Equivalent config |
|
||||
| ---------------------------------------- | ------------------------------------------------------------------ |
|
||||
| `ts<br/>// @jsx h<br/>` | `jsonc<br/>{<br/> "jsxFactory": "h",<br/>}<br/>` |
|
||||
| `ts<br/>// @jsxFrag MyFragment<br/>` | `jsonc<br/>{<br/> "jsxFragmentFactory": "MyFragment",<br/>}<br/>` |
|
||||
| `ts<br/>// @jsxImportSource preact<br/>` | `jsonc<br/>{<br/> "jsxImportSource": "preact",<br/>}<br/>` |
|
||||
|
||||
## Logging
|
||||
|
||||
Bun implements special logging for JSX to make debugging easier. Given the following file:
|
||||
|
||||
```tsx index.tsx icon="/icons/typescript.svg"
|
||||
import { Stack, UserCard } from "./components";
|
||||
|
||||
console.log(
|
||||
<Stack>
|
||||
<UserCard name="Dom" bio="Street racer and Corona lover" />
|
||||
<UserCard name="Jakob" bio="Super spy and Dom's secret brother" />
|
||||
</Stack>,
|
||||
);
|
||||
```
|
||||
|
||||
Bun will pretty-print the component tree when logged:
|
||||
|
||||
<Frame></Frame>
|
||||
|
||||
## Prop punning
|
||||
|
||||
The Bun runtime also supports "prop punning" for JSX. This is a shorthand syntax useful for assigning a variable to a prop with the same name.
|
||||
|
||||
```tsx react.tsx icon="/icons/typescript.svg"
|
||||
function Div(props: {className: string;}) {
|
||||
const {className} = props;
|
||||
|
||||
// without punning
|
||||
return <div className={className} />;
|
||||
// with punning
|
||||
return <div {className} />;
|
||||
}
|
||||
```
|
||||
@@ -1,130 +0,0 @@
|
||||
## TypeScript
|
||||
|
||||
Bun natively supports TypeScript out of the box. All files are transpiled on the fly by Bun's fast native transpiler before being executed. Similar to other build tools, Bun does not perform typechecking; it simply removes type annotations from the file.
|
||||
|
||||
```bash
|
||||
$ bun index.js
|
||||
$ bun index.jsx
|
||||
$ bun index.ts
|
||||
$ bun index.tsx
|
||||
```
|
||||
|
||||
Some aspects of Bun's runtime behavior are affected by the contents of your `tsconfig.json` file. Refer to [Runtime > TypeScript](https://bun.com/docs/runtime/typescript) page for details.
|
||||
|
||||
## JSX
|
||||
|
||||
Bun supports `.jsx` and `.tsx` files out of the box. Bun's internal transpiler converts JSX syntax into vanilla JavaScript before execution.
|
||||
|
||||
```tsx#react.tsx
|
||||
function Component(props: {message: string}) {
|
||||
return (
|
||||
<body>
|
||||
<h1 style={{color: 'red'}}>{props.message}</h1>
|
||||
</body>
|
||||
);
|
||||
}
|
||||
|
||||
console.log(<Component message="Hello world!" />);
|
||||
```
|
||||
|
||||
Bun implements special logging for JSX to make debugging easier.
|
||||
|
||||
```bash
|
||||
$ bun run react.tsx
|
||||
<Component message="Hello world!" />
|
||||
```
|
||||
|
||||
## Text files
|
||||
|
||||
Text files can be imported as strings.
|
||||
|
||||
{% codetabs %}
|
||||
|
||||
```ts#index.ts
|
||||
import text from "./text.txt";
|
||||
console.log(text);
|
||||
// => "Hello world!"
|
||||
```
|
||||
|
||||
```txt#text.txt
|
||||
Hello world!
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
## JSON, TOML, and YAML
|
||||
|
||||
JSON, TOML, and YAML files can be directly imported from a source file. The contents will be loaded and returned as a JavaScript object.
|
||||
|
||||
```ts
|
||||
import pkg from "./package.json";
|
||||
import data from "./data.toml";
|
||||
import config from "./config.yaml";
|
||||
```
|
||||
|
||||
For more details on YAML support, see the [YAML API documentation](/docs/api/yaml).
|
||||
|
||||
## WASI
|
||||
|
||||
{% callout %}
|
||||
🚧 **Experimental**
|
||||
{% /callout %}
|
||||
|
||||
Bun has experimental support for WASI, the [WebAssembly System Interface](https://github.com/WebAssembly/WASI). To run a `.wasm` binary with Bun:
|
||||
|
||||
```bash
|
||||
$ bun ./my-wasm-app.wasm
|
||||
# if the filename doesn't end with ".wasm"
|
||||
$ bun run ./my-wasm-app.whatever
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
|
||||
**Note** — WASI support is based on [wasi-js](https://github.com/sagemathinc/cowasm/tree/main/core/wasi-js). Currently, it only supports WASI binaries that use the `wasi_snapshot_preview1` or `wasi_unstable` APIs. Bun's implementation is not fully optimized for performance; this will become more of a priority as WASM grows in popularity.
|
||||
{% /callout %}
|
||||
|
||||
## SQLite
|
||||
|
||||
You can import sqlite databases directly into your code. Bun will automatically load the database and return a `Database` object.
|
||||
|
||||
```ts
|
||||
import db from "./my.db" with { type: "sqlite" };
|
||||
console.log(db.query("select * from users LIMIT 1").get());
|
||||
```
|
||||
|
||||
This uses [`bun:sqlite`](https://bun.com/docs/api/sqlite).
|
||||
|
||||
## Custom loaders
|
||||
|
||||
Support for additional file types can be implemented with plugins. Refer to [Runtime > Plugins](https://bun.com/docs/bundler/plugins) for full documentation.
|
||||
|
||||
<!--
|
||||
|
||||
A loader determines how to map imports & file extensions to transforms and output.
|
||||
|
||||
Currently, Bun implements the following loaders:
|
||||
|
||||
| Input | Loader | Output |
|
||||
| ----- | ----------------------------- | ------ |
|
||||
| .js | JSX + JavaScript | .js |
|
||||
| .jsx | JSX + JavaScript | .js |
|
||||
| .ts | TypeScript + JavaScript | .js |
|
||||
| .tsx | TypeScript + JSX + JavaScript | .js |
|
||||
| .mjs | JavaScript | .js |
|
||||
| .cjs | JavaScript | .js |
|
||||
| .mts | TypeScript | .js |
|
||||
| .cts | TypeScript | .js |
|
||||
| .toml | TOML | .js |
|
||||
| .css | CSS | .css |
|
||||
| .env | Env | N/A |
|
||||
| .\* | file | string |
|
||||
|
||||
Everything else is treated as `file`. `file` replaces the import with a URL (or a path).
|
||||
|
||||
You can configure which loaders map to which extensions by passing `--loaders` to `bun`. For example:
|
||||
|
||||
```sh
|
||||
$ bun --loader=.js:js
|
||||
```
|
||||
|
||||
This will disable JSX transforms for `.js` files. -->
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: "Module Resolution"
|
||||
description: "How Bun resolves modules and handles imports in JavaScript and TypeScript"
|
||||
---
|
||||
|
||||
Module resolution in JavaScript is a complex topic.
|
||||
|
||||
The ecosystem is currently in the midst of a years-long transition from CommonJS modules to native ES modules. TypeScript enforces its own set of rules around import extensions that aren't compatible with ESM. Different build tools support path re-mapping via disparate non-compatible mechanisms.
|
||||
@@ -8,26 +13,26 @@ Bun aims to provide a consistent and predictable module resolution system that j
|
||||
|
||||
Consider the following files.
|
||||
|
||||
{% codetabs %}
|
||||
<CodeGroup>
|
||||
|
||||
```ts#index.ts
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { hello } from "./hello";
|
||||
|
||||
hello();
|
||||
```
|
||||
|
||||
```ts#hello.ts
|
||||
```ts hello.ts icon="/icons/typescript.svg"
|
||||
export function hello() {
|
||||
console.log("Hello world!");
|
||||
}
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
</CodeGroup>
|
||||
|
||||
When we run `index.ts`, it prints "Hello world!".
|
||||
|
||||
```bash
|
||||
$ bun index.ts
|
||||
```bash icon="terminal" terminal
|
||||
bun index.ts
|
||||
Hello world!
|
||||
```
|
||||
|
||||
@@ -50,14 +55,14 @@ In this case, we are importing from `./hello`, a relative path with no extension
|
||||
|
||||
Import paths can optionally include extensions. If an extension is present, Bun will only check for a file with that exact extension.
|
||||
|
||||
```ts#index.ts
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { hello } from "./hello";
|
||||
import { hello } from "./hello.ts"; // this works
|
||||
```
|
||||
|
||||
If you import `from "*.js{x}"`, Bun will additionally check for a matching `*.ts{x}` file, to be compatible with TypeScript's [ES module support](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#new-file-extensions).
|
||||
|
||||
```ts#index.ts
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { hello } from "./hello";
|
||||
import { hello } from "./hello.ts"; // this works
|
||||
import { hello } from "./hello.js"; // this also works
|
||||
@@ -65,15 +70,15 @@ import { hello } from "./hello.js"; // this also works
|
||||
|
||||
Bun supports both ES modules (`import`/`export` syntax) and CommonJS modules (`require()`/`module.exports`). The following CommonJS version would also work in Bun.
|
||||
|
||||
{% codetabs %}
|
||||
<CodeGroup>
|
||||
|
||||
```ts#index.js
|
||||
```js index.js icon="/icons/javascript.svg"
|
||||
const { hello } = require("./hello");
|
||||
|
||||
hello();
|
||||
```
|
||||
|
||||
```ts#hello.js
|
||||
```js hello.js icon="/icons/javascript.svg"
|
||||
function hello() {
|
||||
console.log("Hello world!");
|
||||
}
|
||||
@@ -81,10 +86,12 @@ function hello() {
|
||||
exports.hello = hello;
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
</CodeGroup>
|
||||
|
||||
That said, using CommonJS is discouraged in new projects.
|
||||
|
||||
---
|
||||
|
||||
## Module systems
|
||||
|
||||
Bun has native support for CommonJS and ES modules. ES Modules are the recommended module format for new projects, but CommonJS modules are still widely used in the Node.js ecosystem.
|
||||
@@ -100,20 +107,19 @@ In Bun's JavaScript runtime, `require` can be used by both ES Modules and Common
|
||||
|
||||
You can `require()` any file or package, even `.ts` or `.mjs` files.
|
||||
|
||||
```ts
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
const { foo } = require("./foo"); // extensions are optional
|
||||
const { bar } = require("./bar.mjs");
|
||||
const { baz } = require("./baz.tsx");
|
||||
```
|
||||
|
||||
{% details summary="What is a CommonJS module?" %}
|
||||
<Accordion title="What is a CommonJS module?">
|
||||
|
||||
In 2016, ECMAScript added support for [ES Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). ES Modules are the standard for JavaScript modules. However, millions of npm packages still use CommonJS modules.
|
||||
|
||||
CommonJS modules are modules that use `module.exports` to export values. Typically, `require` is used to import CommonJS modules.
|
||||
|
||||
```ts
|
||||
// my-commonjs.cjs
|
||||
```ts my-commonjs.cjs icon="/icons/javascript.svg"
|
||||
const stuff = require("./stuff");
|
||||
module.exports = { stuff };
|
||||
```
|
||||
@@ -136,13 +142,13 @@ The biggest difference between CommonJS and ES Modules is that CommonJS modules
|
||||
- CommonJS modules and static ES Modules (`import` statements) work in a similar synchronous way, like reading a book from start to finish.
|
||||
- ES Modules also offer the option to import modules asynchronously using the `import()` function. This is like looking up additional information in the middle of reading the book without stopping.
|
||||
|
||||
{% /details %}
|
||||
</Accordion>
|
||||
|
||||
### Using `import`
|
||||
|
||||
You can `import` any file or package, even `.cjs` files.
|
||||
|
||||
```ts
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import { foo } from "./foo"; // extensions are optional
|
||||
import bar from "./bar.ts";
|
||||
import { stuff } from "./my-commonjs.cjs";
|
||||
@@ -152,9 +158,10 @@ import { stuff } from "./my-commonjs.cjs";
|
||||
|
||||
In Bun, you can use `import` or `require` in the same file—they both work, all the time.
|
||||
|
||||
```ts
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import { stuff } from "./my-commonjs.cjs";
|
||||
import Stuff from "./my-commonjs.cjs";
|
||||
|
||||
const myStuff = require("./my-commonjs.cjs");
|
||||
```
|
||||
|
||||
@@ -164,11 +171,13 @@ The only exception to this rule is top-level await. You can't `require()` a file
|
||||
|
||||
Fortunately, very few libraries use top-level await, so this is rarely a problem. But if you're using top-level await in your application code, make sure that file isn't being `require()` from elsewhere in your application. Instead, you should use `import` or [dynamic `import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import).
|
||||
|
||||
---
|
||||
|
||||
## Importing packages
|
||||
|
||||
Bun implements the Node.js module resolution algorithm, so you can import packages from `node_modules` with a bare specifier.
|
||||
|
||||
```ts
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import { stuff } from "foo";
|
||||
```
|
||||
|
||||
@@ -199,7 +208,7 @@ NODE_PATH=./packages;./lib bun run src/index.js # Windows
|
||||
|
||||
Once it finds the `foo` package, Bun reads the `package.json` to determine how the package should be imported. To determine the package's entrypoint, Bun first reads the `exports` field and checks for the following conditions.
|
||||
|
||||
```jsonc#package.json
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"name": "foo",
|
||||
"exports": {
|
||||
@@ -207,7 +216,7 @@ Once it finds the `foo` package, Bun reads the `package.json` to determine how t
|
||||
"node": "./index.js",
|
||||
"require": "./index.js", // if importer is CommonJS
|
||||
"import": "./index.mjs", // if importer is ES module
|
||||
"default": "./index.js",
|
||||
"default": "./index.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -216,7 +225,7 @@ Whichever one of these conditions occurs _first_ in the `package.json` is used t
|
||||
|
||||
Bun respects subpath [`"exports"`](https://nodejs.org/api/packages.html#subpath-exports) and [`"imports"`](https://nodejs.org/api/packages.html#imports).
|
||||
|
||||
```jsonc#package.json
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"name": "foo",
|
||||
"exports": {
|
||||
@@ -227,7 +236,7 @@ Bun respects subpath [`"exports"`](https://nodejs.org/api/packages.html#subpath-
|
||||
|
||||
Subpath imports and conditional imports work in conjunction with each other.
|
||||
|
||||
```json
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"name": "foo",
|
||||
"exports": {
|
||||
@@ -241,18 +250,20 @@ Subpath imports and conditional imports work in conjunction with each other.
|
||||
|
||||
As in Node.js, Specifying any subpath in the `"exports"` map will prevent other subpaths from being importable; you can only import files that are explicitly exported. Given the `package.json` above:
|
||||
|
||||
```ts
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import stuff from "foo"; // this works
|
||||
import stuff from "foo/index.mjs"; // this doesn't
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
**Shipping TypeScript** — Note that Bun supports the special `"bun"` export condition. If your library is written in TypeScript, you can publish your (un-transpiled!) TypeScript files to `npm` directly. If you specify your package's `*.ts` entrypoint in the `"bun"` condition, Bun will directly import and execute your TypeScript source files.
|
||||
{% /callout %}
|
||||
<Note>
|
||||
**Shipping TypeScript** — Note that Bun supports the special `"bun"` export condition. If your library is written in
|
||||
TypeScript, you can publish your (un-transpiled!) TypeScript files to `npm` directly. If you specify your package's
|
||||
`*.ts` entrypoint in the `"bun"` condition, Bun will directly import and execute your TypeScript source files.
|
||||
</Note>
|
||||
|
||||
If `exports` is not defined, Bun falls back to `"module"` (ESM imports only) then [`"main"`](https://nodejs.org/api/packages.html#main).
|
||||
|
||||
```json#package.json
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"name": "foo",
|
||||
"module": "./index.js",
|
||||
@@ -266,17 +277,17 @@ The `--conditions` flag allows you to specify a list of conditions to use when r
|
||||
|
||||
This flag is supported in both `bun build` and Bun's runtime.
|
||||
|
||||
```sh
|
||||
```sh terminal icon="terminal"
|
||||
# Use it with bun build:
|
||||
$ bun build --conditions="react-server" --target=bun ./app/foo/route.js
|
||||
bun build --conditions="react-server" --target=bun ./app/foo/route.js
|
||||
|
||||
# Use it with bun's runtime:
|
||||
$ bun --conditions="react-server" ./app/foo/route.js
|
||||
bun --conditions="react-server" ./app/foo/route.js
|
||||
```
|
||||
|
||||
You can also use `conditions` programmatically with `Bun.build`:
|
||||
|
||||
```js
|
||||
```ts build.ts icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
conditions: ["react-server"],
|
||||
target: "bun",
|
||||
@@ -284,24 +295,35 @@ await Bun.build({
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Path re-mapping
|
||||
|
||||
In the spirit of treating TypeScript as a first-class citizen, the Bun runtime will re-map import paths according to the [`compilerOptions.paths`](https://www.typescriptlang.org/tsconfig#paths) field in `tsconfig.json`. This is a major divergence from Node.js, which doesn't support any form of import path re-mapping.
|
||||
Bun supports import path re-mapping through TypeScript's [`compilerOptions.paths`](https://www.typescriptlang.org/tsconfig#paths) in `tsconfig.json`, which works well with editors. If you aren't a TypeScript user, you can achieve the same behavior by using a [`jsconfig.json`](https://code.visualstudio.com/docs/languages/jsconfig) in your project root.
|
||||
|
||||
```jsonc#tsconfig.json
|
||||
```json tsconfig.json icon="file-json"
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"config": ["./config.ts"], // map specifier to file
|
||||
"components/*": ["components/*"], // wildcard matching
|
||||
"config": ["./config.ts"], // map specifier to file
|
||||
"components/*": ["components/*"] // wildcard matching
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you aren't a TypeScript user, you can create a [`jsconfig.json`](https://code.visualstudio.com/docs/languages/jsconfig) in your project root to achieve the same behavior.
|
||||
Bun also supports [Node.js-style subpath imports](https://nodejs.org/api/packages.html#subpath-imports) in `package.json`, where mapped paths must start with `#`. This approach doesn’t work as well with editors, but both options can be used together.
|
||||
|
||||
{% details summary="Low-level details of CommonJS interop in Bun" %}
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"imports": {
|
||||
"#config": "./config.ts", // map specifier to file
|
||||
"#components/*": "./components/*" // wildcard matching
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Accordion title="Low-level details of CommonJS interop in Bun">
|
||||
|
||||
Bun's JavaScript runtime has native support for CommonJS. When Bun's JavaScript transpiler detects usages of `module.exports`, it treats the file as CommonJS. The module loader will then wrap the transpiled module in a function shaped like this:
|
||||
|
||||
@@ -317,4 +339,36 @@ Once the CommonJS module is successfully evaluated, a Synthetic Module Record is
|
||||
|
||||
When using Bun's bundler, this works differently. The bundler will wrap the CommonJS module in a `require_${moduleName}` function which returns the `module.exports` object.
|
||||
|
||||
{% /details %}
|
||||
</Accordion>
|
||||
|
||||
---
|
||||
|
||||
## `import.meta`
|
||||
|
||||
The `import.meta` object is a way for a module to access information about itself. It's part of the JavaScript language, but its contents are not standardized. Each "host" (browser, runtime, etc) is free to implement any properties it wishes on the `import.meta` object.
|
||||
|
||||
Bun implements the following properties.
|
||||
|
||||
```ts /path/to/project/file.ts
|
||||
import.meta.dir; // => "/path/to/project"
|
||||
import.meta.file; // => "file.ts"
|
||||
import.meta.path; // => "/path/to/project/file.ts"
|
||||
import.meta.url; // => "file:///path/to/project/file.ts"
|
||||
|
||||
import.meta.main; // `true` if this file is directly executed by `bun run`
|
||||
// `false` otherwise
|
||||
|
||||
import.meta.resolve("zod"); // => "file:///path/to/project/node_modules/zod/index.js"
|
||||
```
|
||||
|
||||
| Property | Description |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `import.meta.dir` | Absolute path to the directory containing the current file, e.g. `/path/to/project`. Equivalent to `__dirname` in CommonJS modules (and Node.js) |
|
||||
| `import.meta.dirname` | An alias to `import.meta.dir`, for Node.js compatibility |
|
||||
| `import.meta.env` | An alias to `process.env`. |
|
||||
| `import.meta.file` | The name of the current file, e.g. `index.tsx` |
|
||||
| `import.meta.path` | Absolute path to the current file, e.g. `/path/to/project/index.ts`. Equivalent to `__filename` in CommonJS modules (and Node.js) |
|
||||
| `import.meta.filename` | An alias to `import.meta.path`, for Node.js compatibility |
|
||||
| `import.meta.main` | Indicates whether the current file is the entrypoint to the current `bun` process. Is the file being directly executed by `bun run` or is it being imported? |
|
||||
| `import.meta.resolve` | Resolve a module specifier (e.g. `"zod"` or `"./file.tsx"`) to a url. Equivalent to [`import.meta.resolve` in browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#resolve). Example: `import.meta.resolve("zod")` returns `"file:///path/to/project/node_modules/zod/index.ts"` |
|
||||
| `import.meta.url` | A `string` url to the current file, e.g. `file:///path/to/project/index.ts`. Equivalent to [`import.meta.url` in browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#url) |
|
||||
111
docs/runtime/networking/dns.mdx
Normal file
111
docs/runtime/networking/dns.mdx
Normal file
@@ -0,0 +1,111 @@
|
||||
---
|
||||
title: DNS
|
||||
description: Use Bun's DNS module to resolve DNS records
|
||||
---
|
||||
|
||||
Bun implements it's own `dns` module, and the `node:dns` module.
|
||||
|
||||
```ts
|
||||
import * as dns from "node:dns";
|
||||
|
||||
const addrs = await dns.promises.resolve4("bun.com", { ttl: true });
|
||||
console.log(addrs);
|
||||
// => [{ address: "172.67.161.226", family: 4, ttl: 0 }, ...]
|
||||
```
|
||||
|
||||
```ts
|
||||
import { dns } from "bun";
|
||||
|
||||
dns.prefetch("bun.com", 443);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DNS caching in Bun
|
||||
|
||||
Bun supports DNS caching. This cache makes repeated connections to the same hosts faster.
|
||||
|
||||
At the time of writing, we cache up to 255 entries for a maximum of 30 seconds (each). If any connections to a host fail, we remove the entry from the cache. When multiple connections are made to the same host simultaneously, DNS lookups are deduplicated to avoid making multiple requests for the same host.
|
||||
|
||||
This cache is automatically used by:
|
||||
|
||||
- `bun install`
|
||||
- `fetch()`
|
||||
- `node:http` (client)
|
||||
- `Bun.connect`
|
||||
- `node:net`
|
||||
- `node:tls`
|
||||
|
||||
### When should I prefetch a DNS entry?
|
||||
|
||||
Web browsers expose [`<link rel="dns-prefetch">`](https://developer.mozilla.org/en-US/docs/Web/Performance/dns-prefetch) to allow developers to prefetch DNS entries. This is useful when you know you'll need to connect to a host in the near future and want to avoid the initial DNS lookup.
|
||||
|
||||
In Bun, you can use the `dns.prefetch` API to achieve the same effect.
|
||||
|
||||
```ts
|
||||
import { dns } from "bun";
|
||||
|
||||
dns.prefetch("my.database-host.com", 5432);
|
||||
```
|
||||
|
||||
An example where you might want to use this is a database driver. When your application first starts up, you can prefetch the DNS entry for the database host so that by the time it finishes loading everything, the DNS query to resolve the database host may already be completed.
|
||||
|
||||
### `dns.prefetch`
|
||||
|
||||
<Warning>This API is experimental and may change in the future.</Warning>
|
||||
|
||||
To prefetch a DNS entry, you can use the `dns.prefetch` API. This API is useful when you know you'll need to connect to a host soon and want to avoid the initial DNS lookup.
|
||||
|
||||
```ts
|
||||
dns.prefetch(hostname: string, port: number): void;
|
||||
```
|
||||
|
||||
Here's an example:
|
||||
|
||||
```ts
|
||||
import { dns } from "bun";
|
||||
|
||||
dns.prefetch("bun.com", 443);
|
||||
//
|
||||
// ... sometime later ...
|
||||
await fetch("https://bun.com");
|
||||
```
|
||||
|
||||
### `dns.getCacheStats()`
|
||||
|
||||
<Warning>This API is experimental and may change in the future.</Warning>
|
||||
|
||||
To get the current cache stats, you can use the `dns.getCacheStats` API. This API returns an object with the following properties:
|
||||
|
||||
```ts
|
||||
{
|
||||
cacheHitsCompleted: number; // Cache hits completed
|
||||
cacheHitsInflight: number; // Cache hits in flight
|
||||
cacheMisses: number; // Cache misses
|
||||
size: number; // Number of items in the DNS cache
|
||||
errors: number; // Number of times a connection failed
|
||||
totalCount: number; // Number of times a connection was requested at all (including cache hits and misses)
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
import { dns } from "bun";
|
||||
|
||||
const stats = dns.getCacheStats();
|
||||
console.log(stats);
|
||||
// => { cacheHitsCompleted: 0, cacheHitsInflight: 0, cacheMisses: 0, size: 0, errors: 0, totalCount: 0 }
|
||||
```
|
||||
|
||||
### Configuring DNS cache TTL
|
||||
|
||||
Bun defaults to 30 seconds for the TTL of DNS cache entries. To change this, you can set the environment variable `$BUN_CONFIG_DNS_TIME_TO_LIVE_SECONDS`. For example, to set the TTL to 5 seconds:
|
||||
|
||||
```sh
|
||||
BUN_CONFIG_DNS_TIME_TO_LIVE_SECONDS=5 bun run my-script.ts
|
||||
```
|
||||
|
||||
#### Why is 30 seconds the default?
|
||||
|
||||
Unfortunately, the system API underneath (`getaddrinfo`) does not provide a way to get the TTL of a DNS entry. This means we have to pick a number arbitrarily. We chose 30 seconds because it's long enough to see the benefits of caching, and short enough to be unlikely to cause issues if a DNS entry changes. [Amazon Web Services recommends 5 seconds](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/jvm-ttl-dns.html) for the Java Virtual Machine, however the JVM defaults to cache indefinitely.
|
||||
484
docs/runtime/networking/fetch.mdx
Normal file
484
docs/runtime/networking/fetch.mdx
Normal file
@@ -0,0 +1,484 @@
|
||||
---
|
||||
title: Fetch
|
||||
description: Send HTTP requests with Bun's fetch API
|
||||
---
|
||||
|
||||
Bun implements the WHATWG `fetch` standard, with some extensions to meet the needs of server-side JavaScript.
|
||||
|
||||
Bun also implements `node:http`, but `fetch` is generally recommended instead.
|
||||
|
||||
## Sending an HTTP request
|
||||
|
||||
To send an HTTP request, use `fetch`
|
||||
|
||||
```ts
|
||||
const response = await fetch("http://example.com");
|
||||
|
||||
console.log(response.status); // => 200
|
||||
|
||||
const text = await response.text(); // or response.json(), response.formData(), etc.
|
||||
```
|
||||
|
||||
`fetch` also works with HTTPS URLs.
|
||||
|
||||
```ts
|
||||
const response = await fetch("https://example.com");
|
||||
```
|
||||
|
||||
You can also pass `fetch` a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object.
|
||||
|
||||
```ts
|
||||
const request = new Request("http://example.com", {
|
||||
method: "POST",
|
||||
body: "Hello, world!",
|
||||
});
|
||||
|
||||
const response = await fetch(request);
|
||||
```
|
||||
|
||||
### Sending a POST request
|
||||
|
||||
To send a POST request, pass an object with the `method` property set to `"POST"`.
|
||||
|
||||
```ts
|
||||
const response = await fetch("http://example.com", {
|
||||
method: "POST",
|
||||
body: "Hello, world!",
|
||||
});
|
||||
```
|
||||
|
||||
`body` can be a string, a `FormData` object, an `ArrayBuffer`, a `Blob`, and more. See the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#setting_a_body) for more information.
|
||||
|
||||
### Proxying requests
|
||||
|
||||
To proxy a request, pass an object with the `proxy` property set to a URL string:
|
||||
|
||||
```ts
|
||||
const response = await fetch("http://example.com", {
|
||||
proxy: "http://proxy.com",
|
||||
});
|
||||
```
|
||||
|
||||
You can also use an object format to send custom headers to the proxy server:
|
||||
|
||||
```ts
|
||||
const response = await fetch("http://example.com", {
|
||||
proxy: {
|
||||
url: "http://proxy.com",
|
||||
headers: {
|
||||
"Proxy-Authorization": "Bearer my-token",
|
||||
"X-Custom-Proxy-Header": "value",
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The `headers` are sent directly to the proxy 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.
|
||||
|
||||
### Custom headers
|
||||
|
||||
To set custom headers, pass an object with the `headers` property set to an object.
|
||||
|
||||
```ts
|
||||
const response = await fetch("http://example.com", {
|
||||
headers: {
|
||||
"X-Custom-Header": "value",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You can also set headers using the [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object.
|
||||
|
||||
```ts
|
||||
const headers = new Headers();
|
||||
headers.append("X-Custom-Header", "value");
|
||||
|
||||
const response = await fetch("http://example.com", {
|
||||
headers,
|
||||
});
|
||||
```
|
||||
|
||||
### Response bodies
|
||||
|
||||
To read the response body, use one of the following methods:
|
||||
|
||||
- `response.text(): Promise<string>`: Returns a promise that resolves with the response body as a string.
|
||||
- `response.json(): Promise<any>`: Returns a promise that resolves with the response body as a JSON object.
|
||||
- `response.formData(): Promise<FormData>`: Returns a promise that resolves with the response body as a `FormData` object.
|
||||
- `response.bytes(): Promise<Uint8Array>`: Returns a promise that resolves with the response body as a `Uint8Array`.
|
||||
- `response.arrayBuffer(): Promise<ArrayBuffer>`: Returns a promise that resolves with the response body as an `ArrayBuffer`.
|
||||
- `response.blob(): Promise<Blob>`: Returns a promise that resolves with the response body as a `Blob`.
|
||||
|
||||
#### Streaming response bodies
|
||||
|
||||
You can use async iterators to stream the response body.
|
||||
|
||||
```ts
|
||||
const response = await fetch("http://example.com");
|
||||
|
||||
for await (const chunk of response.body) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
You can also more directly access the `ReadableStream` object.
|
||||
|
||||
```ts
|
||||
const response = await fetch("http://example.com");
|
||||
|
||||
const stream = response.body;
|
||||
|
||||
const reader = stream.getReader();
|
||||
const { value, done } = await reader.read();
|
||||
```
|
||||
|
||||
### Streaming request bodies
|
||||
|
||||
You can also stream data in request bodies using a `ReadableStream`:
|
||||
|
||||
```ts
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue("Hello");
|
||||
controller.enqueue(" ");
|
||||
controller.enqueue("World");
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
||||
const response = await fetch("http://example.com", {
|
||||
method: "POST",
|
||||
body: stream,
|
||||
});
|
||||
```
|
||||
|
||||
When using streams with HTTP(S):
|
||||
|
||||
- The data is streamed directly to the network without buffering the entire body in memory
|
||||
- If the connection is lost, the stream will be canceled
|
||||
- The `Content-Length` header is not automatically set unless the stream has a known size
|
||||
|
||||
When using streams with S3:
|
||||
|
||||
- For PUT/POST requests, Bun automatically uses multipart upload
|
||||
- The stream is consumed in chunks and uploaded in parallel
|
||||
- Progress can be monitored through the S3 options
|
||||
|
||||
### Fetching a URL with a timeout
|
||||
|
||||
To fetch a URL with a timeout, use `AbortSignal.timeout`:
|
||||
|
||||
```ts
|
||||
const response = await fetch("http://example.com", {
|
||||
signal: AbortSignal.timeout(1000),
|
||||
});
|
||||
```
|
||||
|
||||
#### Canceling a request
|
||||
|
||||
To cancel a request, use an `AbortController`:
|
||||
|
||||
```ts
|
||||
const controller = new AbortController();
|
||||
|
||||
const response = await fetch("http://example.com", {
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
controller.abort();
|
||||
```
|
||||
|
||||
### Unix domain sockets
|
||||
|
||||
To fetch a URL using a Unix domain socket, use the `unix: string` option:
|
||||
|
||||
```ts
|
||||
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",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### TLS
|
||||
|
||||
To use a client certificate, use the `tls` option:
|
||||
|
||||
```ts
|
||||
await fetch("https://example.com", {
|
||||
tls: {
|
||||
key: Bun.file("/path/to/key.pem"),
|
||||
cert: Bun.file("/path/to/cert.pem"),
|
||||
// ca: [Bun.file("/path/to/ca.pem")],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Custom TLS Validation
|
||||
|
||||
To customize the TLS validation, use the `checkServerIdentity` option in `tls`
|
||||
|
||||
```ts
|
||||
await fetch("https://example.com", {
|
||||
tls: {
|
||||
checkServerIdentity: (hostname, peerCertificate) => {
|
||||
// Return an Error if the certificate is invalid
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This is similar to how it works in Node's `net` module.
|
||||
|
||||
#### Disable TLS validation
|
||||
|
||||
To disable TLS validation, set `rejectUnauthorized` to `false`:
|
||||
|
||||
```ts
|
||||
await fetch("https://example.com", {
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This is especially useful to avoid SSL errors when using self-signed certificates, but this disables TLS validation and should be used with caution.
|
||||
|
||||
### Request options
|
||||
|
||||
In addition to the standard fetch options, Bun provides several extensions:
|
||||
|
||||
```ts
|
||||
const response = await fetch("http://example.com", {
|
||||
// Control automatic response decompression (default: true)
|
||||
// Supports gzip, deflate, brotli (br), and zstd
|
||||
decompress: true,
|
||||
|
||||
// Disable connection reuse for this request
|
||||
keepalive: false,
|
||||
|
||||
// Debug logging level
|
||||
verbose: true, // or "curl" for more detailed output
|
||||
});
|
||||
```
|
||||
|
||||
### Protocol support
|
||||
|
||||
Beyond HTTP(S), Bun's fetch supports several additional protocols:
|
||||
|
||||
#### S3 URLs - `s3://`
|
||||
|
||||
Bun supports fetching from S3 buckets directly.
|
||||
|
||||
```ts
|
||||
// Using environment variables for credentials
|
||||
const response = await fetch("s3://my-bucket/path/to/object");
|
||||
|
||||
// Or passing credentials explicitly
|
||||
const response = await fetch("s3://my-bucket/path/to/object", {
|
||||
s3: {
|
||||
accessKeyId: "YOUR_ACCESS_KEY",
|
||||
secretAccessKey: "YOUR_SECRET_KEY",
|
||||
region: "us-east-1",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Note: Only PUT and POST methods support request bodies when using S3. For uploads, Bun automatically uses multipart upload for streaming bodies.
|
||||
|
||||
You can read more about Bun's S3 support in the [S3](/runtime/s3) documentation.
|
||||
|
||||
#### File URLs - `file://`
|
||||
|
||||
You can fetch local files using the `file:` protocol:
|
||||
|
||||
```ts
|
||||
const response = await fetch("file:///path/to/file.txt");
|
||||
const text = await response.text();
|
||||
```
|
||||
|
||||
On Windows, paths are automatically normalized:
|
||||
|
||||
```ts
|
||||
// Both work on Windows
|
||||
const response = await fetch("file:///C:/path/to/file.txt");
|
||||
const response2 = await fetch("file:///c:/path\\to/file.txt");
|
||||
```
|
||||
|
||||
#### Data URLs - `data:`
|
||||
|
||||
Bun supports the `data:` URL scheme:
|
||||
|
||||
```ts
|
||||
const response = await fetch("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==");
|
||||
const text = await response.text(); // "Hello, World!"
|
||||
```
|
||||
|
||||
#### Blob URLs - `blob:`
|
||||
|
||||
You can fetch blobs using URLs created by `URL.createObjectURL()`:
|
||||
|
||||
```ts
|
||||
const blob = new Blob(["Hello, World!"], { type: "text/plain" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const response = await fetch(url);
|
||||
```
|
||||
|
||||
### Error handling
|
||||
|
||||
Bun's fetch implementation includes several specific error cases:
|
||||
|
||||
- Using a request body with GET/HEAD methods will throw an error (which is expected for the fetch API)
|
||||
- Attempting to use both `proxy` and `unix` options together will throw an error
|
||||
- TLS certificate validation failures when `rejectUnauthorized` is true (or undefined)
|
||||
- S3 operations may throw specific errors related to authentication or permissions
|
||||
|
||||
### Content-Type handling
|
||||
|
||||
Bun automatically sets the `Content-Type` header for request bodies when not explicitly provided:
|
||||
|
||||
- For `Blob` objects, uses the blob's `type`
|
||||
- For `FormData`, sets appropriate multipart boundary
|
||||
|
||||
## Debugging
|
||||
|
||||
To help with debugging, you can pass `verbose: true` to `fetch`:
|
||||
|
||||
```ts
|
||||
const response = await fetch("http://example.com", {
|
||||
verbose: true,
|
||||
});
|
||||
```
|
||||
|
||||
This will print the request and response headers to your terminal:
|
||||
|
||||
```sh
|
||||
[fetch] > HTTP/1.1 GET http://example.com/
|
||||
[fetch] > Connection: keep-alive
|
||||
[fetch] > User-Agent: Bun/1.3.3
|
||||
[fetch] > Accept: */*
|
||||
[fetch] > Host: example.com
|
||||
[fetch] > Accept-Encoding: gzip, deflate, br, zstd
|
||||
|
||||
[fetch] < 200 OK
|
||||
[fetch] < Content-Encoding: gzip
|
||||
[fetch] < Age: 201555
|
||||
[fetch] < Cache-Control: max-age=604800
|
||||
[fetch] < Content-Type: text/html; charset=UTF-8
|
||||
[fetch] < Date: Sun, 21 Jul 2024 02:41:14 GMT
|
||||
[fetch] < Etag: "3147526947+gzip"
|
||||
[fetch] < Expires: Sun, 28 Jul 2024 02:41:14 GMT
|
||||
[fetch] < Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
|
||||
[fetch] < Server: ECAcc (sac/254F)
|
||||
[fetch] < Vary: Accept-Encoding
|
||||
[fetch] < X-Cache: HIT
|
||||
[fetch] < Content-Length: 648
|
||||
```
|
||||
|
||||
Note: `verbose: boolean` is not part of the Web standard `fetch` API and is specific to Bun.
|
||||
|
||||
## Performance
|
||||
|
||||
Before an HTTP request can be sent, the DNS lookup must be performed. This can take a significant amount of time, especially if the DNS server is slow or the network connection is poor.
|
||||
|
||||
After the DNS lookup, the TCP socket must be connected and the TLS handshake might need to be performed. This can also take a significant amount of time.
|
||||
|
||||
After the request completes, consuming the response body can also take a significant amount of time and memory.
|
||||
|
||||
At every step of the way, Bun provides APIs to help you optimize the performance of your application.
|
||||
|
||||
### DNS prefetching
|
||||
|
||||
To prefetch a DNS entry, you can use the `dns.prefetch` API. This API is useful when you know you'll need to connect to a host soon and want to avoid the initial DNS lookup.
|
||||
|
||||
```ts
|
||||
import { dns } from "bun";
|
||||
|
||||
dns.prefetch("bun.com");
|
||||
```
|
||||
|
||||
#### DNS caching
|
||||
|
||||
By default, Bun caches and deduplicates DNS queries in-memory for up to 30 seconds. You can see the cache stats by calling `dns.getCacheStats()`:
|
||||
|
||||
To learn more about DNS caching in Bun, see the [DNS caching](/runtime/networking/dns) documentation.
|
||||
|
||||
### Preconnect to a host
|
||||
|
||||
To preconnect to a host, you can use the `fetch.preconnect` API. This API is useful when you know you'll need to connect to a host soon and want to start the initial DNS lookup, TCP socket connection, and TLS handshake early.
|
||||
|
||||
```ts
|
||||
import { fetch } from "bun";
|
||||
|
||||
fetch.preconnect("https://bun.com");
|
||||
```
|
||||
|
||||
Note: calling `fetch` immediately after `fetch.preconnect` will not make your request faster. Preconnecting only helps if you know you'll need to connect to a host soon, but you're not ready to make the request yet.
|
||||
|
||||
#### Preconnect at startup
|
||||
|
||||
To preconnect to a host at startup, you can pass `--fetch-preconnect`:
|
||||
|
||||
```sh
|
||||
bun --fetch-preconnect https://bun.com ./my-script.ts
|
||||
```
|
||||
|
||||
This is sort of like `<link rel="preconnect">` in HTML.
|
||||
|
||||
This feature is not implemented on Windows yet. If you're interested in using this feature on Windows, please file an issue and we can implement support for it on Windows.
|
||||
|
||||
### Connection pooling & HTTP keep-alive
|
||||
|
||||
Bun automatically reuses connections to the same host. This is known as connection pooling. This can significantly reduce the time it takes to establish a connection. You don't need to do anything to enable this; it's automatic.
|
||||
|
||||
#### Simultaneous connection limit
|
||||
|
||||
By default, Bun limits the maximum number of simultaneous `fetch` requests to 256. We do this for several reasons:
|
||||
|
||||
- It improves overall system stability. Operating systems have an upper limit on the number of simultaneous open TCP sockets, usually in the low thousands. Nearing this limit causes your entire computer to behave strangely. Applications hang and crash.
|
||||
- It encourages HTTP Keep-Alive connection reuse. For short-lived HTTP requests, the slowest step is often the initial connection setup. Reusing connections can save a lot of time.
|
||||
|
||||
When the limit is exceeded, the requests are queued and sent as soon as the next request ends.
|
||||
|
||||
You can increase the maximum number of simultaneous connections via the `BUN_CONFIG_MAX_HTTP_REQUESTS` environment variable:
|
||||
|
||||
```sh
|
||||
BUN_CONFIG_MAX_HTTP_REQUESTS=512 bun ./my-script.ts
|
||||
```
|
||||
|
||||
The max value for this limit is currently set to 65,336. The maximum port number is 65,535, so it's quite difficult for any one computer to exceed this limit.
|
||||
|
||||
### Response buffering
|
||||
|
||||
Bun goes to great lengths to optimize the performance of reading the response body. The fastest way to read the response body is to use one of these methods:
|
||||
|
||||
- `response.text(): Promise<string>`
|
||||
- `response.json(): Promise<any>`
|
||||
- `response.formData(): Promise<FormData>`
|
||||
- `response.bytes(): Promise<Uint8Array>`
|
||||
- `response.arrayBuffer(): Promise<ArrayBuffer>`
|
||||
- `response.blob(): Promise<Blob>`
|
||||
|
||||
You can also use `Bun.write` to write the response body to a file on disk:
|
||||
|
||||
```ts
|
||||
import { write } from "bun";
|
||||
|
||||
await write("output.txt", response);
|
||||
```
|
||||
|
||||
### Implementation details
|
||||
|
||||
- Connection pooling is enabled by default but can be disabled per-request with `keepalive: false`. The `"Connection: close"` header can also be used to disable keep-alive.
|
||||
- Large file uploads are optimized using the operating system's `sendfile` syscall under specific conditions:
|
||||
- The file must be larger than 32KB
|
||||
- The request must not be using a proxy
|
||||
- On macOS, only regular files (not pipes, sockets, or devices) can use `sendfile`
|
||||
- When these conditions aren't met, or when using S3/streaming uploads, Bun falls back to reading the file into memory
|
||||
- This optimization is particularly effective for HTTP (not HTTPS) requests where the file can be sent directly from the kernel to the network stack
|
||||
- S3 operations automatically handle signing requests and merging authentication headers
|
||||
|
||||
Note: Many of these features are Bun-specific extensions to the standard fetch API.
|
||||
239
docs/runtime/networking/tcp.mdx
Normal file
239
docs/runtime/networking/tcp.mdx
Normal file
@@ -0,0 +1,239 @@
|
||||
---
|
||||
title: TCP
|
||||
description: Use Bun's native TCP API to implement performance sensitive systems like database clients, game servers, or anything that needs to communicate over TCP (instead of HTTP)
|
||||
---
|
||||
|
||||
This is a low-level API intended for library authors and for advanced use cases.
|
||||
|
||||
## Start a server (`Bun.listen()`)
|
||||
|
||||
To start a TCP server with `Bun.listen`:
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.listen({
|
||||
hostname: "localhost",
|
||||
port: 8080,
|
||||
socket: {
|
||||
data(socket, data) {}, // message received from client
|
||||
open(socket) {}, // socket opened
|
||||
close(socket, error) {}, // socket closed
|
||||
drain(socket) {}, // socket ready for more data
|
||||
error(socket, error) {}, // error handler
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Accordion title="An API designed for speed">
|
||||
|
||||
In Bun, a set of handlers are declared once per server instead of assigning callbacks to each socket, as with Node.js `EventEmitters` or the web-standard `WebSocket` API.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.listen({
|
||||
hostname: "localhost",
|
||||
port: 8080,
|
||||
socket: {
|
||||
open(socket) {},
|
||||
data(socket, data) {},
|
||||
drain(socket) {},
|
||||
close(socket, error) {},
|
||||
error(socket, error) {},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
For performance-sensitive servers, assigning listeners to each socket can cause significant garbage collector pressure and increase memory usage. By contrast, Bun only allocates one handler function for each event and shares it among all sockets. This is a small optimization, but it adds up.
|
||||
|
||||
</Accordion>
|
||||
|
||||
Contextual data can be attached to a socket in the `open` handler.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
type SocketData = { sessionId: string };
|
||||
|
||||
Bun.listen<SocketData>({
|
||||
hostname: "localhost",
|
||||
port: 8080,
|
||||
socket: {
|
||||
data(socket, data) {
|
||||
socket.write(`${socket.data.sessionId}: ack`); // [!code ++]
|
||||
},
|
||||
open(socket) {
|
||||
socket.data = { sessionId: "abcd" }; // [!code ++]
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
To enable TLS, pass a `tls` object containing `key` and `cert` fields.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.listen({
|
||||
hostname: "localhost",
|
||||
port: 8080,
|
||||
socket: {
|
||||
data(socket, data) {},
|
||||
},
|
||||
tls: {
|
||||
// can be string, BunFile, TypedArray, Buffer, or array thereof
|
||||
key: Bun.file("./key.pem"), // [!code ++]
|
||||
cert: Bun.file("./cert.pem"), // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The `key` and `cert` fields expect the _contents_ of your TLS key and certificate. This can be a string, `BunFile`, `TypedArray`, or `Buffer`.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.listen({
|
||||
// ...
|
||||
tls: {
|
||||
key: Bun.file("./key.pem"), // BunFile
|
||||
key: fs.readFileSync("./key.pem"), // Buffer
|
||||
key: fs.readFileSync("./key.pem", "utf8"), // string
|
||||
key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")], // array of above
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The result of `Bun.listen` is a server that conforms to the `TCPSocket` interface.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const server = Bun.listen({
|
||||
/* config*/
|
||||
});
|
||||
|
||||
// stop listening
|
||||
// parameter determines whether active connections are closed
|
||||
server.stop(true);
|
||||
|
||||
// let Bun process exit even if server is still listening
|
||||
server.unref();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a connection (`Bun.connect()`)
|
||||
|
||||
Use `Bun.connect` to connect to a TCP server. Specify the server to connect to with `hostname` and `port`. TCP clients can define the same set of handlers as `Bun.listen`, plus a couple client-specific handlers.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
// The client
|
||||
const socket = await Bun.connect({
|
||||
hostname: "localhost",
|
||||
port: 8080,
|
||||
|
||||
socket: {
|
||||
data(socket, data) {},
|
||||
open(socket) {},
|
||||
close(socket, error) {},
|
||||
drain(socket) {},
|
||||
error(socket, error) {},
|
||||
|
||||
// client-specific handlers
|
||||
connectError(socket, error) {}, // connection failed
|
||||
end(socket) {}, // connection closed by server
|
||||
timeout(socket) {}, // connection timed out
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
To require TLS, specify `tls: true`.
|
||||
|
||||
```ts
|
||||
// The client
|
||||
const socket = await Bun.connect({
|
||||
// ... config
|
||||
tls: true, // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hot reloading
|
||||
|
||||
Both TCP servers and sockets can be hot reloaded with new handlers.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const server = Bun.listen({
|
||||
/* config */
|
||||
});
|
||||
|
||||
// reloads handlers for all active server-side sockets
|
||||
server.reload({
|
||||
socket: {
|
||||
data() {
|
||||
// new 'data' handler
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts client.ts icon="/icons/typescript.svg"
|
||||
const socket = await Bun.connect({
|
||||
/* config */
|
||||
});
|
||||
|
||||
socket.reload({
|
||||
data() {
|
||||
// new 'data' handler
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
---
|
||||
|
||||
## Buffering
|
||||
|
||||
Currently, TCP sockets in Bun do not buffer data. For performance-sensitive code, it's important to consider buffering carefully. For example, this:
|
||||
|
||||
```ts
|
||||
socket.write("h");
|
||||
socket.write("e");
|
||||
socket.write("l");
|
||||
socket.write("l");
|
||||
socket.write("o");
|
||||
```
|
||||
|
||||
...performs significantly worse than this:
|
||||
|
||||
```ts
|
||||
socket.write("hello");
|
||||
```
|
||||
|
||||
To simplify this for now, consider using Bun's `ArrayBufferSink` with the `{stream: true}` option:
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
import { ArrayBufferSink } from "bun";
|
||||
|
||||
const sink = new ArrayBufferSink();
|
||||
sink.start({
|
||||
stream: true, // [!code ++]
|
||||
highWaterMark: 1024,
|
||||
});
|
||||
|
||||
sink.write("h");
|
||||
sink.write("e");
|
||||
sink.write("l");
|
||||
sink.write("l");
|
||||
sink.write("o");
|
||||
|
||||
queueMicrotask(() => {
|
||||
const data = sink.flush();
|
||||
const wrote = socket.write(data);
|
||||
if (wrote < data.byteLength) {
|
||||
// put it back in the sink if the socket is full
|
||||
sink.write(data.subarray(wrote));
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
**Corking**
|
||||
|
||||
Support for corking is planned, but in the meantime backpressure must be managed manually with the `drain` handler.
|
||||
|
||||
</Note>
|
||||
129
docs/runtime/networking/udp.mdx
Normal file
129
docs/runtime/networking/udp.mdx
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
title: UDP
|
||||
description: Use Bun's UDP API to implement services with advanced real-time requirements, such as voice chat.
|
||||
---
|
||||
|
||||
## Bind a UDP socket (`Bun.udpSocket()`)
|
||||
|
||||
To create a new (bound) UDP socket:
|
||||
|
||||
```ts
|
||||
const socket = await Bun.udpSocket({});
|
||||
console.log(socket.port); // assigned by the operating system
|
||||
```
|
||||
|
||||
Specify a port:
|
||||
|
||||
```ts
|
||||
const socket = await Bun.udpSocket({
|
||||
port: 41234, // [!code ++]
|
||||
});
|
||||
|
||||
console.log(socket.port); // 41234
|
||||
```
|
||||
|
||||
### Send a datagram
|
||||
|
||||
Specify the data to send, as well as the destination port and address.
|
||||
|
||||
```ts
|
||||
socket.send("Hello, world!", 41234, "127.0.0.1");
|
||||
```
|
||||
|
||||
Note that the address must be a valid IP address - `send` does not perform
|
||||
DNS resolution, as it is intended for low-latency operations.
|
||||
|
||||
### Receive datagrams
|
||||
|
||||
When creating your socket, add a callback to specify what should be done when packets are received:
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const server = await Bun.udpSocket({
|
||||
socket: {
|
||||
data(socket, buf, port, addr) {
|
||||
console.log(`message from ${addr}:${port}:`);
|
||||
console.log(buf.toString());
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const client = await Bun.udpSocket({});
|
||||
client.send("Hello!", server.port, "127.0.0.1");
|
||||
```
|
||||
|
||||
### Connections
|
||||
|
||||
While UDP does not have a concept of a connection, many UDP communications (especially as a client) involve only one peer.
|
||||
In such cases it can be beneficial to connect the socket to that peer, which specifies to which address all packets are sent
|
||||
and restricts incoming packets to that peer only.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const server = await Bun.udpSocket({
|
||||
socket: {
|
||||
data(socket, buf, port, addr) {
|
||||
console.log(`message from ${addr}:${port}:`);
|
||||
console.log(buf.toString());
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const client = await Bun.udpSocket({
|
||||
connect: {
|
||||
port: server.port,
|
||||
hostname: "127.0.0.1",
|
||||
},
|
||||
});
|
||||
|
||||
client.send("Hello");
|
||||
```
|
||||
|
||||
Because connections are implemented on the operating system level, you can potentially observe performance benefits, too.
|
||||
|
||||
### Send many packets at once using `sendMany()`
|
||||
|
||||
If you want to send a large volume of packets at once, it can make sense to batch them all together to avoid the overhead
|
||||
of making a system call for each. This is made possible by the `sendMany()` API:
|
||||
|
||||
For an unconnected socket, `sendMany` takes an array as its only argument. Each set of three array elements describes a packet:
|
||||
The first item is the data to be sent, the second is the target port, and the last is the target address.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const socket = await Bun.udpSocket({});
|
||||
|
||||
// sends 'Hello' to 127.0.0.1:41234, and 'foo' to 1.1.1.1:53 in a single operation
|
||||
socket.sendMany(["Hello", 41234, "127.0.0.1", "foo", 53, "1.1.1.1"]);
|
||||
```
|
||||
|
||||
With a connected socket, `sendMany` simply takes an array, where each element represents the data to be sent to the peer.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const socket = await Bun.udpSocket({
|
||||
connect: {
|
||||
port: 41234,
|
||||
hostname: "localhost",
|
||||
},
|
||||
});
|
||||
|
||||
socket.sendMany(["foo", "bar", "baz"]);
|
||||
```
|
||||
|
||||
`sendMany` returns the number of packets that were successfully sent. As with `send`, `sendMany` only takes valid IP addresses
|
||||
as destinations, as it does not perform DNS resolution.
|
||||
|
||||
### Handle backpressure
|
||||
|
||||
It may happen that a packet that you're sending does not fit into the operating system's packet buffer. You can detect that this
|
||||
has happened when:
|
||||
|
||||
- `send` returns `false`
|
||||
- `sendMany` returns a number smaller than the number of packets you specified. In this case, the `drain` socket handler will be called once the socket becomes writable again:
|
||||
|
||||
```ts
|
||||
const socket = await Bun.udpSocket({
|
||||
socket: {
|
||||
drain(socket) {
|
||||
// continue sending data
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
19
docs/runtime/node-api.mdx
Normal file
19
docs/runtime/node-api.mdx
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: Node-API
|
||||
description: Use Bun's Node-API module to build native add-ons to Node.js
|
||||
---
|
||||
|
||||
Node-API is an interface for building native add-ons to Node.js. Bun implements 95% of this interface from scratch, so most existing Node-API extensions will work with Bun out of the box. Track the completion status of it in [this issue](https://github.com/oven-sh/bun/issues/158).
|
||||
|
||||
As in Node.js, `.node` files (Node-API modules) can be required directly in Bun.
|
||||
|
||||
```js
|
||||
const napi = require("./my-node-module.node");
|
||||
```
|
||||
|
||||
Alternatively, use `process.dlopen`:
|
||||
|
||||
```js
|
||||
let mod = { exports: {} };
|
||||
process.dlopen(mod, "./my-node-module.node");
|
||||
```
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: "Node.js Compatibility"
|
||||
description: "Bun's compatibility status with Node.js APIs, modules, and globals"
|
||||
---
|
||||
|
||||
Every day, Bun gets closer to 100% Node.js API compatibility. Today, popular frameworks like Next.js, Express, and millions of `npm` packages intended for Node just work with Bun. To ensure compatibility, we run thousands of tests from Node.js' test suite before every release of Bun.
|
||||
|
||||
**If a package works in Node.js but doesn't work in Bun, we consider it a bug in Bun.** Please [open an issue](https://bun.com/issues) and we'll fix it.
|
||||
@@ -96,7 +101,7 @@ This page is updated regularly to reflect compatibility status of the latest ver
|
||||
|
||||
### [`node:child_process`](https://nodejs.org/api/child_process.html)
|
||||
|
||||
🟡 Missing `proc.gid` `proc.uid`. `Stream` class not exported. IPC cannot send socket handles. Node.js <> Bun IPC can be used with JSON serialization.
|
||||
🟡 Missing `proc.gid` `proc.uid`. `Stream` class not exported. IPC cannot send socket handles. Node.js ↔ Bun IPC can be used with JSON serialization.
|
||||
|
||||
### [`node:cluster`](https://nodejs.org/api/cluster.html)
|
||||
|
||||
@@ -116,7 +121,7 @@ This page is updated regularly to reflect compatibility status of the latest ver
|
||||
|
||||
### [`node:module`](https://nodejs.org/api/module.html)
|
||||
|
||||
🟡 Missing `syncBuiltinESMExports`, `Module#load()`. Overriding `require.cache` is supported for ESM & CJS modules. `module._extensions`, `module._pathCache`, `module._cache` are no-ops. `module.register` is not implemented and we recommend using a [`Bun.plugin`](https://bun.com/docs/runtime/plugins) in the meantime.
|
||||
🟡 Missing `syncBuiltinESMExports`, `Module#load()`. Overriding `require.cache` is supported for ESM & CJS modules. `module._extensions`, `module._pathCache`, `module._cache` are no-ops. `module.register` is not implemented and we recommend using a [`Bun.plugin`](/runtime/plugins) in the meantime.
|
||||
|
||||
### [`node:net`](https://nodejs.org/api/net.html)
|
||||
|
||||
@@ -144,7 +149,7 @@ This page is updated regularly to reflect compatibility status of the latest ver
|
||||
|
||||
### [`node:v8`](https://nodejs.org/api/v8.html)
|
||||
|
||||
🟡 `writeHeapSnapshot` and `getHeapSnapshot` are implemented. `serialize` and `deserialize` use JavaScriptCore's wire format instead of V8's. Other methods are not implemented. For profiling, use [`bun:jsc`](https://bun.com/docs/project/benchmarking#bunjsc) instead.
|
||||
🟡 `writeHeapSnapshot` and `getHeapSnapshot` are implemented. `serialize` and `deserialize` use JavaScriptCore's wire format instead of V8's. Other methods are not implemented. For profiling, use [`bun:jsc`](/project/benchmarking#javascript-heap-stats) instead.
|
||||
|
||||
### [`node:vm`](https://nodejs.org/api/vm.html)
|
||||
|
||||
@@ -172,7 +177,7 @@ This page is updated regularly to reflect compatibility status of the latest ver
|
||||
|
||||
### [`node:test`](https://nodejs.org/api/test.html)
|
||||
|
||||
🟡 Partly implemented. Missing mocks, snapshots, timers. Use [`bun:test`](https://bun.com/docs/cli/test) instead.
|
||||
🟡 Partly implemented. Missing mocks, snapshots, timers. Use [`bun:test`](/test) instead.
|
||||
|
||||
### [`node:trace_events`](https://nodejs.org/api/tracing.html)
|
||||
|
||||
@@ -240,7 +245,7 @@ The table below lists all globals implemented by Node.js and Bun's current compa
|
||||
|
||||
### [`CompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream)
|
||||
|
||||
🔴 Not implemented.
|
||||
🟢 Fully implemented.
|
||||
|
||||
### [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console)
|
||||
|
||||
@@ -268,7 +273,7 @@ The table below lists all globals implemented by Node.js and Bun's current compa
|
||||
|
||||
### [`DecompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream)
|
||||
|
||||
🔴 Not implemented.
|
||||
🟢 Fully implemented.
|
||||
|
||||
### [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event)
|
||||
|
||||
@@ -1,561 +0,0 @@
|
||||
Bun provides a universal plugin API that can be used to extend both the _runtime_ and [_bundler_](https://bun.com/docs/bundler).
|
||||
|
||||
Plugins intercept imports and perform custom loading logic: reading files, transpiling code, etc. They can be used to add support for additional file types, like `.scss` or `.yaml`. In the context of Bun's bundler, plugins can be used to implement framework-level features like CSS extraction, macros, and client-server code co-location.
|
||||
|
||||
## Usage
|
||||
|
||||
A plugin is defined as simple JavaScript object containing a `name` property and a `setup` function. Register a plugin with Bun using the `plugin` function.
|
||||
|
||||
```tsx#myPlugin.ts
|
||||
import { plugin, type BunPlugin } from "bun";
|
||||
|
||||
const myPlugin: BunPlugin = {
|
||||
name: "Custom loader",
|
||||
setup(build) {
|
||||
// implementation
|
||||
},
|
||||
};
|
||||
|
||||
plugin(myPlugin);
|
||||
```
|
||||
|
||||
Plugins have to be loaded before any other code runs! To achieve this, use the `preload` option in your [`bunfig.toml`](https://bun.com/docs/runtime/bunfig). Bun automatically loads the files/modules specified in `preload` before running a file.
|
||||
|
||||
```toml
|
||||
preload = ["./myPlugin.ts"]
|
||||
```
|
||||
|
||||
Preloads can be either local files or npm packages. Anything that can be imported/required can be preloaded.
|
||||
|
||||
```toml
|
||||
preload = ["bun-plugin-foo"]
|
||||
```
|
||||
|
||||
To preload files before `bun test`:
|
||||
|
||||
```toml
|
||||
[test]
|
||||
preload = ["./myPlugin.ts"]
|
||||
```
|
||||
|
||||
## Plugin conventions
|
||||
|
||||
By convention, third-party plugins intended for consumption should export a factory function that accepts some configuration and returns a plugin object.
|
||||
|
||||
```ts
|
||||
import { plugin } from "bun";
|
||||
import fooPlugin from "bun-plugin-foo";
|
||||
|
||||
plugin(
|
||||
fooPlugin({
|
||||
// configuration
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
Bun's plugin API is loosely based on [esbuild](https://esbuild.github.io/plugins). Only [a subset](https://bun.com/docs/bundler/vs-esbuild#plugin-api) of the esbuild API is implemented, but some esbuild plugins "just work" in Bun, like the official [MDX loader](https://mdxjs.com/packages/esbuild/):
|
||||
|
||||
```jsx
|
||||
import { plugin } from "bun";
|
||||
import mdx from "@mdx-js/esbuild";
|
||||
|
||||
plugin(mdx());
|
||||
```
|
||||
|
||||
## Loaders
|
||||
|
||||
Plugins are primarily used to extend Bun with loaders for additional file types. Let's look at a simple plugin that implements a loader for `.yaml` files.
|
||||
|
||||
```ts#yamlPlugin.ts
|
||||
import { plugin } from "bun";
|
||||
|
||||
await plugin({
|
||||
name: "YAML",
|
||||
async setup(build) {
|
||||
const { load } = await import("js-yaml");
|
||||
|
||||
// when a .yaml file is imported...
|
||||
build.onLoad({ filter: /\.(yaml|yml)$/ }, async (args) => {
|
||||
|
||||
// read and parse the file
|
||||
const text = await Bun.file(args.path).text();
|
||||
const exports = load(text) as Record<string, any>;
|
||||
|
||||
// and returns it as a module
|
||||
return {
|
||||
exports,
|
||||
loader: "object", // special loader for JS objects
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Register this file in `preload`:
|
||||
|
||||
```toml#bunfig.toml
|
||||
preload = ["./yamlPlugin.ts"]
|
||||
```
|
||||
|
||||
Once the plugin is registered, `.yaml` and `.yml` files can be directly imported.
|
||||
|
||||
{% codetabs %}
|
||||
|
||||
```ts#index.ts
|
||||
import * as data from "./data.yml"
|
||||
|
||||
console.log(data);
|
||||
```
|
||||
|
||||
```yaml#data.yml
|
||||
name: Fast X
|
||||
releaseYear: 2023
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
Note that the returned object has a `loader` property. This tells Bun which of its internal loaders should be used to handle the result. Even though we're implementing a loader for `.yaml`, the result must still be understandable by one of Bun's built-in loaders. It's loaders all the way down.
|
||||
|
||||
In this case we're using `"object"`—a built-in loader (intended for use by plugins) that converts a plain JavaScript object to an equivalent ES module. Any of Bun's built-in loaders are supported; these same loaders are used by Bun internally for handling files of various kinds. The table below is a quick reference; refer to [Bundler > Loaders](https://bun.com/docs/bundler/loaders) for complete documentation.
|
||||
|
||||
{% table %}
|
||||
|
||||
- Loader
|
||||
- Extensions
|
||||
- Output
|
||||
|
||||
---
|
||||
|
||||
- `js`
|
||||
- `.mjs` `.cjs`
|
||||
- Transpile to JavaScript files
|
||||
|
||||
---
|
||||
|
||||
- `jsx`
|
||||
- `.js` `.jsx`
|
||||
- Transform JSX then transpile
|
||||
|
||||
---
|
||||
|
||||
- `ts`
|
||||
- `.ts` `.mts` `.cts`
|
||||
- Transform TypeScript then transpile
|
||||
|
||||
---
|
||||
|
||||
- `tsx`
|
||||
- `.tsx`
|
||||
- Transform TypeScript, JSX, then transpile
|
||||
|
||||
---
|
||||
|
||||
- `toml`
|
||||
- `.toml`
|
||||
- Parse using Bun's built-in TOML parser
|
||||
|
||||
---
|
||||
|
||||
- `json`
|
||||
- `.json`
|
||||
- Parse using Bun's built-in JSON parser
|
||||
|
||||
---
|
||||
|
||||
- `napi`
|
||||
- `.node`
|
||||
- Import a native Node.js addon
|
||||
|
||||
---
|
||||
|
||||
- `wasm`
|
||||
- `.wasm`
|
||||
- Import a native Node.js addon
|
||||
|
||||
---
|
||||
|
||||
- `object`
|
||||
- _none_
|
||||
- A special loader intended for plugins that converts a plain JavaScript object to an equivalent ES module. Each key in the object corresponds to a named export.
|
||||
|
||||
{% /callout %}
|
||||
|
||||
Loading a YAML file is useful, but plugins support more than just data loading. Let's look at a plugin that lets Bun import `*.svelte` files.
|
||||
|
||||
```ts#sveltePlugin.ts
|
||||
import { plugin } from "bun";
|
||||
|
||||
await plugin({
|
||||
name: "svelte loader",
|
||||
async setup(build) {
|
||||
const { compile } = await import("svelte/compiler");
|
||||
|
||||
// when a .svelte file is imported...
|
||||
build.onLoad({ filter: /\.svelte$/ }, async ({ path }) => {
|
||||
|
||||
// read and compile it with the Svelte compiler
|
||||
const file = await Bun.file(path).text();
|
||||
const contents = compile(file, {
|
||||
filename: path,
|
||||
generate: "ssr",
|
||||
}).js.code;
|
||||
|
||||
// and return the compiled source code as "js"
|
||||
return {
|
||||
contents,
|
||||
loader: "js",
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
> Note: in a production implementation, you'd want to cache the compiled output and include additional error handling.
|
||||
|
||||
The object returned from `build.onLoad` contains the compiled source code in `contents` and specifies `"js"` as its loader. That tells Bun to consider the returned `contents` to be a JavaScript module and transpile it using Bun's built-in `js` loader.
|
||||
|
||||
With this plugin, Svelte components can now be directly imported and consumed.
|
||||
|
||||
```js
|
||||
import "./sveltePlugin.ts";
|
||||
import MySvelteComponent from "./component.svelte";
|
||||
|
||||
console.log(MySvelteComponent.render());
|
||||
```
|
||||
|
||||
## Virtual Modules
|
||||
|
||||
{% note %}
|
||||
|
||||
This feature is currently only available at runtime with `Bun.plugin` and not yet supported in the bundler, but you can mimic the behavior using `onResolve` and `onLoad`.
|
||||
|
||||
{% /note %}
|
||||
|
||||
To create virtual modules at runtime, use `builder.module(specifier, callback)` in the `setup` function of a `Bun.plugin`.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
import { plugin } from "bun";
|
||||
|
||||
plugin({
|
||||
name: "my-virtual-module",
|
||||
|
||||
setup(build) {
|
||||
build.module(
|
||||
// The specifier, which can be any string - except a built-in, such as "buffer"
|
||||
"my-transpiled-virtual-module",
|
||||
// The callback to run when the module is imported or required for the first time
|
||||
() => {
|
||||
return {
|
||||
contents: "console.log('hello world!')",
|
||||
loader: "js",
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
build.module("my-object-virtual-module", () => {
|
||||
return {
|
||||
exports: {
|
||||
foo: "bar",
|
||||
},
|
||||
loader: "object",
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Sometime later
|
||||
// All of these work
|
||||
import "my-transpiled-virtual-module";
|
||||
require("my-transpiled-virtual-module");
|
||||
await import("my-transpiled-virtual-module");
|
||||
require.resolve("my-transpiled-virtual-module");
|
||||
|
||||
import { foo } from "my-object-virtual-module";
|
||||
const object = require("my-object-virtual-module");
|
||||
await import("my-object-virtual-module");
|
||||
require.resolve("my-object-virtual-module");
|
||||
```
|
||||
|
||||
### Overriding existing modules
|
||||
|
||||
You can also override existing modules with `build.module`.
|
||||
|
||||
```js
|
||||
import { plugin } from "bun";
|
||||
build.module("my-object-virtual-module", () => {
|
||||
return {
|
||||
exports: {
|
||||
foo: "bar",
|
||||
},
|
||||
loader: "object",
|
||||
};
|
||||
});
|
||||
|
||||
require("my-object-virtual-module"); // { foo: "bar" }
|
||||
await import("my-object-virtual-module"); // { foo: "bar" }
|
||||
|
||||
build.module("my-object-virtual-module", () => {
|
||||
return {
|
||||
exports: {
|
||||
baz: "quix",
|
||||
},
|
||||
loader: "object",
|
||||
};
|
||||
});
|
||||
require("my-object-virtual-module"); // { baz: "quix" }
|
||||
await import("my-object-virtual-module"); // { baz: "quix" }
|
||||
```
|
||||
|
||||
## Reading or modifying the config
|
||||
|
||||
Plugins can read and write to the [build config](https://bun.com/docs/bundler#api) with `build.config`.
|
||||
|
||||
```ts
|
||||
await Bun.build({
|
||||
entrypoints: ["./app.ts"],
|
||||
outdir: "./dist",
|
||||
sourcemap: "external",
|
||||
plugins: [
|
||||
{
|
||||
name: "demo",
|
||||
setup(build) {
|
||||
console.log(build.config.sourcemap); // "external"
|
||||
|
||||
build.config.minify = true; // enable minification
|
||||
|
||||
// `plugins` is readonly
|
||||
console.log(`Number of plugins: ${build.config.plugins.length}`);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
|
||||
**NOTE**: Plugin lifecycle callbacks (`onStart()`, `onResolve()`, etc.) do not have the ability to modify the `build.config` object in the `setup()` function. If you want to mutate `build.config`, you must do so directly in the `setup()` function:
|
||||
|
||||
```ts
|
||||
await Bun.build({
|
||||
entrypoints: ["./app.ts"],
|
||||
outdir: "./dist",
|
||||
sourcemap: "external",
|
||||
plugins: [
|
||||
{
|
||||
name: "demo",
|
||||
setup(build) {
|
||||
// ✅ good! modifying it directly in the setup() function
|
||||
build.config.minify = true;
|
||||
|
||||
build.onStart(() => {
|
||||
// 🚫 uh-oh! this won't work!
|
||||
build.config.minify = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
{% /callout %}
|
||||
|
||||
## Lifecycle hooks
|
||||
|
||||
Plugins can register callbacks to be run at various points in the lifecycle of a bundle:
|
||||
|
||||
- [`onStart()`](#onstart): Run once the bundler has started a bundle
|
||||
- [`onResolve()`](#onresolve): Run before a module is resolved
|
||||
- [`onLoad()`](#onload): Run before a module is loaded.
|
||||
|
||||
### Reference
|
||||
|
||||
A rough overview of the types (please refer to Bun's `bun.d.ts` for the full type definitions):
|
||||
|
||||
```ts
|
||||
namespace Bun {
|
||||
function plugin(plugin: {
|
||||
name: string;
|
||||
setup: (build: PluginBuilder) => void;
|
||||
}): void;
|
||||
}
|
||||
|
||||
type PluginBuilder = {
|
||||
onStart(callback: () => 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 }) => {
|
||||
loader?: Loader;
|
||||
contents?: string;
|
||||
exports?: Record<string, any>;
|
||||
},
|
||||
) => void;
|
||||
config: BuildConfig;
|
||||
};
|
||||
|
||||
type Loader = "js" | "jsx" | "ts" | "tsx" | "css" | "json" | "toml" | "object";
|
||||
```
|
||||
|
||||
### Namespaces
|
||||
|
||||
`onLoad` and `onResolve` accept an optional `namespace` string. What is a namespace?
|
||||
|
||||
Every module has a namespace. Namespaces are used to prefix the import in transpiled code; for instance, a loader with a `filter: /\.yaml$/` and `namespace: "yaml:"` will transform an import from `./myfile.yaml` into `yaml:./myfile.yaml`.
|
||||
|
||||
The default namespace is `"file"` and it is not necessary to specify it, for instance: `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 (e.g. `"bun:test"`, `"bun:sqlite"`)
|
||||
- `"node"`: for Node.js modules (e.g. `"node:fs"`, `"node:path"`)
|
||||
|
||||
### `onStart`
|
||||
|
||||
```ts
|
||||
onStart(callback: () => void): Promise<void> | void;
|
||||
```
|
||||
|
||||
Registers a callback to be run when the bundler starts a new bundle.
|
||||
|
||||
```ts
|
||||
import { plugin } from "bun";
|
||||
|
||||
plugin({
|
||||
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
|
||||
const result = await Bun.build({
|
||||
entrypoints: ["./app.ts"],
|
||||
outdir: "./dist",
|
||||
sourcemap: "external",
|
||||
plugins: [
|
||||
{
|
||||
name: "Sleep for 10 seconds",
|
||||
setup(build) {
|
||||
build.onStart(async () => {
|
||||
await Bunlog.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 the above example, Bun will wait until the first `onStart()` (sleeping for 10 seconds) has completed, _as well as_ the second `onStart()` (writing the bundle time to a file).
|
||||
|
||||
Note that `onStart()` callbacks (like every other lifecycle callback) do not have the ability to modify the `build.config` object. If you want to mutate `build.config`, you must do so directly in the `setup()` function.
|
||||
|
||||
### `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 actually has to find and read that module. The "finding" part is known as "resolving" a module.
|
||||
|
||||
The `onResolve()` plugin lifecycle callback allows you to configure how a module is resolved.
|
||||
|
||||
The first argument to `onResolve()` is an object with a `filter` and [`namespace`](#what-is-a-namespace) property. The filter is a regular expression which is run on the import string. Effectively, these allow you to filter which modules your custom resolution logic will apply to.
|
||||
|
||||
The second argument to `onResolve()` is a callback which is run for each module import Bun finds that matches the `filter` and `namespace` defined in the first argument.
|
||||
|
||||
The callback receives as input the _path_ to the matching module. The callback can return a _new path_ for the module. Bun will read the contents of the _new path_ and parse it as a module.
|
||||
|
||||
For example, redirecting all imports to `images/` to `./public/images/`:
|
||||
|
||||
```ts
|
||||
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, importer: string, namespace: string, kind: ImportKind }) => {
|
||||
loader?: Loader;
|
||||
contents?: string;
|
||||
exports?: Record<string, any>;
|
||||
},
|
||||
): void;
|
||||
```
|
||||
|
||||
After Bun's bundler has resolved a module, it needs to read the contents of the module and parse it.
|
||||
|
||||
The `onLoad()` plugin lifecycle callback allows you to modify the _contents_ of a module before it is read and parsed by Bun.
|
||||
|
||||
Like `onResolve()`, the first argument to `onLoad()` allows you to filter which modules this invocation of `onLoad()` will apply to.
|
||||
|
||||
The second argument to `onLoad()` is a callback which is run for each matching module _before_ Bun loads the contents of the module into memory.
|
||||
|
||||
This callback receives as input the _path_ to the matching module, the _importer_ of the module (the module that imported the module), the _namespace_ of the module, and the _kind_ of the module.
|
||||
|
||||
The callback can return a new `contents` string for the module as well as a new `loader`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { plugin } from "bun";
|
||||
|
||||
plugin({
|
||||
name: "env plugin",
|
||||
setup(build) {
|
||||
build.onLoad({ filter: /env/, namespace: "file" }, args => {
|
||||
return {
|
||||
contents: `export default ${JSON.stringify(process.env)}`,
|
||||
loader: "js",
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This plugin will transform all imports of the form `import env from "env"` into a JavaScript module that exports the current environment variables.
|
||||
419
docs/runtime/plugins.mdx
Normal file
419
docs/runtime/plugins.mdx
Normal file
@@ -0,0 +1,419 @@
|
||||
---
|
||||
title: "Plugins"
|
||||
description: "Universal plugin API for extending Bun's runtime and bundler"
|
||||
---
|
||||
|
||||
Bun provides a universal plugin API that can be used to extend both the _runtime_ and _bundler_.
|
||||
|
||||
Plugins intercept imports and perform custom loading logic: reading files, transpiling code, etc. They can be used to add support for additional file types, like `.scss` or `.yaml`. In the context of Bun's bundler, plugins can be used to implement framework-level features like CSS extraction, macros, and client-server code co-location.
|
||||
|
||||
## Lifecycle hooks
|
||||
|
||||
Plugins can register callbacks to be run at various points in the lifecycle of a bundle:
|
||||
|
||||
- [`onStart()`](#onstart): Run once the bundler has started a bundle
|
||||
- [`onResolve()`](#onresolve): Run before a module is resolved
|
||||
- [`onLoad()`](#onload): Run before a module is loaded.
|
||||
- [`onBeforeParse()`](#onbeforeparse): Run zero-copy native addons in the parser thread before a file is parsed.
|
||||
|
||||
### Reference
|
||||
|
||||
A rough overview of the types (please refer to Bun's `bun.d.ts` for the full type definitions):
|
||||
|
||||
```ts Plugin Types icon="/icons/typescript.svg"
|
||||
type PluginBuilder = {
|
||||
onStart(callback: () => 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 },
|
||||
defer: () => Promise<void>,
|
||||
callback: (args: { path: string }) => {
|
||||
loader?: Loader;
|
||||
contents?: string;
|
||||
exports?: Record<string, any>;
|
||||
},
|
||||
) => void;
|
||||
config: BuildConfig;
|
||||
};
|
||||
|
||||
type Loader =
|
||||
| "js"
|
||||
| "jsx"
|
||||
| "ts"
|
||||
| "tsx"
|
||||
| "json"
|
||||
| "jsonc"
|
||||
| "toml"
|
||||
| "yaml"
|
||||
| "file"
|
||||
| "napi"
|
||||
| "wasm"
|
||||
| "text"
|
||||
| "css"
|
||||
| "html";
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
A plugin is defined as simple JavaScript object containing a `name` property and a `setup` function.
|
||||
|
||||
```tsx myPlugin.ts icon="/icons/typescript.svg"
|
||||
import type { BunPlugin } from "bun";
|
||||
|
||||
const myPlugin: BunPlugin = {
|
||||
name: "Custom loader",
|
||||
setup(build) {
|
||||
// implementation
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
This plugin can be passed into the `plugins` array when calling `Bun.build`.
|
||||
|
||||
```ts 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. What is a namespace?
|
||||
|
||||
Every module has a namespace. Namespaces are used to prefix the import in transpiled code; for instance, a loader with a `filter: /\.yaml$/` and `namespace: "yaml:"` will transform an import from `./myfile.yaml` into `yaml:./myfile.yaml`.
|
||||
|
||||
The default namespace is `"file"` and it is not necessary to specify it, for instance: `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 (e.g. `"bun:test"`, `"bun:sqlite"`)
|
||||
- `"node"`: for Node.js modules (e.g. `"node:fs"`, `"node:path"`)
|
||||
|
||||
### `onStart`
|
||||
|
||||
```ts
|
||||
onStart(callback: () => void): Promise<void> | void;
|
||||
```
|
||||
|
||||
Registers a callback to be run when the bundler starts a new bundle.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { plugin } from "bun";
|
||||
|
||||
plugin({
|
||||
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 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 Bunlog.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 the above example, Bun will wait until the first `onStart()` (sleeping for 10 seconds) has completed, _as well as_ the second `onStart()` (writing the bundle time to a file).
|
||||
|
||||
Note that `onStart()` callbacks (like every other lifecycle callback) do not have the ability to modify the `build.config` object. If you want to mutate `build.config`, you must do so directly in the `setup()` function.
|
||||
|
||||
### `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 actually has to find and read that module. The "finding" part is known as "resolving" a module.
|
||||
|
||||
The `onResolve()` plugin lifecycle callback allows you to configure how a module is resolved.
|
||||
|
||||
The first argument to `onResolve()` is an object with a `filter` and [`namespace`](#what-is-a-namespace) property. The filter is a regular expression which is run on the import string. Effectively, these allow you to filter which modules your custom resolution logic will apply to.
|
||||
|
||||
The second argument to `onResolve()` is a callback which is run for each module import Bun finds that matches the `filter` and `namespace` defined in the first argument.
|
||||
|
||||
The callback receives as input the _path_ to the matching module. The callback can return a _new path_ for the module. Bun will read the contents of the _new path_ and parse it as a module.
|
||||
|
||||
For example, redirecting all imports to `images/` to `./public/images/`:
|
||||
|
||||
```ts 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 },
|
||||
defer: () => Promise<void>,
|
||||
callback: (args: { path: string, importer: string, namespace: string, kind: ImportKind }) => {
|
||||
loader?: Loader;
|
||||
contents?: string;
|
||||
exports?: Record<string, any>;
|
||||
},
|
||||
): void;
|
||||
```
|
||||
|
||||
After Bun's bundler has resolved a module, it needs to read the contents of the module and parse it.
|
||||
|
||||
The `onLoad()` plugin lifecycle callback allows you to modify the _contents_ of a module before it is read and parsed by Bun.
|
||||
|
||||
Like `onResolve()`, the first argument to `onLoad()` allows you to filter which modules this invocation of `onLoad()` will apply to.
|
||||
|
||||
The second argument to `onLoad()` is a callback which is run for each matching module _before_ Bun loads the contents of the module into memory.
|
||||
|
||||
This callback receives as input the _path_ to the matching module, the _importer_ of the module (the module that imported the module), the _namespace_ of the module, and the _kind_ of the module.
|
||||
|
||||
The callback can return a new `contents` string for the module as well as a new `loader`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { plugin } from "bun";
|
||||
|
||||
const envPlugin: BunPlugin = {
|
||||
name: "env plugin",
|
||||
setup(build) {
|
||||
build.onLoad({ filter: /env/, namespace: "file" }, 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 will transform 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. This function returns a `Promise` that is resolved when all _other_ modules have been loaded.
|
||||
|
||||
This allows you to delay execution of the `onLoad` callback until all other modules have been loaded.
|
||||
|
||||
This is useful for returning contents of a module that depends on other modules.
|
||||
|
||||
##### Example: tracking and reporting unused exports
|
||||
|
||||
```ts 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",
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Note that the `.defer()` function currently has the limitation that it can only be called once per `onLoad` callback.
|
||||
|
||||
## Native plugins
|
||||
|
||||
One of the reasons why Bun's bundler is so fast is that it is written in native code and leverages multi-threading to load and parse modules in parallel.
|
||||
|
||||
However, one limitation of plugins written in JavaScript is that JavaScript itself is single-threaded.
|
||||
|
||||
Native plugins are written as [NAPI](/runtime/node-api) modules and can be run on multiple threads. This allows native plugins to run much faster than JavaScript plugins.
|
||||
|
||||
In addition, native plugins can skip unnecessary work such as the UTF-8 -> UTF-16 conversion needed to pass strings to JavaScript.
|
||||
|
||||
These are the following lifecycle hooks which are available to native plugins:
|
||||
|
||||
- [`onBeforeParse()`](#onbeforeparse): Called on any thread before a file is parsed by Bun's bundler.
|
||||
|
||||
Native plugins are NAPI modules which expose lifecycle hooks as C ABI functions.
|
||||
|
||||
To create a native plugin, you must export a C ABI function which matches the signature of the native lifecycle hook you want to implement.
|
||||
|
||||
### Creating a native plugin in Rust
|
||||
|
||||
Native plugins are NAPI modules which expose lifecycle hooks as C ABI functions.
|
||||
|
||||
To create a native plugin, you must export a C ABI function which matches the signature of the native lifecycle hook you want to implement.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Now, inside the `lib.rs` file, we'll use the `bun_native_plugin::bun` proc macro to define a function which
|
||||
will implement our native plugin.
|
||||
|
||||
Here's an example implementing the `onBeforeParse` hook:
|
||||
|
||||
```rs lib.rs icon="/icons/rust.svg"
|
||||
use bun_native_plugin::{define_bun_plugin, OnBeforeParse, bun, Result, anyhow, BunLoader};
|
||||
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, BunLoader::BUN_LOADER_JSX);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
And to use it in Bun.build():
|
||||
|
||||
```typescript
|
||||
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;
|
||||
```
|
||||
|
||||
This lifecycle callback is run immediately before a file is parsed by Bun's bundler.
|
||||
|
||||
As input, it receives the file's contents and can optionally return new source code.
|
||||
|
||||
This callback can be called from any thread and so the napi module implementation must be thread-safe.
|
||||
583
docs/runtime/redis.mdx
Normal file
583
docs/runtime/redis.mdx
Normal file
@@ -0,0 +1,583 @@
|
||||
---
|
||||
title: Redis
|
||||
description: Use Bun's native Redis client with a Promise-based API
|
||||
---
|
||||
|
||||
<Note>Bun's Redis client supports Redis server versions 7.2 and up.</Note>
|
||||
|
||||
Bun provides native bindings for working with Redis databases with a modern, Promise-based API. The interface is designed to be simple and performant, with built-in connection management, fully typed responses, and TLS support.
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
import { redis } from "bun";
|
||||
|
||||
// Set a key
|
||||
await redis.set("greeting", "Hello from Bun!");
|
||||
|
||||
// Get a key
|
||||
const greeting = await redis.get("greeting");
|
||||
console.log(greeting); // "Hello from Bun!"
|
||||
|
||||
// Increment a counter
|
||||
await redis.set("counter", 0);
|
||||
await redis.incr("counter");
|
||||
|
||||
// Check if a key exists
|
||||
const exists = await redis.exists("greeting");
|
||||
|
||||
// Delete a key
|
||||
await redis.del("greeting");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
To use the Redis client, you first need to create a connection:
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
import { redis, RedisClient } from "bun";
|
||||
|
||||
// Using the default client (reads connection info from environment)
|
||||
// process.env.REDIS_URL is used by default
|
||||
await redis.set("hello", "world");
|
||||
const result = await redis.get("hello");
|
||||
|
||||
// Creating a custom client
|
||||
const client = new RedisClient("redis://username:password@localhost:6379");
|
||||
await client.set("counter", "0");
|
||||
await client.incr("counter");
|
||||
```
|
||||
|
||||
By default, the client reads connection information from the following environment variables (in order of precedence):
|
||||
|
||||
- `REDIS_URL`
|
||||
- `VALKEY_URL`
|
||||
- If not set, defaults to `"redis://localhost:6379"`
|
||||
|
||||
### Connection Lifecycle
|
||||
|
||||
The Redis client automatically handles connections in the background:
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
// No connection is made until a command is executed
|
||||
const client = new RedisClient();
|
||||
|
||||
// First command initiates the connection
|
||||
await client.set("key", "value");
|
||||
|
||||
// Connection remains open for subsequent commands
|
||||
await client.get("key");
|
||||
|
||||
// Explicitly close the connection when done
|
||||
client.close();
|
||||
```
|
||||
|
||||
You can also manually control the connection lifecycle:
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
const client = new RedisClient();
|
||||
|
||||
// Explicitly connect
|
||||
await client.connect();
|
||||
|
||||
// Run commands
|
||||
await client.set("key", "value");
|
||||
|
||||
// Disconnect when done
|
||||
client.close();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Basic Operations
|
||||
|
||||
### String Operations
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
// Set a key
|
||||
await redis.set("user:1:name", "Alice");
|
||||
|
||||
// Get a key
|
||||
const name = await redis.get("user:1:name");
|
||||
|
||||
// Get a key as Uint8Array
|
||||
const buffer = await redis.getBuffer("user:1:name");
|
||||
|
||||
// Delete a key
|
||||
await redis.del("user:1:name");
|
||||
|
||||
// Check if a key exists
|
||||
const exists = await redis.exists("user:1:name");
|
||||
|
||||
// Set expiration (in seconds)
|
||||
await redis.set("session:123", "active");
|
||||
await redis.expire("session:123", 3600); // expires in 1 hour
|
||||
|
||||
// Get time to live (in seconds)
|
||||
const ttl = await redis.ttl("session:123");
|
||||
```
|
||||
|
||||
### Numeric Operations
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
// Set initial value
|
||||
await redis.set("counter", "0");
|
||||
|
||||
// Increment by 1
|
||||
await redis.incr("counter");
|
||||
|
||||
// Decrement by 1
|
||||
await redis.decr("counter");
|
||||
```
|
||||
|
||||
### Hash Operations
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
// Set multiple fields in a hash
|
||||
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com", "active", "true"]);
|
||||
|
||||
// Get multiple fields from a hash
|
||||
const userFields = await redis.hmget("user:123", ["name", "email"]);
|
||||
console.log(userFields); // ["Alice", "alice@example.com"]
|
||||
|
||||
// Get single field from hash (returns value directly, null if missing)
|
||||
const userName = await redis.hget("user:123", "name");
|
||||
console.log(userName); // "Alice"
|
||||
|
||||
// Increment a numeric field in a hash
|
||||
await redis.hincrby("user:123", "visits", 1);
|
||||
|
||||
// Increment a float field in a hash
|
||||
await redis.hincrbyfloat("user:123", "score", 1.5);
|
||||
```
|
||||
|
||||
### Set Operations
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
// Add member to set
|
||||
await redis.sadd("tags", "javascript");
|
||||
|
||||
// Remove member from set
|
||||
await redis.srem("tags", "javascript");
|
||||
|
||||
// Check if member exists in set
|
||||
const isMember = await redis.sismember("tags", "javascript");
|
||||
|
||||
// Get all members of a set
|
||||
const allTags = await redis.smembers("tags");
|
||||
|
||||
// Get a random member
|
||||
const randomTag = await redis.srandmember("tags");
|
||||
|
||||
// Pop (remove and return) a random member
|
||||
const poppedTag = await redis.spop("tags");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pub/Sub
|
||||
|
||||
Bun provides native bindings for the [Redis
|
||||
Pub/Sub](https://redis.io/docs/latest/develop/pubsub/) protocol. **New in Bun
|
||||
1.2.23**
|
||||
|
||||
<Warning>
|
||||
The Redis Pub/Sub feature is experimental. Although we expect it to be stable, we're currently actively looking for
|
||||
feedback and areas for improvement.
|
||||
</Warning>
|
||||
|
||||
### Basic Usage
|
||||
|
||||
To get started publishing messages, you can set up a publisher in
|
||||
`publisher.ts`:
|
||||
|
||||
```typescript publisher.ts icon="/icons/typescript.svg"
|
||||
import { RedisClient } from "bun";
|
||||
|
||||
const writer = new RedisClient("redis://localhost:6739");
|
||||
await writer.connect();
|
||||
|
||||
writer.publish("general", "Hello everyone!");
|
||||
|
||||
writer.close();
|
||||
```
|
||||
|
||||
In another file, create the subscriber in `subscriber.ts`:
|
||||
|
||||
```typescript subscriber.ts icon="/icons/typescript.svg"
|
||||
import { RedisClient } from "bun";
|
||||
|
||||
const listener = new RedisClient("redis://localhost:6739");
|
||||
await listener.connect();
|
||||
|
||||
await listener.subscribe("general", (message, channel) => {
|
||||
console.log(`Received: ${message}`);
|
||||
});
|
||||
```
|
||||
|
||||
In one shell, run your subscriber:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run subscriber.ts
|
||||
```
|
||||
|
||||
and, in another, run your publisher:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run publisher.ts
|
||||
```
|
||||
|
||||
<Note>
|
||||
The subscription mode takes over the `RedisClient` connection. A
|
||||
client with subscriptions can only call `RedisClient.prototype.subscribe()`. In
|
||||
other words, applications which need to message Redis need a separate
|
||||
connection, acquirable through `.duplicate()`:
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
import { RedisClient } from "bun";
|
||||
|
||||
const redis = new RedisClient("redis://localhost:6379");
|
||||
await redis.connect();
|
||||
const subscriber = await redis.duplicate(); // [!code ++]
|
||||
|
||||
await subscriber.subscribe("foo", () => {});
|
||||
await redis.set("bar", "baz");
|
||||
```
|
||||
|
||||
</Note>
|
||||
|
||||
### Publishing
|
||||
|
||||
Publishing messages is done through the `publish()` method:
|
||||
|
||||
```typescript redis.ts icon="/icons/typescript.svg"
|
||||
await client.publish(channelName, message);
|
||||
```
|
||||
|
||||
### Subscriptions
|
||||
|
||||
The Bun `RedisClient` allows you to subscribe to channels through the
|
||||
`.subscribe()` method:
|
||||
|
||||
```typescript redis.ts icon="/icons/typescript.svg"
|
||||
await client.subscribe(channel, (message, channel) => {});
|
||||
```
|
||||
|
||||
You can unsubscribe through the `.unsubscribe()` method:
|
||||
|
||||
```typescript redis.ts icon="/icons/typescript.svg"
|
||||
await client.unsubscribe(); // Unsubscribe from all channels.
|
||||
await client.unsubscribe(channel); // Unsubscribe a particular channel.
|
||||
await client.unsubscribe(channel, listener); // Unsubscribe a particular listener.
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Command Execution and Pipelining
|
||||
|
||||
The client automatically pipelines commands, improving performance by sending multiple commands in a batch and processing responses as they arrive.
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
// Commands are automatically pipelined by default
|
||||
const [infoResult, listResult] = await Promise.all([redis.get("user:1:name"), redis.get("user:2:email")]);
|
||||
```
|
||||
|
||||
To disable automatic pipelining, you can set the `enableAutoPipelining` option to `false`:
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
const client = new RedisClient("redis://localhost:6379", {
|
||||
enableAutoPipelining: false, // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
### Raw Commands
|
||||
|
||||
When you need to use commands that don't have convenience methods, you can use the `send` method:
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
// Run any Redis command
|
||||
const info = await redis.send("INFO", []);
|
||||
|
||||
// LPUSH to a list
|
||||
await redis.send("LPUSH", ["mylist", "value1", "value2"]);
|
||||
|
||||
// Get list range
|
||||
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);
|
||||
```
|
||||
|
||||
The `send` method allows you to use any Redis command, even ones that don't have dedicated methods in the client. The first argument is the command name, and the second argument is an array of string arguments.
|
||||
|
||||
### Connection Events
|
||||
|
||||
You can register handlers for connection events:
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
const client = new RedisClient();
|
||||
|
||||
// Called when successfully connected to Redis server
|
||||
client.onconnect = () => {
|
||||
console.log("Connected to Redis server");
|
||||
};
|
||||
|
||||
// Called when disconnected from Redis server
|
||||
client.onclose = error => {
|
||||
console.error("Disconnected from Redis server:", error);
|
||||
};
|
||||
|
||||
// Manually connect/disconnect
|
||||
await client.connect();
|
||||
client.close();
|
||||
```
|
||||
|
||||
### Connection Status and Monitoring
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
// Check if connected
|
||||
console.log(client.connected); // boolean indicating connection status
|
||||
|
||||
// Check amount of data buffered (in bytes)
|
||||
console.log(client.bufferedAmount);
|
||||
```
|
||||
|
||||
### Type Conversion
|
||||
|
||||
The Redis client handles automatic type conversion for Redis responses:
|
||||
|
||||
- Integer responses are returned as JavaScript numbers
|
||||
- Bulk strings are returned as JavaScript strings
|
||||
- Simple strings are returned as JavaScript strings
|
||||
- Null bulk strings are returned as `null`
|
||||
- Array responses are returned as JavaScript arrays
|
||||
- Error responses throw JavaScript errors with appropriate error codes
|
||||
- Boolean responses (RESP3) are returned as JavaScript booleans
|
||||
- Map responses (RESP3) are returned as JavaScript objects
|
||||
- Set responses (RESP3) are returned as JavaScript arrays
|
||||
|
||||
Special handling for specific commands:
|
||||
|
||||
- `EXISTS` returns a boolean instead of a number (1 becomes true, 0 becomes false)
|
||||
- `SISMEMBER` returns a boolean (1 becomes true, 0 becomes false)
|
||||
|
||||
The following commands disable automatic pipelining:
|
||||
|
||||
- `AUTH`
|
||||
- `INFO`
|
||||
- `QUIT`
|
||||
- `EXEC`
|
||||
- `MULTI`
|
||||
- `WATCH`
|
||||
- `SCRIPT`
|
||||
- `SELECT`
|
||||
- `CLUSTER`
|
||||
- `DISCARD`
|
||||
- `UNWATCH`
|
||||
- `PIPELINE`
|
||||
- `SUBSCRIBE`
|
||||
- `UNSUBSCRIBE`
|
||||
- `UNPSUBSCRIBE`
|
||||
|
||||
---
|
||||
|
||||
## Connection Options
|
||||
|
||||
When creating a client, you can pass various options to configure the connection:
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
const client = new RedisClient("redis://localhost:6379", {
|
||||
// Connection timeout in milliseconds (default: 10000)
|
||||
connectionTimeout: 5000,
|
||||
|
||||
// Idle timeout in milliseconds (default: 0 = no timeout)
|
||||
idleTimeout: 30000,
|
||||
|
||||
// Whether to automatically reconnect on disconnection (default: true)
|
||||
autoReconnect: true,
|
||||
|
||||
// Maximum number of reconnection attempts (default: 10)
|
||||
maxRetries: 10,
|
||||
|
||||
// Whether to queue commands when disconnected (default: true)
|
||||
enableOfflineQueue: true,
|
||||
|
||||
// Whether to automatically pipeline commands (default: true)
|
||||
enableAutoPipelining: true,
|
||||
|
||||
// TLS options (default: false)
|
||||
tls: true,
|
||||
// Alternatively, provide custom TLS config:
|
||||
// tls: {
|
||||
// rejectUnauthorized: true,
|
||||
// ca: "path/to/ca.pem",
|
||||
// cert: "path/to/cert.pem",
|
||||
// key: "path/to/key.pem",
|
||||
// }
|
||||
});
|
||||
```
|
||||
|
||||
### Reconnection Behavior
|
||||
|
||||
When a connection is lost, the client automatically attempts to reconnect with exponential backoff:
|
||||
|
||||
1. The client starts with a small delay (50ms) and doubles it with each attempt
|
||||
2. Reconnection delay is capped at 2000ms (2 seconds)
|
||||
3. The client attempts to reconnect up to `maxRetries` times (default: 10)
|
||||
4. Commands executed during disconnection are:
|
||||
- Queued if `enableOfflineQueue` is true (default)
|
||||
- Rejected immediately if `enableOfflineQueue` is false
|
||||
|
||||
---
|
||||
|
||||
## Supported URL Formats
|
||||
|
||||
The Redis client supports various URL formats:
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
// Standard Redis URL
|
||||
new RedisClient("redis://localhost:6379");
|
||||
new RedisClient("redis://localhost:6379");
|
||||
|
||||
// With authentication
|
||||
new RedisClient("redis://username:password@localhost:6379");
|
||||
|
||||
// With database number
|
||||
new RedisClient("redis://localhost:6379/0");
|
||||
|
||||
// TLS connections
|
||||
new RedisClient("rediss://localhost:6379");
|
||||
new RedisClient("rediss://localhost:6379");
|
||||
new RedisClient("redis+tls://localhost:6379");
|
||||
new RedisClient("redis+tls://localhost:6379");
|
||||
|
||||
// Unix socket connections
|
||||
new RedisClient("redis+unix:///path/to/socket");
|
||||
new RedisClient("redis+unix:///path/to/socket");
|
||||
|
||||
// TLS over Unix socket
|
||||
new RedisClient("redis+tls+unix:///path/to/socket");
|
||||
new RedisClient("redis+tls+unix:///path/to/socket");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
The Redis client throws typed errors for different scenarios:
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
try {
|
||||
await redis.get("non-existent-key");
|
||||
} catch (error) {
|
||||
if (error.code === "ERR_REDIS_CONNECTION_CLOSED") {
|
||||
console.error("Connection to Redis server was closed");
|
||||
} else if (error.code === "ERR_REDIS_AUTHENTICATION_FAILED") {
|
||||
console.error("Authentication failed");
|
||||
} else {
|
||||
console.error("Unexpected error:", error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Common error codes:
|
||||
|
||||
- `ERR_REDIS_CONNECTION_CLOSED` - Connection to the server was closed
|
||||
- `ERR_REDIS_AUTHENTICATION_FAILED` - Failed to authenticate with the server
|
||||
- `ERR_REDIS_INVALID_RESPONSE` - Received an invalid response from the server
|
||||
|
||||
---
|
||||
|
||||
## Example Use Cases
|
||||
|
||||
### Caching
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
async function getUserWithCache(userId) {
|
||||
const cacheKey = `user:${userId}`;
|
||||
|
||||
// Try to get from cache first
|
||||
const cachedUser = await redis.get(cacheKey);
|
||||
if (cachedUser) {
|
||||
return JSON.parse(cachedUser);
|
||||
}
|
||||
|
||||
// Not in cache, fetch from database
|
||||
const user = await database.getUser(userId);
|
||||
|
||||
// Store in cache for 1 hour
|
||||
await redis.set(cacheKey, JSON.stringify(user));
|
||||
await redis.expire(cacheKey, 3600);
|
||||
|
||||
return user;
|
||||
}
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
async function rateLimit(ip, limit = 100, windowSecs = 3600) {
|
||||
const key = `ratelimit:${ip}`;
|
||||
|
||||
// Increment counter
|
||||
const count = await redis.incr(key);
|
||||
|
||||
// Set expiry if this is the first request in window
|
||||
if (count === 1) {
|
||||
await redis.expire(key, windowSecs);
|
||||
}
|
||||
|
||||
// Check if limit exceeded
|
||||
return {
|
||||
limited: count > limit,
|
||||
remaining: Math.max(0, limit - count),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Session Storage
|
||||
|
||||
```ts redis.ts icon="/icons/typescript.svg"
|
||||
async function createSession(userId, data) {
|
||||
const sessionId = crypto.randomUUID();
|
||||
const key = `session:${sessionId}`;
|
||||
|
||||
// Store session with expiration
|
||||
await redis.hmset(key, ["userId", userId.toString(), "created", Date.now().toString(), "data", JSON.stringify(data)]);
|
||||
await redis.expire(key, 86400); // 24 hours
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
async function getSession(sessionId) {
|
||||
const key = `session:${sessionId}`;
|
||||
|
||||
// Get session data
|
||||
const exists = await redis.exists(key);
|
||||
if (!exists) return null;
|
||||
|
||||
const [userId, created, data] = await redis.hmget(key, ["userId", "created", "data"]);
|
||||
|
||||
return {
|
||||
userId: Number(userId),
|
||||
created: Number(created),
|
||||
data: JSON.parse(data),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Bun's Redis client is implemented in Zig and uses the Redis Serialization Protocol (RESP3). It manages connections efficiently and provides automatic reconnection with exponential backoff.
|
||||
|
||||
The client supports pipelining commands, meaning multiple commands can be sent without waiting for the replies to previous commands. This significantly improves performance when sending multiple commands in succession.
|
||||
|
||||
## Limitations and Future Plans
|
||||
|
||||
Current limitations of the Redis client we are planning to address in future versions:
|
||||
|
||||
- Transactions (MULTI/EXEC) must be done through raw commands for now
|
||||
|
||||
Unsupported features:
|
||||
|
||||
- Redis Sentinel
|
||||
- Redis Cluster
|
||||
863
docs/runtime/s3.mdx
Normal file
863
docs/runtime/s3.mdx
Normal file
@@ -0,0 +1,863 @@
|
||||
---
|
||||
title: S3
|
||||
description: Bun provides fast, native bindings for interacting with S3-compatible object storage services.
|
||||
---
|
||||
|
||||
Production servers often read, upload, and write files to S3-compatible object storage services instead of the local filesystem. Historically, that means local filesystem APIs you use in development can't be used in production. When you use Bun, things are different.
|
||||
|
||||
### Bun's S3 API is fast
|
||||
|
||||
<Frame caption="Left: Bun v1.1.44. Right: Node.js v23.6.0">
|
||||
<img src="/images/bun-s3-node.gif" alt="Bun's S3 API is fast" />
|
||||
</Frame>
|
||||
|
||||
Bun provides fast, native bindings for interacting with S3-compatible object storage services. Bun's S3 API is designed to be simple and feel similar to fetch's `Response` and `Blob` APIs (like Bun's local filesystem APIs).
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
import { s3, write, S3Client } from "bun";
|
||||
|
||||
// Bun.s3 reads environment variables for credentials
|
||||
// file() returns a lazy reference to a file on S3
|
||||
const metadata = s3.file("123.json");
|
||||
|
||||
// Download from S3 as JSON
|
||||
const data = await metadata.json();
|
||||
|
||||
// Upload to S3
|
||||
await write(metadata, JSON.stringify({ name: "John", age: 30 }));
|
||||
|
||||
// Presign a URL (synchronous - no network request needed)
|
||||
const url = metadata.presign({
|
||||
acl: "public-read",
|
||||
expiresIn: 60 * 60 * 24, // 1 day
|
||||
});
|
||||
|
||||
// Delete the file
|
||||
await metadata.delete();
|
||||
```
|
||||
|
||||
S3 is the [de facto standard](https://en.wikipedia.org/wiki/De_facto_standard) internet filesystem. Bun's S3 API works with S3-compatible storage services like:
|
||||
|
||||
- AWS S3
|
||||
- Cloudflare R2
|
||||
- DigitalOcean Spaces
|
||||
- MinIO
|
||||
- Backblaze B2
|
||||
- ...and any other S3-compatible storage service
|
||||
|
||||
## Basic Usage
|
||||
|
||||
There are several ways to interact with Bun's S3 API.
|
||||
|
||||
### `Bun.S3Client` & `Bun.s3`
|
||||
|
||||
`Bun.s3` is equivalent to `new Bun.S3Client()`, relying on environment variables for credentials.
|
||||
|
||||
To explicitly set credentials, pass them to the `Bun.S3Client` constructor.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
import { S3Client } from "bun";
|
||||
|
||||
const client = new S3Client({
|
||||
accessKeyId: "your-access-key",
|
||||
secretAccessKey: "your-secret-key",
|
||||
bucket: "my-bucket",
|
||||
// sessionToken: "..."
|
||||
// acl: "public-read",
|
||||
// endpoint: "https://s3.us-east-1.amazonaws.com",
|
||||
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
|
||||
// endpoint: "https://<region>.digitaloceanspaces.com", // DigitalOcean Spaces
|
||||
// endpoint: "http://localhost:9000", // MinIO
|
||||
});
|
||||
|
||||
// Bun.s3 is a global singleton that is equivalent to `new Bun.S3Client()`
|
||||
```
|
||||
|
||||
### Working with S3 Files
|
||||
|
||||
The **`file`** method in `S3Client` returns a **lazy reference to a file on S3**.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
// A lazy reference to a file on S3
|
||||
const s3file: S3File = client.file("123.json");
|
||||
```
|
||||
|
||||
Like `Bun.file(path)`, the `S3Client`'s `file` method is synchronous. It does zero network requests until you call a method that depends on a network request.
|
||||
|
||||
### Reading files from S3
|
||||
|
||||
If you've used the `fetch` API, you're familiar with the `Response` and `Blob` APIs. `S3File` extends `Blob`. The same methods that work on `Blob` also work on `S3File`.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
// Read an S3File as text
|
||||
const text = await s3file.text();
|
||||
|
||||
// Read an S3File as JSON
|
||||
const json = await s3file.json();
|
||||
|
||||
// Read an S3File as an ArrayBuffer
|
||||
const buffer = await s3file.arrayBuffer();
|
||||
|
||||
// Get only the first 1024 bytes
|
||||
const partial = await s3file.slice(0, 1024).text();
|
||||
|
||||
// Stream the file
|
||||
const stream = s3file.stream();
|
||||
for await (const chunk of stream) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
#### Memory optimization
|
||||
|
||||
Methods like `text()`, `json()`, `bytes()`, or `arrayBuffer()` avoid duplicating the string or bytes in memory when possible.
|
||||
|
||||
If the text happens to be ASCII, Bun directly transfers the string to JavaScriptCore (the engine) without transcoding and without duplicating the string in memory. When you use `.bytes()` or `.arrayBuffer()`, it will also avoid duplicating the bytes in memory.
|
||||
|
||||
These helper methods not only simplify the API, they also make it faster.
|
||||
|
||||
### Writing & uploading files to S3
|
||||
|
||||
Writing to S3 is just as simple.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
// Write a string (replacing the file)
|
||||
await s3file.write("Hello World!");
|
||||
|
||||
// Write a Buffer (replacing the file)
|
||||
await s3file.write(Buffer.from("Hello World!"));
|
||||
|
||||
// Write a Response (replacing the file)
|
||||
await s3file.write(new Response("Hello World!"));
|
||||
|
||||
// Write with content type
|
||||
await s3file.write(JSON.stringify({ name: "John", age: 30 }), {
|
||||
type: "application/json",
|
||||
});
|
||||
|
||||
// Write using a writer (streaming)
|
||||
const writer = s3file.writer({ type: "application/json" });
|
||||
writer.write("Hello");
|
||||
writer.write(" World!");
|
||||
await writer.end();
|
||||
|
||||
// Write using Bun.write
|
||||
await Bun.write(s3file, "Hello World!");
|
||||
```
|
||||
|
||||
### Working with large files (streams)
|
||||
|
||||
Bun automatically handles multipart uploads for large files and provides streaming capabilities. The same API that works for local files also works for S3 files.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
// Write a large file
|
||||
const bigFile = Buffer.alloc(10 * 1024 * 1024); // 10MB
|
||||
const writer = s3file.writer({
|
||||
// Automatically retry on network errors up to 3 times
|
||||
retry: 3,
|
||||
|
||||
// Queue up to 10 requests at a time
|
||||
queueSize: 10,
|
||||
|
||||
// Upload in 5 MB chunks
|
||||
partSize: 5 * 1024 * 1024,
|
||||
});
|
||||
for (let i = 0; i < 10; i++) {
|
||||
writer.write(bigFile);
|
||||
await writer.flush();
|
||||
}
|
||||
await writer.end();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Presigning URLs
|
||||
|
||||
When your production service needs to let users upload files to your server, it's often more reliable for the user to upload directly to S3 instead of your server acting as an intermediary.
|
||||
|
||||
To facilitate this, you can presign URLs for S3 files. This generates a URL with a signature that allows a user to securely upload that specific file to S3, without exposing your credentials or granting them unnecessary access to your bucket.
|
||||
|
||||
The default behaviour is to generate a `GET` URL that expires in 24 hours. Bun attempts to infer the content type from the file extension. If inference is not possible, it will default to `application/octet-stream`.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
import { s3 } from "bun";
|
||||
|
||||
// Generate a presigned URL that expires in 24 hours (default)
|
||||
const download = s3.presign("my-file.txt"); // GET, text/plain, expires in 24 hours
|
||||
|
||||
const upload = s3.presign("my-file", {
|
||||
expiresIn: 3600, // 1 hour
|
||||
method: "PUT",
|
||||
type: "application/json", // No extension for inferring, so we can specify the content type to be JSON
|
||||
});
|
||||
|
||||
// You can call .presign() if on a file reference, but avoid doing so
|
||||
// unless you already have a reference (to avoid memory usage).
|
||||
const myFile = s3.file("my-file.txt");
|
||||
const presignedFile = myFile.presign({
|
||||
expiresIn: 3600, // 1 hour
|
||||
});
|
||||
```
|
||||
|
||||
### Setting ACLs
|
||||
|
||||
To set an ACL (access control list) on a presigned URL, pass the `acl` option:
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
const url = s3file.presign({
|
||||
acl: "public-read",
|
||||
expiresIn: 3600,
|
||||
});
|
||||
```
|
||||
|
||||
You can pass any of the following ACLs:
|
||||
|
||||
| ACL | Explanation |
|
||||
| ----------------------------- | ------------------------------------------------------------------- |
|
||||
| `"public-read"` | The object is readable by the public. |
|
||||
| `"private"` | The object is readable only by the bucket owner. |
|
||||
| `"public-read-write"` | The object is readable and writable by the public. |
|
||||
| `"authenticated-read"` | The object is readable by the bucket owner and authenticated users. |
|
||||
| `"aws-exec-read"` | The object is readable by the AWS account that made the request. |
|
||||
| `"bucket-owner-read"` | The object is readable by the bucket owner. |
|
||||
| `"bucket-owner-full-control"` | The object is readable and writable by the bucket owner. |
|
||||
| `"log-delivery-write"` | The object is writable by AWS services used for log delivery. |
|
||||
|
||||
### Expiring URLs
|
||||
|
||||
To set an expiration time for a presigned URL, pass the `expiresIn` option.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
const url = s3file.presign({
|
||||
// Seconds
|
||||
expiresIn: 3600, // 1 hour
|
||||
|
||||
// access control list
|
||||
acl: "public-read",
|
||||
|
||||
// HTTP method
|
||||
method: "PUT",
|
||||
});
|
||||
```
|
||||
|
||||
### `method`
|
||||
|
||||
To set the HTTP method for a presigned URL, pass the `method` option.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
const url = s3file.presign({
|
||||
method: "PUT",
|
||||
// method: "DELETE",
|
||||
// method: "GET",
|
||||
// method: "HEAD",
|
||||
// method: "POST",
|
||||
// method: "PUT",
|
||||
});
|
||||
```
|
||||
|
||||
### `new Response(S3File)`
|
||||
|
||||
To quickly redirect users to a presigned URL for an S3 file, pass an `S3File` instance to a `Response` object as the body.
|
||||
|
||||
This will automatically redirect the user to the presigned URL for the S3 file, saving you the memory, time, and bandwidth cost of downloading the file to your server and sending it back to the user.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
const response = new Response(s3file);
|
||||
console.log(response);
|
||||
```
|
||||
|
||||
```txt
|
||||
Response (0 KB) {
|
||||
ok: false,
|
||||
url: "",
|
||||
status: 302,
|
||||
statusText: "",
|
||||
headers: Headers {
|
||||
"location": "https://<account-id>.r2.cloudflarestorage.com/...",
|
||||
},
|
||||
redirected: true,
|
||||
bodyUsed: false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Support for S3-Compatible Services
|
||||
|
||||
Bun's S3 implementation works with any S3-compatible storage service. Just specify the appropriate endpoint:
|
||||
|
||||
### Using Bun's S3Client with AWS S3
|
||||
|
||||
AWS S3 is the default. You can also pass a `region` option instead of an `endpoint` option for AWS S3.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
import { S3Client } from "bun";
|
||||
|
||||
// AWS S3
|
||||
const s3 = new S3Client({
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
bucket: "my-bucket",
|
||||
// endpoint: "https://s3.us-east-1.amazonaws.com",
|
||||
// region: "us-east-1",
|
||||
});
|
||||
```
|
||||
|
||||
### Using Bun's S3Client with Google Cloud Storage
|
||||
|
||||
To use Bun's S3 client with [Google Cloud Storage](https://cloud.google.com/storage), set `endpoint` to `"https://storage.googleapis.com"` in the `S3Client` constructor.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={8}
|
||||
import { S3Client } from "bun";
|
||||
|
||||
// Google Cloud Storage
|
||||
const gcs = new S3Client({
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
bucket: "my-bucket",
|
||||
endpoint: "https://storage.googleapis.com",
|
||||
});
|
||||
```
|
||||
|
||||
### Using Bun's S3Client with Cloudflare R2
|
||||
|
||||
To use Bun's S3 client with [Cloudflare R2](https://developers.cloudflare.com/r2/), set `endpoint` to the R2 endpoint in the `S3Client` constructor. The R2 endpoint includes your account ID.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={8}
|
||||
import { S3Client } from "bun";
|
||||
|
||||
// CloudFlare R2
|
||||
const r2 = new S3Client({
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
bucket: "my-bucket",
|
||||
endpoint: "https://<account-id>.r2.cloudflarestorage.com",
|
||||
});
|
||||
```
|
||||
|
||||
### Using Bun's S3Client with DigitalOcean Spaces
|
||||
|
||||
To use Bun's S3 client with [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces/), set `endpoint` to the DigitalOcean Spaces endpoint in the `S3Client` constructor.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={8}
|
||||
import { S3Client } from "bun";
|
||||
|
||||
const spaces = new S3Client({
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
bucket: "my-bucket",
|
||||
// region: "nyc3",
|
||||
endpoint: "https://<region>.digitaloceanspaces.com",
|
||||
});
|
||||
```
|
||||
|
||||
### Using Bun's S3Client with MinIO
|
||||
|
||||
To use Bun's S3 client with [MinIO](https://min.io/), set `endpoint` to the URL that MinIO is running on in the `S3Client` constructor.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={10}
|
||||
import { S3Client } from "bun";
|
||||
|
||||
const minio = new S3Client({
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
bucket: "my-bucket",
|
||||
|
||||
// Make sure to use the correct endpoint URL
|
||||
// It might not be localhost in production!
|
||||
endpoint: "http://localhost:9000",
|
||||
});
|
||||
```
|
||||
|
||||
### Using Bun's S3Client with supabase
|
||||
|
||||
To use Bun's S3 client with [supabase](https://supabase.com/), set `endpoint` to the supabase endpoint in the `S3Client` constructor. The supabase endpoint includes your account ID and /storage/v1/s3 path. Make sure to set Enable connection via S3 protocol on in the supabase dashboard in `https://supabase.com/dashboard/project/<account-id>/settings/storage` and to set the region informed in the same section.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={3-10}
|
||||
import { S3Client } from "bun";
|
||||
|
||||
const supabase = new S3Client({
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
bucket: "my-bucket",
|
||||
region: "us-west-1",
|
||||
endpoint: "https://<account-id>.supabase.co/storage/v1/s3/storage",
|
||||
});
|
||||
```
|
||||
|
||||
### Using Bun's S3Client with S3 Virtual Hosted-Style endpoints
|
||||
|
||||
When using a S3 Virtual Hosted-Style endpoint, you need to set the `virtualHostedStyle` option to `true`.
|
||||
|
||||
<Note>
|
||||
- If you don’t specify an endpoint, Bun will automatically determine the AWS S3 endpoint using the provided region and
|
||||
bucket. - If no region is specified, Bun defaults to us-east-1. - If you explicitly provide an endpoint, you don’t
|
||||
need to specify a bucket name.
|
||||
</Note>
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={17, 25}
|
||||
import { S3Client } from "bun";
|
||||
|
||||
// AWS S3 endpoint inferred from region and bucket
|
||||
const s3 = new S3Client({
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
bucket: "my-bucket",
|
||||
virtualHostedStyle: true, // [!code ++]
|
||||
// endpoint: "https://my-bucket.s3.us-east-1.amazonaws.com",
|
||||
// region: "us-east-1",
|
||||
});
|
||||
|
||||
// AWS S3
|
||||
const s3WithEndpoint = new S3Client({
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
endpoint: "https://<bucket-name>.s3.<region>.amazonaws.com",
|
||||
virtualHostedStyle: true, // [!code ++]
|
||||
});
|
||||
|
||||
// Cloudflare R2
|
||||
const r2WithEndpoint = new S3Client({
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
endpoint: "https://<bucket-name>.<account-id>.r2.cloudflarestorage.com",
|
||||
virtualHostedStyle: true, // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Credentials
|
||||
|
||||
Credentials are one of the hardest parts of using S3, and we've tried to make it as easy as possible. By default, Bun reads the following environment variables for credentials.
|
||||
|
||||
| Option name | Environment variable |
|
||||
| ----------------- | ---------------------- |
|
||||
| `accessKeyId` | `S3_ACCESS_KEY_ID` |
|
||||
| `secretAccessKey` | `S3_SECRET_ACCESS_KEY` |
|
||||
| `region` | `S3_REGION` |
|
||||
| `endpoint` | `S3_ENDPOINT` |
|
||||
| `bucket` | `S3_BUCKET` |
|
||||
| `sessionToken` | `S3_SESSION_TOKEN` |
|
||||
|
||||
If the `S3_*` environment variable is not set, Bun will also check for the `AWS_*` environment variable, for each of the above options.
|
||||
|
||||
| Option name | Fallback environment variable |
|
||||
| ----------------- | ----------------------------- |
|
||||
| `accessKeyId` | `AWS_ACCESS_KEY_ID` |
|
||||
| `secretAccessKey` | `AWS_SECRET_ACCESS_KEY` |
|
||||
| `region` | `AWS_REGION` |
|
||||
| `endpoint` | `AWS_ENDPOINT` |
|
||||
| `bucket` | `AWS_BUCKET` |
|
||||
| `sessionToken` | `AWS_SESSION_TOKEN` |
|
||||
|
||||
These environment variables are read from [`.env` files](/runtime/environment-variables) or from the process environment at initialization time (`process.env` is not used for this).
|
||||
|
||||
These defaults are overridden by the options you pass to `s3.file(credentials)`, `new Bun.S3Client(credentials)`, or any of the methods that accept credentials. So if, for example, you use the same credentials for different buckets, you can set the credentials once in your `.env` file and then pass `bucket: "my-bucket"` to the `s3.file()` function without having to specify all the credentials again.
|
||||
|
||||
### `S3Client` objects
|
||||
|
||||
When you're not using environment variables or using multiple buckets, you can create a `S3Client` object to explicitly set credentials.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={3-11}
|
||||
import { S3Client } from "bun";
|
||||
|
||||
const client = new S3Client({
|
||||
accessKeyId: "your-access-key",
|
||||
secretAccessKey: "your-secret-key",
|
||||
bucket: "my-bucket",
|
||||
// sessionToken: "..."
|
||||
endpoint: "https://s3.us-east-1.amazonaws.com",
|
||||
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
|
||||
// endpoint: "http://localhost:9000", // MinIO
|
||||
});
|
||||
|
||||
// Write using a Response
|
||||
await file.write(new Response("Hello World!"));
|
||||
|
||||
// Presign a URL
|
||||
const url = file.presign({
|
||||
expiresIn: 60 * 60 * 24, // 1 day
|
||||
acl: "public-read",
|
||||
});
|
||||
|
||||
// Delete the file
|
||||
await file.delete();
|
||||
```
|
||||
|
||||
### `S3Client.prototype.write`
|
||||
|
||||
To upload or write a file to S3, call `write` on the `S3Client` instance.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={8, 9}
|
||||
const client = new Bun.S3Client({
|
||||
accessKeyId: "your-access-key",
|
||||
secretAccessKey: "your-secret-key",
|
||||
endpoint: "https://s3.us-east-1.amazonaws.com",
|
||||
bucket: "my-bucket",
|
||||
});
|
||||
|
||||
await client.write("my-file.txt", "Hello World!");
|
||||
await client.write("my-file.txt", new Response("Hello World!"));
|
||||
|
||||
// equivalent to
|
||||
// await client.file("my-file.txt").write("Hello World!");
|
||||
```
|
||||
|
||||
### `S3Client.prototype.delete`
|
||||
|
||||
To delete a file from S3, call `delete` on the `S3Client` instance.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={7}
|
||||
const client = new Bun.S3Client({
|
||||
accessKeyId: "your-access-key",
|
||||
secretAccessKey: "your-secret-key",
|
||||
bucket: "my-bucket",
|
||||
});
|
||||
|
||||
await client.delete("my-file.txt");
|
||||
// equivalent to
|
||||
// await client.file("my-file.txt").delete();
|
||||
```
|
||||
|
||||
### `S3Client.prototype.exists`
|
||||
|
||||
To check if a file exists in S3, call `exists` on the `S3Client` instance.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={7}
|
||||
const client = new Bun.S3Client({
|
||||
accessKeyId: "your-access-key",
|
||||
secretAccessKey: "your-secret-key",
|
||||
bucket: "my-bucket",
|
||||
});
|
||||
|
||||
const exists = await client.exists("my-file.txt");
|
||||
// equivalent to
|
||||
// const exists = await client.file("my-file.txt").exists();
|
||||
```
|
||||
|
||||
## `S3File`
|
||||
|
||||
`S3File` instances are created by calling the `S3Client` instance method or the `s3.file()` function. Like `Bun.file()`, `S3File` instances are lazy. They don't refer to something that necessarily exists at the time of creation. That's why all the methods that don't involve network requests are fully synchronous.
|
||||
|
||||
```ts Type Reference icon="/icons/typescript.svg" expandable
|
||||
interface S3File extends Blob {
|
||||
slice(start: number, end?: number): S3File;
|
||||
exists(): Promise<boolean>;
|
||||
unlink(): Promise<void>;
|
||||
presign(options: S3Options): string;
|
||||
text(): Promise<string>;
|
||||
json(): Promise<any>;
|
||||
bytes(): Promise<Uint8Array>;
|
||||
arrayBuffer(): Promise<ArrayBuffer>;
|
||||
stream(options: S3Options): ReadableStream;
|
||||
write(
|
||||
data: string | Uint8Array | ArrayBuffer | Blob | ReadableStream | Response | Request,
|
||||
options?: BlobPropertyBag,
|
||||
): Promise<number>;
|
||||
|
||||
exists(options?: S3Options): Promise<boolean>;
|
||||
unlink(options?: S3Options): Promise<void>;
|
||||
delete(options?: S3Options): Promise<void>;
|
||||
presign(options?: S3Options): string;
|
||||
|
||||
stat(options?: S3Options): Promise<S3Stat>;
|
||||
/**
|
||||
* Size is not synchronously available because it requires a network request.
|
||||
*
|
||||
* @deprecated Use `stat()` instead.
|
||||
*/
|
||||
size: NaN;
|
||||
|
||||
// ... more omitted for brevity
|
||||
}
|
||||
```
|
||||
|
||||
Like `Bun.file()`, `S3File` extends [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob), so all the methods that are available on `Blob` are also available on `S3File`. The same API for reading data from a local file is also available for reading data from S3.
|
||||
|
||||
| Method | Output |
|
||||
| ---------------------------- | ---------------- |
|
||||
| `await s3File.text()` | `string` |
|
||||
| `await s3File.bytes()` | `Uint8Array` |
|
||||
| `await s3File.json()` | `JSON` |
|
||||
| `await s3File.stream()` | `ReadableStream` |
|
||||
| `await s3File.arrayBuffer()` | `ArrayBuffer` |
|
||||
|
||||
That means using `S3File` instances with `fetch()`, `Response`, and other web APIs that accept `Blob` instances just works.
|
||||
|
||||
### Partial reads with `slice`
|
||||
|
||||
To read a partial range of a file, you can use the `slice` method.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={1}
|
||||
const partial = s3file.slice(0, 1024);
|
||||
|
||||
// Read the partial range as a Uint8Array
|
||||
const bytes = await partial.bytes();
|
||||
|
||||
// Read the partial range as a string
|
||||
const text = await partial.text();
|
||||
```
|
||||
|
||||
Internally, this works by using the HTTP `Range` header to request only the bytes you want. This `slice` method is the same as [`Blob.prototype.slice`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice).
|
||||
|
||||
### Deleting files from S3
|
||||
|
||||
To delete a file from S3, you can use the `delete` method.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={1}
|
||||
await s3file.delete();
|
||||
// await s3File.unlink();
|
||||
```
|
||||
|
||||
`delete` is the same as `unlink`.
|
||||
|
||||
## Error codes
|
||||
|
||||
When Bun's S3 API throws an error, it will have a `code` property that matches one of the following values:
|
||||
|
||||
- `ERR_S3_MISSING_CREDENTIALS`
|
||||
- `ERR_S3_INVALID_METHOD`
|
||||
- `ERR_S3_INVALID_PATH`
|
||||
- `ERR_S3_INVALID_ENDPOINT`
|
||||
- `ERR_S3_INVALID_SIGNATURE`
|
||||
- `ERR_S3_INVALID_SESSION_TOKEN`
|
||||
|
||||
When the S3 Object Storage service returns an error (that is, not Bun), it will be an `S3Error` instance (an `Error` instance with the name `"S3Error"`).
|
||||
|
||||
## `S3Client` static methods
|
||||
|
||||
The `S3Client` class provides several static methods for interacting with S3.
|
||||
|
||||
### `S3Client.write` (static)
|
||||
|
||||
To write data directly to a path in the bucket, you can use the `S3Client.write` static method.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={12, 15-18, 22, 25-29}
|
||||
import { S3Client } from "bun";
|
||||
|
||||
const credentials = {
|
||||
accessKeyId: "your-access-key",
|
||||
secretAccessKey: "your-secret-key",
|
||||
bucket: "my-bucket",
|
||||
// endpoint: "https://s3.us-east-1.amazonaws.com",
|
||||
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
|
||||
};
|
||||
|
||||
// Write string
|
||||
await S3Client.write("my-file.txt", "Hello World");
|
||||
|
||||
// Write JSON with type
|
||||
await S3Client.write("data.json", JSON.stringify({ hello: "world" }), {
|
||||
...credentials,
|
||||
type: "application/json",
|
||||
});
|
||||
|
||||
// Write from fetch
|
||||
const res = await fetch("https://example.com/data");
|
||||
await S3Client.write("data.bin", res, credentials);
|
||||
|
||||
// Write with ACL
|
||||
await S3Client.write("public.html", html, {
|
||||
...credentials,
|
||||
acl: "public-read",
|
||||
type: "text/html",
|
||||
});
|
||||
```
|
||||
|
||||
This is equivalent to calling `new S3Client(credentials).write("my-file.txt", "Hello World")`.
|
||||
|
||||
### `S3Client.presign` (static)
|
||||
|
||||
To generate a presigned URL for an S3 file, you can use the `S3Client.presign` static method.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={11-14}
|
||||
import { S3Client } from "bun";
|
||||
|
||||
const credentials = {
|
||||
accessKeyId: "your-access-key",
|
||||
secretAccessKey: "your-secret-key",
|
||||
bucket: "my-bucket",
|
||||
// endpoint: "https://s3.us-east-1.amazonaws.com",
|
||||
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
|
||||
};
|
||||
|
||||
const url = S3Client.presign("my-file.txt", {
|
||||
...credentials,
|
||||
expiresIn: 3600,
|
||||
});
|
||||
```
|
||||
|
||||
This is equivalent to calling `new S3Client(credentials).presign("my-file.txt", { expiresIn: 3600 })`.
|
||||
|
||||
### `S3Client.list` (static)
|
||||
|
||||
To list some or all (up to 1,000) objects in a bucket, you can use the `S3Client.list` static method.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={12, 15-20, 24-29}
|
||||
import { S3Client } from "bun";
|
||||
|
||||
const credentials = {
|
||||
accessKeyId: "your-access-key",
|
||||
secretAccessKey: "your-secret-key",
|
||||
bucket: "my-bucket",
|
||||
// endpoint: "https://s3.us-east-1.amazonaws.com",
|
||||
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
|
||||
};
|
||||
|
||||
// List (up to) 1000 objects in the bucket
|
||||
const allObjects = await S3Client.list(null, credentials);
|
||||
|
||||
// List (up to) 500 objects under `uploads/` prefix, with owner field for each object
|
||||
const uploads = await S3Client.list({
|
||||
prefix: 'uploads/',
|
||||
maxKeys: 500,
|
||||
fetchOwner: true,
|
||||
}, credentials);
|
||||
|
||||
// Check if more results are available
|
||||
if (uploads.isTruncated) {
|
||||
// List next batch of objects under `uploads/` prefix
|
||||
const moreUploads = await S3Client.list({
|
||||
prefix: 'uploads/',
|
||||
maxKeys: 500,
|
||||
startAfter: uploads.contents!.at(-1).key
|
||||
fetchOwner: true,
|
||||
}, credentials);
|
||||
}
|
||||
```
|
||||
|
||||
This is equivalent to calling `new S3Client(credentials).list()`.
|
||||
|
||||
### `S3Client.exists` (static)
|
||||
|
||||
To check if an S3 file exists, you can use the `S3Client.exists` static method.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={11}
|
||||
import { S3Client } from "bun";
|
||||
|
||||
const credentials = {
|
||||
accessKeyId: "your-access-key",
|
||||
secretAccessKey: "your-secret-key",
|
||||
bucket: "my-bucket",
|
||||
// endpoint: "https://s3.us-east-1.amazonaws.com",
|
||||
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
|
||||
};
|
||||
|
||||
const exists = await S3Client.exists("my-file.txt", credentials);
|
||||
```
|
||||
|
||||
The same method also works on `S3File` instances.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight=7}
|
||||
import { s3 } from "bun";
|
||||
|
||||
const s3file = s3.file("my-file.txt", {
|
||||
// ...credentials,
|
||||
});
|
||||
|
||||
const exists = await s3file.exists();
|
||||
```
|
||||
|
||||
### `S3Client.size` (static)
|
||||
|
||||
To quickly check the size of S3 file without downloading it, you can use the `S3Client.size` static method.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={11}
|
||||
import { S3Client } from "bun";
|
||||
|
||||
const credentials = {
|
||||
accessKeyId: "your-access-key",
|
||||
secretAccessKey: "your-secret-key",
|
||||
bucket: "my-bucket",
|
||||
// endpoint: "https://s3.us-east-1.amazonaws.com",
|
||||
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
|
||||
};
|
||||
|
||||
const bytes = await S3Client.size("my-file.txt", credentials);
|
||||
```
|
||||
|
||||
This is equivalent to calling `new S3Client(credentials).size("my-file.txt")`.
|
||||
|
||||
### `S3Client.stat` (static)
|
||||
|
||||
To get the size, etag, and other metadata of an S3 file, you can use the `S3Client.stat` static method.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
import { S3Client } from "bun";
|
||||
|
||||
const credentials = {
|
||||
accessKeyId: "your-access-key",
|
||||
secretAccessKey: "your-secret-key",
|
||||
bucket: "my-bucket",
|
||||
// endpoint: "https://s3.us-east-1.amazonaws.com",
|
||||
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
|
||||
};
|
||||
|
||||
const stat = await S3Client.stat("my-file.txt", credentials);
|
||||
```
|
||||
|
||||
```txt
|
||||
{
|
||||
etag: "\"7a30b741503c0b461cc14157e2df4ad8\"",
|
||||
lastModified: 2025-01-07T00:19:10.000Z,
|
||||
size: 1024,
|
||||
type: "text/plain;charset=utf-8",
|
||||
}
|
||||
```
|
||||
|
||||
### `S3Client.delete` (static)
|
||||
|
||||
To delete an S3 file, you can use the `S3Client.delete` static method.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={10, 15}
|
||||
import { S3Client } from "bun";
|
||||
|
||||
const credentials = {
|
||||
accessKeyId: "your-access-key",
|
||||
secretAccessKey: "your-secret-key",
|
||||
bucket: "my-bucket",
|
||||
// endpoint: "https://s3.us-east-1.amazonaws.com",
|
||||
};
|
||||
|
||||
await S3Client.delete("my-file.txt", credentials);
|
||||
// equivalent to
|
||||
// await new S3Client(credentials).delete("my-file.txt");
|
||||
|
||||
// S3Client.unlink is alias of S3Client.delete
|
||||
await S3Client.unlink("my-file.txt", credentials);
|
||||
```
|
||||
|
||||
## `s3://` protocol
|
||||
|
||||
To make it easier to use the same code for local files and S3 files, the `s3://` protocol is supported in `fetch` and `Bun.file()`.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg"
|
||||
const response = await fetch("s3://my-bucket/my-file.txt");
|
||||
const file = Bun.file("s3://my-bucket/my-file.txt");
|
||||
```
|
||||
|
||||
You can additionally pass `s3` options to the `fetch` and `Bun.file` functions.
|
||||
|
||||
```ts s3.ts icon="/icons/typescript.svg" highlight={2-6}
|
||||
const response = await fetch("s3://my-bucket/my-file.txt", {
|
||||
s3: {
|
||||
accessKeyId: "your-access-key",
|
||||
secretAccessKey: "your-secret-key",
|
||||
endpoint: "https://s3.us-east-1.amazonaws.com",
|
||||
},
|
||||
headers: {
|
||||
range: "bytes=0-1023",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### UTF-8, UTF-16, and BOM (byte order mark)
|
||||
|
||||
Like `Response` and `Blob`, `S3File` assumes UTF-8 encoding by default.
|
||||
|
||||
When calling one of the `text()` or `json()` methods on an `S3File`:
|
||||
|
||||
- When a UTF-16 byte order mark (BOM) is detected, it will be treated as UTF-16. JavaScriptCore natively supports UTF-16, so it skips the UTF-8 transcoding process (and strips the BOM). This is mostly good, but it does mean if you have invalid surrogate pairs characters in your UTF-16 string, they will be passed through to JavaScriptCore (same as source code).
|
||||
- When a UTF-8 BOM is detected, it gets stripped before the string is passed to JavaScriptCore and invalid UTF-8 codepoints are replaced with the Unicode replacement character (`\uFFFD`).
|
||||
- UTF-32 is not supported.
|
||||
339
docs/runtime/secrets.mdx
Normal file
339
docs/runtime/secrets.mdx
Normal file
@@ -0,0 +1,339 @@
|
||||
---
|
||||
title: Secrets
|
||||
description: Use Bun's Secrets API to store and retrieve sensitive credentials securely
|
||||
---
|
||||
|
||||
Store and retrieve sensitive credentials securely using the operating system's native credential storage APIs.
|
||||
|
||||
<Warning>This API is new and experimental. It may change in the future.</Warning>
|
||||
|
||||
```typescript index.ts icon="/icons/typescript.svg"
|
||||
import { secrets } from "bun";
|
||||
|
||||
let githubToken: string | null = await secrets.get({
|
||||
service: "my-cli-tool",
|
||||
name: "github-token",
|
||||
});
|
||||
|
||||
if (!githubToken) {
|
||||
githubToken = prompt("Please enter your GitHub token");
|
||||
|
||||
await secrets.set({
|
||||
service: "my-cli-tool",
|
||||
name: "github-token",
|
||||
value: githubToken,
|
||||
});
|
||||
console.log("GitHub token stored");
|
||||
}
|
||||
|
||||
const response = await fetch("https://api.github.com/name", {
|
||||
headers: { Authorization: `token ${githubToken}` },
|
||||
});
|
||||
|
||||
console.log(`Logged in as ${(await response.json()).login}`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
`Bun.secrets` provides a cross-platform API for managing sensitive credentials that CLI tools and development applications typically store in plaintext files like `~/.npmrc`, `~/.aws/credentials`, or `.env` files. It uses:
|
||||
|
||||
- **macOS**: Keychain Services
|
||||
- **Linux**: libsecret (GNOME Keyring, KWallet, etc.)
|
||||
- **Windows**: Windows Credential Manager
|
||||
|
||||
All operations are asynchronous and non-blocking, running on Bun's threadpool.
|
||||
|
||||
<Note>
|
||||
In the future, we may add an additional `provider` option to make this better for production deployment secrets, but
|
||||
today this API is mostly useful for local development tools.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
### `Bun.secrets.get(options)`
|
||||
|
||||
Retrieve a stored credential.
|
||||
|
||||
```typescript
|
||||
import { secrets } from "bun";
|
||||
|
||||
const password = await Bun.secrets.get({
|
||||
service: "my-app",
|
||||
name: "alice@example.com",
|
||||
});
|
||||
// Returns: string | null
|
||||
|
||||
// Or if you prefer without an object
|
||||
const password = await Bun.secrets.get("my-app", "alice@example.com");
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `options.service` (string, required) - The service or application name
|
||||
- `options.name` (string, required) - The username or account identifier
|
||||
|
||||
**Returns:**
|
||||
|
||||
- `Promise<string | null>` - The stored password, or `null` if not found
|
||||
|
||||
### `Bun.secrets.set(options, value)`
|
||||
|
||||
Store or update a credential.
|
||||
|
||||
```typescript
|
||||
import { secrets } from "bun";
|
||||
|
||||
await secrets.set({
|
||||
service: "my-app",
|
||||
name: "alice@example.com",
|
||||
value: "super-secret-password",
|
||||
});
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `options.service` (string, required) - The service or application name
|
||||
- `options.name` (string, required) - The username or account identifier
|
||||
- `value` (string, required) - The password or secret to store
|
||||
|
||||
**Notes:**
|
||||
|
||||
- If a credential already exists for the given service/name combination, it will be replaced
|
||||
- The stored value is encrypted by the operating system
|
||||
|
||||
### `Bun.secrets.delete(options)`
|
||||
|
||||
Delete a stored credential.
|
||||
|
||||
```typescript
|
||||
const deleted = await Bun.secrets.delete({
|
||||
service: "my-app",
|
||||
name: "alice@example.com",
|
||||
value: "super-secret-password",
|
||||
});
|
||||
// Returns: boolean
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `options.service` (string, required) - The service or application name
|
||||
- `options.name` (string, required) - The username or account identifier
|
||||
|
||||
**Returns:**
|
||||
|
||||
- `Promise<boolean>` - `true` if a credential was deleted, `false` if not found
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Storing CLI Tool Credentials
|
||||
|
||||
```javascript
|
||||
// Store GitHub CLI token (instead of ~/.config/gh/hosts.yml)
|
||||
await Bun.secrets.set({
|
||||
service: "my-app.com",
|
||||
name: "github-token",
|
||||
value: "ghp_xxxxxxxxxxxxxxxxxxxx",
|
||||
});
|
||||
|
||||
// Or if you prefer without an object
|
||||
await Bun.secrets.set("my-app.com", "github-token", "ghp_xxxxxxxxxxxxxxxxxxxx");
|
||||
|
||||
// Store npm registry token (instead of ~/.npmrc)
|
||||
await Bun.secrets.set({
|
||||
service: "npm-registry",
|
||||
name: "https://registry.npmjs.org",
|
||||
value: "npm_xxxxxxxxxxxxxxxxxxxx",
|
||||
});
|
||||
|
||||
// Retrieve for API calls
|
||||
const token = await Bun.secrets.get({
|
||||
service: "gh-cli",
|
||||
name: "github.com",
|
||||
});
|
||||
|
||||
if (token) {
|
||||
const response = await fetch("https://api.github.com/name", {
|
||||
headers: {
|
||||
Authorization: `token ${token}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Migrating from Plaintext Config Files
|
||||
|
||||
```javascript
|
||||
// Instead of storing in ~/.aws/credentials
|
||||
await Bun.secrets.set({
|
||||
service: "aws-cli",
|
||||
name: "AWS_SECRET_ACCESS_KEY",
|
||||
value: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
});
|
||||
|
||||
// Instead of .env files with sensitive data
|
||||
await Bun.secrets.set({
|
||||
service: "my-app",
|
||||
name: "api-key",
|
||||
value: "sk_live_xxxxxxxxxxxxxxxxxxxx",
|
||||
});
|
||||
|
||||
// Load at runtime
|
||||
const apiKey =
|
||||
(await Bun.secrets.get({
|
||||
service: "my-app",
|
||||
name: "api-key",
|
||||
})) || process.env.API_KEY; // Fallback for CI/production
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```javascript
|
||||
try {
|
||||
await Bun.secrets.set({
|
||||
service: "my-app",
|
||||
name: "alice",
|
||||
value: "password123",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to store credential:", error.message);
|
||||
}
|
||||
|
||||
// Check if a credential exists
|
||||
const password = await Bun.secrets.get({
|
||||
service: "my-app",
|
||||
name: "alice",
|
||||
});
|
||||
|
||||
if (password === null) {
|
||||
console.log("No credential found");
|
||||
}
|
||||
```
|
||||
|
||||
### Updating Credentials
|
||||
|
||||
```javascript
|
||||
// Initial password
|
||||
await Bun.secrets.set({
|
||||
service: "email-server",
|
||||
name: "admin@example.com",
|
||||
value: "old-password",
|
||||
});
|
||||
|
||||
// Update to new password
|
||||
await Bun.secrets.set({
|
||||
service: "email-server",
|
||||
name: "admin@example.com",
|
||||
value: "new-password",
|
||||
});
|
||||
|
||||
// The old password is replaced
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Platform Behavior
|
||||
|
||||
### macOS (Keychain)
|
||||
|
||||
- Credentials are stored in the name's login keychain
|
||||
- The keychain may prompt for access permission on first use
|
||||
- Credentials persist across system restarts
|
||||
- Accessible by the name who stored them
|
||||
|
||||
### Linux (libsecret)
|
||||
|
||||
- Requires a secret service daemon (GNOME Keyring, KWallet, etc.)
|
||||
- Credentials are stored in the default collection
|
||||
- May prompt for unlock if the keyring is locked
|
||||
- The secret service must be running
|
||||
|
||||
### Windows (Credential Manager)
|
||||
|
||||
- Credentials are stored in Windows Credential Manager
|
||||
- Visible in Control Panel → Credential Manager → Windows Credentials
|
||||
- Persist with `CRED_PERSIST_ENTERPRISE` flag so it's scoped per user
|
||||
- Encrypted using Windows Data Protection API
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Encryption**: Credentials are encrypted by the operating system's credential manager
|
||||
2. **Access Control**: Only the name who stored the credential can retrieve it
|
||||
3. **No Plain Text**: Passwords are never stored in plain text
|
||||
4. **Memory Safety**: Bun zeros out password memory after use
|
||||
5. **Process Isolation**: Credentials are isolated per name account
|
||||
|
||||
## Limitations
|
||||
|
||||
- Maximum password length varies by platform (typically 2048-4096 bytes)
|
||||
- Service and name names should be reasonable lengths (< 256 characters)
|
||||
- Some special characters may need escaping depending on the platform
|
||||
- Requires appropriate system services:
|
||||
- Linux: Secret service daemon must be running
|
||||
- macOS: Keychain Access must be available
|
||||
- Windows: Credential Manager service must be enabled
|
||||
|
||||
---
|
||||
|
||||
## Comparison with Environment Variables
|
||||
|
||||
Unlike environment variables, `Bun.secrets`:
|
||||
|
||||
- ✅ Encrypts credentials at rest (thanks to the operating system)
|
||||
- ✅ Avoids exposing secrets in process memory dumps (memory is zeroed after its no longer needed)
|
||||
- ✅ Survives application restarts
|
||||
- ✅ Can be updated without restarting the application
|
||||
- ✅ Provides name-level access control
|
||||
- ❌ Requires OS credential service
|
||||
- ❌ Not very useful for deployment secrets (use environment variables in production)
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use descriptive service names**: Match the tool or application name
|
||||
If you're building a CLI for external use, you probably should use a UTI (Uniform Type Identifier) for the service name.
|
||||
|
||||
```javascript
|
||||
// Good - matches the actual tool
|
||||
{ service: "com.docker.hub", name: "username" }
|
||||
{ service: "com.vercel.cli", name: "team-name" }
|
||||
|
||||
// Avoid - too generic
|
||||
{ service: "api", name: "key" }
|
||||
```
|
||||
|
||||
2. **Credentials-only**: Don't store application configuration in this API
|
||||
This API is slow, you probably still need to use a config file for some things.
|
||||
|
||||
3. **Use for local development tools**:
|
||||
- ✅ CLI tools (gh, npm, docker, kubectl)
|
||||
- ✅ Local development servers
|
||||
- ✅ Personal API keys for testing
|
||||
- ❌ Production servers (use proper secret management)
|
||||
|
||||
---
|
||||
|
||||
## TypeScript
|
||||
|
||||
```typescript
|
||||
namespace Bun {
|
||||
interface SecretsOptions {
|
||||
service: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Secrets {
|
||||
get(options: SecretsOptions): Promise<string | null>;
|
||||
set(options: SecretsOptions, value: string): Promise<void>;
|
||||
delete(options: SecretsOptions): Promise<boolean>;
|
||||
}
|
||||
|
||||
const secrets: Secrets;
|
||||
}
|
||||
```
|
||||
57
docs/runtime/semver.mdx
Normal file
57
docs/runtime/semver.mdx
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Semver
|
||||
description: Use Bun's semantic versioning API
|
||||
---
|
||||
|
||||
Bun implements a semantic versioning API which can be used to compare versions and determine if a version is compatible with another range of versions. The versions and ranges are designed to be compatible with `node-semver`, which is used by npm clients.
|
||||
|
||||
It's about 20x faster than `node-semver`.
|
||||
|
||||
<Frame></Frame>
|
||||
|
||||
Currently, this API provides two functions:
|
||||
|
||||
## `Bun.semver.satisfies(version: string, range: string): boolean`
|
||||
|
||||
Returns `true` if `version` satisfies `range`, otherwise `false`.
|
||||
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
import { semver } from "bun";
|
||||
|
||||
semver.satisfies("1.0.0", "^1.0.0"); // true
|
||||
semver.satisfies("1.0.0", "^1.0.1"); // false
|
||||
semver.satisfies("1.0.0", "~1.0.0"); // true
|
||||
semver.satisfies("1.0.0", "~1.0.1"); // false
|
||||
semver.satisfies("1.0.0", "1.0.0"); // true
|
||||
semver.satisfies("1.0.0", "1.0.1"); // false
|
||||
semver.satisfies("1.0.1", "1.0.0"); // false
|
||||
semver.satisfies("1.0.0", "1.0.x"); // true
|
||||
semver.satisfies("1.0.0", "1.x.x"); // true
|
||||
semver.satisfies("1.0.0", "x.x.x"); // true
|
||||
semver.satisfies("1.0.0", "1.0.0 - 2.0.0"); // true
|
||||
semver.satisfies("1.0.0", "1.0.0 - 1.0.1"); // true
|
||||
```
|
||||
|
||||
If `range` is invalid, it returns false. If `version` is invalid, it returns false.
|
||||
|
||||
## `Bun.semver.order(versionA: string, versionB: string): 0 | 1 | -1`
|
||||
|
||||
Returns `0` if `versionA` and `versionB` are equal, `1` if `versionA` is greater than `versionB`, and `-1` if `versionA` is less than `versionB`.
|
||||
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
import { semver } from "bun";
|
||||
|
||||
semver.order("1.0.0", "1.0.0"); // 0
|
||||
semver.order("1.0.0", "1.0.1"); // -1
|
||||
semver.order("1.0.1", "1.0.0"); // 1
|
||||
|
||||
const unsorted = ["1.0.0", "1.0.1", "1.0.0-alpha", "1.0.0-beta", "1.0.0-rc"];
|
||||
unsorted.sort(semver.order); // ["1.0.0-alpha", "1.0.0-beta", "1.0.0-rc", "1.0.0", "1.0.1"]
|
||||
console.log(unsorted);
|
||||
```
|
||||
|
||||
If you need other semver functions, feel free to open an issue or pull request.
|
||||
@@ -1,8 +1,13 @@
|
||||
---
|
||||
title: Shell
|
||||
description: Use Bun's shell scripting API to run shell commands from JavaScript
|
||||
---
|
||||
|
||||
Bun Shell makes shell scripting with JavaScript & TypeScript fun. It's a cross-platform bash-like shell with seamless JavaScript interop.
|
||||
|
||||
Quickstart:
|
||||
|
||||
```js
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
import { $ } from "bun";
|
||||
|
||||
const response = await fetch("https://example.com");
|
||||
@@ -11,7 +16,9 @@ const response = await fetch("https://example.com");
|
||||
await $`cat < ${response} | wc -c`; // 1256
|
||||
```
|
||||
|
||||
## Features:
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Cross-platform**: works on Windows, Linux & macOS. Instead of `rimraf` or `cross-env`', you can use Bun Shell without installing extra dependencies. Common shell commands like `ls`, `cd`, `rm` are implemented natively.
|
||||
- **Familiar**: Bun Shell is a bash-like shell, supporting redirection, pipes, environment variables and more.
|
||||
@@ -22,6 +29,8 @@ await $`cat < ${response} | wc -c`; // 1256
|
||||
- **Shell scripting**: Bun Shell can be used to run shell scripts (`.bun.sh` files).
|
||||
- **Custom interpreter**: Bun Shell is written in Zig, along with its lexer, parser, and interpreter. Bun Shell is a small programming language.
|
||||
|
||||
---
|
||||
|
||||
## Getting started
|
||||
|
||||
The simplest shell command is `echo`. To run it, use the `$` template literal tag:
|
||||
@@ -62,6 +71,8 @@ console.log(stdout); // Buffer(7) [ 72, 101, 108, 108, 111, 33, 10 ]
|
||||
console.log(stderr); // Buffer(0) []
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error handling
|
||||
|
||||
By default, non-zero exit codes will throw an error. This `ShellError` contains information about the command run.
|
||||
@@ -84,9 +95,7 @@ Throwing can be disabled with `.nothrow()`. The result's `exitCode` will need to
|
||||
```js
|
||||
import { $ } from "bun";
|
||||
|
||||
const { stdout, stderr, exitCode } = await $`something-that-may-fail`
|
||||
.nothrow()
|
||||
.quiet();
|
||||
const { stdout, stderr, exitCode } = await $`something-that-may-fail`.nothrow().quiet();
|
||||
|
||||
if (exitCode !== 0) {
|
||||
console.log(`Non-zero exit code ${exitCode}`);
|
||||
@@ -113,6 +122,8 @@ $.throws(false);
|
||||
await $`something-that-may-fail`; // No exception thrown
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Redirection
|
||||
|
||||
A command's _input_ or _output_ may be _redirected_ using the typical Bash operators:
|
||||
@@ -258,11 +269,11 @@ await $`
|
||||
`;
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
<Note>
|
||||
|
||||
**NOTE**: Because Bun internally uses the special [`raw`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#raw_strings) property on the input template literal, using the backtick syntax for command substitution won't work:
|
||||
Because Bun internally uses the special [`raw`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#raw_strings) property on the input template literal, using the backtick syntax for command substitution won't work:
|
||||
|
||||
```js
|
||||
```ts icon="file-code"
|
||||
import { $ } from "bun";
|
||||
|
||||
await $`echo \`echo hi\``;
|
||||
@@ -282,7 +293,9 @@ echo hi
|
||||
|
||||
We instead recommend sticking to the `$(...)` syntax.
|
||||
|
||||
{% /callout %}
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Environment variables
|
||||
|
||||
@@ -378,6 +391,8 @@ await $`pwd`; // /tmp
|
||||
await $`pwd`.cwd("/"); // /
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reading output
|
||||
|
||||
To read the output of a command as a string, use `.text()`:
|
||||
@@ -438,6 +453,8 @@ const result = await $`echo "Hello World!"`.blob();
|
||||
console.log(result); // Blob(13) { size: 13, type: "text/plain" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Builtin Commands
|
||||
|
||||
For cross-platform compatibility, Bun Shell implements a set of builtin commands, in addition to reading commands from the PATH environment variable.
|
||||
@@ -469,6 +486,8 @@ For cross-platform compatibility, Bun Shell implements a set of builtin commands
|
||||
|
||||
- See [Issue #9716](https://github.com/oven-sh/bun/issues/9716) for the full list.
|
||||
|
||||
---
|
||||
|
||||
## Utilities
|
||||
|
||||
Bun Shell also implements a set of utilities for working with shells.
|
||||
@@ -506,32 +525,44 @@ await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`;
|
||||
// => baz
|
||||
```
|
||||
|
||||
## .sh file loader
|
||||
---
|
||||
|
||||
## `.sh` file loader
|
||||
|
||||
For simple shell scripts, instead of `/bin/sh`, you can use Bun Shell to run shell scripts.
|
||||
|
||||
To do so, just run the script with `bun` on a file with the `.sh` extension.
|
||||
|
||||
```sh#script.sh
|
||||
```sh script.sh icon="file-code"
|
||||
echo "Hello World! pwd=$(pwd)"
|
||||
```
|
||||
|
||||
```sh
|
||||
$ bun ./script.sh
|
||||
```sh terminal icon="terminal"
|
||||
bun ./script.sh
|
||||
```
|
||||
|
||||
```txt
|
||||
Hello World! pwd=/home/demo
|
||||
```
|
||||
|
||||
Scripts with Bun Shell are cross platform, which means they work on Windows:
|
||||
|
||||
```powershell
|
||||
> bun .\script.sh
|
||||
```powershell powershell icon="windows"
|
||||
bun .\script.sh
|
||||
```
|
||||
|
||||
```txt
|
||||
Hello World! pwd=C:\Users\Demo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation notes
|
||||
|
||||
Bun Shell is a small programming language in Bun that is implemented in Zig. It includes a handwritten lexer, parser, and interpreter. Unlike bash, zsh, and other shells, Bun Shell runs operations concurrently.
|
||||
|
||||
---
|
||||
|
||||
## Security in the Bun shell
|
||||
|
||||
By design, the Bun shell _does not invoke a system shell_ (like `/bin/sh`) and
|
||||
@@ -594,11 +625,12 @@ const branch = "--upload-pack=echo pwned";
|
||||
await $`git ls-remote origin ${branch}`;
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
**Recommendation** — As is best practice in every language, always sanitize
|
||||
user-provided input before passing it as an argument to an external command.
|
||||
The responsibility for validating arguments rests with your application code.
|
||||
{% /callout %}
|
||||
<Note>
|
||||
**Recommendation** — As is best practice in every language, always sanitize user-provided input before passing it as
|
||||
an argument to an external command. The responsibility for validating arguments rests with your application code.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Credits
|
||||
|
||||
1404
docs/runtime/sql.mdx
Normal file
1404
docs/runtime/sql.mdx
Normal file
File diff suppressed because it is too large
Load Diff
708
docs/runtime/sqlite.mdx
Normal file
708
docs/runtime/sqlite.mdx
Normal file
@@ -0,0 +1,708 @@
|
||||
---
|
||||
title: SQLite
|
||||
description: Bun natively implements a high-performance SQLite3 driver.
|
||||
---
|
||||
|
||||
Bun natively implements a high-performance [SQLite3](https://www.sqlite.org/) driver. To use it import from the built-in `bun:sqlite` module.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const db = new Database(":memory:");
|
||||
const query = db.query("select 'Hello world' as message;");
|
||||
query.get();
|
||||
```
|
||||
|
||||
```txt
|
||||
{ message: "Hello world" }
|
||||
```
|
||||
|
||||
The API is simple, synchronous, and fast. Credit to [better-sqlite3](https://github.com/JoshuaWise/better-sqlite3) and its contributors for inspiring the API of `bun:sqlite`.
|
||||
|
||||
Features include:
|
||||
|
||||
- Transactions
|
||||
- Parameters (named & positional)
|
||||
- Prepared statements
|
||||
- Datatype conversions (`BLOB` becomes `Uint8Array`)
|
||||
- Map query results to classes without an ORM - `query.as(MyClass)`
|
||||
- The fastest performance of any SQLite driver for JavaScript
|
||||
- `bigint` support
|
||||
- Multi-query statements (e.g. `SELECT 1; SELECT 2;`) in a single call to database.run(query)
|
||||
|
||||
The `bun:sqlite` module is roughly 3-6x faster than `better-sqlite3` and 8-9x faster than `deno.land/x/sqlite` for read queries. Each driver was benchmarked against the [Northwind Traders](https://github.com/jpwhite3/northwind-SQLite3/blob/46d5f8a64f396f87cd374d1600dbf521523980e8/Northwind_large.sqlite.zip) dataset. View and run the [benchmark source](https://github.com/oven-sh/bun/tree/main/bench/sqlite).
|
||||
|
||||
<Frame caption="Benchmarked on an M1 MacBook Pro (64GB) running macOS 12.3.1">
|
||||

|
||||
</Frame>
|
||||
|
||||
---
|
||||
|
||||
## Database
|
||||
|
||||
To open or create a SQLite3 database:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const db = new Database("mydb.sqlite");
|
||||
```
|
||||
|
||||
To open an in-memory database:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
// all of these do the same thing
|
||||
const db = new Database(":memory:");
|
||||
const db = new Database();
|
||||
const db = new Database("");
|
||||
```
|
||||
|
||||
To open in `readonly` mode:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={2}
|
||||
import { Database } from "bun:sqlite";
|
||||
const db = new Database("mydb.sqlite", { readonly: true });
|
||||
```
|
||||
|
||||
To create the database if the file doesn't exist:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={2}
|
||||
import { Database } from "bun:sqlite";
|
||||
const db = new Database("mydb.sqlite", { create: true });
|
||||
```
|
||||
|
||||
### Strict mode
|
||||
|
||||
By default, `bun:sqlite` requires binding parameters to include the `$`, `:`, or `@` prefix, and does not throw an error if a parameter is missing.
|
||||
|
||||
To instead throw an error when a parameter is missing and allow binding without a prefix, set `strict: true` on the `Database` constructor:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={3}
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const strict = new Database(":memory:", { strict: true });
|
||||
|
||||
// throws error because of the typo:
|
||||
const query = strict.query("SELECT $message;").all({ messag: "Hello world" });
|
||||
|
||||
const notStrict = new Database(":memory:");
|
||||
// does not throw error:
|
||||
notStrict.query("SELECT $message;").all({ messag: "Hello world" });
|
||||
```
|
||||
|
||||
### Load via ES module import
|
||||
|
||||
You can also use an import attribute to load a database.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={1}
|
||||
import db from "./mydb.sqlite" with { type: "sqlite" };
|
||||
|
||||
console.log(db.query("select * from users LIMIT 1").get());
|
||||
```
|
||||
|
||||
This is equivalent to the following:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
import { Database } from "bun:sqlite";
|
||||
const db = new Database("./mydb.sqlite");
|
||||
```
|
||||
|
||||
### `.close(throwOnError: boolean = false)`
|
||||
|
||||
To close a database connection, but allow existing queries to finish, call `.close(false)`:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={3}
|
||||
const db = new Database();
|
||||
// ... do stuff
|
||||
db.close(false);
|
||||
```
|
||||
|
||||
To close the database and throw an error if there are any pending queries, call `.close(true)`:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={3}
|
||||
const db = new Database();
|
||||
// ... do stuff
|
||||
db.close(true);
|
||||
```
|
||||
|
||||
<Note>
|
||||
`close(false)` is called automatically when the database is garbage collected. It is safe to call multiple times but
|
||||
has no effect after the first.
|
||||
</Note>
|
||||
|
||||
### `using` statement
|
||||
|
||||
You can use the `using` statement to ensure that a database connection is closed when the `using` block is exited.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={4, 5}
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
{
|
||||
using db = new Database("mydb.sqlite");
|
||||
using query = db.query("select 'Hello world' as message;");
|
||||
console.log(query.get());
|
||||
}
|
||||
```
|
||||
|
||||
```txt
|
||||
{ message: "Hello world" }
|
||||
```
|
||||
|
||||
### `.serialize()`
|
||||
|
||||
`bun:sqlite` supports SQLite's built-in mechanism for [serializing](https://www.sqlite.org/c3ref/serialize.html) and [deserializing](https://www.sqlite.org/c3ref/deserialize.html) databases to and from memory.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={2}
|
||||
const olddb = new Database("mydb.sqlite");
|
||||
const contents = olddb.serialize(); // => Uint8Array
|
||||
const newdb = Database.deserialize(contents);
|
||||
```
|
||||
|
||||
Internally, `.serialize()` calls [`sqlite3_serialize`](https://www.sqlite.org/c3ref/serialize.html).
|
||||
|
||||
### `.query()`
|
||||
|
||||
Use the `db.query()` method on your `Database` instance to [prepare](https://www.sqlite.org/c3ref/prepare.html) a SQL query. The result is a `Statement` instance that will be cached on the `Database` instance. _The query will not be executed._
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
const query = db.query(`select "Hello world" as message`);
|
||||
```
|
||||
|
||||
<Note>
|
||||
Use the `.prepare()` method to prepare a query _without_ caching it on the `Database` instance.
|
||||
|
||||
```ts
|
||||
// compile the prepared statement
|
||||
const query = db.prepare("SELECT * FROM foo WHERE bar = ?");
|
||||
```
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## WAL mode
|
||||
|
||||
SQLite supports [write-ahead log mode](https://www.sqlite.org/wal.html) (WAL) which dramatically improves performance, especially in situations with many concurrent readers and a single writer. It's broadly recommended to enable WAL mode for most typical applications.
|
||||
|
||||
To enable WAL mode, run this pragma query at the beginning of your application:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
db.exec("PRAGMA journal_mode = WAL;");
|
||||
```
|
||||
|
||||
<Accordion title="What is WAL mode?">
|
||||
In WAL mode, writes to the database are written directly to a separate file called the "WAL file" (write-ahead log). This file will be later integrated into the main database file. Think of it as a buffer for pending writes. Refer to the [SQLite docs](https://www.sqlite.org/wal.html) for a more detailed overview.
|
||||
|
||||
On macOS, WAL files may be persistent by default. This is not a bug, it is how macOS configured the system version of SQLite.
|
||||
|
||||
</Accordion>
|
||||
|
||||
---
|
||||
|
||||
## Statements
|
||||
|
||||
A `Statement` is a _prepared query_, which means it's been parsed and compiled into an efficient binary form. It can be executed multiple times in a performant way.
|
||||
|
||||
Create a statement with the `.query` method on your `Database` instance.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
const query = db.query(`select "Hello world" as message`);
|
||||
```
|
||||
|
||||
Queries can contain parameters. These can be numerical (`?1`) or named (`$param` or `:param` or `@param`).
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
const query = db.query(`SELECT ?1, ?2;`);
|
||||
const query = db.query(`SELECT $param1, $param2;`);
|
||||
```
|
||||
|
||||
Values are bound to these parameters when the query is executed. A `Statement` can be executed with several different methods, each returning the results in a different form.
|
||||
|
||||
### Binding values
|
||||
|
||||
To bind values to a statement, pass an object to the `.all()`, `.get()`, `.run()`, or `.values()` method.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={2}
|
||||
const query = db.query(`select $message;`);
|
||||
query.all({ $message: "Hello world" });
|
||||
```
|
||||
|
||||
You can bind using positional parameters too:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
const query = db.query(`select ?1;`);
|
||||
query.all("Hello world");
|
||||
```
|
||||
|
||||
#### `strict: true` lets you bind values without prefixes
|
||||
|
||||
By default, the `$`, `:`, and `@` prefixes are **included** when binding values to named parameters. To bind without these prefixes, use the `strict` option in the `Database` constructor.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const db = new Database(":memory:", {
|
||||
// bind values without prefixes
|
||||
strict: true, // [!code ++]
|
||||
});
|
||||
|
||||
const query = db.query(`select $message;`);
|
||||
|
||||
// strict: true
|
||||
query.all({ message: "Hello world" });
|
||||
|
||||
// strict: false
|
||||
// query.all({ $message: "Hello world" });
|
||||
```
|
||||
|
||||
### `.all()`
|
||||
|
||||
Use `.all()` to run a query and get back the results as an array of objects.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={2}
|
||||
const query = db.query(`select $message;`);
|
||||
query.all({ $message: "Hello world" });
|
||||
```
|
||||
|
||||
```txt
|
||||
[{ message: "Hello world" }]
|
||||
```
|
||||
|
||||
Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) and repeatedly calls [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) until it returns `SQLITE_DONE`.
|
||||
|
||||
### `.get()`
|
||||
|
||||
Use `.get()` to run a query and get back the first result as an object.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={2}
|
||||
const query = db.query(`select $message;`);
|
||||
query.get({ $message: "Hello world" });
|
||||
```
|
||||
|
||||
```txt
|
||||
{ $message: "Hello world" }
|
||||
```
|
||||
|
||||
Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) followed by [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) until it no longer returns `SQLITE_ROW`. If the query returns no rows, `undefined` is returned.
|
||||
|
||||
### `.run()`
|
||||
|
||||
Use `.run()` to run a query and get back `undefined`. This is useful for schema-modifying queries (e.g. `CREATE TABLE`) or bulk write operations.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={2}
|
||||
const query = db.query(`create table foo;`);
|
||||
query.run();
|
||||
```
|
||||
|
||||
```txt
|
||||
{
|
||||
lastInsertRowid: 0,
|
||||
changes: 0,
|
||||
}
|
||||
```
|
||||
|
||||
Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) and calls [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) once. Stepping through all the rows is not necessary when you don't care about the results.
|
||||
|
||||
The `lastInsertRowid` property returns the ID of the last row inserted into the database. The `changes` property is the number of rows affected by the query.
|
||||
|
||||
### `.as(Class)` - Map query results to a class
|
||||
|
||||
Use `.as(Class)` to run a query and get back the results as instances of a class. This lets you attach methods & getters/setters to results.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={10}
|
||||
class Movie {
|
||||
title: string;
|
||||
year: number;
|
||||
|
||||
get isMarvel() {
|
||||
return this.title.includes("Marvel");
|
||||
}
|
||||
}
|
||||
|
||||
const query = db.query("SELECT title, year FROM movies").as(Movie);
|
||||
const movies = query.all();
|
||||
const first = query.get();
|
||||
|
||||
console.log(movies[0].isMarvel);
|
||||
console.log(first.isMarvel);
|
||||
```
|
||||
|
||||
```txt
|
||||
true
|
||||
true
|
||||
```
|
||||
|
||||
As a performance optimization, the class constructor is not called, default initializers are not run, and private fields are not accessible. This is more like using `Object.create` than `new`. The class's prototype is assigned to the object, methods are attached, and getters/setters are set up, but the constructor is not called.
|
||||
|
||||
The database columns are set as properties on the class instance.
|
||||
|
||||
### `.iterate()` (`@@iterator`)
|
||||
|
||||
Use `.iterate()` to run a query and incrementally return results. This is useful for large result sets that you want to process one row at a time without loading all the results into memory.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={2}
|
||||
const query = db.query("SELECT * FROM foo");
|
||||
for (const row of query.iterate()) {
|
||||
console.log(row);
|
||||
}
|
||||
```
|
||||
|
||||
You can also use the `@@iterator` protocol:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={2}
|
||||
const query = db.query("SELECT * FROM foo");
|
||||
for (const row of query) {
|
||||
console.log(row);
|
||||
}
|
||||
```
|
||||
|
||||
### `.values()`
|
||||
|
||||
Use `values()` to run a query and get back all results as an array of arrays.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={3, 4}
|
||||
const query = db.query(`select $message;`);
|
||||
|
||||
query.values({ $message: "Hello world" });
|
||||
query.values(2);
|
||||
```
|
||||
|
||||
```txt
|
||||
[
|
||||
[ "Iron Man", 2008 ],
|
||||
[ "The Avengers", 2012 ],
|
||||
[ "Ant-Man: Quantumania", 2023 ],
|
||||
]
|
||||
```
|
||||
|
||||
Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) and repeatedly calls [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) until it returns `SQLITE_DONE`.
|
||||
|
||||
### `.finalize()`
|
||||
|
||||
Use `.finalize()` to destroy a `Statement` and free any resources associated with it. Once finalized, a `Statement` cannot be executed again. Typically, the garbage collector will do this for you, but explicit finalization may be useful in performance-sensitive applications.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={3}
|
||||
const query = db.query("SELECT title, year FROM movies");
|
||||
const movies = query.all();
|
||||
query.finalize();
|
||||
```
|
||||
|
||||
### `.toString()`
|
||||
|
||||
Calling `toString()` on a `Statement` instance prints the expanded SQL query. This is useful for debugging.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={6, 9, 12}
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
// setup
|
||||
const query = db.query("SELECT $param;");
|
||||
|
||||
console.log(query.toString()); // => "SELECT NULL"
|
||||
|
||||
query.run(42);
|
||||
console.log(query.toString()); // => "SELECT 42"
|
||||
|
||||
query.run(365);
|
||||
console.log(query.toString()); // => "SELECT 365"
|
||||
```
|
||||
|
||||
Internally, this calls [`sqlite3_expanded_sql`](https://www.sqlite.org/capi3ref.html#sqlite3_expanded_sql). The parameters are expanded using the most recently bound values.
|
||||
|
||||
## Parameters
|
||||
|
||||
Queries can contain parameters. These can be numerical (`?1`) or named (`$param` or `:param` or `@param`). Bind values to these parameters when executing the query:
|
||||
|
||||
```ts title="query.ts" icon="/icons/typescript.svg"
|
||||
const query = db.query("SELECT * FROM foo WHERE bar = $bar");
|
||||
const results = query.all({
|
||||
$bar: "bar",
|
||||
});
|
||||
```
|
||||
|
||||
```txt
|
||||
[{ "$bar": "bar" }]
|
||||
```
|
||||
|
||||
Numbered (positional) parameters work too:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
const query = db.query("SELECT ?1, ?2");
|
||||
const results = query.all("hello", "goodbye");
|
||||
```
|
||||
|
||||
```txt
|
||||
[
|
||||
{
|
||||
"?1": "hello",
|
||||
"?2": "goodbye",
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integers
|
||||
|
||||
sqlite supports signed 64 bit integers, but JavaScript only supports signed 52 bit integers or arbitrary precision integers with `bigint`.
|
||||
|
||||
`bigint` input is supported everywhere, but by default `bun:sqlite` returns integers as `number` types. If you need to handle integers larger than 2^53, set `safeIntegers` option to `true` when creating a `Database` instance. This also validates that `bigint` passed to `bun:sqlite` do not exceed 64 bits.
|
||||
|
||||
By default, `bun:sqlite` returns integers as `number` types. If you need to handle integers larger than 2^53, you can use the `bigint` type.
|
||||
|
||||
### `safeIntegers: true`
|
||||
|
||||
When `safeIntegers` is `true`, `bun:sqlite` will return integers as `bigint` types:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={3}
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const db = new Database(":memory:", { safeIntegers: true });
|
||||
const query = db.query(`SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 102n} as max_int`);
|
||||
const result = query.get();
|
||||
|
||||
console.log(result.max_int);
|
||||
```
|
||||
|
||||
```txt
|
||||
9007199254741093n
|
||||
```
|
||||
|
||||
When `safeIntegers` is `true`, `bun:sqlite` will throw an error if a `bigint` value in a bound parameter exceeds 64 bits:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={3}
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const db = new Database(":memory:", { safeIntegers: true });
|
||||
db.run("CREATE TABLE test (id INTEGER PRIMARY KEY, value INTEGER)");
|
||||
|
||||
const query = db.query("INSERT INTO test (value) VALUES ($value)");
|
||||
|
||||
try {
|
||||
query.run({ $value: BigInt(Number.MAX_SAFE_INTEGER) ** 2n });
|
||||
} catch (e) {
|
||||
console.log(e.message);
|
||||
}
|
||||
```
|
||||
|
||||
```txt
|
||||
BigInt value '81129638414606663681390495662081' is out of range
|
||||
```
|
||||
|
||||
### `safeIntegers: false` (default)
|
||||
|
||||
When `safeIntegers` is `false`, `bun:sqlite` will return integers as `number` types and truncate any bits beyond 53:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={3}
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const db = new Database(":memory:", { safeIntegers: false });
|
||||
const query = db.query(`SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 102n} as max_int`);
|
||||
const result = query.get();
|
||||
console.log(result.max_int);
|
||||
```
|
||||
|
||||
```txt
|
||||
9007199254741092
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Transactions
|
||||
|
||||
Transactions are a mechanism for executing multiple queries in an _atomic_ way; that is, either all of the queries succeed or none of them do. Create a transaction with the `db.transaction()` method:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={2}
|
||||
const insertCat = db.prepare("INSERT INTO cats (name) VALUES ($name)");
|
||||
const insertCats = db.transaction(cats => {
|
||||
for (const cat of cats) insertCat.run(cat);
|
||||
});
|
||||
```
|
||||
|
||||
At this stage, we haven't inserted any cats! The call to `db.transaction()` returns a new function (`insertCats`) that _wraps_ the function that executes the queries.
|
||||
|
||||
To execute the transaction, call this function. All arguments will be passed through to the wrapped function; the return value of the wrapped function will be returned by the transaction function. The wrapped function also has access to the `this` context as defined where the transaction is executed.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={3}
|
||||
const insert = db.prepare("INSERT INTO cats (name) VALUES ($name)");
|
||||
const insertCats = db.transaction(cats => {
|
||||
for (const cat of cats) insert.run(cat);
|
||||
return cats.length;
|
||||
});
|
||||
|
||||
const count = insertCats([{ $name: "Keanu" }, { $name: "Salem" }, { $name: "Crookshanks" }]);
|
||||
|
||||
console.log(`Inserted ${count} cats`);
|
||||
```
|
||||
|
||||
The driver will automatically [`begin`](https://www.sqlite.org/lang_transaction.html) a transaction when `insertCats` is called and `commit` it when the wrapped function returns. If an exception is thrown, the transaction will be rolled back. The exception will propagate as usual; it is not caught.
|
||||
|
||||
<Note>
|
||||
**Nested transactions** — Transaction functions can be called from inside other transaction functions. When doing so, the inner transaction becomes a [savepoint](https://www.sqlite.org/lang_savepoint.html).
|
||||
|
||||
<Accordion title="View nested transaction example">
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
// setup
|
||||
import { Database } from "bun:sqlite";
|
||||
const db = Database.open(":memory:");
|
||||
db.run("CREATE TABLE expenses (id INTEGER PRIMARY KEY AUTOINCREMENT, note TEXT, dollars INTEGER);");
|
||||
db.run("CREATE TABLE cats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, age INTEGER)");
|
||||
const insertExpense = db.prepare("INSERT INTO expenses (note, dollars) VALUES (?, ?)");
|
||||
const insert = db.prepare("INSERT INTO cats (name, age) VALUES ($name, $age)");
|
||||
const insertCats = db.transaction(cats => {
|
||||
for (const cat of cats) insert.run(cat);
|
||||
});
|
||||
|
||||
const adopt = db.transaction(cats => {
|
||||
insertExpense.run("adoption fees", 20);
|
||||
insertCats(cats); // nested transaction
|
||||
});
|
||||
|
||||
adopt([
|
||||
{ $name: "Joey", $age: 2 },
|
||||
{ $name: "Sally", $age: 4 },
|
||||
{ $name: "Junior", $age: 1 },
|
||||
]);
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</Note>
|
||||
|
||||
Transactions also come with `deferred`, `immediate`, and `exclusive` versions.
|
||||
|
||||
```ts
|
||||
insertCats(cats); // uses "BEGIN"
|
||||
insertCats.deferred(cats); // uses "BEGIN DEFERRED"
|
||||
insertCats.immediate(cats); // uses "BEGIN IMMEDIATE"
|
||||
insertCats.exclusive(cats); // uses "BEGIN EXCLUSIVE"
|
||||
```
|
||||
|
||||
### `.loadExtension()`
|
||||
|
||||
To load a [SQLite extension](https://www.sqlite.org/loadext.html), call `.loadExtension(name)` on your `Database` instance
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={4}
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const db = new Database();
|
||||
db.loadExtension("myext");
|
||||
```
|
||||
|
||||
<Note>
|
||||
**MacOS users** By default, macOS ships with Apple's proprietary build of SQLite, which doesn't support extensions. To use extensions, you'll need to install a vanilla build of SQLite.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
brew install sqlite
|
||||
which sqlite # get path to binary
|
||||
```
|
||||
|
||||
To point `bun:sqlite` to the new build, call `Database.setCustomSQLite(path)` before creating any `Database` instances. (On other operating systems, this is a no-op.) Pass a path to the SQLite `.dylib` file, _not_ the executable. With recent versions of Homebrew this is something like `/opt/homebrew/Cellar/sqlite/<version>/libsqlite3.dylib`.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={3}
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
Database.setCustomSQLite("/path/to/libsqlite.dylib");
|
||||
|
||||
const db = new Database();
|
||||
db.loadExtension("myext");
|
||||
```
|
||||
|
||||
</Note>
|
||||
|
||||
### `.fileControl(cmd: number, value: any)`
|
||||
|
||||
To use the advanced `sqlite3_file_control` API, call `.fileControl(cmd, value)` on your `Database` instance.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={6}
|
||||
import { Database, constants } from "bun:sqlite";
|
||||
|
||||
const db = new Database();
|
||||
// Ensure WAL mode is NOT persistent
|
||||
// this prevents wal files from lingering after the database is closed
|
||||
db.fileControl(constants.SQLITE_FCNTL_PERSIST_WAL, 0);
|
||||
```
|
||||
|
||||
`value` can be:
|
||||
|
||||
- `number`
|
||||
- `TypedArray`
|
||||
- `undefined` or `null`
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
```ts Type Reference icon="/icons/typescript.svg" expandable
|
||||
class Database {
|
||||
constructor(
|
||||
filename: string,
|
||||
options?:
|
||||
| number
|
||||
| {
|
||||
readonly?: boolean;
|
||||
create?: boolean;
|
||||
readwrite?: boolean;
|
||||
safeIntegers?: boolean;
|
||||
strict?: boolean;
|
||||
},
|
||||
);
|
||||
|
||||
prepare<ReturnType, Params>(sql: string): Statement<ReturnType, Params>;
|
||||
query<ReturnType, Params>(sql: string): Statement<ReturnType, Params>;
|
||||
run(sql: string, params?: SQLQueryBindings): { lastInsertRowid: number; changes: number };
|
||||
exec = this.run;
|
||||
|
||||
transaction(insideTransaction: (...args: any) => void): CallableFunction & {
|
||||
deferred: (...args: any) => void;
|
||||
immediate: (...args: any) => void;
|
||||
exclusive: (...args: any) => void;
|
||||
};
|
||||
|
||||
close(throwOnError?: boolean): void;
|
||||
}
|
||||
|
||||
class Statement<ReturnType, Params> {
|
||||
all(params: Params): ReturnType[];
|
||||
get(params: Params): ReturnType | undefined;
|
||||
run(params: Params): {
|
||||
lastInsertRowid: number;
|
||||
changes: number;
|
||||
};
|
||||
values(params: Params): unknown[][];
|
||||
|
||||
finalize(): void; // destroy statement and clean up resources
|
||||
toString(): string; // serialize to SQL
|
||||
|
||||
columnNames: string[]; // the column names of the result set
|
||||
columnTypes: string[]; // types based on actual values in first row (call .get()/.all() first)
|
||||
declaredTypes: (string | null)[]; // types from CREATE TABLE schema (call .get()/.all() first)
|
||||
paramsCount: number; // the number of parameters expected by the statement
|
||||
native: any; // the native object representing the statement
|
||||
|
||||
as(Class: new () => ReturnType): this;
|
||||
}
|
||||
|
||||
type SQLQueryBindings =
|
||||
| string
|
||||
| bigint
|
||||
| TypedArray
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| Record<string, string | bigint | TypedArray | number | boolean | null>;
|
||||
```
|
||||
|
||||
### Datatypes
|
||||
|
||||
| JavaScript type | SQLite type |
|
||||
| --------------- | ---------------------- |
|
||||
| `string` | `TEXT` |
|
||||
| `number` | `INTEGER` or `DECIMAL` |
|
||||
| `boolean` | `INTEGER` (1 or 0) |
|
||||
| `Uint8Array` | `BLOB` |
|
||||
| `Buffer` | `BLOB` |
|
||||
| `bigint` | `INTEGER` |
|
||||
| `null` | `NULL` |
|
||||
232
docs/runtime/streams.mdx
Normal file
232
docs/runtime/streams.mdx
Normal file
@@ -0,0 +1,232 @@
|
||||
---
|
||||
title: Streams
|
||||
description: Use Bun's streams API to work with binary data without loading it all into memory at once
|
||||
---
|
||||
|
||||
Streams are an important abstraction for working with binary data without loading it all into memory at once. They are commonly used for reading and writing files, sending and receiving network requests, and processing large amounts of data.
|
||||
|
||||
Bun implements the Web APIs [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) and [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream).
|
||||
|
||||
<Note>
|
||||
Bun also implements the `node:stream` module, including
|
||||
[`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams),
|
||||
[`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), and
|
||||
[`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). For complete documentation, refer
|
||||
to the [Node.js docs](https://nodejs.org/api/stream.html).
|
||||
</Note>
|
||||
|
||||
To create a simple `ReadableStream`:
|
||||
|
||||
```ts
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue("hello");
|
||||
controller.enqueue("world");
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The contents of a `ReadableStream` can be read chunk-by-chunk with `for await` syntax.
|
||||
|
||||
```ts
|
||||
for await (const chunk of stream) {
|
||||
console.log(chunk);
|
||||
}
|
||||
|
||||
// hello
|
||||
// world
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Direct `ReadableStream`
|
||||
|
||||
Bun implements an optimized version of `ReadableStream` that avoid unnecessary data copying & queue management logic.
|
||||
|
||||
With a traditional `ReadableStream`, chunks of data are _enqueued_. Each chunk is copied into a queue, where it sits until the stream is ready to send more data.
|
||||
|
||||
```ts
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue("hello");
|
||||
controller.enqueue("world");
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
With a direct `ReadableStream`, chunks of data are written directly to the stream. No queueing happens, and there's no need to clone the chunk data into memory. The `controller` API is updated to reflect this; instead of `.enqueue()` you call `.write`.
|
||||
|
||||
```ts
|
||||
const stream = new ReadableStream({
|
||||
type: "direct", // [!code ++]
|
||||
pull(controller) {
|
||||
controller.write("hello");
|
||||
controller.write("world");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
When using a direct `ReadableStream`, all chunk queueing is handled by the destination. The consumer of the stream receives exactly what is passed to `controller.write()`, without any encoding or modification.
|
||||
|
||||
---
|
||||
|
||||
## Async generator streams
|
||||
|
||||
Bun also supports async generator functions as a source for `Response` and `Request`. This is an easy way to create a `ReadableStream` that fetches data from an asynchronous source.
|
||||
|
||||
```ts
|
||||
const response = new Response(
|
||||
(async function* () {
|
||||
yield "hello";
|
||||
yield "world";
|
||||
})(),
|
||||
);
|
||||
|
||||
await response.text(); // "helloworld"
|
||||
```
|
||||
|
||||
You can also use `[Symbol.asyncIterator]` directly.
|
||||
|
||||
```ts
|
||||
const response = new Response({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield "hello";
|
||||
yield "world";
|
||||
},
|
||||
});
|
||||
|
||||
await response.text(); // "helloworld"
|
||||
```
|
||||
|
||||
If you need more granular control over the stream, `yield` will return the direct ReadableStream controller.
|
||||
|
||||
```ts
|
||||
const response = new Response({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
const controller = yield "hello";
|
||||
await controller.end();
|
||||
},
|
||||
});
|
||||
|
||||
await response.text(); // "hello"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `Bun.ArrayBufferSink`
|
||||
|
||||
The `Bun.ArrayBufferSink` class is a fast incremental writer for constructing an `ArrayBuffer` of unknown size.
|
||||
|
||||
```ts
|
||||
const sink = new Bun.ArrayBufferSink();
|
||||
|
||||
sink.write("h");
|
||||
sink.write("e");
|
||||
sink.write("l");
|
||||
sink.write("l");
|
||||
sink.write("o");
|
||||
|
||||
sink.end();
|
||||
// ArrayBuffer(5) [ 104, 101, 108, 108, 111 ]
|
||||
```
|
||||
|
||||
To instead retrieve the data as a `Uint8Array`, pass the `asUint8Array` option to the `start` method.
|
||||
|
||||
```ts
|
||||
const sink = new Bun.ArrayBufferSink();
|
||||
sink.start({
|
||||
asUint8Array: true, // [!code ++]
|
||||
});
|
||||
|
||||
sink.write("h");
|
||||
sink.write("e");
|
||||
sink.write("l");
|
||||
sink.write("l");
|
||||
sink.write("o");
|
||||
|
||||
sink.end();
|
||||
// Uint8Array(5) [ 104, 101, 108, 108, 111 ]
|
||||
```
|
||||
|
||||
The `.write()` method supports strings, typed arrays, `ArrayBuffer`, and `SharedArrayBuffer`.
|
||||
|
||||
```ts
|
||||
sink.write("h");
|
||||
sink.write(new Uint8Array([101, 108]));
|
||||
sink.write(Buffer.from("lo").buffer);
|
||||
|
||||
sink.end();
|
||||
```
|
||||
|
||||
Once `.end()` is called, no more data can be written to the `ArrayBufferSink`. However, in the context of buffering a stream, it's useful to continuously write data and periodically `.flush()` the contents (say, into a `WriteableStream`). To support this, pass `stream: true` to the constructor.
|
||||
|
||||
```ts
|
||||
const sink = new Bun.ArrayBufferSink();
|
||||
sink.start({
|
||||
stream: true, // [!code ++]
|
||||
});
|
||||
|
||||
sink.write("h");
|
||||
sink.write("e");
|
||||
sink.write("l");
|
||||
sink.flush();
|
||||
// ArrayBuffer(5) [ 104, 101, 108 ]
|
||||
|
||||
sink.write("l");
|
||||
sink.write("o");
|
||||
sink.flush();
|
||||
// ArrayBuffer(5) [ 108, 111 ]
|
||||
```
|
||||
|
||||
The `.flush()` method returns the buffered data as an `ArrayBuffer` (or `Uint8Array` if `asUint8Array: true`) and clears internal buffer.
|
||||
|
||||
To manually set the size of the internal buffer in bytes, pass a value for `highWaterMark`:
|
||||
|
||||
```ts
|
||||
const sink = new Bun.ArrayBufferSink();
|
||||
sink.start({
|
||||
highWaterMark: 1024 * 1024, // 1 MB // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
```ts See Typescript Definitions expandable
|
||||
/**
|
||||
* Fast incremental writer that becomes an `ArrayBuffer` on end().
|
||||
*/
|
||||
export class ArrayBufferSink {
|
||||
constructor();
|
||||
|
||||
start(options?: {
|
||||
asUint8Array?: boolean;
|
||||
/**
|
||||
* Preallocate an internal buffer of this size
|
||||
* This can significantly improve performance when the chunk size is small
|
||||
*/
|
||||
highWaterMark?: number;
|
||||
/**
|
||||
* On {@link ArrayBufferSink.flush}, return the written data as a `Uint8Array`.
|
||||
* Writes will restart from the beginning of the buffer.
|
||||
*/
|
||||
stream?: boolean;
|
||||
}): void;
|
||||
|
||||
write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
|
||||
/**
|
||||
* Flush the internal buffer
|
||||
*
|
||||
* If {@link ArrayBufferSink.start} was passed a `stream` option, this will return a `ArrayBuffer`
|
||||
* If {@link ArrayBufferSink.start} was passed a `stream` option and `asUint8Array`, this will return a `Uint8Array`
|
||||
* Otherwise, this will return the number of bytes written since the last flush
|
||||
*
|
||||
* This API might change later to separate Uint8ArraySink and ArrayBufferSink
|
||||
*/
|
||||
flush(): number | Uint8Array<ArrayBuffer> | ArrayBuffer;
|
||||
end(): ArrayBuffer | Uint8Array<ArrayBuffer>;
|
||||
}
|
||||
```
|
||||
269
docs/runtime/templating/create.mdx
Normal file
269
docs/runtime/templating/create.mdx
Normal file
@@ -0,0 +1,269 @@
|
||||
---
|
||||
title: bun create
|
||||
description: Create a new Bun project from a React component, a `create-<template>` npm package, a GitHub repo, or a local template
|
||||
---
|
||||
|
||||
<Note>
|
||||
You don't need `bun create` to use Bun. You don't need any configuration at all. This command exists to make getting
|
||||
started a bit quicker and easier.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
Template a new Bun project with `bun create`. This is a flexible command that can be used to create a new project from a React component, a `create-<template>` npm package, a GitHub repo, or a local template.
|
||||
|
||||
If you're looking to create a brand new empty project, use [`bun init`](/runtime/templating/init).
|
||||
|
||||
## From a React component
|
||||
|
||||
`bun create ./MyComponent.tsx` turns an existing React component into a complete dev environment with hot reload and production builds in one command.
|
||||
|
||||
```bash
|
||||
$ bun create ./MyComponent.jsx # .tsx also supported
|
||||
```
|
||||
|
||||
<Frame>
|
||||
<video
|
||||
style={{ aspectRatio: "2062 / 1344", width: "100%", height: "100%", objectFit: "contain" }}
|
||||
loop
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
>
|
||||
<source
|
||||
src="/images/bun-create-shadcn.mp4"
|
||||
style={{ width: "100%", height: "100%", objectFit: "contain" }}
|
||||
type="video/mp4"
|
||||
/>
|
||||
</video>
|
||||
</Frame>
|
||||
|
||||
<Note>
|
||||
🚀 **Create React App Successor** — `bun create <component>` provides everything developers loved about Create React App, but with modern tooling, faster builds, and backend support.
|
||||
</Note>
|
||||
|
||||
#### How this works
|
||||
|
||||
When you run `bun create <component>`, Bun:
|
||||
|
||||
1. Uses [Bun's JavaScript bundler](/bundler) to analyze your module graph.
|
||||
2. Collects all the dependencies needed to run the component.
|
||||
3. Scans the exports of the entry point for a React component.
|
||||
4. Generates a `package.json` file with the dependencies and scripts needed to run the component.
|
||||
5. Installs any missing dependencies using [`bun install --only-missing`](/pm/cli/install).
|
||||
6. Generates the following files:
|
||||
- `${component}.html`
|
||||
- `${component}.client.tsx` (entry point for the frontend)
|
||||
- `${component}.css` (css file)
|
||||
7. Starts a frontend dev server automatically.
|
||||
|
||||
### Using TailwindCSS with Bun
|
||||
|
||||
[TailwindCSS](https://tailwindcss.com/) is an extremely popular utility-first CSS framework used to style web applications.
|
||||
|
||||
When you run `bun create <component>`, Bun scans your JSX/TSX file for TailwindCSS class names (and any files it imports). If it detects TailwindCSS class names, it will add the following dependencies to your `package.json`:
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "^4",
|
||||
"bun-plugin-tailwind": "latest"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We also configure `bunfig.toml` to use Bun's TailwindCSS plugin with `Bun.serve()`
|
||||
|
||||
```toml bunfig.toml icon="settings"
|
||||
[serve.static]
|
||||
plugins = ["bun-plugin-tailwind"]
|
||||
```
|
||||
|
||||
And a `${component}.css` file with `@import "tailwindcss";` at the top:
|
||||
|
||||
```css MyComponent.css icon="file-code"
|
||||
@import "tailwindcss";
|
||||
```
|
||||
|
||||
### Using `shadcn/ui` with Bun
|
||||
|
||||
[`shadcn/ui`](https://ui.shadcn.com/) is an extremely popular component library tool for building web applications.
|
||||
|
||||
`bun create <component>` scans for any shadcn/ui components imported from `@/components/ui`.
|
||||
|
||||
If it finds any, it runs:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
# Assuming bun detected imports to @/components/ui/accordion and @/components/ui/button
|
||||
bunx shadcn@canary add accordion button # and any other components
|
||||
```
|
||||
|
||||
Since `shadcn/ui` itself uses TailwindCSS, `bun create` also adds the necessary TailwindCSS dependencies to your `package.json` and configures `bunfig.toml` to use Bun's TailwindCSS plugin with `Bun.serve()` as described above.
|
||||
|
||||
Additionally, we setup the following:
|
||||
|
||||
- `tsconfig.json` to alias `"@/*"` to `"src/*"` or `.` (depending on if there is a `src/` directory)
|
||||
- `components.json` so that shadcn/ui knows its a shadcn/ui project
|
||||
- `styles/globals.css` file that configures Tailwind v4 in the way that shadcn/ui expects
|
||||
- `${component}.build.ts` file that builds the component for production with `bun-plugin-tailwind` configured
|
||||
|
||||
`bun create ./MyComponent.jsx` is one of the easiest ways to run code generated from LLMs like [Claude](https://claude.ai) or ChatGPT locally.
|
||||
|
||||
## From `npm`
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun create <template> [<destination>]
|
||||
```
|
||||
|
||||
Assuming you don't have a [local template](#from-a-local-template) with the same name, this command will download and execute the `create-<template>` package from npm. The following two commands will behave identically:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun create remix
|
||||
bunx create-remix
|
||||
```
|
||||
|
||||
Refer to the documentation of the associated `create-<template>` package for complete documentation and usage instructions.
|
||||
|
||||
## From GitHub
|
||||
|
||||
This will download the contents of the GitHub repo to disk.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun create <user>/<repo>
|
||||
bun create github.com/<user>/<repo>
|
||||
```
|
||||
|
||||
Optionally specify a name for the destination folder. If no destination is specified, the repo name will be used.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun create <user>/<repo> mydir
|
||||
bun create github.com/<user>/<repo> mydir
|
||||
```
|
||||
|
||||
Bun will perform the following steps:
|
||||
|
||||
- Download the template
|
||||
- Copy all template files into the destination folder
|
||||
- Install dependencies with `bun install`.
|
||||
- Initialize a fresh Git repo. Opt out with the `--no-git` flag.
|
||||
- Run the template's configured `start` script, if defined.
|
||||
|
||||
<Note>By default Bun will _not overwrite_ any existing files. Use the `--force` flag to overwrite existing files.</Note>
|
||||
|
||||
## From a local template
|
||||
|
||||
<Warning>
|
||||
Unlike remote templates, running `bun create` with a local template will delete the entire destination folder if it
|
||||
already exists! Be careful.
|
||||
</Warning>
|
||||
|
||||
Bun's templater can be extended to support custom templates defined on your local file system. These templates should live in one of the following directories:
|
||||
|
||||
- `$HOME/.bun-create/<name>`: global templates
|
||||
- `<project root>/.bun-create/<name>`: project-specific templates
|
||||
|
||||
<Note>You can customize the global template path by setting the `BUN_CREATE_DIR` environment variable.</Note>
|
||||
|
||||
To create a local template, navigate to `$HOME/.bun-create` and create a new directory with the desired name of your template.
|
||||
|
||||
```bash
|
||||
cd $HOME/.bun-create
|
||||
mkdir foo
|
||||
cd foo
|
||||
```
|
||||
|
||||
Then, create a `package.json` file in that directory with the following contents:
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"name": "foo"
|
||||
}
|
||||
```
|
||||
|
||||
You can run `bun create foo` elsewhere on your file system to verify that Bun is correctly finding your local template.
|
||||
|
||||
#### Setup logic
|
||||
|
||||
You can specify pre- and post-install setup scripts in the `"bun-create"` section of your local template's `package.json`.
|
||||
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"name": "@bun-examples/simplereact",
|
||||
"version": "0.0.1",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"bun-create": {
|
||||
"preinstall": "echo 'Installing...'", // a single command
|
||||
"postinstall": ["echo 'Done!'"], // an array of commands
|
||||
"start": "bun run echo 'Hello world!'"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following fields are supported. Each of these can correspond to a string or array of strings. An array of commands will be executed in order.
|
||||
|
||||
| Field | Description |
|
||||
| ------------- | ----------------------------------- |
|
||||
| `postinstall` | runs after installing dependencies |
|
||||
| `preinstall` | runs before installing dependencies |
|
||||
|
||||
After cloning a template, `bun create` will automatically remove the `"bun-create"` section from `package.json` before writing it to the destination folder.
|
||||
|
||||
## Reference
|
||||
|
||||
### CLI flags
|
||||
|
||||
| Flag | Description |
|
||||
| -------------- | -------------------------------------- |
|
||||
| `--force` | Overwrite existing files |
|
||||
| `--no-install` | Skip installing `node_modules` & tasks |
|
||||
| `--no-git` | Don't initialize a git repository |
|
||||
| `--open` | Start & open in-browser after finish |
|
||||
|
||||
### Environment variables
|
||||
|
||||
| Name | Description |
|
||||
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `GITHUB_API_DOMAIN` | If you're using a GitHub enterprise or a proxy, you can customize the GitHub domain Bun pings for downloads |
|
||||
| `GITHUB_TOKEN` (or `GITHUB_ACCESS_TOKEN`) | This lets `bun create` work with private repositories or if you get rate-limited. `GITHUB_TOKEN` is chosen over `GITHUB_ACCESS_TOKEN` if both exist. |
|
||||
|
||||
<Accordion title={<span>How <code>bun create</code> works</span>}>
|
||||
|
||||
When you run `bun create ${template} ${destination}`, here’s what happens:
|
||||
|
||||
IF remote template
|
||||
|
||||
1. GET `registry.npmjs.org/@bun-examples/${template}/latest` and parse it
|
||||
2. GET `registry.npmjs.org/@bun-examples/${template}/-/${template}-${latestVersion}.tgz`
|
||||
3. Decompress & extract `${template}-${latestVersion}.tgz` into `${destination}`
|
||||
- If there are files that would overwrite, warn and exit unless `--force` is passed
|
||||
|
||||
IF GitHub repo
|
||||
|
||||
1. Download the tarball from GitHub’s API
|
||||
2. Decompress & extract into `${destination}`
|
||||
- If there are files that would overwrite, warn and exit unless `--force` is passed
|
||||
|
||||
ELSE IF local template
|
||||
|
||||
1. Open local template folder
|
||||
2. Delete destination directory recursively
|
||||
3. Copy files recursively using the fastest system calls available (on macOS `fcopyfile` and Linux, `copy_file_range`). Do not copy or traverse into `node_modules` folder if exists (this alone makes it faster than `cp`)
|
||||
|
||||
4. Parse the `package.json` (again!), update `name` to be `${basename(destination)}`, remove the `bun-create` section from the `package.json` and save the updated `package.json` to disk.
|
||||
- IF Next.js is detected, add `bun-framework-next` to the list of dependencies
|
||||
- IF Create React App is detected, add the entry point in `/src/index.{js,jsx,ts,tsx}` to `public/index.html`
|
||||
- IF Relay is detected, add `bun-macro-relay` so that Relay works
|
||||
5. Auto-detect the npm client, preferring `pnpm`, `yarn` (v1), and lastly `npm`
|
||||
6. Run any tasks defined in `"bun-create": { "preinstall" }` with the npm client
|
||||
7. Run `${npmClient} install` unless `--no-install` is passed OR no dependencies are in package.json
|
||||
8. Run any tasks defined in `"bun-create": { "postinstall" }` with the npm client
|
||||
9. Run `git init; git add -A .; git commit -am "Initial Commit";`
|
||||
- Rename `gitignore` to `.gitignore`. NPM automatically removes `.gitignore` files from appearing in packages.
|
||||
- If there are dependencies, this runs in a separate thread concurrently while node_modules are being installed
|
||||
- Using libgit2 if available was tested and performed 3x slower in microbenchmarks
|
||||
|
||||
</Accordion>
|
||||
58
docs/runtime/templating/init.mdx
Normal file
58
docs/runtime/templating/init.mdx
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: "bun init"
|
||||
description: "Scaffold an empty Bun project with the interactive `bun init` command"
|
||||
---
|
||||
|
||||
import Init from "/snippets/cli/init.mdx";
|
||||
|
||||
Get started with Bun by scaffolding a new project with `bun init`.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun init my-app
|
||||
```
|
||||
|
||||
```txt
|
||||
? Select a project template - Press return to submit.
|
||||
❯ Blank
|
||||
React
|
||||
Library
|
||||
|
||||
✓ Select a project template: Blank
|
||||
|
||||
+ .gitignore
|
||||
+ CLAUDE.md
|
||||
+ .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc -> CLAUDE.md
|
||||
+ index.ts
|
||||
+ tsconfig.json (for editor autocomplete)
|
||||
+ README.md
|
||||
```
|
||||
|
||||
Press `enter` to accept the default answer for each prompt, or pass the `-y` flag to auto-accept the defaults.
|
||||
|
||||
---
|
||||
|
||||
`bun init` is a quick way to start a blank project with Bun. It guesses with sane defaults and is non-destructive when run multiple times.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
It creates:
|
||||
|
||||
- a `package.json` file with a name that defaults to the current directory name
|
||||
- a `tsconfig.json` file or a `jsconfig.json` file, depending if the entry point is a TypeScript file or not
|
||||
- an entry point which defaults to `index.ts` unless any of `index.{tsx, jsx, js, mts, mjs}` exist or the `package.json` specifies a `module` or `main` field
|
||||
- a `README.md` file
|
||||
|
||||
AI Agent rules (disable with `$BUN_AGENT_RULE_DISABLED=1`):
|
||||
|
||||
- a `CLAUDE.md` file when Claude CLI is detected (disable with `CLAUDE_CODE_AGENT_RULE_DISABLED` env var)
|
||||
- a `.cursor/rules/*.mdc` file to guide [Cursor AI](https://cursor.sh) to use Bun instead of Node.js and npm when Cursor is detected
|
||||
|
||||
If you pass `-y` or `--yes`, it will assume you want to continue without asking questions.
|
||||
|
||||
At the end, it runs `bun install` to install `@types/bun`.
|
||||
|
||||
---
|
||||
|
||||
<Init />
|
||||
288
docs/runtime/transpiler.mdx
Normal file
288
docs/runtime/transpiler.mdx
Normal file
@@ -0,0 +1,288 @@
|
||||
---
|
||||
title: Transpiler
|
||||
description: Use Bun's transpiler to transpile JavaScript and TypeScript code
|
||||
---
|
||||
|
||||
Bun exposes its internal transpiler via the `Bun.Transpiler` class. To create an instance of Bun's transpiler:
|
||||
|
||||
```ts
|
||||
const transpiler = new Bun.Transpiler({
|
||||
loader: "tsx", // "js | "jsx" | "ts" | "tsx"
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `.transformSync()`
|
||||
|
||||
Transpile code synchronously with the `.transformSync()` method. Modules are not resolved and the code is not executed. The result is a string of vanilla JavaScript code.
|
||||
|
||||
<CodeGroup>
|
||||
```ts transpile.ts icon="/icons/typescript.svg"
|
||||
const transpiler = new Bun.Transpiler({
|
||||
loader: 'tsx',
|
||||
});
|
||||
|
||||
const code = `
|
||||
import * as whatever from "./whatever.ts"
|
||||
export function Home(props: {title: string}){
|
||||
return <p>{props.title}</p>;
|
||||
}`;
|
||||
|
||||
const result = transpiler.transformSync(code);
|
||||
|
||||
````
|
||||
|
||||
```ts output
|
||||
import { __require as require } from "bun:wrap";
|
||||
import * as JSX from "react/jsx-dev-runtime";
|
||||
var jsx = require(JSX).jsxDEV;
|
||||
|
||||
export default jsx(
|
||||
"div",
|
||||
{
|
||||
children: "hi!",
|
||||
},
|
||||
undefined,
|
||||
false,
|
||||
undefined,
|
||||
this,
|
||||
);
|
||||
````
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
To override the default loader specified in the `new Bun.Transpiler()` constructor, pass a second argument to `.transformSync()`.
|
||||
|
||||
```ts
|
||||
transpiler.transformSync("<div>hi!</div>", "tsx");
|
||||
```
|
||||
|
||||
<Accordion title="Nitty gritty">
|
||||
|
||||
When `.transformSync` is called, the transpiler is run in the same thread as the currently executed code.
|
||||
|
||||
If a macro is used, it will be run in the same thread as the transpiler, but in a separate event loop from the rest of your application. Currently, globals between macros and regular code are shared, which means it is possible (but not recommended) to share states between macros and regular code. Attempting to use AST nodes outside of a macro is undefined behavior.
|
||||
|
||||
</Accordion>
|
||||
|
||||
---
|
||||
|
||||
## `.transform()`
|
||||
|
||||
The `transform()` method is an async version of `.transformSync()` that returns a `Promise<string>`.
|
||||
|
||||
```js
|
||||
const transpiler = new Bun.Transpiler({ loader: "jsx" });
|
||||
const result = await transpiler.transform("<div>hi!</div>");
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
Unless you're transpiling _many_ large files, you should probably use `Bun.Transpiler.transformSync`. The cost of the threadpool will often take longer than actually transpiling code.
|
||||
|
||||
```ts
|
||||
await transpiler.transform("<div>hi!</div>", "tsx");
|
||||
```
|
||||
|
||||
<Accordion title="Nitty gritty">
|
||||
|
||||
The `.transform()` method runs the transpiler in Bun's worker threadpool, so if you run it 100 times, it will run it across `Math.floor($cpu_count * 0.8)` threads, without blocking the main JavaScript thread.
|
||||
|
||||
If your code uses a macro, it will potentially spawn a new copy of Bun's JavaScript runtime environment in that new thread.
|
||||
|
||||
</Accordion>
|
||||
|
||||
## `.scan()`
|
||||
|
||||
The `Transpiler` instance can also scan some source code and return a list of its imports and exports, plus additional metadata about each one. [Type-only](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export) imports and exports are ignored.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```ts example.ts icon="/icons/typescript.svg"
|
||||
const transpiler = new Bun.Transpiler({
|
||||
loader: "tsx",
|
||||
});
|
||||
|
||||
const code = `
|
||||
import React from 'react';
|
||||
import type {ReactNode} from 'react';
|
||||
const val = require('./cjs.js')
|
||||
import('./loader');
|
||||
|
||||
export const name = "hello";
|
||||
`;
|
||||
|
||||
const result = transpiler.scan(code);
|
||||
```
|
||||
|
||||
```json output
|
||||
{
|
||||
"exports": ["name"],
|
||||
"imports": [
|
||||
{
|
||||
"kind": "import-statement",
|
||||
"path": "react"
|
||||
},
|
||||
{
|
||||
"kind": "import-statement",
|
||||
"path": "remix"
|
||||
},
|
||||
{
|
||||
"kind": "dynamic-import",
|
||||
"path": "./loader"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
Each import in the `imports` array has a `path` and `kind`. Bun categories imports into the following kinds:
|
||||
|
||||
- `import-statement`: `import React from 'react'`
|
||||
- `require-call`: `const val = require('./cjs.js')`
|
||||
- `require-resolve`: `require.resolve('./cjs.js')`
|
||||
- `dynamic-import`: `import('./loader')`
|
||||
- `import-rule`: `@import 'foo.css'`
|
||||
- `url-token`: `url('./foo.png')`
|
||||
|
||||
---
|
||||
|
||||
## `.scanImports()`
|
||||
|
||||
For performance-sensitive code, you can use the `.scanImports()` method to get a list of imports. It's faster than `.scan()` (especially for large files) but marginally less accurate due to some performance optimizations.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```ts example.ts icon="/icons/typescript.svg"
|
||||
const transpiler = new Bun.Transpiler({
|
||||
loader: "tsx",
|
||||
});
|
||||
|
||||
const code = `
|
||||
import React from 'react';
|
||||
import type {ReactNode} from 'react';
|
||||
const val = require('./cjs.js')
|
||||
import('./loader');
|
||||
|
||||
export const name = "hello";
|
||||
`;
|
||||
|
||||
const result = transpiler.scanImports(code);
|
||||
```
|
||||
|
||||
```json results icon="file-json"
|
||||
[
|
||||
{
|
||||
"kind": "import-statement",
|
||||
"path": "react"
|
||||
},
|
||||
{
|
||||
"kind": "require-call",
|
||||
"path": "./cjs.js"
|
||||
},
|
||||
{
|
||||
"kind": "dynamic-import",
|
||||
"path": "./loader"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
```ts See Typescript Definitions expandable
|
||||
type Loader = "jsx" | "js" | "ts" | "tsx";
|
||||
|
||||
interface TranspilerOptions {
|
||||
// Replace key with value. Value must be a JSON string.
|
||||
// { "process.env.NODE_ENV": "\"production\"" }
|
||||
define?: Record<string, string>,
|
||||
|
||||
// Default loader for this transpiler
|
||||
loader?: Loader,
|
||||
|
||||
// Default platform to target
|
||||
// This affects how import and/or require is used
|
||||
target?: "browser" | "bun" | "node",
|
||||
|
||||
// Specify a tsconfig.json file as stringified JSON or an object
|
||||
// Use this to set a custom JSX factory, fragment, or import source
|
||||
// For example, if you want to use Preact instead of React. Or if you want to use Emotion.
|
||||
tsconfig?: string | TSConfig,
|
||||
|
||||
// Replace imports with macros
|
||||
macro?: MacroMap,
|
||||
|
||||
// Specify a set of exports to eliminate
|
||||
// Or rename certain exports
|
||||
exports?: {
|
||||
eliminate?: string[];
|
||||
replace?: Record<string, string>;
|
||||
},
|
||||
|
||||
// Whether to remove unused imports from transpiled file
|
||||
// Default: false
|
||||
trimUnusedImports?: boolean,
|
||||
|
||||
// Whether to enable a set of JSX optimizations
|
||||
// jsxOptimizationInline ...,
|
||||
|
||||
// Experimental whitespace minification
|
||||
minifyWhitespace?: boolean,
|
||||
|
||||
// Whether to inline constant values
|
||||
// Typically improves performance and decreases bundle size
|
||||
// Default: true
|
||||
inline?: boolean,
|
||||
}
|
||||
|
||||
// Map import paths to macros
|
||||
interface MacroMap {
|
||||
// {
|
||||
// "react-relay": {
|
||||
// "graphql": "bun-macro-relay/bun-macro-relay.tsx"
|
||||
// }
|
||||
// }
|
||||
[packagePath: string]: {
|
||||
[importItemName: string]: string,
|
||||
},
|
||||
}
|
||||
|
||||
class Bun.Transpiler {
|
||||
constructor(options: TranspilerOptions)
|
||||
|
||||
transform(code: string, loader?: Loader): Promise<string>
|
||||
transformSync(code: string, loader?: Loader): string
|
||||
|
||||
scan(code: string): {exports: string[], imports: Import}
|
||||
scanImports(code: string): Import[]
|
||||
}
|
||||
|
||||
type Import = {
|
||||
path: string,
|
||||
kind:
|
||||
// import foo from 'bar'; in JavaScript
|
||||
| "import-statement"
|
||||
// require("foo") in JavaScript
|
||||
| "require-call"
|
||||
// require.resolve("foo") in JavaScript
|
||||
| "require-resolve"
|
||||
// Dynamic import() in JavaScript
|
||||
| "dynamic-import"
|
||||
// @import() in CSS
|
||||
| "import-rule"
|
||||
// url() in CSS
|
||||
| "url-token"
|
||||
// The import was injected by Bun
|
||||
| "internal"
|
||||
// Entry point (not common)
|
||||
| "entry-point-build"
|
||||
| "entry-point-run"
|
||||
}
|
||||
|
||||
const transpiler = new Bun.Transpiler({ loader: "jsx" });
|
||||
```
|
||||
@@ -1,139 +0,0 @@
|
||||
Bun treats TypeScript as a first-class citizen.
|
||||
|
||||
{% callout %}
|
||||
|
||||
**Note** — To add type declarations for Bun APIs like the `Bun` global, follow the instructions at [Intro > TypeScript](https://bun.com/docs/typescript). This page describes how the Bun runtime runs TypeScript code.
|
||||
|
||||
{% /callout %}
|
||||
|
||||
## Running `.ts` files
|
||||
|
||||
Bun can directly execute `.ts` and `.tsx` files just like vanilla JavaScript, with no extra configuration. If you import a `.ts` or `.tsx` file (or an `npm` module that exports these files), Bun internally transpiles it into JavaScript then executes the file.
|
||||
|
||||
**Note** — Similar to other build tools, Bun does not typecheck the files. Use [`tsc`](https://www.typescriptlang.org/docs/handbook/compiler-options.html) (the official TypeScript CLI) if you're looking to catch static type errors.
|
||||
|
||||
{% callout %}
|
||||
|
||||
**Is transpiling still necessary?** — Because Bun can directly execute TypeScript, you may not need to transpile your TypeScript to run in production. Bun internally transpiles every file it executes (both `.js` and `.ts`), so the additional overhead of directly executing your `.ts/.tsx` source files is negligible.
|
||||
|
||||
That said, if you are using Bun as a development tool but still targeting Node.js or browsers in production, you'll still need to transpile.
|
||||
|
||||
{% /callout %}
|
||||
|
||||
## Path mapping
|
||||
|
||||
When resolving modules, Bun's runtime respects path mappings defined in [`compilerOptions.paths`](https://www.typescriptlang.org/tsconfig#paths) in your `tsconfig.json`. No other runtime does this.
|
||||
|
||||
Consider the following `tsconfig.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"data": ["./data.ts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Bun will use `baseUrl` to resolve module paths.
|
||||
|
||||
```ts
|
||||
// resolves to ./src/components/Button.tsx
|
||||
import { Button } from "components/Button.tsx";
|
||||
```
|
||||
|
||||
Bun will also correctly resolve imports from `"data"`.
|
||||
|
||||
{% codetabs %}
|
||||
|
||||
```ts#index.ts
|
||||
import { foo } from "data";
|
||||
console.log(foo); // => "Hello world!"
|
||||
```
|
||||
|
||||
```ts#data.ts
|
||||
export const foo = "Hello world!"
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
## Experimental Decorators
|
||||
|
||||
Bun supports the pre-TypeScript 5.0 experimental decorators syntax.
|
||||
|
||||
```ts#hello.ts
|
||||
// Simple logging decorator
|
||||
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = function(...args: any[]) {
|
||||
console.log(`Calling ${propertyKey} with:`, args);
|
||||
return originalMethod.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
class Example {
|
||||
@log
|
||||
greet(name: string) {
|
||||
return `Hello ${name}!`;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const example = new Example();
|
||||
example.greet("world"); // Logs: "Calling greet with: ['world']"
|
||||
```
|
||||
|
||||
To enable it, add `"experimentalDecorators": true` to your `tsconfig.json`:
|
||||
|
||||
```jsonc#tsconfig.json
|
||||
{
|
||||
"compilerOptions": {
|
||||
// ... rest of your config
|
||||
"experimentalDecorators": true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
We generally don't recommend using this in new codebases, but plenty of existing codebases have come to rely on it.
|
||||
|
||||
### emitDecoratorMetadata
|
||||
|
||||
Bun supports `emitDecoratorMetadata` in your `tsconfig.json`. This enables emitting design-time type metadata for decorated declarations in source files.
|
||||
|
||||
```ts#emit-decorator-metadata.ts
|
||||
import "reflect-metadata";
|
||||
|
||||
class User {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
function Injectable(target: Function) {
|
||||
// Get metadata about constructor parameters
|
||||
const params = Reflect.getMetadata("design:paramtypes", target);
|
||||
console.log("Dependencies:", params); // [User]
|
||||
}
|
||||
|
||||
@Injectable
|
||||
class UserService {
|
||||
constructor(private user: User) {}
|
||||
}
|
||||
|
||||
// Creates new UserService instance with dependencies
|
||||
const container = new UserService(new User());
|
||||
```
|
||||
|
||||
To enable it, add `"emitDecoratorMetadata": true` to your `tsconfig.json`:
|
||||
|
||||
```jsonc#tsconfig.json
|
||||
{
|
||||
"compilerOptions": {
|
||||
// ... rest of your config
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
},
|
||||
}
|
||||
```
|
||||
58
docs/runtime/typescript.mdx
Normal file
58
docs/runtime/typescript.mdx
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: "TypeScript"
|
||||
description: "Using TypeScript with Bun, including type definitions and compiler options"
|
||||
---
|
||||
|
||||
To install the TypeScript definitions for Bun's built-in APIs, install `@types/bun`.
|
||||
|
||||
```sh
|
||||
bun add -d @types/bun # dev dependency
|
||||
```
|
||||
|
||||
At this point, you should be able to reference the `Bun` global in your TypeScript files without seeing errors in your editor.
|
||||
|
||||
```ts
|
||||
console.log(Bun.version);
|
||||
```
|
||||
|
||||
## Suggested `compilerOptions`
|
||||
|
||||
Bun supports things like top-level await, JSX, and extensioned `.ts` imports, which TypeScript doesn't allow by default. Below is a set of recommended `compilerOptions` for a Bun project, so you can use these features without seeing compiler warnings from TypeScript.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "Preserve",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If you run `bun init` in a new directory, this `tsconfig.json` will be generated for you. (The stricter flags are disabled by default.)
|
||||
|
||||
```sh
|
||||
bun init
|
||||
```
|
||||
967
docs/runtime/utils.mdx
Normal file
967
docs/runtime/utils.mdx
Normal file
@@ -0,0 +1,967 @@
|
||||
---
|
||||
title: Utils
|
||||
description: Use Bun's utility functions to work with the runtime
|
||||
---
|
||||
|
||||
## `Bun.version`
|
||||
|
||||
A `string` containing the version of the `bun` CLI that is currently running.
|
||||
|
||||
```ts terminal icon="terminal"
|
||||
Bun.version;
|
||||
// => "1.3.3"
|
||||
```
|
||||
|
||||
## `Bun.revision`
|
||||
|
||||
The git commit of [Bun](https://github.com/oven-sh/bun) that was compiled to create the current `bun` CLI.
|
||||
|
||||
```ts terminal icon="terminal"
|
||||
Bun.revision;
|
||||
// => "f02561530fda1ee9396f51c8bc99b38716e38296"
|
||||
```
|
||||
|
||||
## `Bun.env`
|
||||
|
||||
An alias for `process.env`.
|
||||
|
||||
## `Bun.main`
|
||||
|
||||
An absolute path to the entrypoint of the current program (the file that was executed with `bun run`).
|
||||
|
||||
```ts script.ts
|
||||
Bun.main;
|
||||
// /path/to/script.ts
|
||||
```
|
||||
|
||||
This is particular useful for determining whether a script is being directly executed, as opposed to being imported by another script.
|
||||
|
||||
```ts
|
||||
if (import.meta.path === Bun.main) {
|
||||
// this script is being directly executed
|
||||
} else {
|
||||
// this file is being imported from another script
|
||||
}
|
||||
```
|
||||
|
||||
This is analogous to the [`require.main = module` trick](https://stackoverflow.com/questions/6398196/detect-if-called-through-require-or-directly-by-command-line) in Node.js.
|
||||
|
||||
## `Bun.sleep()`
|
||||
|
||||
`Bun.sleep(ms: number)`
|
||||
|
||||
Returns a `Promise` that resolves after the given number of milliseconds.
|
||||
|
||||
```ts
|
||||
console.log("hello");
|
||||
await Bun.sleep(1000);
|
||||
console.log("hello one second later!");
|
||||
```
|
||||
|
||||
Alternatively, pass a `Date` object to receive a `Promise` that resolves at that point in time.
|
||||
|
||||
```ts
|
||||
const oneSecondInFuture = new Date(Date.now() + 1000);
|
||||
|
||||
console.log("hello");
|
||||
await Bun.sleep(oneSecondInFuture);
|
||||
console.log("hello one second later!");
|
||||
```
|
||||
|
||||
## `Bun.sleepSync()`
|
||||
|
||||
`Bun.sleepSync(ms: number)`
|
||||
|
||||
A blocking synchronous version of `Bun.sleep`.
|
||||
|
||||
```ts
|
||||
console.log("hello");
|
||||
Bun.sleepSync(1000); // blocks thread for one second
|
||||
console.log("hello one second later!");
|
||||
```
|
||||
|
||||
## `Bun.which()`
|
||||
|
||||
`Bun.which(bin: string)`
|
||||
|
||||
Returns the path to an executable, similar to typing `which` in your terminal.
|
||||
|
||||
```ts
|
||||
const ls = Bun.which("ls");
|
||||
console.log(ls); // "/usr/bin/ls"
|
||||
```
|
||||
|
||||
By default Bun looks at the current `PATH` environment variable to determine the path. To configure `PATH`:
|
||||
|
||||
```ts
|
||||
const ls = Bun.which("ls", {
|
||||
PATH: "/usr/local/bin:/usr/bin:/bin",
|
||||
});
|
||||
console.log(ls); // "/usr/bin/ls"
|
||||
```
|
||||
|
||||
Pass a `cwd` option to resolve for executable from within a specific directory.
|
||||
|
||||
```ts
|
||||
const ls = Bun.which("ls", {
|
||||
cwd: "/tmp",
|
||||
PATH: "",
|
||||
});
|
||||
|
||||
console.log(ls); // null
|
||||
```
|
||||
|
||||
You can think of this as a builtin alternative to the [`which`](https://www.npmjs.com/package/which) npm package.
|
||||
|
||||
## `Bun.randomUUIDv7()`
|
||||
|
||||
`Bun.randomUUIDv7()` returns a [UUID v7](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-01.html#name-uuidv7-layout-and-bit-order), which is monotonic and suitable for sorting and databases.
|
||||
|
||||
```ts
|
||||
import { randomUUIDv7 } from "bun";
|
||||
|
||||
const id = randomUUIDv7();
|
||||
// => "0192ce11-26d5-7dc3-9305-1426de888c5a"
|
||||
```
|
||||
|
||||
A UUID v7 is a 128-bit value that encodes the current timestamp, a random value, and a counter. The timestamp is encoded using the lowest 48 bits, and the random value and counter are encoded using the remaining bits.
|
||||
|
||||
The `timestamp` parameter defaults to the current time in milliseconds. When the timestamp changes, the counter is reset to a pseudo-random integer wrapped to 4096. This counter is atomic and threadsafe, meaning that using `Bun.randomUUIDv7()` in many Workers within the same process running at the same timestamp will not have colliding counter values.
|
||||
|
||||
The final 8 bytes of the UUID are a cryptographically secure random value. It uses the same random number generator used by `crypto.randomUUID()` (which comes from BoringSSL, which in turn comes from the platform-specific system random number generator usually provided by the underlying hardware).
|
||||
|
||||
```ts
|
||||
namespace Bun {
|
||||
function randomUUIDv7(encoding?: "hex" | "base64" | "base64url" = "hex", timestamp?: number = Date.now()): string;
|
||||
/**
|
||||
* If you pass "buffer", you get a 16-byte buffer instead of a string.
|
||||
*/
|
||||
function randomUUIDv7(encoding: "buffer", timestamp?: number = Date.now()): Buffer;
|
||||
|
||||
// If you only pass a timestamp, you get a hex string
|
||||
function randomUUIDv7(timestamp?: number = Date.now()): string;
|
||||
}
|
||||
```
|
||||
|
||||
You can optionally set encoding to `"buffer"` to get a 16-byte buffer instead of a string. This can sometimes avoid string conversion overhead.
|
||||
|
||||
```ts buffer.ts
|
||||
const buffer = Bun.randomUUIDv7("buffer");
|
||||
```
|
||||
|
||||
`base64` and `base64url` encodings are also supported when you want a slightly shorter string.
|
||||
|
||||
```ts base64.ts
|
||||
const base64 = Bun.randomUUIDv7("base64");
|
||||
const base64url = Bun.randomUUIDv7("base64url");
|
||||
```
|
||||
|
||||
## `Bun.peek()`
|
||||
|
||||
`Bun.peek(prom: Promise)`
|
||||
|
||||
Reads a promise's result without `await` or `.then`, but only if the promise has already fulfilled or rejected.
|
||||
|
||||
```ts
|
||||
import { peek } from "bun";
|
||||
|
||||
const promise = Promise.resolve("hi");
|
||||
|
||||
// no await!
|
||||
const result = peek(promise);
|
||||
console.log(result); // "hi"
|
||||
```
|
||||
|
||||
This is important when attempting to reduce number of extraneous microticks in performance-sensitive code. It's an advanced API and you probably shouldn't use it unless you know what you're doing.
|
||||
|
||||
```ts
|
||||
import { peek } from "bun";
|
||||
import { expect, test } from "bun:test";
|
||||
|
||||
test("peek", () => {
|
||||
const promise = Promise.resolve(true);
|
||||
|
||||
// no await necessary!
|
||||
expect(peek(promise)).toBe(true);
|
||||
|
||||
// if we peek again, it returns the same value
|
||||
const again = peek(promise);
|
||||
expect(again).toBe(true);
|
||||
|
||||
// if we peek a non-promise, it returns the value
|
||||
const value = peek(42);
|
||||
expect(value).toBe(42);
|
||||
|
||||
// if we peek a pending promise, it returns the promise again
|
||||
const pending = new Promise(() => {});
|
||||
expect(peek(pending)).toBe(pending);
|
||||
|
||||
// If we peek a rejected promise, it:
|
||||
// - returns the error
|
||||
// - does not mark the promise as handled
|
||||
const rejected = Promise.reject(new Error("Successfully tested promise rejection"));
|
||||
expect(peek(rejected).message).toBe("Successfully tested promise rejection");
|
||||
});
|
||||
```
|
||||
|
||||
The `peek.status` function lets you read the status of a promise without resolving it.
|
||||
|
||||
```ts
|
||||
import { peek } from "bun";
|
||||
import { expect, test } from "bun:test";
|
||||
|
||||
test("peek.status", () => {
|
||||
const promise = Promise.resolve(true);
|
||||
expect(peek.status(promise)).toBe("fulfilled");
|
||||
|
||||
const pending = new Promise(() => {});
|
||||
expect(peek.status(pending)).toBe("pending");
|
||||
|
||||
const rejected = Promise.reject(new Error("oh nooo"));
|
||||
expect(peek.status(rejected)).toBe("rejected");
|
||||
});
|
||||
```
|
||||
|
||||
## `Bun.openInEditor()`
|
||||
|
||||
Opens a file in your default editor. Bun auto-detects your editor via the `$VISUAL` or `$EDITOR` environment variables.
|
||||
|
||||
```ts
|
||||
const currentFile = import.meta.url;
|
||||
Bun.openInEditor(currentFile);
|
||||
```
|
||||
|
||||
You can override this via the `debug.editor` setting in your [`bunfig.toml`](/runtime/bunfig).
|
||||
|
||||
```toml bunfig.toml icon="settings"
|
||||
[debug] # [!code ++]
|
||||
editor = "code" # [!code ++]
|
||||
```
|
||||
|
||||
Or specify an editor with the `editor` param. You can also specify a line and column number.
|
||||
|
||||
```ts
|
||||
Bun.openInEditor(import.meta.url, {
|
||||
editor: "vscode", // or "subl"
|
||||
line: 10,
|
||||
column: 5,
|
||||
});
|
||||
```
|
||||
|
||||
## `Bun.deepEquals()`
|
||||
|
||||
Recursively checks if two objects are equivalent. This is used internally by `expect().toEqual()` in `bun:test`.
|
||||
|
||||
```ts
|
||||
const foo = { a: 1, b: 2, c: { d: 3 } };
|
||||
|
||||
// true
|
||||
Bun.deepEquals(foo, { a: 1, b: 2, c: { d: 3 } });
|
||||
|
||||
// false
|
||||
Bun.deepEquals(foo, { a: 1, b: 2, c: { d: 4 } });
|
||||
```
|
||||
|
||||
A third boolean parameter can be used to enable "strict" mode. This is used by `expect().toStrictEqual()` in the test runner.
|
||||
|
||||
```ts
|
||||
const a = { entries: [1, 2] };
|
||||
const b = { entries: [1, 2], extra: undefined };
|
||||
|
||||
Bun.deepEquals(a, b); // => true
|
||||
Bun.deepEquals(a, b, true); // => false
|
||||
```
|
||||
|
||||
In strict mode, the following are considered unequal:
|
||||
|
||||
```ts
|
||||
// undefined values
|
||||
Bun.deepEquals({}, { a: undefined }, true); // false
|
||||
|
||||
// undefined in arrays
|
||||
Bun.deepEquals(["asdf"], ["asdf", undefined], true); // false
|
||||
|
||||
// sparse arrays
|
||||
Bun.deepEquals([, 1], [undefined, 1], true); // false
|
||||
|
||||
// object literals vs instances w/ same properties
|
||||
class Foo {
|
||||
a = 1;
|
||||
}
|
||||
Bun.deepEquals(new Foo(), { a: 1 }, true); // false
|
||||
```
|
||||
|
||||
## `Bun.escapeHTML()`
|
||||
|
||||
`Bun.escapeHTML(value: string | object | number | boolean): string`
|
||||
|
||||
Escapes the following characters from an input string:
|
||||
|
||||
- `"` becomes `"`
|
||||
- `&` becomes `&`
|
||||
- `'` becomes `'`
|
||||
- `<` becomes `<`
|
||||
- `>` becomes `>`
|
||||
|
||||
This function is optimized for large input. On an M1X, it processes 480 MB/s -
|
||||
20 GB/s, depending on how much data is being escaped and whether there is non-ascii
|
||||
text. Non-string types will be converted to a string before escaping.
|
||||
|
||||
## `Bun.stringWidth()`
|
||||
|
||||
<Note>~6,756x faster `string-width` alternative</Note>
|
||||
|
||||
Get the column count of a string as it would be displayed in a terminal.
|
||||
Supports ANSI escape codes, emoji, and wide characters.
|
||||
|
||||
Example usage:
|
||||
|
||||
```ts
|
||||
Bun.stringWidth("hello"); // => 5
|
||||
Bun.stringWidth("\u001b[31mhello\u001b[0m"); // => 5
|
||||
Bun.stringWidth("\u001b[31mhello\u001b[0m", { countAnsiEscapeCodes: true }); // => 12
|
||||
```
|
||||
|
||||
This is useful for:
|
||||
|
||||
- Aligning text in a terminal
|
||||
- Quickly checking if a string contains ANSI escape codes
|
||||
- Measuring the width of a string in a terminal
|
||||
|
||||
This API is designed to match the popular "string-width" package, so that
|
||||
existing code can be easily ported to Bun and vice versa.
|
||||
|
||||
[In this benchmark](https://github.com/oven-sh/bun/blob/5147c0ba7379d85d4d1ed0714b84d6544af917eb/bench/snippets/string-width.mjs#L13), `Bun.stringWidth` is a ~6,756x faster than the `string-width` npm package for input larger than about 500 characters. Big thanks to [sindresorhus](https://github.com/sindresorhus) for their work on `string-width`!
|
||||
|
||||
```ts
|
||||
❯ bun string-width.mjs
|
||||
cpu: 13th Gen Intel(R) Core(TM) i9-13900
|
||||
runtime: bun 1.0.29 (x64-linux)
|
||||
|
||||
benchmark time (avg) (min … max) p75 p99 p995
|
||||
------------------------------------------------------------------------------------- -----------------------------
|
||||
Bun.stringWidth 500 chars ascii 37.09 ns/iter (36.77 ns … 41.11 ns) 37.07 ns 38.84 ns 38.99 ns
|
||||
|
||||
❯ node string-width.mjs
|
||||
|
||||
benchmark time (avg) (min … max) p75 p99 p995
|
||||
------------------------------------------------------------------------------------- -----------------------------
|
||||
npm/string-width 500 chars ascii 249,710 ns/iter (239,970 ns … 293,180 ns) 250,930 ns 276,700 ns 281,450 ns
|
||||
```
|
||||
|
||||
To make `Bun.stringWidth` fast, we've implemented it in Zig using optimized SIMD instructions, accounting for Latin1, UTF-16, and UTF-8 encodings. It passes `string-width`'s tests.
|
||||
|
||||
<Accordion title="View full benchmark">
|
||||
|
||||
As a reminder, 1 nanosecond (ns) is 1 billionth of a second. Here's a quick reference for converting between units:
|
||||
|
||||
| Unit | 1 Millisecond |
|
||||
| ---- | ------------- |
|
||||
| ns | 1,000,000 |
|
||||
| µs | 1,000 |
|
||||
| ms | 1 |
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
❯ bun string-width.mjs
|
||||
cpu: 13th Gen Intel(R) Core(TM) i9-13900
|
||||
runtime: bun 1.0.29 (x64-linux)
|
||||
|
||||
benchmark time (avg) (min … max) p75 p99 p995
|
||||
------------------------------------------------------------------------------------- -----------------------------
|
||||
Bun.stringWidth 5 chars ascii 16.45 ns/iter (16.27 ns … 19.71 ns) 16.48 ns 16.93 ns 17.21 ns
|
||||
Bun.stringWidth 50 chars ascii 19.42 ns/iter (18.61 ns … 27.85 ns) 19.35 ns 21.7 ns 22.31 ns
|
||||
Bun.stringWidth 500 chars ascii 37.09 ns/iter (36.77 ns … 41.11 ns) 37.07 ns 38.84 ns 38.99 ns
|
||||
Bun.stringWidth 5,000 chars ascii 216.9 ns/iter (215.8 ns … 228.54 ns) 216.23 ns 228.52 ns 228.53 ns
|
||||
Bun.stringWidth 25,000 chars ascii 1.01 µs/iter (1.01 µs … 1.01 µs) 1.01 µs 1.01 µs 1.01 µs
|
||||
Bun.stringWidth 7 chars ascii+emoji 54.2 ns/iter (53.36 ns … 58.19 ns) 54.23 ns 57.55 ns 57.94 ns
|
||||
Bun.stringWidth 70 chars ascii+emoji 354.26 ns/iter (350.51 ns … 363.96 ns) 355.93 ns 363.11 ns 363.96 ns
|
||||
Bun.stringWidth 700 chars ascii+emoji 3.3 µs/iter (3.27 µs … 3.4 µs) 3.3 µs 3.4 µs 3.4 µs
|
||||
Bun.stringWidth 7,000 chars ascii+emoji 32.69 µs/iter (32.22 µs … 45.27 µs) 32.7 µs 34.57 µs 34.68 µs
|
||||
Bun.stringWidth 35,000 chars ascii+emoji 163.35 µs/iter (161.17 µs … 170.79 µs) 163.82 µs 169.66 µs 169.93 µs
|
||||
Bun.stringWidth 8 chars ansi+emoji 66.15 ns/iter (65.17 ns … 69.97 ns) 66.12 ns 69.8 ns 69.87 ns
|
||||
Bun.stringWidth 80 chars ansi+emoji 492.95 ns/iter (488.05 ns … 499.5 ns) 494.8 ns 498.58 ns 499.5 ns
|
||||
Bun.stringWidth 800 chars ansi+emoji 4.73 µs/iter (4.71 µs … 4.88 µs) 4.72 µs 4.88 µs 4.88 µs
|
||||
Bun.stringWidth 8,000 chars ansi+emoji 47.02 µs/iter (46.37 µs … 67.44 µs) 46.96 µs 49.57 µs 49.63 µs
|
||||
Bun.stringWidth 40,000 chars ansi+emoji 234.45 µs/iter (231.78 µs … 240.98 µs) 234.92 µs 236.34 µs 236.62 µs
|
||||
Bun.stringWidth 19 chars ansi+emoji+ascii 135.46 ns/iter (133.67 ns … 143.26 ns) 135.32 ns 142.55 ns 142.77 ns
|
||||
Bun.stringWidth 190 chars ansi+emoji+ascii 1.17 µs/iter (1.16 µs … 1.17 µs) 1.17 µs 1.17 µs 1.17 µs
|
||||
Bun.stringWidth 1,900 chars ansi+emoji+ascii 11.45 µs/iter (11.26 µs … 20.41 µs) 11.45 µs 12.08 µs 12.11 µs
|
||||
Bun.stringWidth 19,000 chars ansi+emoji+ascii 114.06 µs/iter (112.86 µs … 120.06 µs) 114.25 µs 115.86 µs 116.15 µs
|
||||
Bun.stringWidth 95,000 chars ansi+emoji+ascii 572.69 µs/iter (565.52 µs … 607.22 µs) 572.45 µs 604.86 µs 605.21 µs
|
||||
```
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
❯ node string-width.mjs
|
||||
cpu: 13th Gen Intel(R) Core(TM) i9-13900
|
||||
runtime: node v21.4.0 (x64-linux)
|
||||
|
||||
benchmark time (avg) (min … max) p75 p99 p995
|
||||
-------------------------------------------------------------------------------------- -----------------------------
|
||||
npm/string-width 5 chars ascii 3.19 µs/iter (3.13 µs … 3.48 µs) 3.25 µs 3.48 µs 3.48 µs
|
||||
npm/string-width 50 chars ascii 20.09 µs/iter (18.93 µs … 435.06 µs) 19.49 µs 21.89 µs 22.59 µs
|
||||
npm/string-width 500 chars ascii 249.71 µs/iter (239.97 µs … 293.18 µs) 250.93 µs 276.7 µs 281.45 µs
|
||||
npm/string-width 5,000 chars ascii 6.69 ms/iter (6.58 ms … 6.76 ms) 6.72 ms 6.76 ms 6.76 ms
|
||||
npm/string-width 25,000 chars ascii 139.57 ms/iter (137.17 ms … 143.28 ms) 140.49 ms 143.28 ms 143.28 ms
|
||||
npm/string-width 7 chars ascii+emoji 3.7 µs/iter (3.62 µs … 3.94 µs) 3.73 µs 3.94 µs 3.94 µs
|
||||
npm/string-width 70 chars ascii+emoji 23.93 µs/iter (22.44 µs … 331.2 µs) 23.15 µs 25.98 µs 30.2 µs
|
||||
npm/string-width 700 chars ascii+emoji 251.65 µs/iter (237.78 µs … 444.69 µs) 252.92 µs 325.89 µs 354.08 µs
|
||||
npm/string-width 7,000 chars ascii+emoji 4.95 ms/iter (4.82 ms … 5.19 ms) 5 ms 5.04 ms 5.19 ms
|
||||
npm/string-width 35,000 chars ascii+emoji 96.93 ms/iter (94.39 ms … 102.58 ms) 97.68 ms 102.58 ms 102.58 ms
|
||||
npm/string-width 8 chars ansi+emoji 3.92 µs/iter (3.45 µs … 4.57 µs) 4.09 µs 4.57 µs 4.57 µs
|
||||
npm/string-width 80 chars ansi+emoji 24.46 µs/iter (22.87 µs … 4.2 ms) 23.54 µs 25.89 µs 27.41 µs
|
||||
npm/string-width 800 chars ansi+emoji 259.62 µs/iter (246.76 µs … 480.12 µs) 258.65 µs 349.84 µs 372.55 µs
|
||||
npm/string-width 8,000 chars ansi+emoji 5.46 ms/iter (5.41 ms … 5.57 ms) 5.48 ms 5.55 ms 5.57 ms
|
||||
npm/string-width 40,000 chars ansi+emoji 108.91 ms/iter (107.55 ms … 109.5 ms) 109.25 ms 109.5 ms 109.5 ms
|
||||
npm/string-width 19 chars ansi+emoji+ascii 6.53 µs/iter (6.35 µs … 6.75 µs) 6.54 µs 6.75 µs 6.75 µs
|
||||
npm/string-width 190 chars ansi+emoji+ascii 55.52 µs/iter (52.59 µs … 352.73 µs) 54.19 µs 80.77 µs 167.21 µs
|
||||
npm/string-width 1,900 chars ansi+emoji+ascii 701.71 µs/iter (653.94 µs … 893.78 µs) 715.3 µs 855.37 µs 872.9 µs
|
||||
npm/string-width 19,000 chars ansi+emoji+ascii 27.19 ms/iter (26.89 ms … 27.41 ms) 27.28 ms 27.41 ms 27.41 ms
|
||||
npm/string-width 95,000 chars ansi+emoji+ascii 3.68 s/iter (3.66 s … 3.7 s) 3.69 s 3.7 s 3.7 s
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
TypeScript definition:
|
||||
|
||||
```ts expandable
|
||||
namespace Bun {
|
||||
export function stringWidth(
|
||||
/**
|
||||
* The string to measure
|
||||
*/
|
||||
input: string,
|
||||
options?: {
|
||||
/**
|
||||
* If `true`, count ANSI escape codes as part of the string width. If `false`, ANSI escape codes are ignored when calculating the string width.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
countAnsiEscapeCodes?: boolean;
|
||||
/**
|
||||
* When it's ambiugous and `true`, count emoji as 1 characters wide. If `false`, emoji are counted as 2 character wide.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
ambiguousIsNarrow?: boolean;
|
||||
},
|
||||
): number;
|
||||
}
|
||||
```
|
||||
|
||||
## `Bun.ms`
|
||||
|
||||
Built-in alternative to the [`ms`](https://npmjs.com/package/ms) package. Convert between human-readable time strings and milliseconds. Automatically optimized at compile-time when bundling.
|
||||
|
||||
**Convert to Milliseconds**
|
||||
|
||||
```ts
|
||||
Bun.ms("2 days"); // 172800000
|
||||
Bun.ms("1d"); // 86400000
|
||||
Bun.ms("10h"); // 36000000
|
||||
Bun.ms("2.5 hrs"); // 9000000
|
||||
Bun.ms("2h"); // 7200000
|
||||
Bun.ms("1m"); // 60000
|
||||
Bun.ms("5s"); // 5000
|
||||
Bun.ms("1y"); // 31557600000
|
||||
Bun.ms("100"); // 100
|
||||
Bun.ms("-3 days"); // -259200000
|
||||
Bun.ms("-1h"); // -3600000
|
||||
Bun.ms("-200"); // -200
|
||||
```
|
||||
|
||||
**Convert from Milliseconds**
|
||||
|
||||
```ts
|
||||
Bun.ms(60000); // "1m"
|
||||
Bun.ms(2 * 60000); // "2m"
|
||||
Bun.ms(-3 * 60000); // "-3m"
|
||||
Bun.ms(Bun.ms("10 hours")); // "10h"
|
||||
|
||||
Bun.ms(60000, { long: true }); // "1 minute"
|
||||
Bun.ms(2 * 60000, { long: true }); // "2 minutes"
|
||||
Bun.ms(-3 * 60000, { long: true }); // "-3 minutes"
|
||||
Bun.ms(Bun.ms("10 hours"), { long: true }); // "10 hours"
|
||||
```
|
||||
|
||||
**Bundler Support**
|
||||
|
||||
```sh
|
||||
$ cat ./example.ts
|
||||
console.log(Bun.ms("2d"));
|
||||
|
||||
$ bun build ./example.ts --minify-syntax
|
||||
console.log(172800000);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `Bun.fileURLToPath()`
|
||||
|
||||
Converts a `file://` URL to an absolute path.
|
||||
|
||||
```ts
|
||||
const path = Bun.fileURLToPath(new URL("file:///foo/bar.txt"));
|
||||
console.log(path); // "/foo/bar.txt"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `Bun.pathToFileURL()`
|
||||
|
||||
Converts an absolute path to a `file://` URL.
|
||||
|
||||
```ts
|
||||
const url = Bun.pathToFileURL("/foo/bar.txt");
|
||||
console.log(url); // "file:///foo/bar.txt"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `Bun.gzipSync()`
|
||||
|
||||
Compresses a `Uint8Array` using zlib's GZIP algorithm.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello".repeat(100)); // Buffer extends Uint8Array
|
||||
const compressed = Bun.gzipSync(buf);
|
||||
|
||||
buf; // => Uint8Array(500)
|
||||
compressed; // => Uint8Array(30)
|
||||
```
|
||||
|
||||
Optionally, pass a parameters object as the second argument:
|
||||
|
||||
<Accordion title="zlib compression options">
|
||||
|
||||
```ts expandable
|
||||
export type ZlibCompressionOptions = {
|
||||
/**
|
||||
* The compression level to use. Must be between `-1` and `9`.
|
||||
* - A value of `-1` uses the default compression level (Currently `6`)
|
||||
* - A value of `0` gives no compression
|
||||
* - A value of `1` gives least compression, fastest speed
|
||||
* - A value of `9` gives best compression, slowest speed
|
||||
*/
|
||||
level?: -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
||||
/**
|
||||
* How much memory should be allocated for the internal compression state.
|
||||
*
|
||||
* A value of `1` uses minimum memory but is slow and reduces compression ratio.
|
||||
*
|
||||
* A value of `9` uses maximum memory for optimal speed. The default is `8`.
|
||||
*/
|
||||
memLevel?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
||||
/**
|
||||
* The base 2 logarithm of the window size (the size of the history buffer).
|
||||
*
|
||||
* Larger values of this parameter result in better compression at the expense of memory usage.
|
||||
*
|
||||
* The following value ranges are supported:
|
||||
* - `9..15`: The output will have a zlib header and footer (Deflate)
|
||||
* - `-9..-15`: The output will **not** have a zlib header or footer (Raw Deflate)
|
||||
* - `25..31` (16+`9..15`): The output will have a gzip header and footer (gzip)
|
||||
*
|
||||
* The gzip header will have no file name, no extra data, no comment, no modification time (set to zero) and no header CRC.
|
||||
*/
|
||||
windowBits?:
|
||||
| -9
|
||||
| -10
|
||||
| -11
|
||||
| -12
|
||||
| -13
|
||||
| -14
|
||||
| -15
|
||||
| 9
|
||||
| 10
|
||||
| 11
|
||||
| 12
|
||||
| 13
|
||||
| 14
|
||||
| 15
|
||||
| 25
|
||||
| 26
|
||||
| 27
|
||||
| 28
|
||||
| 29
|
||||
| 30
|
||||
| 31;
|
||||
/**
|
||||
* Tunes the compression algorithm.
|
||||
*
|
||||
* - `Z_DEFAULT_STRATEGY`: For normal data **(Default)**
|
||||
* - `Z_FILTERED`: For data produced by a filter or predictor
|
||||
* - `Z_HUFFMAN_ONLY`: Force Huffman encoding only (no string match)
|
||||
* - `Z_RLE`: Limit match distances to one (run-length encoding)
|
||||
* - `Z_FIXED` prevents the use of dynamic Huffman codes
|
||||
*
|
||||
* `Z_RLE` is designed to be almost as fast as `Z_HUFFMAN_ONLY`, but give better compression for PNG image data.
|
||||
*
|
||||
* `Z_FILTERED` forces more Huffman coding and less string matching, it is
|
||||
* somewhat intermediate between `Z_DEFAULT_STRATEGY` and `Z_HUFFMAN_ONLY`.
|
||||
* Filtered data consists mostly of small values with a somewhat random distribution.
|
||||
*/
|
||||
strategy?: number;
|
||||
};
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
---
|
||||
|
||||
## `Bun.gunzipSync()`
|
||||
|
||||
Decompresses a `Uint8Array` using zlib's GUNZIP algorithm.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello".repeat(100)); // Buffer extends Uint8Array
|
||||
const compressed = Bun.gzipSync(buf);
|
||||
|
||||
const dec = new TextDecoder();
|
||||
const uncompressed = Bun.gunzipSync(compressed);
|
||||
dec.decode(uncompressed);
|
||||
// => "hellohellohello..."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `Bun.deflateSync()`
|
||||
|
||||
Compresses a `Uint8Array` using zlib's DEFLATE algorithm.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello".repeat(100));
|
||||
const compressed = Bun.deflateSync(buf);
|
||||
|
||||
buf; // => Buffer(500)
|
||||
compressed; // => Uint8Array(12)
|
||||
```
|
||||
|
||||
The second argument supports the same set of configuration options as [`Bun.gzipSync`](#bun-gzipsync).
|
||||
|
||||
---
|
||||
|
||||
## `Bun.inflateSync()`
|
||||
|
||||
Decompresses a `Uint8Array` using zlib's INFLATE algorithm.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello".repeat(100));
|
||||
const compressed = Bun.deflateSync(buf);
|
||||
|
||||
const dec = new TextDecoder();
|
||||
const decompressed = Bun.inflateSync(compressed);
|
||||
dec.decode(decompressed);
|
||||
// => "hellohellohello..."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `Bun.zstdCompress()` / `Bun.zstdCompressSync()`
|
||||
|
||||
Compresses a `Uint8Array` using the Zstandard algorithm.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello".repeat(100));
|
||||
|
||||
// Synchronous
|
||||
const compressedSync = Bun.zstdCompressSync(buf);
|
||||
// Asynchronous
|
||||
const compressedAsync = await Bun.zstdCompress(buf);
|
||||
|
||||
// With compression level (1-22, default: 3)
|
||||
const compressedLevel = Bun.zstdCompressSync(buf, { level: 6 });
|
||||
```
|
||||
|
||||
## `Bun.zstdDecompress()` / `Bun.zstdDecompressSync()`
|
||||
|
||||
Decompresses a `Uint8Array` using the Zstandard algorithm.
|
||||
|
||||
```ts
|
||||
const buf = Buffer.from("hello".repeat(100));
|
||||
const compressed = Bun.zstdCompressSync(buf);
|
||||
|
||||
// Synchronous
|
||||
const decompressedSync = Bun.zstdDecompressSync(compressed);
|
||||
// Asynchronous
|
||||
const decompressedAsync = await Bun.zstdDecompress(compressed);
|
||||
|
||||
const dec = new TextDecoder();
|
||||
dec.decode(decompressedSync);
|
||||
// => "hellohellohello..."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `Bun.inspect()`
|
||||
|
||||
Serializes an object to a `string` exactly as it would be printed by `console.log`.
|
||||
|
||||
```ts
|
||||
const obj = { foo: "bar" };
|
||||
const str = Bun.inspect(obj);
|
||||
// => '{\nfoo: "bar" \n}'
|
||||
|
||||
const arr = new Uint8Array([1, 2, 3]);
|
||||
const str = Bun.inspect(arr);
|
||||
// => "Uint8Array(3) [ 1, 2, 3 ]"
|
||||
```
|
||||
|
||||
### `Bun.inspect.custom`
|
||||
|
||||
This is the symbol that Bun uses to implement `Bun.inspect`. You can override this to customize how your objects are printed. It is identical to `util.inspect.custom` in Node.js.
|
||||
|
||||
```ts
|
||||
class Foo {
|
||||
[Bun.inspect.custom]() {
|
||||
return "foo";
|
||||
}
|
||||
}
|
||||
|
||||
const foo = new Foo();
|
||||
console.log(foo); // => "foo"
|
||||
```
|
||||
|
||||
### `Bun.inspect.table(tabularData, properties, options)`
|
||||
|
||||
Format tabular data into a string. Like [`console.table`](https://developer.mozilla.org/en-US/docs/Web/API/console/table_static), except it returns a string rather than printing to the console.
|
||||
|
||||
```ts
|
||||
console.log(
|
||||
Bun.inspect.table([
|
||||
{ a: 1, b: 2, c: 3 },
|
||||
{ a: 4, b: 5, c: 6 },
|
||||
{ a: 7, b: 8, c: 9 },
|
||||
]),
|
||||
);
|
||||
//
|
||||
// ┌───┬───┬───┬───┐
|
||||
// │ │ a │ b │ c │
|
||||
// ├───┼───┼───┼───┤
|
||||
// │ 0 │ 1 │ 2 │ 3 │
|
||||
// │ 1 │ 4 │ 5 │ 6 │
|
||||
// │ 2 │ 7 │ 8 │ 9 │
|
||||
// └───┴───┴───┴───┘
|
||||
```
|
||||
|
||||
Additionally, you can pass an array of property names to display only a subset of properties.
|
||||
|
||||
```ts
|
||||
console.log(
|
||||
Bun.inspect.table(
|
||||
[
|
||||
{ a: 1, b: 2, c: 3 },
|
||||
{ a: 4, b: 5, c: 6 },
|
||||
],
|
||||
["a", "c"],
|
||||
),
|
||||
);
|
||||
//
|
||||
// ┌───┬───┬───┐
|
||||
// │ │ a │ c │
|
||||
// ├───┼───┼───┤
|
||||
// │ 0 │ 1 │ 3 │
|
||||
// │ 1 │ 4 │ 6 │
|
||||
// └───┴───┴───┘
|
||||
```
|
||||
|
||||
You can also conditionally enable ANSI colors by passing `{ colors: true }`.
|
||||
|
||||
```ts
|
||||
console.log(
|
||||
Bun.inspect.table(
|
||||
[
|
||||
{ a: 1, b: 2, c: 3 },
|
||||
{ a: 4, b: 5, c: 6 },
|
||||
],
|
||||
{
|
||||
colors: true,
|
||||
},
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `Bun.nanoseconds()`
|
||||
|
||||
Returns the number of nanoseconds since the current `bun` process started, as a `number`. Useful for high-precision timing and benchmarking.
|
||||
|
||||
```ts
|
||||
Bun.nanoseconds();
|
||||
// => 7288958
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `Bun.readableStreamTo*()`
|
||||
|
||||
Bun implements a set of convenience functions for asynchronously consuming the body of a `ReadableStream` and converting it to various binary formats.
|
||||
|
||||
```ts
|
||||
const stream = (await fetch("https://bun.com")).body;
|
||||
stream; // => ReadableStream
|
||||
|
||||
await Bun.readableStreamToArrayBuffer(stream);
|
||||
// => ArrayBuffer
|
||||
|
||||
await Bun.readableStreamToBytes(stream);
|
||||
// => Uint8Array
|
||||
|
||||
await Bun.readableStreamToBlob(stream);
|
||||
// => Blob
|
||||
|
||||
await Bun.readableStreamToJSON(stream);
|
||||
// => object
|
||||
|
||||
await Bun.readableStreamToText(stream);
|
||||
// => string
|
||||
|
||||
// returns all chunks as an array
|
||||
await Bun.readableStreamToArray(stream);
|
||||
// => unknown[]
|
||||
|
||||
// returns all chunks as a FormData object (encoded as x-www-form-urlencoded)
|
||||
await Bun.readableStreamToFormData(stream);
|
||||
|
||||
// returns all chunks as a FormData object (encoded as multipart/form-data)
|
||||
await Bun.readableStreamToFormData(stream, multipartFormBoundary);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `Bun.resolveSync()`
|
||||
|
||||
Resolves a file path or module specifier using Bun's internal module resolution algorithm. The first argument is the path to resolve, and the second argument is the "root". If no match is found, an `Error` is thrown.
|
||||
|
||||
```ts
|
||||
Bun.resolveSync("./foo.ts", "/path/to/project");
|
||||
// => "/path/to/project/foo.ts"
|
||||
|
||||
Bun.resolveSync("zod", "/path/to/project");
|
||||
// => "/path/to/project/node_modules/zod/index.ts"
|
||||
```
|
||||
|
||||
To resolve relative to the current working directory, pass `process.cwd()` or `"."` as the root.
|
||||
|
||||
```ts
|
||||
Bun.resolveSync("./foo.ts", process.cwd());
|
||||
Bun.resolveSync("./foo.ts", "/path/to/project");
|
||||
```
|
||||
|
||||
To resolve relative to the directory containing the current file, pass `import.meta.dir`.
|
||||
|
||||
```ts
|
||||
Bun.resolveSync("./foo.ts", import.meta.dir);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `Bun.stripANSI()`
|
||||
|
||||
<Note>~6-57x faster `strip-ansi` alternative</Note>
|
||||
|
||||
`Bun.stripANSI(text: string): string`
|
||||
|
||||
Strip ANSI escape codes from a string. This is useful for removing colors and formatting from terminal output.
|
||||
|
||||
```ts
|
||||
const coloredText = "\u001b[31mHello\u001b[0m \u001b[32mWorld\u001b[0m";
|
||||
const plainText = Bun.stripANSI(coloredText);
|
||||
console.log(plainText); // => "Hello World"
|
||||
|
||||
// Works with various ANSI codes
|
||||
const formatted = "\u001b[1m\u001b[4mBold and underlined\u001b[0m";
|
||||
console.log(Bun.stripANSI(formatted)); // => "Bold and underlined"
|
||||
```
|
||||
|
||||
`Bun.stripANSI` is significantly faster than the popular [`strip-ansi`](https://www.npmjs.com/package/strip-ansi) npm package:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun bench/snippets/strip-ansi.mjs
|
||||
```
|
||||
|
||||
```txt
|
||||
cpu: Apple M3 Max
|
||||
runtime: bun 1.2.21 (arm64-darwin)
|
||||
|
||||
benchmark avg (min … max) p75 / p99
|
||||
------------------------------------------------------- ----------
|
||||
Bun.stripANSI 11 chars no-ansi 8.13 ns/iter 8.27 ns
|
||||
(7.45 ns … 33.59 ns) 10.29 ns
|
||||
|
||||
Bun.stripANSI 13 chars ansi 51.68 ns/iter 52.51 ns
|
||||
(46.16 ns … 113.71 ns) 57.71 ns
|
||||
|
||||
Bun.stripANSI 16,384 chars long-no-ansi 298.39 ns/iter 305.44 ns
|
||||
(281.50 ns … 331.65 ns) 320.70 ns
|
||||
|
||||
Bun.stripANSI 212,992 chars long-ansi 227.65 µs/iter 234.50 µs
|
||||
(216.46 µs … 401.92 µs) 262.25 µs
|
||||
```
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
node bench/snippets/strip-ansi.mjs
|
||||
```
|
||||
|
||||
```txt
|
||||
cpu: Apple M3 Max
|
||||
runtime: node 24.6.0 (arm64-darwin)
|
||||
|
||||
benchmark avg (min … max) p75 / p99
|
||||
-------------------------------------------------------- ---------
|
||||
npm/strip-ansi 11 chars no-ansi 466.79 ns/iter 468.67 ns
|
||||
(454.08 ns … 570.67 ns) 543.67 ns
|
||||
|
||||
npm/strip-ansi 13 chars ansi 546.77 ns/iter 550.23 ns
|
||||
(532.74 ns … 651.08 ns) 590.35 ns
|
||||
|
||||
npm/strip-ansi 16,384 chars long-no-ansi 4.85 µs/iter 4.89 µs
|
||||
(4.71 µs … 5.00 µs) 4.98 µs
|
||||
|
||||
npm/strip-ansi 212,992 chars long-ansi 1.36 ms/iter 1.38 ms
|
||||
(1.27 ms … 1.73 ms) 1.49 ms
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `serialize` & `deserialize` in `bun:jsc`
|
||||
|
||||
To save a JavaScript value into an ArrayBuffer & back, use `serialize` and `deserialize` from the `"bun:jsc"` module.
|
||||
|
||||
```js
|
||||
import { serialize, deserialize } from "bun:jsc";
|
||||
|
||||
const buf = serialize({ foo: "bar" });
|
||||
const obj = deserialize(buf);
|
||||
console.log(obj); // => { foo: "bar" }
|
||||
```
|
||||
|
||||
Internally, [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) and [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) serialize and deserialize the same way. This exposes the underlying [HTML Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) to JavaScript as an ArrayBuffer.
|
||||
|
||||
---
|
||||
|
||||
## `estimateShallowMemoryUsageOf` in `bun:jsc`
|
||||
|
||||
The `estimateShallowMemoryUsageOf` function returns a best-effort estimate of the memory usage of an object in bytes, excluding the memory usage of properties or other objects it references. For accurate per-object memory usage, use `Bun.generateHeapSnapshot`.
|
||||
|
||||
```js
|
||||
import { estimateShallowMemoryUsageOf } from "bun:jsc";
|
||||
|
||||
const obj = { foo: "bar" };
|
||||
const usage = estimateShallowMemoryUsageOf(obj);
|
||||
console.log(usage); // => 16
|
||||
|
||||
const buffer = Buffer.alloc(1024 * 1024);
|
||||
estimateShallowMemoryUsageOf(buffer);
|
||||
// => 1048624
|
||||
|
||||
const req = new Request("https://bun.com");
|
||||
estimateShallowMemoryUsageOf(req);
|
||||
// => 167
|
||||
|
||||
const array = Array(1024).fill({ a: 1 });
|
||||
// Arrays are usually not stored contiguously in memory, so this will not return a useful value (which isn't a bug).
|
||||
estimateShallowMemoryUsageOf(array);
|
||||
// => 16
|
||||
```
|
||||
@@ -1,87 +1,104 @@
|
||||
---
|
||||
title: "Watch Mode"
|
||||
description: "Automatic reloading in Bun with --watch and --hot modes"
|
||||
---
|
||||
|
||||
Bun supports two kinds of automatic reloading via CLI flags:
|
||||
|
||||
- `--watch` mode, which hard restarts Bun's process when imported files change.
|
||||
- `--hot` mode, which soft reloads the code (without restarting the process) when imported files change.
|
||||
|
||||
---
|
||||
|
||||
## `--watch` mode
|
||||
|
||||
Watch mode can be used with `bun test` or when running TypeScript, JSX, and JavaScript files.
|
||||
|
||||
To run a file in `--watch` mode:
|
||||
|
||||
```bash
|
||||
$ bun --watch index.tsx
|
||||
```bash terminal icon="terminal"
|
||||
bun --watch index.tsx
|
||||
```
|
||||
|
||||
To run your tests in `--watch` mode:
|
||||
|
||||
```bash
|
||||
$ bun --watch test
|
||||
```bash terminal icon="terminal"
|
||||
bun --watch test
|
||||
```
|
||||
|
||||
In `--watch` mode, Bun keeps track of all imported files and watches them for changes. When a change is detected, Bun restarts the process, preserving the same set of CLI arguments and environment variables used in the initial run. If Bun crashes, `--watch` will attempt to automatically restart the process.
|
||||
|
||||
{% callout %}
|
||||
<Note>
|
||||
|
||||
**⚡️ Reloads are fast.** The filesystem watchers you're probably used to have several layers of libraries wrapping the native APIs or worse, rely on polling.
|
||||
|
||||
Instead, Bun uses operating system native filesystem watcher APIs like kqueue or inotify to detect changes to files. Bun also does a number of optimizations to enable it scale to larger projects (such as setting a high rlimit for file descriptors, statically allocated file path buffers, reuse file descriptors when possible, etc).
|
||||
|
||||
{% /callout %}
|
||||
</Note>
|
||||
|
||||
The following examples show Bun live-reloading a file as it is edited, with VSCode configured to save the file [on each keystroke](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save).
|
||||
|
||||
{% codetabs %}
|
||||
|
||||
```bash
|
||||
$ bun run --watch watchy.tsx
|
||||
```sh terminal icon="terminal"
|
||||
bun run --watch watchy.tsx
|
||||
```
|
||||
|
||||
```tsx#watchy.tsx
|
||||
```tsx title="watchy.tsx" icon="/icons/typescript.svg"
|
||||
import { serve } from "bun";
|
||||
|
||||
console.log("I restarted at:", Date.now());
|
||||
|
||||
serve({
|
||||
port: 4003,
|
||||
|
||||
fetch(request) {
|
||||
return new Response("Sup");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
In this example, Bun is
|
||||

|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
Running `bun test` in watch mode and `save-on-keypress` enabled:
|
||||
|
||||
```bash
|
||||
$ bun --watch test
|
||||
```bash terminal icon="terminal"
|
||||
bun --watch test
|
||||
```
|
||||
|
||||

|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
{% callout %}
|
||||
<Note>
|
||||
The **`--no-clear-screen`** flag is useful in scenarios where you don't want the terminal to clear, such as when
|
||||
running multiple `bun build --watch` commands simultaneously using tools like `concurrently`. Without this flag, the
|
||||
output of one instance could clear the output of others, potentially hiding errors from one instance beneath the
|
||||
output of another. The `--no-clear-screen` flag, similar to TypeScript's `--preserveWatchOutput`, prevents this issue.
|
||||
It can be used in combination with `--watch`, for example: `bun build --watch --no-clear-screen`.
|
||||
</Note>
|
||||
|
||||
The **`--no-clear-screen`** flag is useful in scenarios where you don’t want the terminal to clear, such as when running multiple `bun build --watch` commands simultaneously using tools like `concurrently`. Without this flag, the output of one instance could clear the output of others, potentially hiding errors from one instance beneath the output of another. The `--no-clear-screen` flag, similar to TypeScript’s `--preserveWatchOutput`, prevents this issue. It can be used in combination with `--watch`, for example: `bun build --watch --no-clear-screen`.
|
||||
|
||||
{% /callout %}
|
||||
---
|
||||
|
||||
## `--hot` mode
|
||||
|
||||
Use `bun --hot` to enable hot reloading when executing code with Bun. This is distinct from `--watch` mode in that Bun does not hard-restart the entire process. Instead, it detects code changes and updates its internal module cache with the new code.
|
||||
|
||||
**Note** — This is not the same as hot reloading in the browser! Many frameworks provide a "hot reloading" experience, where you can edit & save your frontend code (say, a React component) and see the changes reflected in the browser without refreshing the page. Bun's `--hot` is the server-side equivalent of this experience. To get hot reloading in the browser, use a framework like [Vite](https://vitejs.dev).
|
||||
<Note>
|
||||
This is not the same as hot reloading in the browser! Many frameworks provide a "hot reloading" experience, where you
|
||||
can edit & save your frontend code (say, a React component) and see the changes reflected in the browser without
|
||||
refreshing the page. Bun's `--hot` is the server-side equivalent of this experience. To get hot reloading in the
|
||||
browser, use a framework like [Vite](https://vite.dev).
|
||||
</Note>
|
||||
|
||||
```bash
|
||||
$ bun --hot server.ts
|
||||
```bash terminal icon="terminal"
|
||||
bun --hot server.ts
|
||||
```
|
||||
|
||||
Starting from the entrypoint (`server.ts` in the example above), Bun builds a registry of all imported source files (excluding those in `node_modules`) and watches them for changes. When a change is detected, Bun performs a "soft reload". All files are re-evaluated, but all global state (notably, the `globalThis` object) is persisted.
|
||||
|
||||
```ts#server.ts
|
||||
```ts title="server.ts" icon="/icons/typescript.svg"
|
||||
// make TypeScript happy
|
||||
declare global {
|
||||
var count: number;
|
||||
@@ -97,8 +114,11 @@ setInterval(function () {}, 1000000);
|
||||
|
||||
If you run this file with `bun --hot server.ts`, you'll see the reload count increment every time you save the file.
|
||||
|
||||
```bash
|
||||
$ bun --hot index.ts
|
||||
```bash terminal icon="terminal"
|
||||
bun --hot index.ts
|
||||
```
|
||||
|
||||
```txt
|
||||
Reloaded 1 times
|
||||
Reloaded 2 times
|
||||
Reloaded 3 times
|
||||
@@ -110,7 +130,7 @@ Traditional file watchers like `nodemon` restart the entire process, so HTTP ser
|
||||
|
||||
This makes it possible, for instance, to update your HTTP request handler without shutting down the server itself. When you save the file, your HTTP server will be reloaded with the updated code without the process being restarted. This results in seriously fast refresh speeds.
|
||||
|
||||
```ts#server.ts
|
||||
```ts title="server.ts" icon="/icons/typescript.svg"
|
||||
globalThis.count ??= 0;
|
||||
globalThis.count++;
|
||||
|
||||
@@ -122,16 +142,12 @@ Bun.serve({
|
||||
});
|
||||
```
|
||||
|
||||
<!-- The file above is simply exporting an object with a `fetch` handler defined. When this file is executed, Bun interprets this as an HTTP server and passes the exported object into `Bun.serve`. -->
|
||||
|
||||
<!-- {% image src="https://user-images.githubusercontent.com/709451/195477632-5fd8a73e-014d-4589-9ba2-e075ad9eb040.gif" alt="Bun vs Nodemon refresh speeds" caption="Bun on the left, Nodemon on the right." /%} -->
|
||||
|
||||
{% callout %}
|
||||
<Note>
|
||||
**Note** — In a future version of Bun, support for Vite's `import.meta.hot` is planned to enable better lifecycle management for hot reloading and to align with the ecosystem.
|
||||
|
||||
{% /callout %}
|
||||
</Note>
|
||||
|
||||
{% details summary="Implementation details" %}
|
||||
<Accordion title="Implementation details">
|
||||
|
||||
On hot reload, Bun:
|
||||
|
||||
@@ -142,4 +158,4 @@ On hot reload, Bun:
|
||||
|
||||
This implementation isn't particularly optimized. It re-transpiles files that haven't changed. It makes no attempt at incremental compilation. It's a starting point.
|
||||
|
||||
{% /details %}
|
||||
</Accordion>
|
||||
@@ -1,128 +0,0 @@
|
||||
Some Web APIs aren't relevant in the context of a server-first runtime like Bun, such as the [DOM API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API#html_dom_api_interfaces) or [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API). Many others, though, are broadly useful outside of the browser context; when possible, Bun implements these Web-standard APIs instead of introducing new APIs.
|
||||
|
||||
The following Web APIs are partially or completely supported.
|
||||
|
||||
{% table %}
|
||||
|
||||
---
|
||||
|
||||
- HTTP
|
||||
- [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch)
|
||||
[`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
|
||||
[`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
|
||||
[`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers)
|
||||
[`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
|
||||
[`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
|
||||
|
||||
---
|
||||
|
||||
- URLs
|
||||
- [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL)
|
||||
[`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
|
||||
|
||||
---
|
||||
|
||||
- Web Workers
|
||||
- [`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker)
|
||||
[`self.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope/postMessage)
|
||||
[`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone)
|
||||
[`MessagePort`](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort)
|
||||
[`MessageChannel`](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel)
|
||||
[`BroadcastChannel`](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel).
|
||||
|
||||
---
|
||||
|
||||
- Streams
|
||||
- [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
|
||||
[`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream)
|
||||
[`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream)
|
||||
[`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy)
|
||||
[`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) and associated classes
|
||||
|
||||
---
|
||||
|
||||
- Blob
|
||||
- [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
|
||||
|
||||
---
|
||||
|
||||
- WebSockets
|
||||
- [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
|
||||
|
||||
---
|
||||
|
||||
- Encoding and decoding
|
||||
- [`atob`](https://developer.mozilla.org/en-US/docs/Web/API/atob)
|
||||
[`btoa`](https://developer.mozilla.org/en-US/docs/Web/API/btoa)
|
||||
[`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder)
|
||||
[`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder)
|
||||
|
||||
---
|
||||
|
||||
- JSON
|
||||
- [`JSON`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON)
|
||||
|
||||
---
|
||||
|
||||
- Timeouts
|
||||
- [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout)
|
||||
[`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout)
|
||||
|
||||
---
|
||||
|
||||
- Intervals
|
||||
- [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval)
|
||||
[`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)
|
||||
|
||||
---
|
||||
|
||||
- Crypto
|
||||
- [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto)
|
||||
[`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto)
|
||||
[`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey)
|
||||
|
||||
---
|
||||
|
||||
- Debugging
|
||||
|
||||
- [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console)
|
||||
[`performance`](https://developer.mozilla.org/en-US/docs/Web/API/Performance)
|
||||
|
||||
---
|
||||
|
||||
- Microtasks
|
||||
- [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask)
|
||||
|
||||
---
|
||||
|
||||
- Errors
|
||||
- [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError)
|
||||
|
||||
---
|
||||
|
||||
- User interaction
|
||||
- [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert)
|
||||
[`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm)
|
||||
[`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) (intended for interactive CLIs)
|
||||
|
||||
<!-- - Blocking. Prints the alert message to terminal and awaits `[ENTER]` before proceeding. -->
|
||||
<!-- - Blocking. Prints confirmation message and awaits `[y/N]` input from user. Returns `true` if user entered `y` or `Y`, `false` otherwise.
|
||||
- Blocking. Prints prompt message and awaits user input. Returns the user input as a string. -->
|
||||
|
||||
---
|
||||
|
||||
- Realms
|
||||
- [`ShadowRealm`](https://github.com/tc39/proposal-shadowrealm)
|
||||
|
||||
---
|
||||
|
||||
- Events
|
||||
- [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)
|
||||
[`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event)
|
||||
[`ErrorEvent`](https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent)
|
||||
[`CloseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent)
|
||||
[`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent)
|
||||
|
||||
---
|
||||
|
||||
{% /table %}
|
||||
29
docs/runtime/web-apis.mdx
Normal file
29
docs/runtime/web-apis.mdx
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: "Web APIs"
|
||||
description: "Web-standard APIs supported by Bun for server-side JavaScript"
|
||||
mode: center
|
||||
---
|
||||
|
||||
Some Web APIs aren't relevant in the context of a server-first runtime like Bun, such as the [DOM API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API#html_dom_api_interfaces) or [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API). Many others, though, are broadly useful outside of the browser context; when possible, Bun implements these Web-standard APIs instead of introducing new APIs.
|
||||
|
||||
The following Web APIs are partially or completely supported.
|
||||
|
||||
| Category | APIs |
|
||||
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| HTTP | [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers), [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController), [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) |
|
||||
| URLs | [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL), [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) |
|
||||
| Web Workers | [`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker), [`self.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope/postMessage), [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone), [`MessagePort`](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort), [`MessageChannel`](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel), [`BroadcastChannel`](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel) |
|
||||
| Streams | [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream), [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream), [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream), [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy), [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) and associated classes |
|
||||
| Blob | [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) |
|
||||
| WebSockets | [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) |
|
||||
| Encoding and decoding | [`atob`](https://developer.mozilla.org/en-US/docs/Web/API/atob), [`btoa`](https://developer.mozilla.org/en-US/docs/Web/API/btoa), [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder), [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) |
|
||||
| JSON | [`JSON`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) |
|
||||
| Timeouts | [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout), [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout) |
|
||||
| Intervals | [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval), [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval) |
|
||||
| Crypto | [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto), [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto), [`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey) |
|
||||
| Debugging | [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console), [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/Performance) |
|
||||
| Microtasks | [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) |
|
||||
| Errors | [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError) |
|
||||
| User interaction | [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert), [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm), [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) (intended for interactive CLIs) |
|
||||
| Realms | [`ShadowRealm`](https://github.com/tc39/proposal-shadowrealm) |
|
||||
| Events | [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget), [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event), [`ErrorEvent`](https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent), [`CloseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent), [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent) |
|
||||
314
docs/runtime/workers.mdx
Normal file
314
docs/runtime/workers.mdx
Normal file
@@ -0,0 +1,314 @@
|
||||
---
|
||||
title: Workers
|
||||
description: Use Bun's Workers API to create and communicate with a new JavaScript instance running on a separate thread while sharing I/O resources with the main thread
|
||||
---
|
||||
|
||||
<Warning>
|
||||
The `Worker` API is still experimental (particularly for terminating workers). We are actively working on improving
|
||||
this.
|
||||
</Warning>
|
||||
|
||||
[`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker) lets you start and communicate with a new JavaScript instance running on a separate thread while sharing I/O resources with the main thread.
|
||||
|
||||
Bun implements a minimal version of the [Web Workers API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) with extensions that make it work better for server-side use cases. Like the rest of Bun, `Worker` in Bun support CommonJS, ES Modules, TypeScript, JSX, TSX and more out of the box. No extra build steps are necessary.
|
||||
|
||||
## Creating a `Worker`
|
||||
|
||||
Like in browsers, [`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker) is a global. Use it to create a new worker thread.
|
||||
|
||||
### From the main thread
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const worker = new Worker("./worker.ts");
|
||||
|
||||
worker.postMessage("hello");
|
||||
worker.onmessage = event => {
|
||||
console.log(event.data);
|
||||
};
|
||||
```
|
||||
|
||||
### Worker thread
|
||||
|
||||
```ts worker.ts icon="file-code"
|
||||
// prevents TS errors
|
||||
declare var self: Worker;
|
||||
|
||||
self.onmessage = (event: MessageEvent) => {
|
||||
console.log(event.data);
|
||||
postMessage("world");
|
||||
};
|
||||
```
|
||||
|
||||
To prevent TypeScript errors when using `self`, add this line to the top of your worker file.
|
||||
|
||||
```ts
|
||||
declare var self: Worker;
|
||||
```
|
||||
|
||||
You can use `import` and `export` syntax in your worker code. Unlike in browsers, there's no need to specify `{type: "module"}` to use ES Modules.
|
||||
|
||||
To simplify error handling, the initial script to load is resolved at the time `new Worker(url)` is called.
|
||||
|
||||
```js
|
||||
const worker = new Worker("/not-found.js");
|
||||
// throws an error immediately
|
||||
```
|
||||
|
||||
The specifier passed to `Worker` is resolved relative to the project root (like typing `bun ./path/to/file.js`).
|
||||
|
||||
### `preload` - load modules before the worker starts
|
||||
|
||||
You can pass an array of module specifiers to the `preload` option to load modules before the worker starts. This is useful when you want to ensure some code is always loaded before the application starts, like loading OpenTelemetry, Sentry, DataDog, etc.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const worker = new Worker("./worker.ts", {
|
||||
preload: ["./load-sentry.js"],
|
||||
});
|
||||
```
|
||||
|
||||
Like the `--preload` CLI argument, the `preload` option is processed before the worker starts.
|
||||
|
||||
You can also pass a single string to the `preload` option:
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const worker = new Worker("./worker.ts", {
|
||||
preload: "./load-sentry.js",
|
||||
});
|
||||
```
|
||||
|
||||
### `blob:` URLs
|
||||
|
||||
You can also pass a `blob:` URL to `Worker`. This is useful for creating workers from strings or other sources.
|
||||
|
||||
```js
|
||||
const blob = new Blob([`self.onmessage = (event: MessageEvent) => postMessage(event.data)`], {
|
||||
type: "application/typescript",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const worker = new Worker(url);
|
||||
```
|
||||
|
||||
Like the rest of Bun, workers created from `blob:` URLs support TypeScript, JSX, and other file types out of the box. You can communicate it should be loaded via typescript either via `type` or by passing a `filename` to the `File` constructor.
|
||||
|
||||
```ts
|
||||
const file = new File([`self.onmessage = (event: MessageEvent) => postMessage(event.data)`], "worker.ts");
|
||||
const url = URL.createObjectURL(file);
|
||||
const worker = new Worker(url);
|
||||
```
|
||||
|
||||
### `"open"`
|
||||
|
||||
The `"open"` event is emitted when a worker is created and ready to receive messages. This can be used to send an initial message to a worker once it's ready. (This event does not exist in browsers.)
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
|
||||
|
||||
worker.addEventListener("open", () => {
|
||||
console.log("worker is ready");
|
||||
});
|
||||
```
|
||||
|
||||
Messages are automatically enqueued until the worker is ready, so there is no need to wait for the `"open"` event to send messages.
|
||||
|
||||
## Messages with `postMessage`
|
||||
|
||||
To send messages, use [`worker.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage) and [`self.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). This leverages the [HTML Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).
|
||||
|
||||
### Performance optimizations
|
||||
|
||||
Bun includes optimized fast paths for `postMessage` to dramatically improve performance for common data types:
|
||||
|
||||
**String fast path** - When posting pure string values, Bun bypasses the structured clone algorithm entirely, achieving significant performance gains with no serialization overhead.
|
||||
|
||||
**Simple object fast path** - For plain objects containing only primitive values (strings, numbers, booleans, null, undefined), Bun uses an optimized serialization path that stores properties directly without full structured cloning.
|
||||
|
||||
The simple object fast path activates when the object:
|
||||
|
||||
- Is a plain object with no prototype chain modifications
|
||||
- Contains only enumerable, configurable data properties
|
||||
- Has no indexed properties or getter/setter methods
|
||||
- All property values are primitives or strings
|
||||
|
||||
With these fast paths, Bun's `postMessage` performs **2-241x faster** because the message length no longer has a meaningful impact on performance.
|
||||
|
||||
**Bun (with fast paths):**
|
||||
|
||||
```ts
|
||||
postMessage({ prop: 11 chars string, ...9 more props }) - 648ns
|
||||
postMessage({ prop: 14 KB string, ...9 more props }) - 719ns
|
||||
postMessage({ prop: 3 MB string, ...9 more props }) - 1.26µs
|
||||
```
|
||||
|
||||
**Node.js v24.6.0 (for comparison):**
|
||||
|
||||
```js
|
||||
postMessage({ prop: 11 chars string, ...9 more props }) - 1.19µs
|
||||
postMessage({ prop: 14 KB string, ...9 more props }) - 2.69µs
|
||||
postMessage({ prop: 3 MB string, ...9 more props }) - 304µs
|
||||
```
|
||||
|
||||
```js
|
||||
// String fast path - optimized
|
||||
postMessage("Hello, worker!");
|
||||
|
||||
// Simple object fast path - optimized
|
||||
postMessage({
|
||||
message: "Hello",
|
||||
count: 42,
|
||||
enabled: true,
|
||||
data: null,
|
||||
});
|
||||
|
||||
// Complex objects still work but use standard structured clone
|
||||
postMessage({
|
||||
nested: { deep: { object: true } },
|
||||
date: new Date(),
|
||||
buffer: new ArrayBuffer(8),
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
// On the worker thread, `postMessage` is automatically "routed" to the parent thread.
|
||||
postMessage({ hello: "world" });
|
||||
|
||||
// On the main thread
|
||||
worker.postMessage({ hello: "world" });
|
||||
```
|
||||
|
||||
To receive messages, use the [`message` event handler](https://developer.mozilla.org/en-US/docs/Web/API/Worker/message_event) on the worker and main thread.
|
||||
|
||||
```js
|
||||
// Worker thread:
|
||||
self.addEventListener("message", event => {
|
||||
console.log(event.data);
|
||||
});
|
||||
// or use the setter:
|
||||
// self.onmessage = fn
|
||||
|
||||
// if on the main thread
|
||||
worker.addEventListener("message", event => {
|
||||
console.log(event.data);
|
||||
});
|
||||
// or use the setter:
|
||||
// worker.onmessage = fn
|
||||
```
|
||||
|
||||
## Terminating a worker
|
||||
|
||||
A `Worker` instance terminates automatically once it's event loop has no work left to do. Attaching a `"message"` listener on the global or any `MessagePort`s will keep the event loop alive. To forcefully terminate a `Worker`, call `worker.terminate()`.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
|
||||
|
||||
// ...some time later
|
||||
worker.terminate();
|
||||
```
|
||||
|
||||
This will cause the worker's to exit as soon as possible.
|
||||
|
||||
### `process.exit()`
|
||||
|
||||
A worker can terminate itself with `process.exit()`. This does not terminate the main process. Like in Node.js, `process.on('beforeExit', callback)` and `process.on('exit', callback)` are emitted on the worker thread (and not on the main thread), and the exit code is passed to the `"close"` event.
|
||||
|
||||
### `"close"`
|
||||
|
||||
The `"close"` event is emitted when a worker has been terminated. It can take some time for the worker to actually terminate, so this event is emitted when the worker has been marked as terminated. The `CloseEvent` will contain the exit code passed to `process.exit()`, or 0 if closed for other reasons.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
|
||||
|
||||
worker.addEventListener("close", event => {
|
||||
console.log("worker is being closed");
|
||||
});
|
||||
```
|
||||
|
||||
This event does not exist in browsers.
|
||||
|
||||
## Managing lifetime
|
||||
|
||||
By default, an active `Worker` will keep the main (spawning) process alive, so async tasks like `setTimeout` and promises will keep the process alive. Attaching `message` listeners will also keep the `Worker` alive.
|
||||
|
||||
### `worker.unref()`
|
||||
|
||||
To stop a running worker from keeping the process alive, call `worker.unref()`. This decouples the lifetime of the worker to the lifetime of the main process, and is equivalent to what Node.js' `worker_threads` does.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
|
||||
worker.unref();
|
||||
```
|
||||
|
||||
Note: `worker.unref()` is not available in browsers.
|
||||
|
||||
### `worker.ref()`
|
||||
|
||||
To keep the process alive until the `Worker` terminates, call `worker.ref()`. A ref'd worker is the default behavior, and still needs something going on in the event loop (such as a `"message"` listener) for the worker to continue running.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
|
||||
worker.unref();
|
||||
// later...
|
||||
worker.ref();
|
||||
```
|
||||
|
||||
Alternatively, you can also pass an `options` object to `Worker`:
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const worker = new Worker(new URL("worker.ts", import.meta.url).href, {
|
||||
ref: false,
|
||||
});
|
||||
```
|
||||
|
||||
Note: `worker.ref()` is not available in browsers.
|
||||
|
||||
## Memory usage with `smol`
|
||||
|
||||
JavaScript instances can use a lot of memory. Bun's `Worker` supports a `smol` mode that reduces memory usage, at a cost of performance. To enable `smol` mode, pass `smol: true` to the `options` object in the `Worker` constructor.
|
||||
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const worker = new Worker("./i-am-smol.ts", {
|
||||
smol: true,
|
||||
});
|
||||
```
|
||||
|
||||
<Accordion title="What does `smol` mode actually do?">
|
||||
Setting `smol: true` sets `JSC::HeapSize` to be `Small` instead of the default `Large`.
|
||||
</Accordion>
|
||||
|
||||
## Environment Data
|
||||
|
||||
Share data between the main thread and workers using `setEnvironmentData()` and `getEnvironmentData()`.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
import { setEnvironmentData, getEnvironmentData } from "worker_threads";
|
||||
|
||||
// In main thread
|
||||
setEnvironmentData("config", { apiUrl: "https://api.example.com" });
|
||||
|
||||
// In worker
|
||||
const config = getEnvironmentData("config");
|
||||
console.log(config); // => { apiUrl: "https://api.example.com" }
|
||||
```
|
||||
|
||||
## Worker Events
|
||||
|
||||
Listen for worker creation events using `process.emit()`:
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
process.on("worker", worker => {
|
||||
console.log("New worker created:", worker.threadId);
|
||||
});
|
||||
```
|
||||
|
||||
## `Bun.isMainThread`
|
||||
|
||||
You can check if you're in the main thread by checking `Bun.isMainThread`.
|
||||
|
||||
```ts
|
||||
if (Bun.isMainThread) {
|
||||
console.log("I'm the main thread");
|
||||
} else {
|
||||
console.log("I'm in a worker");
|
||||
}
|
||||
```
|
||||
|
||||
This is useful for conditionally running code based on whether you're in the main thread or not.
|
||||
469
docs/runtime/yaml.mdx
Normal file
469
docs/runtime/yaml.mdx
Normal file
@@ -0,0 +1,469 @@
|
||||
---
|
||||
title: YAML
|
||||
description: Use Bun's built-in support for YAML files through both runtime APIs and bundler integration
|
||||
---
|
||||
|
||||
In Bun, YAML is a first-class citizen alongside JSON and TOML. You can:
|
||||
|
||||
- Parse YAML strings with `Bun.YAML.parse`
|
||||
- `import` & `require` YAML files as modules at runtime (including hot reloading & watch mode support)
|
||||
- `import` & `require` YAML files in frontend apps via bun's bundler
|
||||
|
||||
---
|
||||
|
||||
## Conformance
|
||||
|
||||
Bun's YAML parser currently passes over 90% of the official YAML test suite. While we're actively working on reaching 100% conformance, the current implementation covers the vast majority of real-world use cases. The parser is written in Zig for optimal performance and is continuously being improved.
|
||||
|
||||
---
|
||||
|
||||
## Runtime API
|
||||
|
||||
### `Bun.YAML.parse()`
|
||||
|
||||
Parse a YAML string into a JavaScript object.
|
||||
|
||||
```ts
|
||||
import { YAML } from "bun";
|
||||
const text = `
|
||||
name: John Doe
|
||||
age: 30
|
||||
email: john@example.com
|
||||
hobbies:
|
||||
- reading
|
||||
- coding
|
||||
- hiking
|
||||
`;
|
||||
|
||||
const data = YAML.parse(text);
|
||||
console.log(data);
|
||||
// {
|
||||
// name: "John Doe",
|
||||
// age: 30,
|
||||
// email: "john@example.com",
|
||||
// hobbies: ["reading", "coding", "hiking"]
|
||||
// }
|
||||
```
|
||||
|
||||
#### Multi-document YAML
|
||||
|
||||
When parsing YAML with multiple documents (separated by `---`), `Bun.YAML.parse()` returns an array:
|
||||
|
||||
```ts
|
||||
const multiDoc = `
|
||||
---
|
||||
name: Document 1
|
||||
---
|
||||
name: Document 2
|
||||
---
|
||||
name: Document 3
|
||||
`;
|
||||
|
||||
const docs = Bun.YAML.parse(multiDoc);
|
||||
console.log(docs);
|
||||
// [
|
||||
// { name: "Document 1" },
|
||||
// { name: "Document 2" },
|
||||
// { name: "Document 3" }
|
||||
// ]
|
||||
```
|
||||
|
||||
#### Supported YAML Features
|
||||
|
||||
Bun's YAML parser supports the full YAML 1.2 specification, including:
|
||||
|
||||
- **Scalars**: strings, numbers, booleans, null values
|
||||
- **Collections**: sequences (arrays) and mappings (objects)
|
||||
- **Anchors and Aliases**: reusable nodes with `&` and `*`
|
||||
- **Tags**: type hints like `!!str`, `!!int`, `!!float`, `!!bool`, `!!null`
|
||||
- **Multi-line strings**: literal (`|`) and folded (`>`) scalars
|
||||
- **Comments**: using `#`
|
||||
- **Directives**: `%YAML` and `%TAG`
|
||||
|
||||
```ts
|
||||
const yaml = `
|
||||
# Employee record
|
||||
employee: &emp
|
||||
name: Jane Smith
|
||||
department: Engineering
|
||||
skills:
|
||||
- JavaScript
|
||||
- TypeScript
|
||||
- React
|
||||
|
||||
manager: *emp # Reference to employee
|
||||
|
||||
config: !!str 123 # Explicit string type
|
||||
|
||||
description: |
|
||||
This is a multi-line
|
||||
literal string that preserves
|
||||
line breaks and spacing.
|
||||
|
||||
summary: >
|
||||
This is a folded string
|
||||
that joins lines with spaces
|
||||
unless there are blank lines.
|
||||
`;
|
||||
|
||||
const data = Bun.YAML.parse(yaml);
|
||||
```
|
||||
|
||||
#### Error Handling
|
||||
|
||||
`Bun.YAML.parse()` throws a `SyntaxError` if the YAML is invalid:
|
||||
|
||||
```ts
|
||||
try {
|
||||
Bun.YAML.parse("invalid: yaml: content:");
|
||||
} catch (error) {
|
||||
console.error("Failed to parse YAML:", error.message);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Import
|
||||
|
||||
### ES Modules
|
||||
|
||||
You can import YAML files directly as ES modules. The YAML content is parsed and made available as both default and named exports:
|
||||
|
||||
```yaml config.yaml
|
||||
database:
|
||||
host: localhost
|
||||
port: 5432
|
||||
name: myapp
|
||||
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
|
||||
features:
|
||||
auth: true
|
||||
rateLimit: true
|
||||
analytics: false
|
||||
```
|
||||
|
||||
#### Default Import
|
||||
|
||||
```ts app.ts icon="/icons/typescript.svg"
|
||||
import config from "./config.yaml";
|
||||
|
||||
console.log(config.database.host); // "localhost"
|
||||
console.log(config.redis.port); // 6379
|
||||
```
|
||||
|
||||
#### Named Imports
|
||||
|
||||
You can destructure top-level YAML properties as named imports:
|
||||
|
||||
```ts app.ts icon="/icons/typescript.svg"
|
||||
import { database, redis, features } from "./config.yaml";
|
||||
|
||||
console.log(database.host); // "localhost"
|
||||
console.log(redis.port); // 6379
|
||||
console.log(features.auth); // true
|
||||
```
|
||||
|
||||
Or combine both:
|
||||
|
||||
```ts app.ts icon="/icons/typescript.svg"
|
||||
import config, { database, features } from "./config.yaml";
|
||||
|
||||
// Use the full config object
|
||||
console.log(config);
|
||||
|
||||
// Or use specific parts
|
||||
if (features.rateLimit) {
|
||||
setupRateLimiting(database);
|
||||
}
|
||||
```
|
||||
|
||||
### CommonJS
|
||||
|
||||
YAML files can also be required in CommonJS:
|
||||
|
||||
```ts app.ts icon="/icons/typescript.svg"
|
||||
const config = require("./config.yaml");
|
||||
console.log(config.database.name); // "myapp"
|
||||
|
||||
// Destructuring also works
|
||||
const { database, redis } = require("./config.yaml");
|
||||
console.log(database.port); // 5432
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hot Reloading with YAML
|
||||
|
||||
One of the most powerful features of Bun's YAML support is hot reloading. When you run your application with `bun --hot`, changes to YAML files are automatically detected and reloaded without closing connections
|
||||
|
||||
### Configuration Hot Reloading
|
||||
|
||||
```yaml config.yaml
|
||||
server:
|
||||
port: 3000
|
||||
host: localhost
|
||||
|
||||
features:
|
||||
debug: true
|
||||
verbose: false
|
||||
```
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
import { server, features } from "./config.yaml";
|
||||
|
||||
console.log(`Starting server on ${server.host}:${server.port}`);
|
||||
|
||||
if (features.debug) {
|
||||
console.log("Debug mode enabled");
|
||||
}
|
||||
|
||||
// Your server code here
|
||||
Bun.serve({
|
||||
port: server.port,
|
||||
hostname: server.host,
|
||||
fetch(req) {
|
||||
if (features.verbose) {
|
||||
console.log(`${req.method} ${req.url}`);
|
||||
}
|
||||
return new Response("Hello World");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Run with hot reloading:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun --hot server.ts
|
||||
```
|
||||
|
||||
Now when you modify `config.yaml`, the changes are immediately reflected in your running application. This is perfect for:
|
||||
|
||||
- Adjusting configuration during development
|
||||
- Testing different settings without restarts
|
||||
- Live debugging with configuration changes
|
||||
- Feature flag toggling
|
||||
|
||||
---
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### Environment-Based Configuration
|
||||
|
||||
YAML excels at managing configuration across different environments:
|
||||
|
||||
```yaml config.yaml
|
||||
defaults: &defaults
|
||||
timeout: 5000
|
||||
retries: 3
|
||||
cache:
|
||||
enabled: true
|
||||
ttl: 3600
|
||||
|
||||
development:
|
||||
<<: *defaults
|
||||
api:
|
||||
url: http://localhost:4000
|
||||
key: dev_key_12345
|
||||
logging:
|
||||
level: debug
|
||||
pretty: true
|
||||
|
||||
staging:
|
||||
<<: *defaults
|
||||
api:
|
||||
url: https://staging-api.example.com
|
||||
key: ${STAGING_API_KEY}
|
||||
logging:
|
||||
level: info
|
||||
pretty: false
|
||||
|
||||
production:
|
||||
<<: *defaults
|
||||
api:
|
||||
url: https://api.example.com
|
||||
key: ${PROD_API_KEY}
|
||||
cache:
|
||||
enabled: true
|
||||
ttl: 86400
|
||||
logging:
|
||||
level: error
|
||||
pretty: false
|
||||
```
|
||||
|
||||
```ts app.ts icon="/icons/typescript.svg"
|
||||
import configs from "./config.yaml";
|
||||
|
||||
const env = process.env.NODE_ENV || "development";
|
||||
const config = configs[env];
|
||||
|
||||
// Environment variables in YAML values can be interpolated
|
||||
function interpolateEnvVars(obj: any): any {
|
||||
if (typeof obj === "string") {
|
||||
return obj.replace(/\${(\w+)}/g, (_, key) => process.env[key] || "");
|
||||
}
|
||||
if (typeof obj === "object") {
|
||||
for (const key in obj) {
|
||||
obj[key] = interpolateEnvVars(obj[key]);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
export default interpolateEnvVars(config);
|
||||
```
|
||||
|
||||
### Feature Flags Configuration
|
||||
|
||||
```yaml features.yaml
|
||||
features:
|
||||
newDashboard:
|
||||
enabled: true
|
||||
rolloutPercentage: 50
|
||||
allowedUsers:
|
||||
- admin@example.com
|
||||
- beta@example.com
|
||||
|
||||
experimentalAPI:
|
||||
enabled: false
|
||||
endpoints:
|
||||
- /api/v2/experimental
|
||||
- /api/v2/beta
|
||||
|
||||
darkMode:
|
||||
enabled: true
|
||||
default: auto # auto, light, dark
|
||||
```
|
||||
|
||||
```ts feature-flags.ts icon="/icons/typescript.svg"
|
||||
import { features } from "./features.yaml";
|
||||
|
||||
export function isFeatureEnabled(featureName: string, userEmail?: string): boolean {
|
||||
const feature = features[featureName];
|
||||
|
||||
if (!feature?.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check rollout percentage
|
||||
if (feature.rolloutPercentage < 100) {
|
||||
const hash = hashCode(userEmail || "anonymous");
|
||||
if (hash % 100 >= feature.rolloutPercentage) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check allowed users
|
||||
if (feature.allowedUsers && userEmail) {
|
||||
return feature.allowedUsers.includes(userEmail);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use with hot reloading to toggle features in real-time
|
||||
if (isFeatureEnabled("newDashboard", user.email)) {
|
||||
renderNewDashboard();
|
||||
} else {
|
||||
renderLegacyDashboard();
|
||||
}
|
||||
```
|
||||
|
||||
### Database Configuration
|
||||
|
||||
```yaml database.yaml icon="yaml"
|
||||
connections:
|
||||
primary:
|
||||
type: postgres
|
||||
host: ${DB_HOST:-localhost}
|
||||
port: ${DB_PORT:-5432}
|
||||
database: ${DB_NAME:-myapp}
|
||||
username: ${DB_USER:-postgres}
|
||||
password: ${DB_PASS}
|
||||
pool:
|
||||
min: 2
|
||||
max: 10
|
||||
idleTimeout: 30000
|
||||
|
||||
cache:
|
||||
type: redis
|
||||
host: ${REDIS_HOST:-localhost}
|
||||
port: ${REDIS_PORT:-6379}
|
||||
password: ${REDIS_PASS}
|
||||
db: 0
|
||||
|
||||
analytics:
|
||||
type: clickhouse
|
||||
host: ${ANALYTICS_HOST:-localhost}
|
||||
port: 8123
|
||||
database: analytics
|
||||
|
||||
migrations:
|
||||
autoRun: ${AUTO_MIGRATE:-false}
|
||||
directory: ./migrations
|
||||
|
||||
seeds:
|
||||
enabled: ${SEED_DB:-false}
|
||||
directory: ./seeds
|
||||
```
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
import { connections, migrations } from "./database.yaml";
|
||||
import { createConnection } from "./database-driver";
|
||||
|
||||
// Parse environment variables with defaults
|
||||
function parseConfig(config: any) {
|
||||
return JSON.parse(
|
||||
JSON.stringify(config).replace(
|
||||
/\${([^:-]+)(?::([^}]+))?}/g,
|
||||
(_, key, defaultValue) => process.env[key] || defaultValue || "",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const dbConfig = parseConfig(connections);
|
||||
|
||||
export const db = await createConnection(dbConfig.primary);
|
||||
export const cache = await createConnection(dbConfig.cache);
|
||||
export const analytics = await createConnection(dbConfig.analytics);
|
||||
|
||||
// Auto-run migrations if configured
|
||||
if (parseConfig(migrations).autoRun === "true") {
|
||||
await runMigrations(db, migrations.directory);
|
||||
}
|
||||
```
|
||||
|
||||
### Bundler Integration
|
||||
|
||||
When you import YAML files in your application and bundle it with Bun, the YAML is parsed at build time and included as a JavaScript module:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build app.ts --outdir=dist
|
||||
```
|
||||
|
||||
This means:
|
||||
|
||||
- Zero runtime YAML parsing overhead in production
|
||||
- Smaller bundle sizes
|
||||
- Tree-shaking support for unused configuration (named imports)
|
||||
|
||||
### Dynamic Imports
|
||||
|
||||
YAML files can be dynamically imported, useful for loading configuration on demand:
|
||||
|
||||
```ts Load configuration based on environment
|
||||
const env = process.env.NODE_ENV || "development";
|
||||
const config = await import(`./configs/${env}.yaml`);
|
||||
|
||||
// Load user-specific settings
|
||||
async function loadUserSettings(userId: string) {
|
||||
try {
|
||||
const settings = await import(`./users/${userId}/settings.yaml`);
|
||||
return settings.default;
|
||||
} catch {
|
||||
return await import("./users/default-settings.yaml");
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user