diff options
55 files changed, 8660 insertions, 14 deletions
diff --git a/docs/api/console.md b/docs/api/console.md new file mode 100644 index 000000000..49b8a1b79 --- /dev/null +++ b/docs/api/console.md @@ -0,0 +1,38 @@ +{% callout %} +**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. +{% /callout %} + +In Bun, the `console` object can be used as an `AsyncIterable` to sequentially read lines from `process.stdin`. + +```ts +for await (const line of console) { + console.log(line); +} +``` + +This is useful for implementing interactive programs, like the following addition calculator. + +```ts#adder.ts +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 +$ bun adder.ts +Let's add some numbers! +Count: 0 +> 5 +Count: 5 +> 5 +Count: 10 +> 5 +Count: 15 +``` diff --git a/docs/api/dns.md b/docs/api/dns.md new file mode 100644 index 000000000..d3d06527c --- /dev/null +++ b/docs/api/dns.md @@ -0,0 +1,41 @@ +Bun implements the `node:dns` module. + +```ts +import * as dns from "node:dns"; + +const addrs = await dns.promises.resolve4("bun.sh", { ttl: true }); +console.log(addrs); +// => [{ address: "172.67.161.226", family: 4, ttl: 0 }, ...] +``` + +<!-- +## `Bun.dns` - lookup a domain +`Bun.dns` includes utilities to make DNS requests, similar to `node:dns`. As of Bun v0.5.0, the only implemented function is `dns.lookup`, though more will be implemented soon. +You can lookup the IP addresses of a hostname by using `dns.lookup`. +```ts +import { dns } from "bun"; +const [{ address }] = await dns.lookup("example.com"); +console.log(address); // "93.184.216.34" +``` +If you need to limit IP addresses to either IPv4 or IPv6, you can specify the `family` as an option. +```ts +import { dns } from "bun"; +const [{ address }] = await dns.lookup("example.com", { family: 6 }); +console.log(address); // "2606:2800:220:1:248:1893:25c8:1946" +``` +Bun supports three backends for DNS resolution: +- `c-ares` - This is the default on Linux, and it uses the [c-ares](https://c-ares.org/) library to perform DNS resolution. +- `system` - Uses the system's non-blocking DNS resolver, if available. Otherwise, falls back to `getaddrinfo`. This is the default on macOS, and the same as `getaddrinfo` on Linux. +- `getaddrinfo` - Uses the POSIX standard `getaddrinfo` function, which may cause performance issues under concurrent load. + +You can choose a particular backend by specifying `backend` as an option. +```ts +import { dns } from "bun"; +const [{ address, ttl }] = await dns.lookup("example.com", { + backend: "c-ares" +}); +console.log(address); // "93.184.216.34" +console.log(ttl); // 21237 +``` +Note: the `ttl` property is only accurate when the `backend` is c-ares. Otherwise, `ttl` will be `0`. +This was added in Bun v0.5.0. --> diff --git a/docs/api/ffi.md b/docs/api/ffi.md new file mode 100644 index 000000000..13b50f9c7 --- /dev/null +++ b/docs/api/ffi.md @@ -0,0 +1,516 @@ +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). + +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 +pub export fn add(a: i32, b: i32) i32 { + return a + b; +} +``` + +To compile: + +```bash +$ 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 path = `libadd.${suffix}`; + +const lib = dlopen(path, { + add: { + args: [FFIType.i32, FFIType.i32], + returns: FFIType.i32, + }, +}); + +lib.symbols.add(1, 2); +``` + +### Rust + +```rust +// add.rs +#[no_mangle] +pub extern "C" fn add(a: isize, b: isize) -> isize { + a + b +} +``` + +To compile: + +```bash +$ rustc --crate-type cdylib add.rs +``` + +## FFI types + +The following `FFIType` values are supported. + +| `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` | | + +## Strings + +JavaScript strings and C-like strings are different, and that complicates using strings with native libraries. + +{% details summary="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 + +{% /details %} + +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 + +{% callout %} + +**Note** — Async functions are not yet supported. + +{% /callout %} + +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. + +{% callout %} + +**⚡️ 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); +``` + +{% /callout %} + +## Pointers + +Bun represents [pointers](<https://en.wikipedia.org/wiki/Pointer_(computer_programming)>) as a `number` in JavaScript. + +{% details summary="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` +{% /details %} + +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 + +{% callout %} +**Note** — Available in Bun v0.1.8 and later. +{% /callout %} + +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`. + +{% details summary="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, +); +``` + +{% /details %} + +### 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); +``` diff --git a/docs/api/file-io.md b/docs/api/file-io.md new file mode 100644 index 000000000..a79f6ad80 --- /dev/null +++ b/docs/api/file-io.md @@ -0,0 +1,256 @@ +{% callout %} +**Note** — Bun also provides an [almost complete](/docs/runtime/nodejs) implementation of the Node.js `fs` module, documented [here](https://nodejs.org/api/fs.html). This page only documents Bun-native APIs. +{% /callout %} + +Bun provides a set of optimized APIs for reading and writing files. + +## Reading files + +`Bun.file(path): BunFile` + +Create a `BunFile` instance with the `Bun.file(path)` function. A `BunFile` represents 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.stream(); // contents as ReadableStream +await foo.arrayBuffer(); // contents as ArrayBuffer +``` + +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" +``` + +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; // => "text/plain;charset=utf-8" +``` + +For convenience, Bun exposes `stdin`, `stdout` and `stderr` as instances of `BunFile`. + +```ts +Bun.stdin; // readonly +Bun.stdout; +Bun.stderr; +``` + +## Writing files + +`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. + +{% details summary="See syscalls" %} + +{% table %} + +- 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 + +{% /table %} + +{% /details %} + +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: + +```js +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 an HTTP response to disk: + +```ts +const response = await fetch("https://bun.sh"); +await Bun.write("index.html", response); +``` + +## Benchmarks + +The following is a 3-line implementation of the Linux `cat` command. + +```ts#cat.ts +// 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 +$ bun ./cat.ts ./path-to-file +``` + +It runs 2x faster than GNU `cat` for large files on Linux. + +{% image src="/images/cat.jpg" /%} + +## Reference + +```ts +interface Bun { + stdin: BunFile; + stdout: BunFile; + stderr: BunFile; + + file(path: string | number | URL, options?: { type?: string }): BunFile; + + write( + destination: string | number | FileBlob | URL, + input: + | string + | Blob + | ArrayBuffer + | SharedArrayBuffer + | TypedArray + | Response, + ): Promise<number>; +} + +interface BunFile { + readonly size: number; + readonly type: string; + + text(): Promise<string>; + stream(): Promise<ReadableStream>; + arrayBuffer(): Promise<ArrayBuffer>; + json(): Promise<any>; +} +``` diff --git a/docs/api/file-system-router.md b/docs/api/file-system-router.md new file mode 100644 index 000000000..e55376446 --- /dev/null +++ b/docs/api/file-system-router.md @@ -0,0 +1,111 @@ +Bun provides a fast API for resolving routes against file-system paths. This API is primarily intended by 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 +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; + }); + + 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 + } +} +``` diff --git a/docs/api/file.md b/docs/api/file.md new file mode 100644 index 000000000..16e535901 --- /dev/null +++ b/docs/api/file.md @@ -0,0 +1,19 @@ +Bun.js has fast paths for common use cases that make Web APIs live up to the performance demands of servers and CLIs. + +`Bun.file(path)` returns a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) that represents a lazily-loaded file. + +When you pass a file blob to `Bun.write`, Bun automatically uses a faster system call: + +```js +const blob = Bun.file("input.txt"); +await Bun.write("output.txt", blob); +``` + +On Linux, this uses the [`copy_file_range`](https://man7.org/linux/man-pages/man2/copy_file_range.2.html) syscall and on macOS, this becomes `clonefile` (or [`fcopyfile`](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/copyfile.3.html)). + +`Bun.write` also supports [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects. It automatically converts to a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob). + +```js +// Eventually, this will stream the response to disk but today it buffers +await Bun.write("index.html", await fetch("https://example.com")); +``` diff --git a/docs/api/globals.md b/docs/api/globals.md new file mode 100644 index 000000000..d966c1861 --- /dev/null +++ b/docs/api/globals.md @@ -0,0 +1,381 @@ +Bun implements the following globals. + +{% table %} + +- 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`](/docs/runtime/nodejs#node_buffer) + +--- + +- [`Bun`](https://nodejs.org/api/buffer.html#class-buffer) +- 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 +- + +--- + +- `BuildError` +- 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`](/docs/runtime/nodejs#node_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`](/docs/api/html-rewriter) +- Cloudflare +- + +--- + +- [`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`](/docs/runtime/nodejs#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 +- + +--- + +- `ResolveError` +- 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 +- + +{% /table %} diff --git a/docs/api/html-rewriter.md b/docs/api/html-rewriter.md new file mode 100644 index 000000000..f67577b13 --- /dev/null +++ b/docs/api/html-rewriter.md @@ -0,0 +1,31 @@ +Bun provides a fast native implementation of the `HTMLRewriter` pattern developed by Cloudflare. It provides a convenient, `EventListener`-like API for traversing and transforming HTML documents. + +```ts +const rewriter = new HTMLRewriter(); + +rewriter.on("*", { + element(el) { + console.log(el.tagName); // "body" | "div" | ... + }, +}); +``` + +To parse and/or transform the HTML: + +```ts#rewriter.ts +rewriter.transform( + new Response(` +<!DOCTYPE html> +<html> +<!-- comment --> +<head> + <title>My First HTML Page</title> +</head> +<body> + <h1>My First Heading</h1> + <p>My first paragraph.</p> +</body> +`)); +``` + +View the full documentation on the [Cloudflare website](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/). diff --git a/docs/api/http.md b/docs/api/http.md new file mode 100644 index 000000000..d05691556 --- /dev/null +++ b/docs/api/http.md @@ -0,0 +1,257 @@ +{% callout %} +**Note** — Bun provides an [almost complete](/docs/runtime/nodejs#node_http) implementation of the Node.js [`http`](https://nodejs.org/api/http.html) and [`https`](https://nodejs.org/api/https.html) modules. This page only documents Bun-native APIs. +{% /callout %} + +## Start a server + +`Bun.serve(options) => Server` + +Start an HTTP server in Bun with `Bun.serve`. + +```ts +Bun.serve({ + fetch(req) { + return new Response(`Bun!`); + }, +}); +``` + +The `fetch` handler handles incoming requests. 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>`. + +```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!`); + }, +}); +``` + +To configure which port and hostname the server will listen on: + +```ts +Bun.serve({ + port: 8080, // defaults to $PORT, then 3000 + hostname: "mydomain.com", // defaults to "0.0.0.0" + fetch(req) { + return new Response(`404!`); + }, +}); +``` + +## Error handling + +To activate development mode, set `development: true`. By default, development mode is _enabled_ unless `NODE_ENV` is `production`. + +```ts +Bun.serve({ + development: true, + fetch(req) { + throw new Error("woops!"); + }, +}); +``` + +In development mode, Bun will surface errors in-browser with a built-in error page. + +{% image src="/images/exception_page.png" caption="Bun's built-in 500 page" /%} + +To handle server-side errors, implement an `error` handler. This function should return a `Response` to served to the client when an error occurs. This response will supercede Bun's default error page in `development` mode. + +```ts +Bun.serve({ + fetch(req) { + throw new Error("woops!"); + }, + error(error: Error) { + return new Response(`<pre>${error}\n${error.stack}</pre>`, { + headers: { + "Content-Type": "text/html", + }, + }); + }, +}); +``` + +{% callout %} +**Note** — Full debugger support is planned. +{% /callout %} + +The call to `Bun.serve` returns a `Server` object. To stop the server, call the `.stop()` method. + +```ts +const server = Bun.serve({ + fetch() { + return new Response("Bun!"); + }, +}); + +server.stop(); +``` + +## TLS + +Bun supports TLS out of the box, powered by [OpenSSL](https://www.openssl.org/). Enable TLS by passing in a value for `keyFile` and `certFile`; both are required to enable TLS. If needed, supply a `passphrase` to decrypt the `keyFile`. + +```ts +Bun.serve({ + fetch(req) { + return new Response("Hello!!!"); + }, + keyFile: "./key.pem", // path to TLS key + certFile: "./cert.pem", // path to TLS cert + passphrase: "super-secret", // optional passphrase +}); +``` + +The root CA and Diffie-Helman parameters can be configured as well. + +```ts +Bun.serve({ + fetch(req) { + return new Response("Hello!!!"); + }, + keyFile: "./key.pem", // path to TLS key + certFile: "./cert.pem", // path to TLS cert + caFile: "./ca.pem", // path to root CA certificate + dhParamsFile: "./dhparams.pem", // Diffie Helman parameters +}); +``` + +## Hot reloading + +Thus far, the examples on this page have used the explicit `Bun.serve` API. Bun also supports an alternate syntax. + +```ts#server.ts +export default { + fetch(req) { + return new Response(`Bun!`); + }, +}; +``` + +Instead of passing the server options into `Bun.serve`, export it. This file can be executed as-is; when Bun runs a file with a `default` export containing a `fetch` handler, it passes it into `Bun.serve` under the hood. + +This syntax has one major advantage: it is hot-reloadable out of the box. When any source file is changed, Bun will reload the server with the updated code _without restarting the process_. This makes hot reloads nearly instantaneous. Use the `--hot` flag when starting the server to enable hot reloading. + +```bash +$ bun --hot server.ts +``` + +It's possible to configure hot reloading while using the explicit `Bun.serve` API; for details refer to [Runtime > Hot reloading](/docs/runtime/hot). + +## Streaming files + +To stream a file, return a `Response` object with a `BunFile` object as the body. + +```ts +import { serve, file } from "bun"; + +serve({ + fetch(req) { + return new Response(Bun.file("./hello.txt")); + }, +}); +``` + +{% callout %} +⚡️ **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. +{% /callout %} + +**[v0.3.0+]** 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)); + }, +}); +``` + +## Benchmarks + +Below are Bun and Node.js implementations of a simple HTTP server that responds `Bun!` to each incoming `Request`. + +{% codetabs %} + +```ts#Bun +Bun.serve({ + fetch(req: Request) { + return new Response(`Bun!`); + }, + port: 3000, +}); +``` + +```ts#Node +require("http") + .createServer((req, res) => res.end("Bun!")) + .listen(8080); +``` + +{% /codetabs %} +The `Bun.serve` server can handle roughly 2.5x more requests per second than Node.js on Linux. + +{% table %} + +- Runtime +- Requests per second + +--- + +- Node 16 +- ~64,000 + +--- + +- Bun +- ~160,000 + +{% /table %} + +{% image width="499" alt="image" src="https://user-images.githubusercontent.com/709451/162389032-fc302444-9d03-46be-ba87-c12bd8ce89a0.png" /%} + +## Reference + +{% details summary="See TypeScript definitions" %} + +```ts +interface Bun { + serve(options: { + fetch: (req: Request, server: Server) => Response | Promise<Response>; + hostname?: string; + port?: number; + development?: boolean; + error?: (error: Error) => Response | Promise<Response>; + keyFile?: string; + certFile?: string; + caFile?: string; + dhParamsFile?: string; + passphrase?: string; + maxRequestBodySize?: number; + lowMemoryMode?: boolean; + }): Server; +} + +interface Server { + development: boolean; + hostname: string; + port: number; + pendingRequests: number; + stop(): void; +} +``` + +{% /details %} diff --git a/docs/api/node-api.md b/docs/api/node-api.md new file mode 100644 index 000000000..09cc91175 --- /dev/null +++ b/docs/api/node-api.md @@ -0,0 +1,16 @@ +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"); +``` + +Bun polyfills the [`detect-libc`](https://npmjs.com/package/detect-libc) package, which is used by many Node-API modules to detect which `.node` binding to `require`. diff --git a/docs/api/spawn.md b/docs/api/spawn.md new file mode 100644 index 000000000..876a0577d --- /dev/null +++ b/docs/api/spawn.md @@ -0,0 +1,336 @@ +Spawn child processes with `Bun.spawn` or `Bun.spawnSync`. + +## Spawn a process + +Provide a command as an array of strings. The result of `Bun.spawn()` is a `Bun.Subprocess` object. + +```ts +Bun.spawn(["echo", "hello"]); +``` + +The second argument to `Bun.spawn` is a parameters object that can be used ton configure the subprocess. + +```ts +const proc = Bun.spawn(["echo", "hello"], { + cwd: "./path/to/subdir", // specify a working direcory + 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 new Response(proc.stdout).text(); +console.log(text); // "const input = "hello world".repeat(400); ..." +``` + +{% table %} + +--- + +- `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. + +--- + +- `number` +- Read from the file with a given file descriptor. + +{% /table %} + +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(); +``` + +## 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(["echo", "hello"]); +const text = await new Response(proc.stdout).text(); +console.log(text); // => "hello" +``` + +Configure the output stream by passing one of the following values to `stdout/stderr`: + +{% table %} + +--- + +- `"pipe"` +- **Default for `stdout`.** Pipe the output to a `ReadableStream` on the returned `Subprocess` object. + +--- + +- `"inherit"` +- **Default for `stderr`.** Inherit from the parent process. + +--- + +- `Bun.file()` +- Write to the specified file. + +--- + +- `null` +- Write to `/dev/null`. + +--- + +- `number` +- Write to the file with the given file descriptor. + +{% /table %} + +## Exit handling + +Use the `onExit` callback to listen for the process exiting or being killed. + +```ts +const proc = Bun.spawn(["echo", "hello"], { + onExit(proc, exitCode, signalCode, error) { + // exit handler + }, +}); +``` + +For convenience, the `exited` property is a `Promise` that resolves when the process exits. + +```ts +const proc = Bun.spawn(["echo", "hello"]); + +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 +const proc = Bun.spawn(["echo", "hello"]); +proc.kill(); +proc.killed; // true + +proc.kill(); // specify an exit code +``` + +The parent `bun` process will not terminate until all child processes have exited. Use `proc.unref()` to detach the child process from the parent. + +``` +const proc = Bun.spawn(["echo", "hello"]); +proc.unref(); +``` + +## Blocking API + +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 + +{%callout%} +⚡️ Under the hood, `Bun.spawn` and `Bun.spawnSync` use [`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html). +{%/callout%} + +Bun's `spawnSync` spawns processes 60% faster than the Node.js `child_process` module. + +```bash +$ bun spawn.mjs +cpu: Apple M1 Max +runtime: bun 0.2.0 (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 +$ node spawn.node.mjs +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 + +```ts +interface Bun { + spawn(command: string[], options?: SpawnOptions): Subprocess; + spawnSync(command: string[], options?: SpawnOptions): SyncSubprocess; +} + +interface SpawnOptions { + cwd?: string; + env?: Record<string, string>; + stdin?: + | "pipe" + | "inherit" + | "ignore" + | ReadableStream + | BunFile + | Blob + | Response + | Request + | number + | null; + stdout?: + | "pipe" + | "inherit" + | "ignore" + | BunFile + | TypedArray + | DataView + | null; + stderr?: + | "pipe" + | "inherit" + | "ignore" + | BunFile + | TypedArray + | DataView + | null; + onExit?: ( + proc: Subprocess, + exitCode: number | null, + signalCode: string | null, + error: Error | null, + ) => void; +} + +interface Subprocess { + readonly pid: number; + readonly stdin?: number | ReadableStream | FileSink; + readonly stdout?: number | ReadableStream; + readonly stderr?: number | ReadableStream; + + readonly exited: Promise<number>; + + readonly exitCode: number | undefined; + readonly signalCode: Signal | null; + readonly killed: boolean; + + ref(): void; + unref(): void; + kill(code?: number): void; +} + +interface SyncSubprocess { + readonly pid: number; + readonly success: boolean; + readonly stdout: Buffer; + readonly stderr: Buffer; +} + +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"; +``` diff --git a/docs/api/sqlite.md b/docs/api/sqlite.md new file mode 100644 index 000000000..9e1668bf6 --- /dev/null +++ b/docs/api/sqlite.md @@ -0,0 +1,411 @@ +Bun natively implements a high-performance [SQLite3](https://www.sqlite.org/) driver. To use it import from the built-in `bun:sqlite` module. + +```ts +import { Database } from "bun:sqlite"; + +const db = new Database(":memory:"); +const query = db.query("select 'Hello world' as message;"); +query.get(); // => { 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`) +- The fastest performance of any SQLite driver for JavaScript + +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](<[./bench/sqlite](https://github.com/oven-sh/bun/tree/main/bench/sqlite)>). + +{% image width="738" alt="SQLite benchmarks for Bun, better-sqlite3, and deno.land/x/sqlite" src="https://user-images.githubusercontent.com/709451/168459263-8cd51ca3-a924-41e9-908d-cf3478a3b7f3.png" caption="Benchmarked on an M1 MacBook Pro (64GB) running macOS 12.3.1" /%} + +## Database + +To open or create a SQLite3 database: + +```ts +import { Database } from "bun:sqlite"; + +const db = new Database("mydb.sqlite"); +``` + +To open an in-memory database: + +```ts +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 +import { Database } from "bun:sqlite"; +const db = new Database("mydb.sqlite", { readonly: true }); +``` + +To create the database if the file doesn't exist: + +```ts +import { Database } from "bun:sqlite"; +const db = new Database("mydb.sqlite", { create: true }); +``` + +### `.close()` + +To close a database: + +```ts +const db = new Database(); +db.close(); +``` + +Note: `close()` is called automatically when the database is garbage collected. It is safe to call multiple times but has no effect after the first. + +### `.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 +const olddb = new Database("mydb.sqlite"); +const contents = db.serialize(); // => Uint8Array +const newdb = new Database(copy); +``` + +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 +const query = db.query(`select "Hello world" as message`); +``` + +{% callout %} + +**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 = ?"); +``` + +{% /callout %} + +## 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 +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 +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. + +### `.all()` + +Use `.all()` to run a query and get back the results as an array of objects. + +```ts +const query = db.query(`select $message;`); +query.all({ $message: "Hello world" }); +// => [{ 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 +const query = db.query(`select $message;`); +query.get({ $message: "Hello world" }); +// => { $message: "Hello world" } +``` + +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 only want the first row. + +### `.run()` + +Use `.run()` to run a query and get back `undefined`. This is useful for queries schema-modifying queries (e.g. `CREATE TABLE`) or bulk write operations. + +```ts +const query = db.query(`create table foo;`); +query.run(); +// => undefined +``` + +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. + +### `.values()` + +Use `values()` to run a query and get back all results as an array of arrays. + +```ts +const query = db.query(`select $message;`); +query.values({ $message: "Hello world" }); + +query.values(2); +// [ +// [ "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 +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 +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: + +{% codetabs %} + +```ts#Query +const query = db.query("SELECT * FROM foo WHERE bar = $bar"); +const results = await query.all({ + $bar: "bar", +}); +``` + +```json#Results +[ + { "$bar": "bar" } +] +``` + +{% /codetabs %} + +Numbered (positional) parameters work too: + +{% codetabs %} + +```ts#Query +const query = db.query("SELECT ?1, ?2"); +const results = await query.all("hello", "goodbye"); +``` + +```ts#Results +[ + { + "?1": "hello", + "?2": "goodbye" + } +] +``` + +{% /codetabs %} + +## 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 +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 +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. + +{% callout %} +**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). + +{% details summary="View nested transaction example" %} + +```ts +// 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 }, +]); +``` + +{% /details %} +{% /callout %} + +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 +import { Database } from "bun:sqlite"; + +const db = new Database(); +db.loadExtension("myext"); +``` + +{% details summary="For macOS users" %} +**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 +$ 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 +import { Database } from "bun:sqlite"; + +Database.setCustomSQLite("/path/to/libsqlite.dylib"); + +const db = new Database(); +db.loadExtension("myext"); +``` + +{% /details %} + +## Reference + +```ts +class Database { + constructor( + filename: string, + options?: + | number + | { + readonly?: boolean; + create?: boolean; + readwrite?: boolean; + }, + ); + + query<Params, ReturnType>(sql: string): Statement<Params, ReturnType>; +} + +class Statement<Params, ReturnType> { + all(params: Params): ReturnType[]; + get(params: Params): ReturnType | undefined; + run(params: Params): void; + 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 + paramsCount: number; // the number of parameters expected by the statement + native: any; // the native object representing the statement +} + +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` | diff --git a/docs/api/tcp.md b/docs/api/tcp.md new file mode 100644 index 000000000..5e04dd348 --- /dev/null +++ b/docs/api/tcp.md @@ -0,0 +1,198 @@ +Use Bun's native TCP API 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 + +To start a TCP server with `Bun.listen`: + +```ts +Bun.listen({ + hostname: "localhost", + port: 8080, + socket: { + data(socket, data) {}, // message received from client + open(socket) {}, // socket opened + close(socket) {}, // socket closed + drain(socket) {}, // socket ready for more data + error(socket, error) {}, // error handler + }, +}); +``` + +{% details summary="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 +Bun.listen({ + hostname: "localhost", + port: 8080, + socket: { + open(socket) {}, + data(socket, data) {}, + drain(socket) {}, + close(socket) {}, + 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. + +{% /details %} + +Contextual data can be attached to a socket in the `open` handler. + +```ts +type SocketData = { sessionId: string }; + +Bun.listen<SocketData>({ + hostname: "localhost", + port: 8080, + socket: { + data(socket, data) { + socket.write(`${socket.data.sessionId}: ack`); + }, + open(socket) { + socket.data = { sessionId: "abcd" }; + }, + }, +}); +``` + +To enable TLS, pass a `tls` object containing `keyFile` and `certFile` properties. + +```ts +Bun.listen({ + hostname: "localhost", + port: 8080, + socket: { + data(socket, data) {}, + }, + tls: { + certFile: "cert.pem", + keyFile: "key.pem", + }, +}); +``` + +The result of `Bun.listen` is a server that conforms to the `TCPSocket` instance. + +```ts +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 + +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 +// The client +const socket = Bun.connect({ + hostname: "localhost", + port: 8080, + + socket: { + data(socket, data) {}, + open(socket) {}, + close(socket) {}, + 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 = Bun.connect({ + // ... config + tls: true, +}); +``` + +## Hot reloading + +Both TCP servers and sockets can be hot reloaded with new handlers. + +{% codetabs %} + +```ts#Server +const server = Bun.listen({ /* config */ }) + +// reloads handlers for all active server-side sockets +server.reload({ + socket: + data(){ + // new 'data' handler + } + } +}) +``` + +```ts#Client +const socket = Bun.connect({ /* config */ }) +socket.reload({ + data(){ + // new 'data' handler + } +}) +``` + +{% /codetabs %} + +## 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 +const sink = new ArrayBufferSink({ stream: true, highWaterMark: 1024 }); + +sink.write("h"); +sink.write("e"); +sink.write("l"); +sink.write("l"); +sink.write("o"); + +queueMicrotask(() => { + var data = sink.flush(); + if (!socket.write(data)) { + // put it back in the sink if the socket is full + sink.write(data); + } +}); +``` + +{% callout %} +**Corking** — Support for corking is planned, but in the meantime backpressure must be managed manually with the `drain` handler. +{% /callout %} diff --git a/docs/api/test.md b/docs/api/test.md new file mode 100644 index 000000000..6704d407d --- /dev/null +++ b/docs/api/test.md @@ -0,0 +1 @@ +See the [`bun test`](/docs/cli/test) documentation. diff --git a/docs/api/transpiler.md b/docs/api/transpiler.md new file mode 100644 index 000000000..184007212 --- /dev/null +++ b/docs/api/transpiler.md @@ -0,0 +1,275 @@ +Bun exposes its internal transpiler via the `Bun.Transpiler` class. To create an instance of Bun's transpiler: + +```ts +const tx = 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. + +<!-- It is synchronous and runs in the same thread as other JavaScript code. --> + +{% codetabs %} + +```js#Example +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 = tx.transformSync(code); +``` + +```js#Result +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, +); +``` + +{% /codetabs %} + +To override the default loader specified in the `new Bun.Transpiler()` constructor, pass a second argument to `.transformSync()`. + +```ts +await transpiler.transform("<div>hi!</div>", "tsx"); +``` + +{% details summary="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. +{% /details %} + +## `.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"); +``` + +{% details summary="Nitty gritty" %} +The `.tranform()` 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. +{% /details %} + +## `.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. + +{% codetabs %} + +```ts#Example +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" + } + ] +} +``` + +{% /codetabs %} + +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')` +<!-- - `internal`: `import {foo} from 'bun:internal'` +- `entry-point`: `import {foo} from 'bun:entry'` --> + +## `.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. + +{% codetabs %} + +```ts#Example +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 +[ + { + kind: "import-statement", + path: "react" + }, { + kind: "require-call", + path: "./cjs.js" + }, { + kind: "dynamic-import", + path: "./loader" + } +] +``` + +{% /codetabs %} + +## Reference + +```ts +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 + platform?: "browser" | "bun" | "macro" | "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" +} + +const transpiler = new Bun.Transpiler({ loader: "jsx" }); +``` diff --git a/docs/api/utils.md b/docs/api/utils.md new file mode 100644 index 000000000..dcd9c0128 --- /dev/null +++ b/docs/api/utils.md @@ -0,0 +1,120 @@ +## `Bun.sleep` + +`Bun.sleep(ms: number)` (added in Bun v0.5.6) + +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.which` + +`Bun.which(bin: string)` + +Find 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 +``` + +## `Bun.peek` + +`Bun.peek(prom: Promise)` (added in Bun v0.2.2) + +`Bun.peek` is a utility function that lets you read 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"); +}); +``` diff --git a/docs/api/websockets.md b/docs/api/websockets.md new file mode 100644 index 000000000..b362d4ee2 --- /dev/null +++ b/docs/api/websockets.md @@ -0,0 +1,464 @@ +As of Bun v0.2.1, `Bun.serve()` supports server-side WebSockets, with on-the-fly compression, TLS support, and a Bun-native pubsub API. + +{% callout %} + +**⚡️ 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). +{% /callout %} + +## Create a client + +To connect to an external socket server, create an instance of `WebSocket` with the constructor. + +```ts +const socket = new WebSocket("ws://localhost:8080"); +``` + +Bun supports setting custom headers. This is a Bun-specific extension of the `WebSocket` standard. + +```ts +const socket = new WebSocket("ws://localhost:8080", { + headers: { + // custom headers + }, +}); +``` + +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 => {}); +``` + +## Create a 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 +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 +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 + }, +}); +``` + +{% details summary="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. +{% /details %} + +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 +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 +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 attched to this `Response` in the call to `server.upgrade()`. + +```ts +Bun.serve({ + fetch(req, server) { + const sessionId = await generateSessionId(); + server.upgrade(req, { + headers: { + "Set-Cookie": `SessionId=${sessionId}`, + }, + }); + }, + 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. + +```ts +type WebSocketData = { + createdAt: number; + channelId: string; +}; + +// TypeScript: specify the type of `data` +Bun.serve<WebSocketData>({ + fetch(req, server) { + server.upgrade(req, { + // TS: this object must conform to WebSocketData + data: { + createdAt: Date.now(), + channelId: new URL(req.url).searchParams.get("channelId"), + }, + }); + + return undefined; + }, + websocket: { + // handler called when a message is received + async message(ws, message) { + ws.data; // WebSocketData + await saveMessageToDatabase({ + channel: ws.data.channelId, + message: String(message), + }); + }, + }, +}); +``` + +## 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. 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 +const pubsubserver = Bun.serve<{username: string}>({ + fetch(req, server) { + if (req.url === '/chat') { + const cookies = getCookieFromRequest(req); + const success = server.upgrade(req, { + data: {username: cookies.username}, + }); + return success + ? undefined + : new Response('WebSocket upgrade error', {status: 400}); + } + + return new Response('Hello world'); + }, + websocket: { + open(ws) { + ws.subscribe('the-group-chat'); + ws.publish('the-group-chat', `${ws.data.username} has entered the chat`); + }, + message(ws, message) { + // this is a group chat + // so the server re-broadcasts incoming message to everyone + ws.publish('the-group-chat', `${ws.data.username}: ${message}`); + }, + close(ws) { + ws.unsubscribe('the-group-chat'); + ws.publish('the-group-chat', `${ws.data.username} has left the chat`); + }, +}); +``` + +## Compression + +Per-message [compression](https://websockets.readthedocs.io/en/stable/topics/compression.html) can be enabled with the `perMessageDeflate` parameter. + +```ts +Bun.serve({ + fetch(req, server) {}, // upgrade logic + websocket: { + // enable compression and decompression + perMessageDeflate: true, + }, +}); +``` + +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. + +## Reference + +```ts +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) => void; + error?: (ws: ServerWebSocket, error: Error) => void; + drain?: (ws: ServerWebSocket) => void; + 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; + 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; +} +``` + +<!-- +### `Bun.serve(params)` + +{% param name="params" %} +Configuration object for WebSocket server +{% /param %} + +{% param name=" fetch" %} +`(req: Request, server: Server) => Response | Promise<Response>` + +Call `server.upgrade(req)` to upgrade the request to a WebSocket connection. This method returns `true` if the upgrade succeeds, or `false` if the upgrade fails. +{% /param %} + +{% param name=" websocket" %} +Configuration object for WebSocket server +{% /param %} + +{% param name=" message" %} +`(ws: ServerWebSocket, message: string | ArrayBuffer | Uint8Array) => void` + +This handler is called when a `WebSocket` receives a message. +{% /param %} + +{% param name=" open" %} +`(ws: ServerWebSocket) => void` + +This handler is called when a `WebSocket` is opened. +{% /param %} + +{% param name=" close" %} +`(ws: ServerWebSocket, code: number, message: string) => void` + +This handler is called when a `WebSocket` is closed. +{% /param %} + +{% param name=" drain" %} +`(ws: ServerWebSocket) => void` + +This handler is called when a `WebSocket` is ready to receive more data. +{% /param %} + +{% param name=" perMessageDeflate" %} +`boolean | {\n compress?: boolean | Compressor;\n decompress?: boolean | Compressor \n}` + +Enable per-message compression and decompression. This is a boolean value or an object with `compress` and `decompress` properties. Each property can be a boolean value or one of the following `Compressor` types: + +- `"disable"` +- `"shared"` +- `"dedicated"` +- `"3KB"` +- `"4KB"` +- `"8KB"` +- `"16KB"` +- `"32KB"` +- `"64KB"` +- `"128KB"` +- `"256KB"` + +{% /param %} + +### `ServerWebSocket` + +{% param name="readyState" %} +`number` + +The current state of the `WebSocket` connection. This is one of the following values: + +- `0` `CONNECTING` +- `1` `OPEN` +- `2` `CLOSING` +- `3` `CLOSED` + +{% /param %} + +{% param name="remoteAddress" %} + +`string` + +The remote address of the `WebSocket` connection +{% /param %} + +{% param name="data" %} +The data associated with the `WebSocket` connection. This is set in the `server.upgrade()` call. +{% /param %} + +{% param name=".send()" %} +`send(message: string | ArrayBuffer | Uint8Array, compress?: boolean): number` + +Send a message to the client. 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 + +The `compress` argument will enable compression for this message, even if the `perMessageDeflate` option is disabled. +{% /param %} + +{% param name=".subscribe()" %} +`subscribe(topic: string): void` + +Subscribe to a topic +{% /param %} + +{% param name=".unsubscribe()" %} +`unsubscribe(topic: string): void` + +Unsubscribe from a topic +{% /param %} + +{% param name=".publish()" %} +`publish(topic: string, data: string | ArrayBufferView | ArrayBuffer, compress?: boolean): number;` + +Send a message to all subscribers of a topic +{% /param %} + +{% param name=".isSubscribed()" %} +`isSubscribed(topic: string): boolean` + +Check if the `WebSocket` is subscribed to a topic +{% /param %} +{% param name=".cork()" %} +`cork(cb: (ws: ServerWebSocket) => void): void;` + +Batch a set of operations on a `WebSocket` connection. The `message`, `open`, and `drain` callbacks are automatically corked, so +you only need to call this if you are sending messages outside of those +callbacks or in async functions. + +```ts +ws.cork((ws) => { + ws.send("first"); + ws.send("second"); + ws.send("third"); +}); +``` + +{% /param %} + +{% param name=".close()" %} +`close(code?: number, message?: string): void` + +Close the `WebSocket` connection +{% /param %} + +### `Server` + +{% param name="pendingWebsockets" %} +Number of in-flight `WebSocket` messages +{% /param %} + +{% param name=".publish()" %} +`publish(topic: string, data: string | ArrayBufferView | ArrayBuffer, compress?: boolean): number;` + +Send a message to all subscribers of a topic +{% /param %} + +{% param name=".upgrade()" %} +`upgrade(req: Request): boolean` + +Upgrade a request to a `WebSocket` connection. Returns `true` if the upgrade succeeds, or `false` if the upgrade fails. +{% /param %} --> diff --git a/docs/benchmarks.md b/docs/benchmarks.md new file mode 100644 index 000000000..3d7943423 --- /dev/null +++ b/docs/benchmarks.md @@ -0,0 +1,121 @@ +Bun.js focuses on performance, developer experience, and compatibility with the JavaScript ecosystem. + +## HTTP Requests + +```ts +// http.ts +export default { + port: 3000, + fetch(request: Request) { + return new Response("Hello World"); + }, +}; + +// bun ./http.ts +``` + +| Requests per second | OS | CPU | Bun version | +| ---------------------------------------------------------------------- | ----- | ------------------------------ | ----------- | +| [260,000](https://twitter.com/jarredsumner/status/1512040623200616449) | macOS | Apple Silicon M1 Max | 0.0.76 | +| [160,000](https://twitter.com/jarredsumner/status/1511988933587976192) | Linux | AMD Ryzen 5 3600 6-Core 2.2ghz | 0.0.76 | + +{% details summary="See benchmark details" %} +Measured with [`http_load_test`](https://github.com/uNetworking/uSockets/blob/master/examples/http_load_test.c) by running: + +```bash +$ ./http_load_test 20 127.0.0.1 3000 +``` + +{% /details %} + +## File System + +`cat` clone that runs [2x faster than GNU cat](https://twitter.com/jarredsumner/status/1511707890708586496) for large files on Linux + +```js +// cat.js +import { resolve } from "path"; +import { write, stdout, file, argv } from "bun"; + +const path = resolve(argv.at(-1)); + +await write( + // stdout is a Blob + stdout, + // file(path) returns a Blob - https://developer.mozilla.org/en-US/docs/Web/API/Blob + file(path), +); +``` + +Run this with `bun cat.js /path/to/big/file`. + +## Reading from standard input + +```ts +// As of Bun v0.3.0, console is an AsyncIterable +for await (const line of console) { + // line of text from stdin + console.log(line); +} +``` + +## React SSR + +```js +import { renderToReadableStream } from "react-dom/server"; + +const dt = new Intl.DateTimeFormat(); + +export default { + port: 3000, + async fetch(request: Request) { + return new Response( + await renderToReadableStream( + <html> + <head> + <title>Hello World</title> + </head> + <body> + <h1>Hello from React!</h1> + <p>The date is {dt.format(new Date())}</p> + </body> + </html>, + ), + ); + }, +}; +``` + +Write to stdout with `console.write`: + +```js +// no trailing newline +// works with strings and typed arrays +console.write("Hello World!"); +``` + +There are some more examples in the [examples](./examples) folder. + +PRs adding more examples are very welcome! + +## Fast paths for Web APIs + +Bun.js has fast paths for common use cases that make Web APIs live up to the performance demands of servers and CLIs. + +`Bun.file(path)` returns a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) that represents a lazily-loaded file. + +When you pass a file blob to `Bun.write`, Bun automatically uses a faster system call: + +```js +const blob = Bun.file("input.txt"); +await Bun.write("output.txt", blob); +``` + +On Linux, this uses the [`copy_file_range`](https://man7.org/linux/man-pages/man2/copy_file_range.2.html) syscall and on macOS, this becomes `clonefile` (or [`fcopyfile`](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/copyfile.3.html)). + +`Bun.write` also supports [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects. It automatically converts to a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob). + +```js +// Eventually, this will stream the response to disk but today it buffers +await Bun.write("index.html", await fetch("https://example.com")); +``` diff --git a/docs/bun-flavored-toml.md b/docs/bun-flavored-toml.md index b041ba055..ab7beb592 100644 --- a/docs/bun-flavored-toml.md +++ b/docs/bun-flavored-toml.md @@ -1,5 +1,3 @@ -# Bun-flavored TOML - [TOML](https://toml.io/) is a minimal configuration file format designed to be easy for humans to read. Bun implements a TOML parser with a few tweaks designed for better interoperability with INI files and with JavaScript. diff --git a/docs/cli/bun-completions.md b/docs/cli/bun-completions.md new file mode 100644 index 000000000..68dcb946d --- /dev/null +++ b/docs/cli/bun-completions.md @@ -0,0 +1,3 @@ +This command installs completions for `zsh` and/or `fish`. It runs automatically on every `bun upgrade` and on install. It reads from `$SHELL` to determine which shell to install for. It tries several common shell completion directories for your shell and OS. + +If you want to copy the completions manually, run `bun completions > path-to-file`. If you know the completions directory to install them to, run `bun completions /path/to/directory`. diff --git a/docs/cli/bun-dev.md b/docs/cli/bun-dev.md new file mode 100644 index 000000000..5d1adec5f --- /dev/null +++ b/docs/cli/bun-dev.md @@ -0,0 +1,21 @@ +In your project folder root (where `package.json` is): + +```bash +$ bun bun ./entry-point-1.js ./entry-point-2.jsx +$ bun dev +``` + +By default, `bun dev` will look for any HTML files in the `public` directory and serve that. For browsers navigating to the page, the `.html` file extension is optional in the URL, and `index.html` will automatically rewrite for the directory. + +Here are examples of routing from `public/` and how they’re matched: +| Dev Server URL | File Path | +|----------------|-----------| +| /dir | public/dir/index.html | +| / | public/index.html | +| /index | public/index.html | +| /hi | public/hi.html | +| /file | public/file.html | +| /font/Inter.woff2 | public/font/Inter.woff2 | +| /hello | public/index.html | + +If `public/index.html` exists, it becomes the default page instead of a 404 page, unless that pathname has a file extension. diff --git a/docs/cli/bun-init.md b/docs/cli/bun-init.md new file mode 100644 index 000000000..3a0e66815 --- /dev/null +++ b/docs/cli/bun-init.md @@ -0,0 +1,20 @@ +`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. + + + +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 + +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 `bun-types`. + +Added in Bun v0.1.7. + +#### How is `bun init` different than `bun create`? + +`bun init` is for blank projects. `bun create` applies templates. diff --git a/docs/cli/bun-install.md b/docs/cli/bun-install.md new file mode 100644 index 000000000..11cf3ee81 --- /dev/null +++ b/docs/cli/bun-install.md @@ -0,0 +1,257 @@ +### `bun install` + +bun install is a fast package manager & npm client. + +bun install can be configured via `bunfig.toml`, environment variables, and CLI flags. + +#### Configuring `bun install` with `bunfig.toml` + +`bunfig.toml` is searched for in the following paths on `bun install`, `bun remove`, and `bun add`: + +1. `$XDG_CONFIG_HOME/.bunfig.toml` or `$HOME/.bunfig.toml` +2. `./bunfig.toml` + +If both are found, the results are merged together. + +Configuring with `bunfig.toml` is optional. Bun tries to be zero configuration in general, but that's not always possible. + +```toml +# Using scoped packages with bun install +[install.scopes] + +# Scope name The value can be a URL string or an object +"@mybigcompany" = { token = "123456", url = "https://registry.mybigcompany.com" } +# URL is optional and fallsback to the default registry + +# The "@" in the scope is optional +mybigcompany2 = { token = "123456" } + +# Environment variables can be referenced as a string that starts with $ and it will be replaced +mybigcompany3 = { token = "$npm_config_token" } + +# Setting username and password turns it into a Basic Auth header by taking base64("username:password") +mybigcompany4 = { username = "myusername", password = "$npm_config_password", url = "https://registry.yarnpkg.com/" } +# You can set username and password in the registry URL. This is the same as above. +mybigcompany5 = "https://username:password@registry.yarnpkg.com/" + +# You can set a token for a registry URL: +mybigcompany6 = "https://:$NPM_CONFIG_TOKEN@registry.yarnpkg.com/" + +[install] +# Default registry +# can be a URL string or an object +registry = "https://registry.yarnpkg.com/" +# as an object +#registry = { url = "https://registry.yarnpkg.com/", token = "123456" } + +# Install for production? This is the equivalent to the "--production" CLI argument +production = false + +# Don't actually install +dryRun = true + +# Install optionalDependencies (default: true) +optional = true + +# Install local devDependencies (default: true) +dev = true + +# Install peerDependencies (default: false) +peer = false + +# When using `bun install -g`, install packages here +globalDir = "~/.bun/install/global" + +# When using `bun install -g`, link package bins here +globalBinDir = "~/.bun/bin" + +# cache-related configuration +[install.cache] +# The directory to use for the cache +dir = "~/.bun/install/cache" + +# Don't load from the global cache. +# Note: Bun may still write to node_modules/.cache +disable = false + + +# Always resolve the latest versions from the registry +disableManifest = false + + +# Lockfile-related configuration +[install.lockfile] + +# Print a yarn v1 lockfile +# Note: it does not load the lockfile, it just converts bun.lockb into a yarn.lock +print = "yarn" + +# Path to read bun.lockb from +path = "bun.lockb" + +# Path to save bun.lockb to +savePath = "bun.lockb" + +# Save the lockfile to disk +save = true + +``` + +If it's easier to read as TypeScript types: + +```ts +export interface Root { + install: Install; +} + +export interface Install { + scopes: Scopes; + registry: Registry; + production: boolean; + dryRun: boolean; + optional: boolean; + dev: boolean; + peer: boolean; + globalDir: string; + globalBinDir: string; + cache: Cache; + lockfile: Lockfile; + logLevel: "debug" | "error" | "warn"; +} + +type Registry = + | string + | { + url?: string; + token?: string; + username?: string; + password?: string; + }; + +type Scopes = Record<string, Registry>; + +export interface Cache { + dir: string; + disable: boolean; + disableManifest: boolean; +} + +export interface Lockfile { + print?: "yarn"; + path: string; + savePath: string; + save: boolean; +} +``` + +## Configuring with environment variables + +Environment variables have a higher priority than `bunfig.toml`. + +| Name | Description | +| -------------------------------- | ------------------------------------------------------------- | +| BUN_CONFIG_REGISTRY | Set an npm registry (default: <https://registry.npmjs.org>) | +| BUN_CONFIG_TOKEN | Set an auth token (currently does nothing) | +| BUN_CONFIG_LOCKFILE_SAVE_PATH | File path to save the lockfile to (default: bun.lockb) | +| BUN_CONFIG_YARN_LOCKFILE | Save a Yarn v1-style yarn.lock | +| BUN_CONFIG_LINK_NATIVE_BINS | Point `bin` in package.json to a platform-specific dependency | +| BUN_CONFIG_SKIP_SAVE_LOCKFILE | Don’t save a lockfile | +| BUN_CONFIG_SKIP_LOAD_LOCKFILE | Don’t load a lockfile | +| BUN_CONFIG_SKIP_INSTALL_PACKAGES | Don’t install any packages | + +Bun always tries to use the fastest available installation method for the target platform. On macOS, that’s `clonefile` and on Linux, that’s `hardlink`. You can change which installation method is used with the `--backend` flag. When unavailable or on error, `clonefile` and `hardlink` fallsback to a platform-specific implementation of copying files. + +Bun stores installed packages from npm in `~/.bun/install/cache/${name}@${version}`. Note that if the semver version has a `build` or a `pre` tag, it is replaced with a hash of that value instead. This is to reduce the chances of errors from long file paths, but unfortunately complicates figuring out where a package was installed on disk. + +When the `node_modules` folder exists, before installing, Bun checks if the `"name"` and `"version"` in `package/package.json` in the expected node_modules folder matches the expected `name` and `version`. This is how it determines whether it should install. It uses a custom JSON parser which stops parsing as soon as it finds `"name"` and `"version"`. + +When a `bun.lockb` doesn’t exist or `package.json` has changed dependencies, tarballs are downloaded & extracted eagerly while resolving. + +When a `bun.lockb` exists and `package.json` hasn’t changed, Bun downloads missing dependencies lazily. If the package with a matching `name` & `version` already exists in the expected location within `node_modules`, Bun won’t attempt to download the tarball. + +## Platform-specific dependencies? + +bun stores normalized `cpu` and `os` values from npm in the lockfile, along with the resolved packages. It skips downloading, extracting, and installing packages disabled for the current target at runtime. This means the lockfile won’t change between platforms/architectures even if the packages ultimately installed do change. + +## Peer dependencies? + +Peer dependencies are handled similarly to yarn. `bun install` does not automatically install peer dependencies and will try to choose an existing dependency. + +## Lockfile + +`bun.lockb` is Bun’s binary lockfile format. + +## Why is it binary? + +In a word: Performance. Bun’s lockfile saves & loads incredibly quickly, and saves a lot more data than what is typically inside lockfiles. + +## How do I inspect it? + +For now, the easiest thing is to run `bun install -y`. That prints a Yarn v1-style yarn.lock file. + +## What does the lockfile store? + +Packages, metadata for those packages, the hoisted install order, dependencies for each package, what packages those dependencies resolved to, an integrity hash (if available), what each package was resolved to and which version (or equivalent). + +## Why is it fast? + +It uses linear arrays for all data. [Packages](https://github.com/oven-sh/bun/blob/be03fc273a487ac402f19ad897778d74b6d72963/src/install/install.zig#L1825) are referenced by an auto-incrementing integer ID or a hash of the package name. Strings longer than 8 characters are de-duplicated. Prior to saving on disk, the lockfile is garbage-collected & made deterministic by walking the package tree and cloning the packages in dependency order. + +## Cache + +To delete the cache: + +```bash +$ rm -rf ~/.bun/install/cache +``` + +## Platform-specific backends + +`bun install` uses different system calls to install dependencies depending on the platform. This is a performance optimization. You can force a specific backend with the `--backend` flag. + +**`hardlink`** is the default backend on Linux. Benchmarking showed it to be the fastest on Linux. + +```bash +$ rm -rf node_modules +$ bun install --backend hardlink +``` + +**`clonefile`** is the default backend on macOS. Benchmarking showed it to be the fastest on macOS. It is only available on macOS. + +```bash +$ rm -rf node_modules +$ bun install --backend clonefile +``` + +**`clonefile_each_dir`** is similar to `clonefile`, except it clones each file individually per directory. It is only available on macOS and tends to perform slower than `clonefile`. Unlike `clonefile`, this does not recursively clone subdirectories in one system call. + +```bash +$ rm -rf node_modules +$ bun install --backend clonefile_each_dir +``` + +**`copyfile`** is the fallback used when any of the above fail, and is the slowest. on macOS, it uses `fcopyfile()` and on linux it uses `copy_file_range()`. + +```bash +$ rm -rf node_modules +$ bun install --backend copyfile +``` + +**`symlink`** is typically only used for `file:` dependencies (and eventually `link:`) internally. To prevent infinite loops, it skips symlinking the `node_modules` folder. + +If you install with `--backend=symlink`, Node.js won't resolve node_modules of dependencies unless each dependency has its own node_modules folder or you pass `--preserve-symlinks` to `node`. See [Node.js documentation on `--preserve-symlinks`](https://nodejs.org/api/cli.html#--preserve-symlinks). + +```bash +$ rm -rf node_modules +$ bun install --backend symlink +$ node --preserve-symlinks ./my-file.js # https://nodejs.org/api/cli.html#--preserve-symlinks +``` + +Bun's runtime does not currently expose an equivalent of `--preserve-symlinks`, though the code for it does exist. + +## npm registry metadata + +bun uses a binary format for caching NPM registry responses. This loads much faster than JSON and tends to be smaller on disk. +You will see these files in `~/.bun/install/cache/*.npm`. The filename pattern is `${hash(packageName)}.npm`. It’s a hash so that extra directories don’t need to be created for scoped packages. + +Bun's usage of `Cache-Control` ignores `Age`. This improves performance, but means bun may be about 5 minutes out of date to receive the latest package version metadata from npm. diff --git a/docs/cli/bun-upgrade.md b/docs/cli/bun-upgrade.md new file mode 100644 index 000000000..a9d0759b6 --- /dev/null +++ b/docs/cli/bun-upgrade.md @@ -0,0 +1,39 @@ +To upgrade Bun, run `bun upgrade`. + +It automatically downloads the latest version of Bun and overwrites the currently-running version. + +This works by checking the latest version of Bun in [bun-releases-for-updater](https://github.com/Jarred-Sumner/bun-releases-for-updater/releases) and unzipping it using the system-provided `unzip` library (so that Gatekeeper works on macOS) + +If for any reason you run into issues, you can also use the curl install script: + +```bash +$ curl https://bun.sh/install | bash +``` + +It will still work when Bun is already installed. + +Bun is distributed as a single binary file, so you can also do this manually: + +- Download the latest version of Bun for your platform in [bun-releases-for-updater](https://github.com/Jarred-Sumner/bun-releases-for-updater/releases/latest) (`darwin` == macOS) +- Unzip the folder +- Move the `bun` binary to `~/.bun/bin` (or anywhere) + +## `--canary` + +[Canary](https://github.com/oven-sh/bun/releases/tag/canary) builds are generated on every commit. + +To install a [canary](https://github.com/oven-sh/bun/releases/tag/canary) build of Bun, run: + +```bash +$ bun upgrade --canary +``` + +This flag is not persistent (though that might change in the future). If you want to always run the canary build of Bun, set the `BUN_CANARY` environment variable to `1` in your shell's startup script. + +This will download the release zip from https://github.com/oven-sh/bun/releases/tag/canary. + +To revert to the latest published version of Bun, run: + +```bash +$ bun upgrade +``` diff --git a/docs/cli/bundler.md b/docs/cli/bundler.md new file mode 100644 index 000000000..6900e9292 --- /dev/null +++ b/docs/cli/bundler.md @@ -0,0 +1,116 @@ +Bundling is currently an important mechanism for building complex web apps. + +Modern apps typically consist of a large number of files and package dependencies. Despite the fact that modern browsers support [ES Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) imports, it's still too slow to fetch each file via inidividual HTTP requests. _Bundling_ is the process of concatenating several source files into a single large file that can be loaded in a single request. + +{% callout %} +**On bundling** — Despite recent advances like [`modulepreload`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/modulepreload) and [HTTP/3](https://en.wikipedia.org/wiki/HTTP/3), bundling is still the most performant approach. +{% /callout %} + +## Bundling your app + +Bun's approach to bundling is a little different from other bundlers. Start by passing your app's entrypoint to `bun bun`. + +```bash +$ bun bun ./app.js +``` + +Your entrypoint can be any `js|jsx|ts|tsx|html` file. With this file as a starting point, Bun will construct a graph of imported files and packages, transpile everything, and generate a file called `node_modules.bun`. + +## What is `.bun`? + +{% callout %} +**Note** — [This format may change soon](https://github.com/oven-sh/bun/issues/121) +{% /callout %} + +A `.bun` file contains the pre-transpiled source code of your application, plus a bunch of binary-encoded metadata about your application's structure. as a contains: + +- all the bundled source code +- all the bundled source code metadata +- project metadata & configuration + +Here are some of the questions `.bun` files answer: + +- when I import `react/index.js`, where in the `.bun` is the code for that? (not resolving, just the code) +- what modules of a package are used? +- what framework is used? (e.g., Next.js) +- where is the routes directory? +- how big is each imported dependency? +- what is the hash of the bundle’s contents? (for etags) +- what is the name & version of every npm package exported in this bundle? +- what modules from which packages are used in this project? ("project" is defined as all the entry points used to generate the .bun) + +All in one file. + +It’s a little like a build cache, but designed for reuse across builds. + +{% details summary="Position-independent code" %} + +From a design perspective, the most important part of the `.bun` format is how code is organized. Each module is exported by a hash like this: + +```js +// preact/dist/preact.module.js +export var $eb6819b = $$m({ + "preact/dist/preact.module.js": (module, exports) => { + let n, l, u, i, t, o, r, f, e = {}, c = [], s = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i; + // ... rest of code +``` + +This makes bundled modules [position-independent](https://en.wikipedia.org/wiki/Position-independent_code). In theory, one could import only the exact modules in-use without reparsing code and without generating a new bundle. One bundle can dynamically become many bundles comprising only the modules in use on the webpage. Thanks to the metadata with the byte offsets, a web server can send each module to browsers [zero-copy](https://en.wikipedia.org/wiki/Zero-copy) using [sendfile](https://man7.org/linux/man-pages/man2/sendfile.2.html). Bun itself is not quite this smart yet, but these optimizations would be useful in production and potentially very useful for React Server Components. + +To see the schema inside, have a look at [`JavascriptBundleContainer`](./src/api/schema.d.ts#:~:text=export%20interface-,JavascriptBundleContainer,-%7B). You can find JavaScript bindings to read the metadata in [src/api/schema.js](./src/api/schema.js). This is not really an API yet. It’s missing the part where it gets the binary data from the bottom of the file. Someday, I want this to be usable by other tools too. +{% /details %} + +## Where is the code? + +`.bun` files are marked as executable. + +To print out the code, run `./node_modules.bun` in your terminal or run `bun ./path-to-node_modules.bun`. + +Here is a copy-pastable example: + +```bash +$ ./node_modules.bun > node_modules.js +``` + +This works because every `.bun` file starts with this: + +``` +#!/usr/bin/env bun +``` + +To deploy to production with Bun, you’ll want to get the code from the `.bun` file and stick that somewhere your web server can find it (or if you’re using Vercel or a Rails app, in a `public` folder). + +Note that `.bun` is a binary file format, so just opening it in VSCode or vim might render strangely. + +## Advanced + +By default, `bun bun` only bundles external dependencies that are `import`ed or `require`d in either app code or another external dependency. An "external dependency" is defined as, "A JavaScript-like file that has `/node_modules/` in the resolved file path and a corresponding `package.json`". + +To force Bun to bundle packages which are not located in a `node_modules` folder (i.e., the final, resolved path following all symlinks), add a `bun` section to the root project’s `package.json` with `alwaysBundle` set to an array of package names to always bundle. Here’s an example: + +```json +{ + "name": "my-package-name-in-here", + "bun": { + "alwaysBundle": ["@mybigcompany/my-workspace-package"] + } +} +``` + +Bundled dependencies are not eligible for Hot Module Reloading. The code is served to browsers & Bun.js verbatim. But, in the future, it may be sectioned off into only parts of the bundle being used. That’s possible in the current version of the `.bun` file (so long as you know which files are necessary), but it’s not implemented yet. Longer-term, it will include all `import` and `export` of each module inside. + +## What is the module ID hash? + +The `$eb6819b` hash used here: + +```js +export var $eb6819b = $$m({ +``` + +Is generated like this: + +1. Murmur3 32-bit hash of `package.name@package.version`. This is the hash uniquely identifying the npm package. +2. Wyhash 64 of the `package.hash` + `package_path`. `package_path` means "relative to the root of the npm package, where is the module imported?". For example, if you imported `react/jsx-dev-runtime.js`, the `package_path` is `jsx-dev-runtime.js`. `react-dom/cjs/react-dom.development.js` would be `cjs/react-dom.development.js` +3. Truncate the hash generated above to a `u32` + +The implementation details of this module ID hash will vary between versions of Bun. The important part is the metadata contains the module IDs, the package paths, and the package hashes, so it shouldn’t really matter in practice if other tooling wants to make use of any of this. diff --git a/docs/cli/create.md b/docs/cli/create.md new file mode 100644 index 000000000..8542db68b --- /dev/null +++ b/docs/cli/create.md @@ -0,0 +1,232 @@ +Template a new Bun project with `bun create`. + +```bash +$ bun create <template> <destination> +``` + +{% callout %} +**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. +{% /callout %} + +A template can take a number of forms: + +```bash +$ bun create <template> # an official template (remote) +$ bun create <username>/<repo> # a GitHub repo (remote) +$ bun create <local-template> # a custom template (local) +``` + +Running `bun create` performs the following steps: + +- Download the template (remote templates only) +- Copy all template files into the destination folder. By default Bun will _not overwrite_ any existing files. Use the `--force` flag to overwrite existing files. +- 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. + +## Official templates + +The following official templates are available. + +```bash +bun create next ./myapp +bun create react ./myapp +bun create svelte-kit ./myapp +bun create elysia ./myapp +bun create hono ./myapp +bun create kingworld ./myapp +``` + +Each of these corresponds to a directory in the [bun-community/create-templates](https://github.com/bun-community/create-templates) repo. If you think a major framework is missing, please open a PR there. This list will change over time as additional examples are added. To see an up-to-date list, run `bun create` with no arguments. + +```bash +$ bun create +Welcome to bun! Create a new project by pasting any of the following: + <list of templates> +``` + +{% callout %} +⚡️ **Speed** — At the time of writing, `bun create react app` runs ~11x faster on a M1 Macbook Pro than `yarn create react-app app`. +{% /callout %} + +## GitHub repos + +A template of the form `<username>/<repo>` will be downloaded from GitHub. + +```bash +$ bun create ahfarmer/calculator ./myapp +``` + +Complete GitHub URLs will also work: + +```bash +$ bun create github.com/ahfarmer/calculator ./myapp +$ bun create https://github.com/ahfarmer/calculator ./myapp +``` + +Bun installs the files as they currently exist current default branch (usually `main`). Unlike `git clone` it doesn't download the commit history or configure a remote. + +## Local templates + +{% callout %} +**⚠️ Warning** — Unlike remote templates, running `bun create` with a local template will delete the entire destination folder if it already exists! Be careful. +{% /callout %} +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 + +{% callout %} +**Note** — You can customize the global template path by setting the `BUN_CREATE_DIR` environment variable. +{% /callout %} + +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 +{ + "name": "foo" +} +``` + +You can run `bun create foo` elsewhere on your file system to verify that Bun is correctly finding your local template. + +{% table %} + +--- + +- `postinstall` +- runs after installing dependencies + +--- + +- `preinstall` +- runs before installing dependencies + +<!-- --- + +- `start` +- a command to auto-start the application --> + +{% /table %} + +Each of these can correspond to a string or array of strings. An array of commands will be executed in order. Here is an example: + +```json +{ + "name": "@bun-examples/simplereact", + "version": "0.0.1", + "main": "index.js", + "dependencies": { + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "bun-create": { + "postinstall": "echo 'Installing...'", // a single command + "postinstall": ["echo 'Done!'"], // an array of commands + "start": "bun run echo 'Hello world!'" + } +} +``` + +When 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 + +{% table %} + +- 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 + +{% /table %} + +### Environment variables + +{% table %} + +- 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_API_TOKEN` +- This lets `bun create` work with private repositories or if you get rate-limited + +{% /table %} + +### How `bun create` works + +{% details summary="View details" %} + +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": { "preinstall" }` 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 + +{% /details %} diff --git a/docs/cli/install.md b/docs/cli/install.md new file mode 100644 index 000000000..777ed2da1 --- /dev/null +++ b/docs/cli/install.md @@ -0,0 +1,344 @@ +The `bun` CLI contains an `npm`-compatible package manager designed to be a faster replacement for existing package management tools like `npm`, `yarn`, and `pnpm`. + +It can be be used as a drop-in replacement for these tools, _regardless of whether you're using Bun's runtime_. + +{% callout %} + +**⚡️ 80x faster** — Switch from `npm install` to `bun install` in any Node.js project to make your installations up to 80x faster. + +{% image src="https://user-images.githubusercontent.com/709451/147004342-571b6123-17a9-49a2-8bfd-dcfc5204047e.png" height="200" /%} + +{% /callout %} + +{% details summary="For Linux users" %} +The minimum Linux Kernel version is 5.1. If you're on Linux kernel 5.1 - 5.5, `bun install` should still work, but HTTP requests will be slow due to a lack of support for io_uring's `connect()` operation. + +If you're using Ubuntu 20.04, here's how to install a [newer kernel](https://wiki.ubuntu.com/Kernel/LTSEnablementStack): + +```bash +# If this returns a version >= 5.6, you don't need to do anything +uname -r + +# Install the official Ubuntu hardware enablement kernel +sudo apt install --install-recommends linux-generic-hwe-20.04 +``` + +{% /details %} + +## Install dependencies + +To install all dependencies of a project: + +```bash +$ bun install +``` + +On Linux, `bun install` tends to install packages 20-100x faster than `npm install`. On macOS, it's more like 4-80x. + + + +Running `bun install` will: + +- **Install** all `dependencies`, `devDependencies`, and `optionalDependencies`. Bun does not install `peerDependencies` by default. +- **Run** your project's `{pre|post}install` scripts at the appropriate time. For security reasons Bun _does not execute_ lifecycle scripts of installed dependencies. +- **Write** a `bun.lockb` lockfile to the project root. + +To install in production mode (i.e. without `devDependencies`): + +```bash +$ bun install --production +``` + +To perform a dry run (i.e. don't actually install anything): + +```bash +$ bun install --dry-run +``` + +To modify logging verbosity: + +```bash +$ bun install --verbose # debug logging +$ bun install --silent # no logging +``` + +{% details summary="Configuring behavior" %} +The default behavior of `bun install` can be configured in `bun.toml`: + +```toml +[install] + +# whether to install optionalDependencies +optional = true + +# whether to install devDependencies +dev = true + +# whether to install devDependencies +peer = false + +# equivalent to `--production` flag +production = false + +# equivalent to `--dry-run` flag +dryRun = false +``` + +{% /details %} + +## Adding packages + +To add or remove a particular package: + +```bash +$ bun add preact +$ bun remove preact +``` + +To specify a version, version range, or tag: + +```bash +$ bun add zod@3.20.0 +$ bun add zod@^3.0.0 +$ bun add zod@latest +``` + +To add a package as a dev dependency (`"devDependencies"`): + +```bash +$ bun add --development @types/react +$ bun add -d @types/react +``` + +To add a package as an optional dependency (`"optionalDependencies"`): + +```bash +$ bun add --optional lodash +``` + +To install a package globally: + +```bash +$ bun add --global cowsay # or `bun add -g cowsay` +$ cowsay "Bun!" + ______ +< Bun! > + ------ + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || +``` + +{% details summary="Configuring global installation behavior" %} + +```toml +[install] +# where `bun install --global` installs packages +globalDir = "~/.bun/install/global" + +# where globally-installed package bins are linked +globalBinDir = "~/.bun/bin" +``` + +{% /details %} +To view a complete list of options for a given command: + +```bash +$ bun add --help +``` + +## Global cache + +All packages downloaded from the registry are stored in a global cache at `~/.bun/install/cache`. They are stored in subdirectories named like `${name}@${version}`, so multiple versions of a package can be cached. + +{% details summary="Configuring cache behavior" %} + +```toml +[install.cache] +# the directory to use for the cache +dir = "~/.bun/install/cache" + +# when true, don't load from the global cache. +# Bun may still write to node_modules/.cache +disable = false + +# when true, always resolve the latest versions from the registry +disableManifest = false +``` + +{% /details %} + +### Minimizing re-downloads + +Bun strives to avoid re-downloading packages mutiple times. When installing a package, if the cache already contains a version in the range specified by `package.json`, Bun will use the cached package instead of downloading it again. + +{% details summary="Installation details" %} +If the semver version has pre-release suffix (`1.0.0-beta.0`) or a build suffix (`1.0.0+20220101`), it is replaced with a hash of that value instead, to reduce the chances of errors associated with long file paths. + +When the `node_modules` folder exists, before installing, Bun checks that `node_modules` contains all expected packages with appropriate versions. If so `bun install` completes. Bun uses a custom JSON parser which stops parsing as soon as it finds `"name"` and `"version"`. + +If a package is missing or has a version incompatible with the `package.json`, Bun checks for a compatible module in the cache. If found, it is installed into `node_modules`. Otherwise, the package will be downloaded from the registry then installed. +{% /details %} + +### Fast copying + +Once a package is downloaded into the cache, Bun still needs to copy those files into `node_modules`. Bun uses the fastest syscalls available to perform this task. On Linux, it uses hardlinks; on macOS, it uses `clonefile`. + +### Saving disk space + +Since Bun uses hardlinks to "copy" a module into a project's `node_modules` directory on Linux, the contents of the package only exist in a single location on disk, greatly reducing the amount of disk space dedicated to `node_modules`. + +This benefit does not extend to macOS, which uses `clonefile` for performance reasons. + +{% details summary="Installation strategies" %} +This behavior is configurable with the `--backend` flag, which is respected by all of Bun's package management commands. + +- **`hardlink`**: Default on Linux. +- **`clonefile`** Default on macOS. +- **`clonefile_each_dir`**: Similar to `clonefile`, except it clones each file individually per directory. It is only available on macOS and tends to perform slower than `clonefile`. +- **`copyfile`**: The fallback used when any of the above fail. It is the slowest option. On macOS, it uses `fcopyfile()`; on Linux it uses `copy_file_range()`. + **`symlink`**: Currently used only `file:` (and eventually `link:`) dependencies. To prevent infinite loops, it skips symlinking the `node_modules` folder. + +If you install with `--backend=symlink`, Node.js won't resolve node_modules of dependencies unless each dependency has its own `node_modules` folder or you pass `--preserve-symlinks` to `node`. See [Node.js documentation on `--preserve-symlinks`](https://nodejs.org/api/cli.html#--preserve-symlinks). + +```bash +$ bun install --backend symlink +$ node --preserve-symlinks ./foo.js +``` + +Bun's runtime does not currently expose an equivalent of `--preserve-symlinks`. +{% /details %} + +## Lockfile + +Running `bun install` will create a binary lockfile called `bun.lockb`. + +#### Why is it binary? + +In a word: Performance. Bun’s lockfile saves & loads incredibly quickly, and saves a lot more data than what is typically inside lockfiles. + +#### How do I inspect it? + +Run `bun install -y` to generate a Yarn-compatible `yarn.lock` (v1) that can be inspected more easily. + +#### Platform-specific dependencies? + +Bun stores normalized `cpu` and `os` values from npm in the lockfile, along with the resolved packages. It skips downloading, extracting, and installing packages disabled for the current target at runtime. This means the lockfile won’t change between platforms/architectures even if the packages ultimately installed do change. + +#### What does the lockfile store? + +Packages, metadata for those packages, the hoisted install order, dependencies for each package, what packages those dependencies resolved to, an integrity hash (if available), what each package was resolved to, and which version (or equivalent). + +#### Why is it fast? + +It uses linear arrays for all data. [Packages](https://github.com/oven-sh/bun/blob/be03fc273a487ac402f19ad897778d74b6d72963/src/install/install.zig#L1825) are referenced by an auto-incrementing integer ID or a hash of the package name. Strings longer than 8 characters are de-duplicated. Prior to saving on disk, the lockfile is garbage-collected & made deterministic by walking the package tree and cloning the packages in dependency order. + +#### Can I opt out? + +To install without creating a lockfile: + +```bash +$ bun install --no-save +``` + +To install a Yarn lockfile _in addition_ to `bun.lockb`. + +{% codetabs %} + +```bash#CLI flag +$ bun install --yarn +``` + +```toml#bunfig.toml +[install.lockfile] +# whether to save a non-Bun lockfile alongside bun.lockb +# only "yarn" is supported +print = "yarn" +``` + +{% /codetabs %} + +{% details summary="Configuring lockfile" %} + +```toml +[install.lockfile] + +# path to read bun.lockb from +path = "bun.lockb" + +# path to save bun.lockb to +savePath = "bun.lockb" + +# whether to save the lockfile to disk +save = true + +# whether to save a non-Bun lockfile alongside bun.lockb +# only "yarn" is supported +print = "yarn" +``` + +{% /details %} + +## Workspaces + +Bun supports [`workspaces`](https://docs.npmjs.com/cli/v9/using-npm/workspaces?v=true#description) in `package.json`. Workspaces make it easy to develop complex software as a _monorepo_ consisting of several independent packages. + +To try it, specify a list of sub-packages in the `workspaces` field of your `package.json`; it's conventional to place these sub-packages in a directory called `packages`. + +```json +{ + "name": "my-project", + "version": "1.0.0", + "workspaces": ["packages/a", "packages/b"] +} +``` + +{% callout %} +**Glob support** — Bun doesn't support globs for workspace names yet, but this is coming soon! +{% /callout %} + +This has a couple major benefits. + +- **Code can be split into logical parts.** If one package relies on another, you can simply add it as a dependency with `bun add`. If package `b` depends on `a`, `bun install` will symlink your local `packages/a` directory into the `node_modules` folder of `b`, instead of trying to download it from the npm registry. +- **Dependencies can be de-duplicated.** If `a` and `b` share a common dependency, it will be _hoisted_ to the root `node_modules` directory. This reduces redundant disk usage and minimizes "dependency hell" issues associated with having multiple versions of a package installed simultaneously. + +{% callout %} +⚡️ **Speed** — Installs are fast, even for big monorepos. Bun installs the [Remix](https://github.com/remix-run/remix) monorepo in about `500ms` on Linux. + +- 28x faster than `npm install` +- 12x faster than `yarn install` (v1) +- 8x faster than `pnpm install` + +{% image src="https://user-images.githubusercontent.com/709451/212829600-77df9544-7c9f-4d8d-a984-b2cd0fd2aa52.png" /%} +{% /callout %} + +## Registries + +The default registry is `registry.npmjs.org`. This can be globally configured in `bunfig.toml`: + +```toml +[install] +# set default registry as a string +registry = "https://registry.npmjs.org" +# set a token +registry = { url = "https://registry.npmjs.org", token = "123456" } +# set a username/password +registry = "https://username:password@registry.npmjs.org" +``` + +To configure organization-scoped registries: + +```toml +[install.scopes] +# registry as string +"@myorg1" = "https://username:password@registry.myorg.com/" + +# registry with username/password +# you can reference environment variables +"@myorg2" = { username = "myusername", password = "$NPM_PASS", url = "https://registry.myorg.com/" } + +# registry with token +"@myorg3" = { token = "$npm_token", url = "https://registry.myorg.com/" } +``` diff --git a/docs/cli/run.md b/docs/cli/run.md new file mode 100644 index 000000000..f180868d2 --- /dev/null +++ b/docs/cli/run.md @@ -0,0 +1,156 @@ +The `bun` CLI can be used to execute JavaScript/TypeScript files, `package.json` scripts, and [executable packages](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#bin). + +## Run a file + +{% callout %} +Compare to `node <file>` +{% /callout %} + +Bun can execute `.js`, `.jsx`, `.ts`, and `.tsx` files. Every file is transpiled to vanilla JavaScript by Bun's fast native transpiler before being executed. For details on Bun's runtime, refer to the [Bun runtime](/docs/runtime) documentation. + +```ts#foo.ts +import { z } from "zod"; + +const schema = z.string() +const result = schema.parse("Billie Eilish"); +console.log(result); +``` + +To run a file in Bun: + +```bash +$ bun foo.ts +Billie Eilish +``` + +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](/docs/cli/install#global-cache). For full details on this algorithm, refer to [Runtime > Modules](/docs/runtime/modules). + +## Run a package script + +{% note %} +Compare to `npm run <script>` or `yarn <script>` +{% /note %} + +Your `package.json` can define a number of named `"scripts"` that correspond to shell commands. + +```jsonc +{ + // ... other fields + "scripts": { + "clean": "rm -rf dist && echo 'Done.'", + "dev": "bun server.ts" + } +} +``` + +Use `bun <script>` to execute these scripts. + +```bash +$ bun clean + $ rm -rf dist && echo 'Done.' + Cleaning... + Done. +``` + +Bun executes the script command in a subshell. It checks for the following shells in order, using the first one it finds: `bash`, `sh`, `zsh`. + +{% callout %} +⚡️ The startup time for `npm run` on Linux is roughly 170ms; with Bun it is `6ms`. +{% /callout %} + +If there is a name conflict between a `package.json` script and a built-in `bun` command (`install`, `dev`, `upgrade`, etc.) Bun's built-in command takes precedence. In this case, use the more explicit `bun run` command to execute your package script. + +```bash +$ bun run dev +``` + +To see a list of available scripts, run `bun run` without any arguments. + +```bash +$ bun run +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. + +## Run an executable + +{% callout %} +Compare to `npx <command>` +{% /callout %} + +Packages can declare executables in the `"bin"` field of their `package.json`. These are known as _package executables_ or _package binaries_. + +```jsonc#package.json +{ + // ... other fields + "name": "my-cli", + "bin": { + "my-cli": "dist/index.js" + } +} +``` + +These executables are commonly plain JavaScript files marked with a [shebang line](<https://en.wikipedia.org/wiki/Shebang_(Unix)>) to indicate which program should be used to execute them. The following file indicates that it should be executed with `node`. + +```js#dist/index.js +#!/usr/bin/env node + +console.log("Hello world!"); +``` + +These executables can be run with `bunx`, Bun's equivalent of `npx`. + +{% callout %} +⚡️ **Speed** — With Bun's fast startup times, `bunx` is [roughly 100x faster](https://twitter.com/jarredsumner/status/1606163655527059458) than `npx` for locally installed packages. +{% /callout %} + +```bash +$ bunx my-cli +``` + +As with `npx`, `bunx` will check for a locally installed package first, then fall back to auto-installing the package from `npm`. Installed packages will be stored in Bun's global cache for future use. + +### Arguments and flags + +To pass additional command-line flags and arguments through to the executable: + +```bash +$ bunx my-cli --foo bar +``` + +### Shebangs + +By default, Bun respects shebangs. If an executable is marked with `#!/usr/bin/env node`, Bun will spin up a `node` process to execute the file. However, in some cases it may be desirable to run executables using [Bun's runtime](/docs/runtime), even if the executable indicates otherwise. To do so, include the `--bun` flag. + +```bash +$ bunx --bun my-cli +``` + +{% callout %} +**Note** — The `--bun` flag must occur _before_ the executable name. Flags that appear _after_ the name are passed through to the executable. + +```bash +$ bunx --bun my-cli # good +$ bunx my-cli --bun # bad +``` + +{% /callout %} + +## Environment variables + +Bun automatically loads environment variables from `.env` files before running a file, script, or executable. The following files are checked, in order: + +1. `.env.local` (first) +2. `NODE_ENV` === `"production"` ? `.env.production` : `.env.development` +3. `.env` + +To debug environment variables, run `bun run env` to view a list of resolved environment variables. diff --git a/docs/cli/test.md b/docs/cli/test.md new file mode 100644 index 000000000..4471a7204 --- /dev/null +++ b/docs/cli/test.md @@ -0,0 +1,224 @@ +Bun ships with a built-in test runner. + +```bash +$ bun wiptest +``` + +The runner recursively searches the working directory for files that match the following patterns: + +- `*.test.{js|jsx|ts|tsx}` +- `*_test.{js|jsx|ts|tsx}` +- `*.spec.{js|jsx|ts|tsx}` +- `*_spec.{js|jsx|ts|tsx}` + +You can filter the set of tests to run by passing additional positional arguments to `wiptest`. Any file in the directory with an _absolute path_ that contains one of the filters will run. Commonly, these filters will be file or directory names; glob patterns are not yet supported. + +```bash +$ bun wiptest <filter> <filter> ... +``` + +<!-- +Consider the following directory structure: + +``` +. +├── a.test.ts +├── b.test.ts +├── c.test.ts +└── foo + ├── a.test.ts + └── b.test.ts +``` + +To run both `a.test.ts` files: + +``` +$ bun wiptest a +``` + +To run all tests in the `foo` directory: + +``` +$ bun wiptest foo +``` + +Any test file in the directory with an _absolute path_ that contains one of the targets will run. Glob patterns are not yet supported. --> + +## Writing tests + +Define tests with a Jest-like API imported from the built-in `bun:test` module. + +```ts#math.test.ts +import { expect, test } from "bun:test"; + +test("2 + 2", () => { + expect(2 + 2).toBe(4); +}); +``` + +Group tests into suites with `describe`. + +```ts#math.test.ts +import { expect, test, describe } from "bun:test"; + +describe("arithmetic", () => { + test("2 + 2", () => { + expect(2 + 2).toBe(4); + }); + + test("2 * 2", () => { + expect(2 * 2).toBe(4); + }); +}); +``` + +Tests can be `async`. + +```ts +import { expect, test } from "bun:test"; + +test("2 * 2", async () => { + const result = await Promise.resolve(2 * 2); + expect(result).toEqual(4); +}); +``` + +Alternatively, use the `done` callback to signal completion. If you include the `done` callback as a parameter in your test definition, you _must_ call it or the test will hang. + +```ts +import { expect, test } from "bun:test"; + +test("2 * 2", done => { + Promise.resolve(2 * 2).then(done => { + expect(result).toEqual(4); + done(); + }); +}); +``` + +Perform per-test setup and teardown logic with `beforeEach` and `afterEach`. + +```ts +import { expect, test } from "bun:test"; + +beforeEach(() => { + console.log("running test."); +}); + +afterEach(() => { + console.log("done with test."); +}); + +// tests... +``` + +Perform per-scope setup and teardown logic with `beforeAll` and `afterAll`. At the top-level, the `*scope* is the current file; in a `describe` block, the scope is the block itself. + +```ts +import { expect, test, beforeAll, afterAll } from "bun:test"; + +let db: Database; +beforeAll(() => { + // connect to database +}); + +afterAll(() => { + // close connection +}); + +// tests... +``` + +Skip individual tests with `test.skip`. + +```ts +import { expect, test } from "bun:test"; + +test.skip("wat", () => { + // TODO: fix this + expect(0.1 + 0.2).toEqual(0.3); +}); +``` + +## Expect matchers + +Bun implements the following matchers. Full Jest compatibility is on the roadmap; track progress [here](https://github.com/oven-sh/bun/issues/1825). + +- [x] [`.not`](https://jestjs.io/docs/expect#not) +- [x] [`.toBe()`](https://jestjs.io/docs/expect#tobevalue) +- [x] [`.toEqual()`](https://jestjs.io/docs/expect#toequalvalue) +- [x] [`.toBeNull()`](https://jestjs.io/docs/expect#tobenull) +- [x] [`.toBeUndefined()`](https://jestjs.io/docs/expect#tobeundefined) +- [x] [`.toBeNaN()`](https://jestjs.io/docs/expect#tobenan) +- [x] [`.toBeDefined()`](https://jestjs.io/docs/expect#tobedefined) +- [x] [`.toBeFalsy()`](https://jestjs.io/docs/expect#tobefalsy) +- [x] [`.toBeTruthy()`](https://jestjs.io/docs/expect#tobetruthy) +- [x] [`.toContain()`](https://jestjs.io/docs/expect#tocontainitem) +- [x] [`.toStrictEqual()`](https://jestjs.io/docs/expect#tostrictequalvalue) +- [x] [`.toThrow()`](https://jestjs.io/docs/expect#tothrowerror) +- [x] [`.toHaveLength()`](https://jestjs.io/docs/expect#tohavelengthnumber) +- [x] [`.toHaveProperty()`](https://jestjs.io/docs/expect#tohavepropertykeypath-value) +- [ ] [`.extend`](https://jestjs.io/docs/expect#expectextendmatchers) +- [ ] [`.anything()`](https://jestjs.io/docs/expect#expectanything) +- [ ] [`.any()`](https://jestjs.io/docs/expect#expectanyconstructor) +- [ ] [`.arrayContaining()`](https://jestjs.io/docs/expect#expectarraycontainingarray) +- [ ] [`.assertions()`](https://jestjs.io/docs/expect#expectassertionsnumber) +- [ ] [`.closeTo()`(number, numDigit](https://jestjs.io/docs/expect#expectclosetonumber-numdigits) +- [ ] [`.hasAssertions()`](https://jestjs.io/docs/expect#expecthasassertions) +- [ ] [`.objectContaining()`](https://jestjs.io/docs/expect#expectobjectcontainingobject) +- [ ] [`.stringContaining()`](https://jestjs.io/docs/expect#expectstringcontainingstring) +- [ ] [`.stringMatching()`](https://jestjs.io/docs/expect#expectstringmatchingstring--regexp) +- [ ] [`.addSnapshotSerializer()`](https://jestjs.io/docs/expect#expectaddsnapshotserializerserializer) +- [ ] [`.resolves()`](https://jestjs.io/docs/expect#resolves) +- [ ] [`.rejects()`](https://jestjs.io/docs/expect#rejects) +- [ ] [`.toHaveBeenCalled()`](https://jestjs.io/docs/expect#tohavebeencalled) +- [ ] [`.toHaveBeenCalledTimes()`](https://jestjs.io/docs/expect#tohavebeencalledtimesnumber) +- [ ] [`.toHaveBeenCalledWith()`](https://jestjs.io/docs/expect#tohavebeencalledwitharg1-arg2-) +- [ ] [`.toHaveBeenLastCalledWith()`](https://jestjs.io/docs/expect#tohavebeenlastcalledwitharg1-arg2-) +- [ ] [`.toHaveBeenNthCalledWith()`](https://jestjs.io/docs/expect#tohavebeennthcalledwithnthcall-arg1-arg2-) +- [ ] [`.toHaveReturned()`](https://jestjs.io/docs/expect#tohavereturned) +- [ ] [`.toHaveReturnedTimes()`](https://jestjs.io/docs/expect#tohavereturnedtimesnumber) +- [ ] [`.toHaveReturnedWith()`](https://jestjs.io/docs/expect#tohavereturnedwithvalue) +- [ ] [`.toHaveLastReturnedWith()`](https://jestjs.io/docs/expect#tohavelastreturnedwithvalue) +- [ ] [`.toHaveNthReturnedWith()`](https://jestjs.io/docs/expect#tohaventhreturnedwithnthcall-value) +- [ ] [`.toBeCloseTo()`](https://jestjs.io/docs/expect#tobeclosetonumber-numdigits) +- [ ] [`.toBeGreaterThan()`](https://jestjs.io/docs/expect#tobegreaterthannumber--bigint) +- [ ] [`.toBeGreaterThanOrEqual()`](https://jestjs.io/docs/expect#tobegreaterthanorequalnumber--bigint) +- [ ] [`.toBeLessThan()`](https://jestjs.io/docs/expect#tobelessthannumber--bigint) +- [ ] [`.toBeLessThanOrEqual()`](https://jestjs.io/docs/expect#tobelessthanorequalnumber--bigint) +- [ ] [`.toBeInstanceOf()`](https://jestjs.io/docs/expect#tobeinstanceofclass) +- [ ] [`.toContainEqual()`](https://jestjs.io/docs/expect#tocontainequalitem) +- [ ] [`.toMatch()`](https://jestjs.io/docs/expect#tomatchregexp--string) +- [ ] [`.toMatchObject()`](https://jestjs.io/docs/expect#tomatchobjectobject) +- [ ] [`.toMatchSnapshot()`](https://jestjs.io/docs/expect#tomatchsnapshotpropertymatchers-hint) +- [ ] [`.toMatchInlineSnapshot()`](https://jestjs.io/docs/expect#tomatchinlinesnapshotpropertymatchers-inlinesnapshot) +- [ ] [`.toThrowErrorMatchingSnapshot()`](https://jestjs.io/docs/expect#tothrowerrormatchingsnapshothint) +- [ ] [`.toThrowErrorMatchingInlineSnapshot()`](https://jestjs.io/docs/expect#tothrowerrormatchinginlinesnapshotinlinesnapshot) + +<!-- ```ts +test('matchers', ()=>{ + + expect(5).toBe(5); + expect("do re mi").toContain("mi"); + expect("do re mi").toEqual("do re mi"); + expect({}).toStrictEqual({}); // uses Bun.deepEquals() + expect([1,2,3]).toHaveLength(3); + expect({ name: "foo" }).toHaveProperty("name"); + expect({ name: "foo" }).toHaveProperty("name", "foo"); + expect(5).toBeTruthy(); + expect(0).toBeFalsy(); + expect("").toBeDefined(); + expect(undefined).toBeUndefined(); + expect(parseInt('tuna')).toBeNaN(); + expect(null).toBeNull(); + expect(5).toBeGreaterThan(4); + expect(5).toBeGreaterThanOrEqual(5); + expect(5).toBeLessThan(6); + expect(5).toBeLessThanOrEqual(5); + expect(()=>throw new Error()).toThrow(); + + // negation + expect(5).not.toBe(4) + +}) +``` --> diff --git a/docs/dev/bundev.md b/docs/dev/bundev.md new file mode 100644 index 000000000..baccf7658 --- /dev/null +++ b/docs/dev/bundev.md @@ -0,0 +1,11 @@ +- pages +- auto-bundle dependencies +- pages is function that returns a list of pages? +- plugins for svelte and vue +- custom loaders +- HMR +- server endpoints + +```ts +Bun.serve({}); +``` diff --git a/docs/dev/cra.md b/docs/dev/cra.md new file mode 100644 index 000000000..8eb868715 --- /dev/null +++ b/docs/dev/cra.md @@ -0,0 +1,31 @@ +To create a new React app: + +```bash +$ bun create react ./app +$ cd app +$ bun dev # start dev server +``` + +To use an existing React app: + +```bash +$ bun add -d react-refresh # install React Fast Refresh +$ bun bun ./src/index.js # generate a bundle for your entry point(s) +$ bun dev # start the dev server +``` + +From there, Bun relies on the filesystem for mapping dev server paths to source files. All URL paths are relative to the project root (where `package.json` is located). + +Here are examples of routing source code file paths: + +| Dev Server URL | File Path (relative to cwd) | +| -------------------------- | --------------------------- | +| /src/components/Button.tsx | src/components/Button.tsx | +| /src/index.tsx | src/index.tsx | +| /pages/index.js | pages/index.js | + +You do not need to include file extensions in `import` paths. CommonJS-style import paths without the file extension work. + +You can override the public directory by passing `--public-dir="path-to-folder"`. + +If no directory is specified and `./public/` doesn’t exist, Bun will try `./static/`. If `./static/` does not exist, but won’t serve from a public directory. If you pass `--public-dir=./` Bun will serve from the current directory, but it will check the current directory last instead of first. diff --git a/docs/dev/css.md b/docs/dev/css.md new file mode 100644 index 000000000..a74f5d1ea --- /dev/null +++ b/docs/dev/css.md @@ -0,0 +1,77 @@ +## With `bun dev` + +When importing CSS in JavaScript-like loaders, CSS is treated special. + +By default, Bun will transform a statement like this: + +```js +import "../styles/global.css"; +``` + +### When `platform` is `browser` + +```js +globalThis.document?.dispatchEvent( + new CustomEvent("onimportcss", { + detail: "http://localhost:3000/styles/globals.css", + }), +); +``` + +An event handler for turning that into a `<link>` is automatically registered when HMR is enabled. That event handler can be turned off either in a framework’s `package.json` or by setting `globalThis["Bun_disableCSSImports"] = true;` in client-side code. Additionally, you can get a list of every .css file imported this way via `globalThis["__BUN"].allImportedStyles`. + +### When `platform` is `bun` + +```js +//@import url("http://localhost:3000/styles/globals.css"); +``` + +Additionally, Bun exposes an API for SSR/SSG that returns a flat list of URLs to css files imported. That function is `Bun.getImportedStyles()`. + +```ts +// This specifically is for "framework" in package.json when loaded via `bun dev` +// This API needs to be changed somewhat to work more generally with Bun.js +// Initially, you could only use Bun.js through `bun dev` +// and this API was created at that time +addEventListener("fetch", async (event: FetchEvent) => { + let route = Bun.match(event); + const App = await import("pages/_app"); + + // This returns all .css files that were imported in the line above. + // It’s recursive, so any file that imports a CSS file will be included. + const appStylesheets = bun.getImportedStyles(); + + // ...rest of code +}); +``` + +This is useful for preventing flash of unstyled content. + +## With `bun bun` + +Bun bundles `.css` files imported via `@import` into a single file. It doesn’t autoprefix or minify CSS today. Multiple `.css` files imported in one JavaScript file will _not_ be bundled into one file. You’ll have to import those from a `.css` file. + +This input: + +```css +@import url("./hi.css"); +@import url("./hello.css"); +@import url("./yo.css"); +``` + +Becomes: + +```css +/* hi.css */ +/* ...contents of hi.css */ +/* hello.css */ +/* ...contents of hello.css */ +/* yo.css */ +/* ...contents of yo.css */ +``` + +## CSS runtime + +To support hot CSS reloading, Bun inserts `@supports` annotations into CSS that tag which files a stylesheet is composed of. Browsers ignore this, so it doesn’t impact styles. + +By default, Bun’s runtime code automatically listens to `onimportcss` and will insert the `event.detail` into a `<link rel="stylesheet" href={${event.detail}}>` if there is no existing `link` tag with that stylesheet. That’s how Bun’s equivalent of `style-loader` works. diff --git a/docs/dev/discord.md b/docs/dev/discord.md new file mode 100644 index 000000000..d3e9c5a2b --- /dev/null +++ b/docs/dev/discord.md @@ -0,0 +1,26 @@ +## Creating a Discord bot with Bun + +Discord bots perform actions in response to _application commands_. There are 3 types of commands accessible in different interfaces: the chat input, a message's context menu (top-right menu or right-clicking in a message), and a user's context menu (right-clicking on a user). + +To get started you can use the interactions template: + +```bash +bun create discord-interactions my-interactions-bot +cd my-interactions-bot +``` + +If you don't have a Discord bot/application yet, you can create one [here (https://discord.com/developers/applications/me)](https://discord.com/developers/applications/me). + +Invite bot to your server by visiting `https://discord.com/api/oauth2/authorize?client_id=<your_application_id>&scope=bot%20applications.commands` + +Afterwards you will need to get your bot's token, public key, and application id from the application page and put them into `.env.example` file + +Then you can run the http server that will handle your interactions: + +```bash +$ bun install +$ mv .env.example .env +$ bun run.js # listening on port 1337 +``` + +Discord does not accept an insecure HTTP server, so you will need to provide an SSL certificate or put the interactions server behind a secure reverse proxy. For development, you can use ngrok/cloudflare tunnel to expose local ports as secure URL. diff --git a/docs/dev/frameworks.md b/docs/dev/frameworks.md new file mode 100644 index 000000000..9af7fc45b --- /dev/null +++ b/docs/dev/frameworks.md @@ -0,0 +1,151 @@ +{% callout %} +**Warning** — This will soon have breaking changes. It was designed when Bun was mostly a dev server and not a JavaScript runtime. +{% /callout %} + +Frameworks preconfigure Bun to enable developers to use Bun with their existing tooling. + +Frameworks are configured via the `framework` object in the `package.json` of the framework (not in the application’s `package.json`): + +Here is an example: + +```json +{ + "name": "bun-framework-next", + "version": "0.0.0-18", + "description": "", + "framework": { + "displayName": "Next.js", + "static": "public", + "assetPrefix": "_next/", + "router": { + "dir": ["pages", "src/pages"], + "extensions": [".js", ".ts", ".tsx", ".jsx"] + }, + "css": "onimportcss", + "development": { + "client": "client.development.tsx", + "fallback": "fallback.development.tsx", + "server": "server.development.tsx", + "css": "onimportcss", + "define": { + "client": { + ".env": "NEXT_PUBLIC_", + "defaults": { + "process.env.__NEXT_TRAILING_SLASH": "false", + "process.env.NODE_ENV": "\"development\"", + "process.env.__NEXT_ROUTER_BASEPATH": "''", + "process.env.__NEXT_SCROLL_RESTORATION": "false", + "process.env.__NEXT_I18N_SUPPORT": "false", + "process.env.__NEXT_HAS_REWRITES": "false", + "process.env.__NEXT_ANALYTICS_ID": "null", + "process.env.__NEXT_OPTIMIZE_CSS": "false", + "process.env.__NEXT_CROSS_ORIGIN": "''", + "process.env.__NEXT_STRICT_MODE": "false", + "process.env.__NEXT_IMAGE_OPTS": "null" + } + }, + "server": { + ".env": "NEXT_", + "defaults": { + "process.env.__NEXT_TRAILING_SLASH": "false", + "process.env.__NEXT_OPTIMIZE_FONTS": "false", + "process.env.NODE_ENV": "\"development\"", + "process.env.__NEXT_OPTIMIZE_IMAGES": "false", + "process.env.__NEXT_OPTIMIZE_CSS": "false", + "process.env.__NEXT_ROUTER_BASEPATH": "''", + "process.env.__NEXT_SCROLL_RESTORATION": "false", + "process.env.__NEXT_I18N_SUPPORT": "false", + "process.env.__NEXT_HAS_REWRITES": "false", + "process.env.__NEXT_ANALYTICS_ID": "null", + "process.env.__NEXT_CROSS_ORIGIN": "''", + "process.env.__NEXT_STRICT_MODE": "false", + "process.env.__NEXT_IMAGE_OPTS": "null", + "global": "globalThis", + "window": "undefined" + } + } + } + } + } +} +``` + +Here are type definitions: + +```ts +type Framework = Environment & { + // This changes what’s printed in the console on load + displayName?: string; + + // This allows a prefix to be added (and ignored) to requests. + // Useful for integrating an existing framework that expects internal routes to have a prefix + // e.g. "_next" + assetPrefix?: string; + + development?: Environment; + production?: Environment; + + // The directory used for serving unmodified assets like fonts and images + // Defaults to "public" if exists, else "static", else disabled. + static?: string; + + // "onimportcss" disables the automatic "onimportcss" feature + // If the framework does routing, you may want to handle CSS manually + // "facade" removes CSS imports from JavaScript files, + // and replaces an imported object with a proxy that mimics CSS module support without doing any class renaming. + css?: "onimportcss" | "facade"; + + // Bun's filesystem router + router?: Router; +}; + +type Define = { + // By passing ".env", Bun will automatically load .env.local, .env.development, and .env if exists in the project root + // (in addition to the processes’ environment variables) + // When "*", all environment variables will be automatically injected into the JavaScript loader + // When a string like "NEXT_PUBLIC_", only environment variables starting with that prefix will be injected + + ".env": string | "*"; + + // These environment variables will be injected into the JavaScript loader + // These are the equivalent of Webpack’s resolve.alias and esbuild’s --define. + // Values are parsed as JSON, so they must be valid JSON. The only exception is '' is a valid string, to simplify writing stringified JSON in JSON. + // If not set, `process.env.NODE_ENV` will be transformed into "development". + "defaults": Record<string, string>; +}; + +type Environment = { + // This is a wrapper for the client-side entry point for a route. + // This allows frameworks to run initialization code on pages. + client: string; + // This is a wrapper for the server-side entry point for a route. + // This allows frameworks to run initialization code on pages. + server: string; + // This runs when "server" code fails to load due to an exception. + fallback: string; + + // This is how environment variables and .env is configured. + define?: Define; +}; + +// Bun's filesystem router +// Currently, Bun supports pages by either an absolute match or a parameter match. +// pages/index.tsx will be executed on navigation to "/" and "/index" +// pages/posts/[id].tsx will be executed on navigation to "/posts/123" +// Routes & parameters are automatically passed to `fallback` and `server`. +type Router = { + // This determines the folder to look for pages + dir: string[]; + + // These are the allowed file extensions for pages. + extensions?: string[]; +}; +``` + +To use a framework, you pass `bun bun --use package-name`. + +Your framework’s `package.json` `name` should start with `bun-framework-`. This is so that people can type something like `bun bun --use next` and it will check `bun-framework-next` first. This is similar to how Babel plugins tend to start with `babel-plugin-`. + +For developing frameworks, you can also do `bun bun --use ./relative-path-to-framework`. + +If you’re interested in adding a framework integration, please reach out. There’s a lot here, and it’s not entirely documented yet. diff --git a/docs/dev/nextjs.md b/docs/dev/nextjs.md new file mode 100644 index 000000000..92849c989 --- /dev/null +++ b/docs/dev/nextjs.md @@ -0,0 +1,33 @@ +To create a new Next.js app with bun: + +```bash +$ bun create next ./app +$ cd app +$ bun dev # start dev server +``` + +To use an existing Next.js app with bun: + +```bash +$ bun add bun-framework-next +$ echo "framework = 'next'" > bunfig.toml +$ bun bun # bundle dependencies +$ bun dev # start dev server +``` + +Many of Next.js’ features are supported, but not all. + +Here’s what doesn’t work yet: + +- `getStaticPaths` +- same-origin `fetch` inside of `getStaticProps` or `getServerSideProps` +- locales, zones, `assetPrefix` (workaround: change `--origin \"http://localhost:3000/assetPrefixInhere\"`) +- `next/image` is polyfilled to a regular `<img src>` tag. +- `proxy` and anything else in `next.config.js` +- API routes, middleware (middleware is easier to support, though! Similar SSR API) +- styled-jsx (technically not Next.js, but often used with it) +- React Server Components + +When using Next.js, Bun automatically reads configuration from `.env.local`, `.env.development` and `.env` (in that order). `process.env.NEXT_PUBLIC_` and `process.env.NEXT_` automatically are replaced via `--define`. + +Currently, any time you import new dependencies from `node_modules`, you will need to re-run `bun bun --use next`. This will eventually be automatic. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..d97e9786a --- /dev/null +++ b/docs/index.md @@ -0,0 +1,64 @@ +Bun is an all-in-one toolkit for building JavaSript and TypeScript apps. It ships as a single executable called `bun`. + +At its core is the _Bun runtime_, a fast JavaScript runtime designed as a drop-in replacement for Node.js. It's written in Zig and powered by JavaScriptCore under the hood. Use `bun run <file>` to execute JavaScript and TypeScript files with Bun. + +The `bun` command-line tool also implements an npm-compatible package manager, test runner, and bundler, all written in Zig and designed for speed. Use `bun` to speed up your development workflow, even if you aren't using the runtime. + +{% callout type="note" %} +**Bun is not yet ready for use in production**. Join the [Discord](https://bun.sh/discord) and watch the [GitHub repository](https://github.com/oven-sh/bun) to keeps tabs on future releases. +{% /callout %} + +### Get started + +{% block className="gap-[15px] grid grid-flow-row grid-cols-1 md:grid-cols-2" %} +{% arrowbutton href="/docs/installation" text="Install Bun" /%} +{% arrowbutton href="/docs/quickstart" text="Do the quickstart" /%} +{% arrowbutton href="/docs/cli/install" text="Install a package" /%} +{% arrowbutton href="/docs/api/http" text="Build an HTTP server" /%} +{% arrowbutton href="/docs/api/websockets" text="Build a Websocket server" /%} +{% arrowbutton href="/docs/api/file-io" text="Read and write files" /%} +{% arrowbutton href="/docs/api/sqlite" text="Run SQLite queries" /%} +{% arrowbutton href="/docs/cli/test" text="Write and run tests" /%} +{% /block %} + +## What is a runtime? + +JavaScript (or, more formally, ECMAScript) is just a _specification_ for a programming language. Anyone can write a JavaScript _engine_ that ingests a valid JavaScript program and executes it. The two most popular engines in use today are V8 (developed by Google) and JavaScriptCore (developed by Apple). Both are open source. + +### Browsers + +But most JavaScript programs don't run a vacuum. They need a way to access to the outside world to perform useful tasks. This is where _runtimes_ come in. They implement additional APIs that are then made available to the JavaScript programs they execute. Notably, browsers ship with JavaScript runtimes that implement a set of Web-specific APIs that are exposed via the global `window` object. Any JavaScript code executed by the browser can use these APIs to implement interactive or dynamic behavior in the context of the current webpage. + +<!-- JavaScript runtime that exposes JavaScript engines are designed to run "vanilla" JavaScript programs, but it's often JavaScript _runtimes_ use an engine internally to execute the code and implement additional APIs that are then made available to executed programs. +JavaScript was [initially designed](https://en.wikipedia.org/wiki/JavaScript) as a language to run in web browsers to implement interactivity and dynamic behavior in web pages. Browsers are the first JavaScript runtimes. JavaScript programs that are executed in browsers have access to a set of Web-specific global APIs on the `window` object. --> + +### Node.js + +Similarly, Node.js is a JavaScript runtime that can be used in non-browser environments, like servers. JavaScript programs executed by Node.js have access to a set of Node.js-specific [globals](https://nodejs.org/api/globals.html) like `Buffer`, `process`, and `__dirname` in addition to built-in modules for performing OS-level tasks like reading/writing files (`node:fs`) and networking (`node:net`, `node:http`). Node.js also implements a CommonJS-based module system and resolution algorithm that pre-dates JavaScript's native module system. + +<!-- Bun.js prefers Web API compatibility instead of designing new APIs when possible. Bun.js also implements some Node.js APIs. --> + +Bun is designed as a faster, leaner, more modern replacement for Node.js. + +<!-- ## Why a new runtime? + +Bun is designed as a faster, leaner, more modern replacement for Node.js. Node.js is burdened by ingrained performance issues, backwards compatibility concerns, and slow development velocity—inevitable issues for a project of its age and magnitude. --> + +## Design goals + +Bun is designed from the ground-up with the today's JavaScript ecosystem in mind. + +- **Speed**. Bun processes start [4x faster than Node.js](https://twitter.com/jarredsumner/status/1499225725492076544) currently (try it yourself!) +- **TypeScript & JSX support**. You can directly execute `.jsx`, `.ts`, and `.tsx` files; Bun's transpiler converts these to vanilla JavaScript before execution. +- **ESM & CommonJS compatibility**. Internally, Bun uses ESM exclusively, but CommonJS modules can be imported as-is. +- **Web-standard APIs**. Bun implements standard Web APIs like `fetch`, `WebSocket`, and `ReadableStream`. Bun is powered by the JavaScriptCore engine, which is developed by Apple for Safari, so some APIs like [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) and [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) directly use [Safari's implementation](https://github.com/oven-sh/bun/blob/HEAD/src/bun.js/bindings/webcore/JSFetchHeaders.cpp). +- **Node.js compatibility**. In addition to supporting Node-style module resolution, Bun aims for full compatibility with built-in Node.js globals (`process`, `Buffer`) and modules (`path`, `fs`, `http`, etc.) _This is an ongoing effort that is not complete._ Refer to the compatibility page for the current status. + +Bun is more than a runtime. The long-term goal is to be a cohesive, infrastructural toolkit for building apps with JavaScript/TypeScript, including a package manager, transpiler, bundler, script runner, test runner, and more. + +<!-- - tsconfig.json `"paths"` is natively supported, along with `"exports"` in package.json +- `fs`, `path`, and `process` from Node.js are partially implemented +- Web APIs like [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) and more are built-in +- [`HTMLRewriter`](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/) makes it easy to transform HTML in Bun.js +- `.env` files automatically load into `process.env` and `Bun.env` +- top level await --> diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 000000000..48d3e188c --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,111 @@ +Bun ships as a single executable that can be installed a few different ways. + +{% callout %} +**Windows users** — Bun does not currently provide a native Windows build. We're working on this; progress can be tracked at [this issue](https://github.com/oven-sh/bun/issues/43). In the meantime, use one of the installation methods below for Windows Subsystem for Linux. + +**Linux users** — Kernel version 5.6 or higher is strongly recommended, but the minimum is 5.1. +{% /callout %} + +{% codetabs %} + +```bash#Native +$ curl -fsSL https://bun.sh/install | bash # for macOS, Linux, and WSL +``` + +```bash#npm +$ npm install -g bun # the last `npm` command you'll ever need +``` + +```bash#Homebrew +$ brew tap oven-sh/bun # for macOS and Linux +$ brew install bun +``` + +```bash#Docker +$ docker pull oven/bun:edge +$ docker run --rm --init --ulimit memlock=-1:-1 oven/bun:edge +``` + +{% /codetabs %} + +## Upgrading + +Once installed, the binary can upgrade itself. + +```sh +$ bun upgrade +``` + +{% callout %} +**Homebrew users** — To avoid conflicts with Homebrew, use `brew upgrade bun` instead. +{% /callout %} + +Bun automatically releases an (untested) canary build on every commit to `main`. To upgrade to the latest canary build: + +```sh +$ bun upgrade --canary +``` + +[View canary build](https://github.com/oven-sh/bun/releases/tag/canary) + +<!-- +## Native + +Works on macOS x64 & Silicon, Linux x64, Windows Subsystem for Linux. + +```sh +$ curl -fsSL https://bun.sh/install | bash +``` + +Once installed, the binary can upgrade itself. + +```sh +$ bun upgrade +``` + +Bun automatically releases an (untested) canary build on every commit to `main`. To upgrade to the latest canary build: + +```sh +$ bun upgrade --canary +``` + +## Homebrew + +Works on macOS and Linux + +```sh +$ brew tap oven-sh/bun +$ brew install bun +``` + +Homebrew recommends using `brew upgrade <package>` to install newer versions. + +## Docker + +Works on Linux x64 + +```sh +# this is a comment +$ docker pull oven/bun:edge +this is some output +$ docker run --rm --init --ulimit memlock=-1:-1 oven/bun:edge +$ docker run --rm --init --ulimit memlock=-1:-1 oven/bun:edge +this is some output +``` --> + +## Completions + +Shell auto-completion should be configured automatically when Bun is installed. + +If not, run the following command. It uses `$SHELL` to determine which shell you're using and writes a completion file to the appropriate place on disk. It's automatically re-run on every `bun upgrade`. + +```bash +$ bun completions +``` + +To write the completions to a custom location: + +```bash +$ bun completions > path-to-file # write to file +$ bun completions /path/to/directory # write into directory +``` diff --git a/docs/nav.ts b/docs/nav.ts new file mode 100644 index 000000000..79953347e --- /dev/null +++ b/docs/nav.ts @@ -0,0 +1,95 @@ +export type Nav = { + items: NavItem[]; +}; + +export type NavItem = NavPage | NavDivider; +export type NavPage = { + type: "page"; + slug: string; + title: string; +}; +type NavDivider = { + type: "divider"; + title: string; +}; + +function page(slug: string, title: string): NavPage { + return { type: "page", slug, title }; +} +function divider(title: string): NavDivider { + return { type: "divider", title }; +} + +export default { + items: [ + divider("Intro"), + page("index", "What is Bun?"), + page("installation", "Installation"), + page("quickstart", "Quickstart"), + page("typescript", "TypeScript"), + + divider("CLI"), + page("cli/run", "`bun run`"), + page("cli/install", "`bun install`"), + page("cli/test", "`bun test`"), + page("cli/create", "`bun create`"), + // page("bundler", "Bundler"), + // page("cli/bun-install", "`bun install`"), + // page("cli/bun-create", "`bun create`"), + // page("cli/bun-upgrade", "`bun upgrade`"), + // page("cli/bun-bun", "`bun bun`"), + // page("cli/bun-init", "`bun init`"), + // page("cli/bun-completions", "`bun completions`"), + // page("bundev", "Dev server"), + // page("benchmarks", "Benchmarks"), + + divider("Runtime"), + page("runtime/index", "Runtime"), + // page("runtime/web-apis", "Web APIs"), + page("runtime/modules", "Module resolution"), + page("runtime/hot", "Hot reloading"), + // page("runtime/loaders", "Loaders"), + page("runtime/plugins", "Plugins"), + page("runtime/nodejs", "Node.js APIs"), + + divider("API"), + page("api/http", "HTTP"), // "`Bun.serve`"), + page("api/websockets", "WebSockets"), // "`Bun.serve`"), + page("api/tcp", "TCP Sockets"), // "`Bun.{listen|connect}`"), + page("api/file-io", "File I/O"), // "`Bun.write`"), + page("api/sqlite", "SQLite"), // "`bun:sqlite`"), + page("api/file-system-router", "FileSystemRouter"), // "`Bun.FileSystemRouter`"), + page("api/globals", "Globals"), // "`Bun.write`"), + page("api/spawn", "Spawn"), // "`Bun.spawn`"), + page("api/transpiler", "Transpiler"), // "`Bun.Transpiler`"), + page("api/console", "Console"), // "`Node-API`"), + page("api/ffi", "FFI"), // "`bun:ffi`"), + page("api/html-rewriter", "HTMLRewriter"), // "`HTMLRewriter`"), + page("api/test", "Testing"), // "`bun:test`"), + page("api/utils", "Utils"), // "`Bun.peek`"), + page("api/dns", "DNS"), // "`bun:dns`"), + page("api/node-api", "Node-API"), // "`Node-API`"), + + + // divider("Dev Server"), + // page("bun-dev", "Vanilla"), + // page("dev/css", "CSS"), + // page("dev/frameworks", "Frameworks"), + // page("dev/nextjs", "Next.js"), + // page("dev/cra", "Create React App"), + + divider("Project"), + page("project/roadmap", "Roadmap"), + page("project/configuration", "Configuration"), + page("project/profiling", "Profiling"), + page("project/developing", "Development"), + page("project/licensing", "License"), + + // misc + // page("roadmap", "Roadmap"), + // page("troubleshooting", "Troubleshooting"), + // page("bunfig", "bunfig.toml"), + // page("upgrading-webkit", "Upgrading WebKit"), + // page("bun-flavored-toml", "Bun-flavored TOML"), + ], +} satisfies Nav; diff --git a/docs/project/configuration.md b/docs/project/configuration.md new file mode 100644 index 000000000..08eeb13ca --- /dev/null +++ b/docs/project/configuration.md @@ -0,0 +1,182 @@ +## Environment variables + +- `GOMAXPROCS`: For `bun bun`, this sets the maximum number of threads to use. If you’re experiencing an issue with `bun bun`, try setting `GOMAXPROCS=1` to force Bun to run single-threaded +- `DISABLE_BUN_ANALYTICS=1` this disables Bun's analytics. 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 +- `TMPDIR`: Before `bun bun` completes, it stores the new `.bun` in `$TMPDIR`. If unset, `TMPDIR` defaults to the platform-specific temporary directory (on Linux, `/tmp` and on macOS `/private/tmp`) + +## `bunfig.toml` + +Bun's configuration file is called `bunfig.toml`. Configuring with `bunfig.toml` is optional. Bun aims to be zero-configuration out of the box, but is also highly configurable for advanced use cases.. + +Your `bunfig.toml` should live in your project root alongside `package.json`. You can also create a global configuration file at the following paths: + +- `$HOME/.bunfig.toml` +- `$XDG_CONFIG_HOME/.bunfig.toml` + +If both a global and local `bunfig` are detected, the results are shallow-merged, with local overridding global. CLI flags will override `bunfig` setting where applicable. + +## Configure `bun install` + +Package management is a complex issue; to support a range of use cases, the behavior of `bun install` can be configured in [`bunfig.toml`](/docs/project/configuration). + +### Default flags + +The following settings modify the core behavior of Bun's package management commands. **The default values are shown below.** + +```toml +[install] + +# whether to install optionalDependencies +optional = true + +# whether to install devDependencies +dev = true + +# whether to install devDependencies +peer = false + +# equivalent to `--production` flag +production = false + +# equivalent to `--dry-run` flag +dryRun = false +``` + +### Registries + +The default registry is `https://registry.npmjs.org/`. This can be globally configured in `bunfig.toml`: + +```toml +[install] +# set default registry as a string +registry = "https://registry.npmjs.org" +# set a token +registry = { url = "https://registry.npmjs.org", token = "123456" } +# set a username/password +registry = "https://username:password@registry.npmjs.org" +``` + +To configure scoped registries: + +```toml +[install.scopes] +# registry as string +myorg1 = "https://username:password@registry.myorg.com/" + +# registry with username/password +# you can reference environment variables +myorg12 = { username = "myusername", password = "$NPM_PASS", url = "https://registry.myorg.com/" } + +# registry with token +myorg3 = { token = "$npm_token", url = "https://registry.myorg.com/" } +``` + +### Cache + +To configure caching behavior: + +```toml +[install] +# where `bun install --global` installs packages +globalDir = "~/.bun/install/global" + +# where globally-installed package bins are linked +globalBinDir = "~/.bun/bin" + +[install.cache] +# the directory to use for the cache +dir = "~/.bun/install/cache" + +# when true, don't load from the global cache. +# Bun may still write to node_modules/.cache +disable = false + +# when true, always resolve the latest versions from the registry +disableManifest = false +``` + +### Lockfile + +To configure lockfile behavior: + +```toml {% disabled %} +[install.lockfile] + +# path to read bun.lockb from +path = "bun.lockb" + +# path to save bun.lockb to +savePath = "bun.lockb" + +# whether to save the lockfile to disk +save = true + +# whether to save a non-Bun lockfile alongside bun.lockb +# only "yarn" is supported +print = "yarn" +``` + +## Configure `bun dev` + +Here is an example: + +```toml +# Set a default framework to use +# By default, Bun will look for an npm package like `bun-framework-${framework}`, followed by `${framework}` +framework = "next" +logLevel = "debug" + +# publicDir = "public" +# external = ["jquery"] + +[macros] +# Remap any import like this: +# import {graphql} from 'react-relay'; +# To: +# import {graphql} from 'macro:bun-macro-relay'; +react-relay = { "graphql" = "bun-macro-relay" } + +[bundle] +saveTo = "node_modules.bun" +# Don't need this if `framework` is set, but showing it here as an example anyway +entryPoints = ["./app/index.ts"] + +[bundle.packages] +# If you're bundling packages that do not actually live in a `node_modules` folder or do not have the full package name in the file path, you can pass this to bundle them anyway +"@bigapp/design-system" = true + +[dev] +# Change the default port from 3000 to 5000 +# Also inherited by Bun.serve +port = 5000 + +[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. +# This will probably change in a future release to be just regular TOML instead. It is a holdover from the CLI argument parsing. +"process.env.bagel" = "'lox'" + +[loaders] +# When loading a .bagel file, run the JS parser +".bagel" = "js" + +[debug] +# When navigating to a blob: or src: link, open the file in your editor +# If not, it tries $EDITOR or $VISUAL +# If that still fails, it will try Visual Studio Code, then Sublime Text, then a few others +# This is used by Bun.openInEditor() +editor = "code" + +# List of editors: +# - "subl", "sublime" +# - "vscode", "code" +# - "textmate", "mate" +# - "idea" +# - "webstorm" +# - "nvim", "neovim" +# - "vim","vi" +# - "emacs" +# - "atom" +# If you pass it a file path, it will open with the file path instead +# It will recognize non-GUI editors, but I don't think it will work yet +``` diff --git a/docs/project/developing.md b/docs/project/developing.md new file mode 100644 index 000000000..e532a3d0a --- /dev/null +++ b/docs/project/developing.md @@ -0,0 +1,227 @@ +{% callout %} +**⚠️ Warning** — Bun currently needs about 22 GB of RAM to compile. If you have less than that, it will be difficult or impossible to building Bun locally. We're working on improving this. +{% /callout %} + +Configuring a development environment for Bun usually takes 30-90 minutes depending on your operating system. + +## Linux/Windows + +The best way to build Bun on Linux and Windows is with the official [Dev Container](https://containers.dev). It ships with Zig, JavaScriptCore, Zig Language Server, `vscode-zig`, and more pre-installed on an instance of Ubuntu. + +{% image src="https://user-images.githubusercontent.com/709451/147319227-6446589c-a4d9-480d-bd5b-43037a9e56fd.png" /%} + +To develop on Linux/Windows, [Docker](https://www.docker.com) is required. If using WSL on Windows, it is recommended to use [Docker Desktop](https://docs.microsoft.com/en-us/windows/wsl/tutorials/wsl-containers) for its WSL2 integration. + +### VSCode + +If you're using VSCode, you'll need to have the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension installed. + +To get started, open VS Code in the `bun` repository. The first time you try to open the dev container, the extension will automatically build it for you, based on [`Dockerfile.devcontainer`](Dockerfile.devcontainer). + +To open the dev container, open the command palette (`Ctrl` + `Shift` + `P`) and run: `Dev Containers: Reopen in Container`. To later rebuild it (only needed when the devcontainer itself changes, not the Bun code), run: `Dev Containers: Rebuild and Reopen in Container`. + +### Other editors and CLI + +If you're using another editor or want to manually control the dev container from the command line or a script, you'll need to install the [Dev Container CLI](https://www.npmjs.com/package/@devcontainers/cli): `npm install -g @devcontainers/cli`. + +To create and start the dev container, in the `bun` repository, locally run: + +```bash +# `make devcontainer-<command>` should be equivalent +# to `devcontainer <command>`, it just sets the architecture +# so if you're on ARM64, it'll do the right thing +$ make devcontainer-up +``` + +To just build the dev container image, run: + +```bash +$ make devcontainer-build +``` + +To start a shell inside the container, run: + +```bash +$ make devcontainer-sh + +# if it attaches to the container non-interactively, +# instead use the regular docker exec command: +$ docker exec -it <container-name/id> zsh +``` + +### Cloning + +You will then need to clone the GitHub repository inside that container. + +```bash +# First time setup +$ gh auth login # if it fails to open a browser, use Personal Access Token instead +$ gh repo clone oven-sh/bun . -- --depth=1 --progress -j8 +``` + +### Building + +```bash +# Compile Bun dependencies (zig is already compiled) +$ make devcontainer + +# It initializes and updates all submodules except WebKit, because WebKit +# takes a while and it's already compiled for you. To do it manually, use: +$ git -c submodule."src/bun.js/WebKit".update=none submodule update --init --recursive --depth=1 --progress + +# Build Bun for development +$ make dev + +# Run Bun +$ bun-debug +``` + +## MacOS + +Install LLVM 15 and `homebrew` dependencies: + +```bash +$ brew install llvm@15 coreutils libtool cmake libiconv automake ninja gnu-sed pkg-config esbuild go rust +``` + +Bun (& the version of Zig) need LLVM 15 and Clang 15 (`clang` is part of LLVM). Make sure LLVM 15 is in your `$PATH`: + +```bash +$ which clang-15 +``` + +If not, run this to manually link it: + +```bash#bash +# use fish_add_path if you're using fish +$ export PATH="$PATH:$(brew --prefix llvm@15)/bin" +$ export LDFLAGS="$LDFLAGS -L$(brew --prefix llvm@15)/lib" +$ export CPPFLAGS="$CPPFLAGS -I$(brew --prefix llvm@15)/include" +``` + +### Install Zig + +{% callout %} +**⚠️ Warning** — You must use the same version of Zig used by Bun in [oven-sh/zig](https://github.com/oven-sh/zig). Installing with `brew` or via Zig's download page will not work! +{% /callout %} + +Use [`zigup`](<[zigup](https://github.com/marler8997/zigup)>) to install the version of Zig (`ZIG_VERSION`) specified in the official [`Dockerfile`](https://github.com/oven-sh/bun/blob/main/Dockerfile). For example: + +```bash +$ zigup 0.11.0-dev.1393+38eebf3c4 +``` + +### Building + +To install and build dependencies: + +```bash +# without --depth=1 this will take 20+ minutes on 1gbps internet +# mostly due to WebKit +$ git submodule update --init --recursive --progress --depth=1 --checkout +$ bun install +$ make vendor identifier-cache webcrypto-debug +``` + +To compile the C++ bindings: + +```bash +# without -j this will take 30+ minutes +$ make bindings -j12 +``` + +<!-- If you're building on a macOS device, you'll need to have a valid Developer Certificate, or else the code signing step will fail. To check if you have one, open the `Keychain Access` app, go to the `login` profile and search for `Apple Development`. You should have at least one certificate with a name like `Apple Development: user@example.com (WDYABC123)`. If you don't have one, follow [this guide](https://ioscodesigning.com/generating-code-signing-files/#generate-a-code-signing-certificate-using-xcode) to get one. --> + +<!-- You can still work with the generated binary locally at `packages/debug-bun-*/bun-debug` even if the code signing fails. --> + +### Testing + +To verify the build worked, lets print the version number on the development build of Bun. + +```bash +$ packages/debug-bun-darwin-*/bun-debug --version +bun 0.x.y__dev +``` + +You will want to add `packages/debug-bun-darwin-arm64/` or `packages/debug-bun-darwin-x64/` (depending on your architecture) to `$PATH` so you can run `bun-debug` from anywhere. + +### Troubleshooting + +If you see an error when compiling `libarchive`, run this: + +```bash +$ brew install pkg-config +``` + +If you see an error about missing files on `zig build obj`, make sure you built the headers. + +```bash +$ make headers +``` + +## JavaScript builtins + +When you change anything in `src/bun.js/builtins/js/*`, run this: + +```bash +$ make clean-bindings generate-builtins && make bindings -j12 +``` + +That inlines the JavaScript code into C++ headers using the same builtins generator script that Safari uses. + +## Code generation scripts + +Bun leverages a lot of code generation scripts. + +The [./src/bun.js/bindings/headers.h](https://github.com/oven-sh/bun/blob/main/src/bun.js/bindings/headers.h) file has bindings to & from Zig <> C++ code. This file is generated by running the following: + +```bash +$ make headers +``` + +This ensures that the types for Zig and the types for C++ match up correctly, by using comptime reflection over functions exported/imported. + +TypeScript files that end with `*.classes.ts` are another code generation script. They generate C++ boilerplate for classes implemented in Zig. The generated code lives in: + +- [src/bun.js/bindings/ZigGeneratedClasses.cpp](src/bun.js/bindings/ZigGeneratedClasses.cpp) +- [src/bun.js/bindings/ZigGeneratedClasses.h](src/bun.js/bindings/ZigGeneratedClasses.h) +- [src/bun.js/bindings/generated_classes.zig](src/bun.js/bindings/generated_classes.zig) + To generate the code, run: + +```bash +$ make codegen +``` + +Lastly, we also have a [code generation script](src/bun.js/scripts/generate-jssink.js) for our native stream implementations. +To run that, run: + +```bash +$ make generate-sink +``` + +You probably won't need to run that one much. + +## Modifying ESM core modules + +Certain modules like `node:fs`, `node:path`, `node:stream`, and `bun:sqlite` are implemented in JavaScript. These live in `src/bun.js/*.exports.js` files. + +While Bun is in beta, you can modify them at runtime in release builds via the environment variable `BUN_OVERRIDE_MODULE_PATH`. When set, Bun will look in the override directory for `<name>.exports.js` before checking the files from `src/bun.js` (which are now baked in to the binary). This lets you test changes to the ESM modules without needing to re-compile Bun. + +## `vscode-zig` + +{% callout %} +**Note** — This is automatically installed on the devcontainer. +{% /callout %} + +We maintain a fork of the `vscode-zig` extension that adds a `Run test` and a `Debug test` button into the dev environment. To install it: + +```bash +$ curl -L https://github.com/Jarred-Sumner/vscode-zig/releases/download/fork-v1/zig-0.2.5.vsix > vscode-zig.vsix +$ code --install-extension vscode-zig.vsix +``` + +{% image src="https://pbs.twimg.com/media/FBZsKHlUcAYDzm5?format=jpg&name=large" href="https://github.com/jarred-sumner/vscode-zig" /%} + +## Troubleshooting + +If you encounter `error: the build command failed with exit code 9` during the build process, this means you ran out of memory or swap. Bun currently needs about 22 GB of RAM to compile. diff --git a/docs/project/licensing.md b/docs/project/licensing.md new file mode 100644 index 000000000..ef88d0674 --- /dev/null +++ b/docs/project/licensing.md @@ -0,0 +1,207 @@ +Bun itself is MIT-licensed. + +## JavaScriptCore + +Bun statically links JavaScriptCore (and WebKit) which is LGPL-2 licensed. WebCore files from WebKit are also licensed under LGPL2. Per LGPL2: + +> (1) If you statically link against an LGPL’d library, you must also provide your application in an object (not necessarily source) format, so that a user has the opportunity to modify the library and relink the application. + +You can find the patched version of WebKit used by Bun here: <https://github.com/oven-sh/webkit>. If you would like to relink Bun with changes: + +- `git submodule update --init --recursive` +- `make jsc` +- `zig build` + +This compiles JavaScriptCore, compiles Bun’s `.cpp` bindings for JavaScriptCore (which are the object files using JavaScriptCore) and outputs a new `bun` binary with your changes. + +## Linked libraries + +Bun statically links these libraries: + +{% table %} + +- Library +- License + +--- + +- [`boringssl`](https://boringssl.googlesource.com/boringssl/) +- [several licenses](https://boringssl.googlesource.com/boringssl/+/refs/heads/master/LICENSE) + +--- + +- [`libarchive`](https://github.com/libarchive/libarchive) +- [several licenses](https://github.com/libarchive/libarchive/blob/master/COPYING) + +--- + +- [`lol-html`](https://github.com/cloudflare/lol-html/tree/master/c-api) +- BSD 3-Clause + +--- + +- [`mimalloc`](https://github.com/microsoft/mimalloc) +- MIT + +--- + +- [`picohttp`](https://github.com/h2o/picohttpparser) +- dual-licensed under the Perl License or the MIT License + +--- + +- [`simdutf`](https://github.com/simdutf/simdutf) +- Apache 2.0 + +--- + +- [`tinycc`](https://github.com/tinycc/tinycc) +- LGPL v2.1 + +--- + +- [`uSockets`](https://github.com/uNetworking/uSockets) +- Apache 2.0 + +--- + +- [`zlib-cloudflare`](https://github.com/cloudflare/zlib) +- zlib + +--- + +- [`c-ares`](https://github.com/c-ares/c-ares) +- MIT licensed + +--- + +- [`libicu`](https://github.com/unicode-org/icu) 72 +- [license here](https://github.com/unicode-org/icu/blob/main/icu4c/LICENSE) + +--- + +- A fork of [`uWebsockets`](https://github.com/jarred-sumner/uwebsockets) +- Apache 2.0 licensed + +{% /table %} + +## Polyfills + +For compatibility reasons, the following packages are embedded into Bun's binary and injected if imported. + +{% table %} + +- Package +- License + +--- + +- [`assert`](https://npmjs.com/package/assert) +- MIT + +--- + +- [`browserify-zlib`](https://npmjs.com/package/browserify-zlib) +- MIT + +--- + +- [`buffer`](https://npmjs.com/package/buffer) +- MIT + +--- + +- [`constants-browserify`](https://npmjs.com/package/constants-browserify) +- MIT + +--- + +- [`crypto-browserify`](https://npmjs.com/package/crypto-browserify) +- MIT + +--- + +- [`domain-browser`](https://npmjs.com/package/domain-browser) +- MIT + +--- + +- [`events`](https://npmjs.com/package/events) +- MIT + +--- + +- [`https-browserify`](https://npmjs.com/package/https-browserify) +- MIT + +--- + +- [`os-browserify`](https://npmjs.com/package/os-browserify) +- MIT + +--- + +- [`path-browserify`](https://npmjs.com/package/path-browserify) +- MIT + +--- + +- [`process`](https://npmjs.com/package/process) +- MIT + +--- + +- [`punycode`](https://npmjs.com/package/punycode) +- MIT + +--- + +- [`querystring-es3`](https://npmjs.com/package/querystring-es3) +- MIT + +--- + +- [`stream-browserify`](https://npmjs.com/package/stream-browserify) +- MIT + +--- + +- [`stream-http`](https://npmjs.com/package/stream-http) +- MIT + +--- + +- [`string_decoder`](https://npmjs.com/package/string_decoder) +- MIT + +--- + +- [`timers-browserify`](https://npmjs.com/package/timers-browserify) +- MIT + +--- + +- [`tty-browserify`](https://npmjs.com/package/tty-browserify) +- MIT + +--- + +- [`url`](https://npmjs.com/package/url) +- MIT + +--- + +- [`util`](https://npmjs.com/package/util) +- MIT + +--- + +- [`vm-browserify`](https://npmjs.com/package/vm-browserify) +- MIT + +{% /table %} + +## Additional credits + +- Bun's JS transpiler, CSS lexer, and Node.js module resolver source code is a Zig port of [@evanw](https://github.com/evanw)’s [esbuild](https://github.com/evanw/esbuild) project. +- Credit to [@kipply](https://github.com/kipply) for the name "Bun"! diff --git a/docs/project/profiling.md b/docs/project/profiling.md new file mode 100644 index 000000000..d734588b2 --- /dev/null +++ b/docs/project/profiling.md @@ -0,0 +1,193 @@ +To precisely measure time, Bun offers two runtime APIs functions: + +1. The web-standard [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) function +2. `Bun.nanoseconds()` which is similar to `performance.now()` except it returns the current time since the application started in nanoseconds. You can use `performance.timeOrigin` to convert this to a Unix timestamp. + +## Benchmarking `Bun.serve` + +You will need an HTTP client which is at least as fast as `Bun.serve()`. + +That means popular Node.js-based benchmarking tools like **autocannon is not fast enough**. + +Recommended HTTP clients: + +- [`bombardier`](https://github.com/codesenberg/bombardier) +- [`oha`](https://github.com/hatoo/oha) +- [`http_load_test`](https://github.com/uNetworking/uSockets/blob/master/examples/http_load_test.c) + +## Measuring memory usage + +Bun has two heaps. One heap is for the JavaScript runtime and the other heap is for everything else. + +### JavaScript heap stats + +The `bun:jsc` module exposes a few functions for measuring memory usage: + +```ts +import { heapStats } from "bun:jsc"; +console.log(heapStats()); + +// will show something like this: +{ + heapSize: 1657575, + heapCapacity: 2872775, + extraMemorySize: 598199, + objectCount: 13790, + protectedObjectCount: 62, + globalObjectCount: 1, + protectedGlobalObjectCount: 1, + // A count of every object type in the heap + objectTypeCounts: { + CallbackObject: 25, + FunctionExecutable: 2078, + AsyncGeneratorFunction: 2, + 'RegExp String Iterator': 1, + FunctionCodeBlock: 188, + ModuleProgramExecutable: 13, + String: 1, + UnlinkedModuleProgramCodeBlock: 13, + JSON: 1, + AsyncGenerator: 1, + Symbol: 1, + GetterSetter: 68, + ImportMeta: 10, + DOMAttributeGetterSetter: 1, + UnlinkedFunctionCodeBlock: 174, + RegExp: 52, + ModuleLoader: 1, + Intl: 1, + WeakMap: 4, + Generator: 2, + PropertyTable: 95, + 'Array Iterator': 1, + JSLexicalEnvironment: 75, + UnlinkedFunctionExecutable: 2067, + WeakSet: 1, + console: 1, + Map: 23, + SparseArrayValueMap: 14, + StructureChain: 19, + Set: 18, + 'String Iterator': 1, + FunctionRareData: 3, + JSGlobalLexicalEnvironment: 1, + Object: 481, + BigInt: 2, + StructureRareData: 55, + Array: 179, + AbortController: 2, + ModuleNamespaceObject: 11, + ShadowRealm: 1, + 'Immutable Butterfly': 103, + Primordials: 1, + 'Set Iterator': 1, + JSProxy: 1, + AsyncFromSyncIterator: 1, + ModuleRecord: 13, + FinalizationRegistry: 1, + AsyncIterator: 1, + InternalPromise: 22, + Iterator: 1, + CustomGetterSetter: 65, + Promise: 19, + WeakRef: 1, + InternalPromisePrototype: 1, + Function: 2381, + AsyncFunction: 2, + GlobalObject: 1, + ArrayBuffer: 2, + Boolean: 1, + Math: 1, + CallbackConstructor: 1, + Error: 2, + JSModuleEnvironment: 13, + WebAssembly: 1, + HashMapBucket: 300, + Callee: 3, + symbol: 37, + string: 2484, + Performance: 1, + ModuleProgramCodeBlock: 12, + JSSourceCode: 13, + JSPropertyNameEnumerator: 3, + NativeExecutable: 290, + Number: 1, + Structure: 1550, + SymbolTable: 108, + GeneratorFunction: 2, + 'Map Iterator': 1 + }, + protectedObjectTypeCounts: { + CallbackConstructor: 1, + BigInt: 1, + RegExp: 2, + GlobalObject: 1, + UnlinkedModuleProgramCodeBlock: 13, + HashMapBucket: 2, + Structure: 41, + JSPropertyNameEnumerator: 1 + } +} +``` + +JavaScript is a garbage-collected language, not reference counted. It's normal and correct for objects to not be freed immediately in all cases, though it's not normal for objects to never be freed. + +You can force garbage collection to run manually by calling: + +```js +const synchronously = true; +Bun.gc(synchronously); +``` + +### JavaScript heap snapshot + +Heap snapshots let you inspect what objects are not being freed. You can use the `bun:jsc` module to take a heap snapshot and then view it with Safari or WebKit GTK developer tools. + +{% image alt="image" src="https://user-images.githubusercontent.com/709451/204429337-b0d8935f-3509-4071-b991-217794d1fb27.png" /%} + +To generate a heap snapshot: + +```ts +import { generateHeapSnapshot } from "bun"; + +const snapshot = generateHeapSnapshot(); +await Bun.write("heap.json", JSON.stringify(snapshot, null, 2)); +``` + +To view the snapshot, open the `heap.json` file in Safari's Developer Tools (or WebKit GTK) + +1. Open the Developer Tools +2. Click "Timeline" +3. Click "JavaScript Allocations" in the menu on the left. It might not be visible until you click the pencil icon to show all the timelines +4. Click "Import" and select your heap snapshot JSON + + + +### Native heap stats + +Bun uses mimalloc for the other heap. To report a summary of non-JavaScript memory usage, set the `MIMALLOC_SHOW_STATS=1` environment variable. and stats will print on exit. + +```js +MIMALLOC_SHOW_STATS=1 bun script.js + +# will show something like this: +heap stats: peak total freed current unit count + reserved: 64.0 MiB 64.0 MiB 0 64.0 MiB not all freed! + committed: 64.0 MiB 64.0 MiB 0 64.0 MiB not all freed! + reset: 0 0 0 0 ok + touched: 128.5 KiB 128.5 KiB 5.4 MiB -5.3 MiB ok + segments: 1 1 0 1 not all freed! +-abandoned: 0 0 0 0 ok + -cached: 0 0 0 0 ok + pages: 0 0 53 -53 ok +-abandoned: 0 0 0 0 ok + -extended: 0 + -noretire: 0 + mmaps: 0 + commits: 0 + threads: 0 0 0 0 ok + searches: 0.0 avg +numa nodes: 1 + elapsed: 0.068 s + process: user: 0.061 s, system: 0.014 s, faults: 0, rss: 57.4 MiB, commit: 64.0 MiB +``` diff --git a/docs/project/roadmap.md b/docs/project/roadmap.md new file mode 100644 index 000000000..7c5c28cf6 --- /dev/null +++ b/docs/project/roadmap.md @@ -0,0 +1,87 @@ +Bun is a project with an incredibly large scope and is still in its early days. Long-term, Bun aims to provide an all-in-one tookit to replace the complex, fragmented toolchains common today: Node.js, Jest, Webpack, esbuild, Babel, yarn, PostCSS, etc. + +Refer to [Bun's Roadmap](https://github.com/oven-sh/bun/issues/159) on GitHub to learn more about the project's long-term plans and priorities. + +<!-- +{% table %} + +- Feature +- Implemented in + +--- + +- Web Streams with HTMLRewriter +- Bun.js + +--- + +- Source Maps (unbundled is supported) +- JS Bundler + +--- + +- Source Maps +- CSS + +--- + +- JavaScript Minifier +- JS Transpiler + +--- + +- CSS Minifier +- CSS + +--- + +- CSS Parser (it only bundles) +- CSS + +--- + +- Tree-shaking +- JavaScript + +--- + +- Tree-shaking +- CSS + +--- + +- [TypeScript Decorators](https://www.typescriptlang.org/docs/handbook/decorators.html) +- TS Transpiler + +--- + +- `@jsxPragma` comments +- JS Transpiler + +--- + +- Sharing `.bun` files +- Bun + +--- + +- Dates & timestamps +- TOML parser + +--- + +- [Hash components for Fast Refresh](https://github.com/oven-sh/bun/issues/18) +- JSX Transpiler + +{% /table %} --> + +<!-- ## Limitations & intended usage + +Today, Bun is mostly focused on Bun.js: the JavaScript runtime. + +While you could use Bun's bundler & transpiler separately to build for browsers or node, Bun doesn't have a minifier or support tree-shaking yet. For production browser builds, you probably should use a tool like esbuild or swc. + +## Upcoming breaking changes + +- Bun's CLI flags will change to better support Bun as a JavaScript runtime. They were chosen when Bun was just a frontend development tool. +- Bun's bundling format will change to accommodate production browser bundles and on-demand production bundling --> diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 000000000..a6b68b42c --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,117 @@ +Let's write a simple HTTP server using the built-in `Bun.serve` API. First, create a fresh directory. + +```bash +$ mkdir quickstart +$ cd quickstart +``` + +Run `bun init` to scaffold a new project. It's an interactive tool; for this tutorial, just press `enter` to accept the default answer for each prompt. + +```bash +$ bun init +bun init helps you get started with a minimal project and tries to +guess sensible defaults. Press ^C anytime to quit. + +package name (quickstart): +entry point (index.ts): + +Done! A package.json file was saved in the current directory. + + index.ts + + .gitignore + + tsconfig.json (for editor auto-complete) + + README.md + +To get started, run: + bun run index.ts +``` + +Since our entry point is a `*.ts` file, Bun generates a `tsconfig.json` for you. If you're using plain JavaScript, it will generate a [`jsconfig.json`](https://code.visualstudio.com/docs/languages/jsconfig) instead. + +## Run a file + +Open `index.ts` and paste the following code snippet, which implements a simple HTTP server with [`Bun.serve`](/docs/api/http). + +```ts +const server = Bun.serve({ + port: 3000, + fetch(req) { + return new Response(`Bun!`); + }, +}); + +console.log(`Listening on http://localhost:${server.port}...`); +``` + +Run the file from your shell. + +```bash +$ bun index.ts +Listening at http://localhost:3000... +``` + +Visit [http://localhost:3000](http://localhost:3000) to test the server. You should see a simple page that says "Bun!". + +## Run a script + +Bun can also execute `"scripts"` from your `package.json`. Add the following script: + +```json-diff + { + "name": "quickstart", + "module": "index.ts", + "type": "module", ++ "scripts": { ++ "start": "bun run index.ts", ++ }, + "devDependencies": { + "bun-types": "^0.4.0" + } + } +``` + +Then run it with `bun run start`. + +```bash +$ bun run start + $ bun run index.ts + Listening on http://localhost:4000... +``` + +{% callout %} +⚡️ **Performance** — With Bun's fast startup times, this script executes in roughly `6ms`. +By contrast `npm run` adds roughly `170ms` of overhead when executing scripts. +{% /callout %} + +## Install a package + +Let's make our server a little more interesting by installing a package. First install the `figlet` package and its type declarations. Figlet is a utility for converting strings into ASCII art. + +```bash +$ bun add figlet +$ bun add -d @types/figlet # TypeScript users only +``` + +Update `index.ts` to use `figlet` in the `fetch` handler. + +```ts-diff ++ import figlet from "figlet"; + + const server = Bun.serve({ + fetch() { ++ const body = figlet.textSync('Bun!'); ++ return new Response(body); +- return new Response(`Bun!`); + }, + port: 3000, + }); +``` + +Restart the server and refresh the page. You should see a new ASCII art banner. + +```txt + ____ _ + | __ ) _ _ _ __ | | + | _ \| | | | '_ \| | + | |_) | |_| | | | |_| + |____/ \__,_|_| |_(_) +``` diff --git a/docs/runtime/hot.md b/docs/runtime/hot.md new file mode 100644 index 000000000..bdd376011 --- /dev/null +++ b/docs/runtime/hot.md @@ -0,0 +1,93 @@ +{% callout %} +🚧 **Experimental** — Introduced in Bun v0.2.0. +{% /callout %} +Use `bun --hot` to enable hot reloading when executing code with Bun. + +```bash +$ 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 +globalThis.count = globalThis.count ?? 0; +console.log(`Reloaded ${globalThis.count} times`); +globalThis.count++; + +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. The call to `setInterval` is there to prevent the process from exiting. + +```bash +$ bun --hot index.ts +Reloaded 1 times +Reloaded 2 times +Reloaded 3 times +``` + +Traditional file watchers like `nodemon` restart the entire process, so HTTP servers and other stateful objects are lost. By contrast, `bun --hot` is able to reflect the updated code without restarting the process. + +### HTTP servers + +Bun provides the following simplified API for implementing HTTP servers. Refer to [API > HTTP](/docs/api/http) for full details. + +```ts#server.ts +globalThis.count = globalThis.count ?? 0; +globalThis.reloadCount++; + +export default { + fetch(req: Request) { + return new Response(`Reloaded ${globalThis.count} times`); + }, + port: 3000, +}; +``` + +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`. + +Unlike an explicit call to `Bun.serve`, the object-based syntax works out of the box with `bun --hot`. When you save the file, your HTTP server be reloaded with the updated code without the process being restarted. This results in seriously fast refresh speeds. + +{% 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." /%} + +For more fine-grained control, you can use the `Bun.serve` API directly and handle the server reloading manually. + +```ts#server.ts +import type {Serve} from "bun"; + +globalThis.count = globalThis.count ?? 0; +globalThis.reloadCount++; + +// define server parameters +const serverOptions: Serve = { + port: 3000, + fetch(req) { + return new Response(`Reloaded ${globalThis.count} times`); + } +}; + +if (!globalThis.server) { + globalThis.server = Bun.serve(serverOptions); +} else { + // reload server + globalThis.server.reload(serverOptions); +} +``` + +{% callout %} +**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 %} + +{% details summary="Implementation `details`" %} + +On reload, Bun: + +- Resets the internal `require` cache and ES module registry (`Loader.registry`) +- Runs the garbage collector synchronously (to minimize memory leaks, at the cost of runtime performance) +- Re-transpiles all of your code from scratch (including sourcemaps) +- Re-evaluates the code with JavaScriptCore + +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 %} diff --git a/docs/runtime/index.md b/docs/runtime/index.md new file mode 100644 index 000000000..29ea99c0f --- /dev/null +++ b/docs/runtime/index.md @@ -0,0 +1,286 @@ +Bun is a new JavaScript runtime designed to be a faster, leaner, more modern replacement for Node.js. + +## Speed + +Bun is designed to start fast and run fast. It's 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. 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. + +## File types + +Bun natively supports TypeScript and JSX out of the box. + +```bash +$ bun server.tsx +``` + +<!-- 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. It's 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 it's 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 %} --> + +Source files can import a `*.json` or `*.toml` file to load its contents as a plain old JavaScript object. + +```ts +import pkg from "./package.json"; +import bunfig from "./bunfig.toml"; +``` + +As of v0.5.2, experimental support has been for the [WebAssembly System Interface](https://github.com/WebAssembly/WASI) (WASI), you can run `.wasm` binaries. + +```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/packages/wasi-js). Currently, it only supports WASI binaries that use the `wasi_snapshot_preview1` or `wasi_unstable` APIs. Bun's implementation is not optimized for performance, but if this feature gets popular, we'll definitely invest time in making it faster. +{% /callout %} + +Support for additional file types can be implemented with [Plugins](/docs/runtime/plugins). + +## 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 [Runtime > Node.js API](/docs/runtime/nodejs). + +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](/docs/cli/install) for a performance boost over other npm clients. +{% /callout %} + +## Web-standard + +<!-- When prudent, Bun attempts to implement Web-standard APIs instead of introducing new APIs. Refer to [Runtime > Web APIs](/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) [`ResolveError`](https://developer.mozilla.org/en-US/docs/Web/API/ResolveError) + [`BuildError`](https://developer.mozilla.org/en-US/docs/Web/API/BuildError) + +--- + +- 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-native 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](/docs/api/http) +- `Bun.serve` + +--- + +- [File I/O](/docs/api/file-io) +- `Bun.file` `Bun.write` + +--- + +- [Processes](/docs/api/spawn) +- `Bun.spawn` `Bun.spawnSync` + +--- + +- [TCP](/docs/api/tcp) +- `Bun.listen` `Bun.connect` + +--- + +- [Transpiler](/docs/api/transpiler) +- `Bun.Transpiler` + +--- + +- [Routing](/docs/api/file-system-router) +- `Bun.FileSystemRouter` + +--- + +- [HTMLRewriter](/docs/api/html-rewriter) +- `HTMLRewriter` + +--- + +- [Utils](/docs/api/utils) +- `Bun.peek` `Bun.which` + +--- + +- [SQLite](/docs/api/sqlite) +- `bun:sqlite` + +--- + +- [FFI](/docs/api/ffi) +- `bun:ffi` + +--- + +- [DNS](/docs/api/dns) +- `bun:dns` + +--- + +- [Testing](/docs/api/test) +- `bun:test` + +--- + +- [Node-API](/docs/api/node-api) +- `Node-API` + +--- + +{% /table %} diff --git a/docs/runtime/loaders.md b/docs/runtime/loaders.md new file mode 100644 index 000000000..c7977534c --- /dev/null +++ b/docs/runtime/loaders.md @@ -0,0 +1,28 @@ +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. diff --git a/docs/runtime/modules.md b/docs/runtime/modules.md new file mode 100644 index 000000000..f10dad378 --- /dev/null +++ b/docs/runtime/modules.md @@ -0,0 +1,259 @@ +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. + +Bun aims to provide a consistent and predictable module resolution system that just works. Unfortunately it's still quite complex. + +## Syntax + +Consider the following files. + +{% codetabs %} + +```ts#index.ts +import { hello } from "./hello"; + +hello(); +``` + +```ts#hello.ts +export function hello() { + console.log("Hello world!"); +} +``` + +{% /codetabs %} + +When we run `index.ts`, it prints "Hello world". + +```bash +$ bun index.ts +Hello world! +``` + +In this case, we are importing from `./hello`, a relative path with no extension. To resolve this import, Bun will check for the following files in order: + +- `./hello.ts` +- `./hello.tsx` +- `./hello.js` +- `./hello.mjs` +- `./hello.cjs` +- `./hello/index.ts` +- `./hello/index.js` +- `./hello/index.json` +- `./hello/index.mjs` + +Import paths are case-insensitive. + +```ts#index.ts +import { hello } from "./hello"; +import { hello } from "./HELLO"; +import { hello } from "./hElLo"; +``` + +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 +import { hello } from "./hello"; +import { hello } from "./hello.ts"; // this works +``` + +There is one exception: 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 +import { hello } from "./hello"; +import { hello } from "./hello.ts"; // this works +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 %} + +```ts#index.js +const { hello } = require("./hello"); + +hello(); +``` + +```ts#hello.js +function hello() { + console.log("Hello world!"); +} + +exports.hello = hello; +``` + +{% /codetabs %} + +That said, using CommonJS is discouraged in new projects. + +## Resolution + +Bun implements the Node.js module resolution algorithm, so you can import packages from `node_modules` with a bare specifier. + +```ts +import { stuff } from "foo"; +``` + +The full specification of this algorithm are officially documented in the [Node.js documentation](https://nodejs.org/api/modules.html); we won't rehash it here. Briefly: if you import `from "foo"`, Bun scans up the file system for a `node_modules` directory containing the package `foo`. + +Once it finds the `foo` package, Bun reads the `package.json` to determine how the package should be imported. Unless `"type": "module"` is specified, Bun assumes the package is using CommonJS and transpiles into a synchronous ES module internally. To determine the package's entrypoint, Bun first reads the `exports` field in and checks the following conditions in order: + +```jsonc#package.json +{ + "name": "foo", + "exports": { + "bun": "./index.js", // highest priority + "worker": "./index.js", + "module": "./index.js", + "node": "./index.js", + "browser": "./index.js", + "default": "./index.js" // lowest priority + } +} +``` + +Bun respects subpath [`"exports"`](https://nodejs.org/api/packages.html#subpath-exports) and [`"imports"`](https://nodejs.org/api/packages.html#imports). Specifying any subpath in the `"exports"` map will prevent other subpaths from being importable. + +```jsonc#package.json +{ + "name": "foo", + "exports": { + ".": "./index.js", + "./package.json": "./package.json" # subpath + } +} +``` + +{% 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 %} + +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 +{ + "name": "foo", + "module": "./index.js", + "main": "./index.js" +} +``` + +## 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. + +```jsonc#tsconfig.json +{ + "compilerOptions": { + "paths": { + "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-style resolution + +{% callout %} +**Note** — Added in Bun v0.3.0 +{% /callout %} + +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](/docs/cli/install#global-cache) during execution (the same cache used by [`bun install`](/docs/cli/install)). + +```ts +import { foo } from "foo"; // install `latest` version + +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: + +1. Check for a `bun.lockb` file in the project root. If it exists, use the version specified in the lockfile. +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: + +1. Check the module cache for a compatible version. If one exists, use it. +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. Additional, a symlink is created under `<cache>/<pkg>/<version>` to make it faster to look up all versions of a package 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 +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: + +- **Space efficiency** — Each version of a dependency only exists in one place on disk. This is a huge space and time savings compared to redundant per-project installations. +- **Portability** — To share simple scripts and gists, your source file is _self-contained_. No need to `zip` together a directory containing your code and config files. With version specifiers in `import` statements, even a `package.json` isn't necessary. +- **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?" %} + +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 %} + +{% 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. + +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 %} + +{% details summary="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. +{% /details %} diff --git a/docs/runtime/nodejs.md b/docs/runtime/nodejs.md new file mode 100644 index 000000000..a394ca3a3 --- /dev/null +++ b/docs/runtime/nodejs.md @@ -0,0 +1,682 @@ +Bun aims for complete Node.js API compatibility. Most `npm` packages intended for `Node.js` environments will work with Bun out of the box; the best way to know for certain is to try it. + +This page is updated regularly to reflect compatibility status of the latest version of Bun. + +## Built-in modules + +{% block className="ScrollFrame" %} +{% table %} + +- Module +- Status +- Notes + +--- + +- {% anchor id="node_assert" %} [`node:assert`](https://nodejs.org/api/assert.html) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_async_hooks" %} [`node:async_hooks`](https://nodejs.org/api/async_hooks.html) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_buffer" %} [`node:buffer`](https://nodejs.org/api/buffer.html) {% /anchor %} +- 🟡 +- Incomplete implementation of `base64` and `base64url` encodings. + +--- + +- {% anchor id="node_child_process" %} [`node:child_process`](https://nodejs.org/api/child_process.html) {% /anchor %} +- 🟡 +- Missing IPC, `Stream` stdio, `proc.gid`, `proc.uid`, advanced serialization + +--- + +- {% anchor id="node_cluster" %} [`node:cluster`](https://nodejs.org/api/cluster.html) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_console" %} [`node:console`](https://nodejs.org/api/console.html) {% /anchor %} +- 🟢 +- Recommended to use `console` global instead + +--- + +- {% anchor id="node_crypto" %} [`node:crypto`](https://nodejs.org/api/crypto.html) {% /anchor %} +- 🟡 +- Missing `crypto.Certificate` `crypto.ECDH` `crypto.KeyObject` `crypto.X509Certificate` `crypto.checkPrime{Sync}` `crypto.createPrivateKey` `crypto.createPublicKey` `crypto.createSecretKey` `crypto.diffieHellman` `crypto.generateKey{Sync}` `crypto.generateKeyPair{Sync}` `crypto.generatePrime{Sync}` `crypto.getCipherInfo` `crypto.getCurves` `crypto.{get|set}Fips` `crypto.hkdf` `crypto.hkdfSync` `crypto.randomInt` `crypto.secureHeapUsed` `crypto.setEngine` `crypto.sign` `crypto.verify` + +--- + +- {% anchor id="node_dgram" %} [`node:dgram`](https://nodejs.org/api/dgram.html) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_diagnostics_channel" %} [`node:diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_dns" %} [`node:dns`](https://nodejs.org/api/dns.html) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_domain" %} [`node:domain`](https://nodejs.org/api/domain.html) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_events" %} [`node:events`](https://nodejs.org/api/events.html) {% /anchor %} +- 🟡 +- Missing `EventEmitterAsyncResource`. `EventEmitter` is missing `{get}set}MaxListeners` `usingDomains` `init`. + +--- + +- {% anchor id="node_fs" %} [`node:fs`](https://nodejs.org/api/fs.html) {% /anchor %} +- 🟡 +- Missing `fs.constants` `fs.fdatasync{Sync}` `fs.opendir{Sync}` `fs.readv{Sync}` `fs.{watch|watchFile|unwatchFile}` `fs.writev{Sync}`. + +--- + +- {% anchor id="node_http" %} [`node:http`](https://nodejs.org/api/http.html) {% /anchor %} +- 🟡 +- Missing `http.Agent` `http.ClientRequest` `http.IncomingMessage` `http.OutgoingMessage` `http.globalAgent` `http.get` `http.maxHeaderSize` `http.request` `http.setMaxIdleHTTPParsers` `http.validateHeader{Name|Value}`. + +--- + +- {% anchor id="node_http2" %} [`node:http2`](https://nodejs.org/api/http2.html) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_https" %} [`node:https`](https://nodejs.org/api/https.html) {% /anchor %} +- 🟡 +- See `node:http`. + +--- + +- {% anchor id="node_inspector" %} [`node:inspector`](https://nodejs.org/api/inspector.html) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_module" %} [`node:module`](https://nodejs.org/api/module.html) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_net" %} [`node:net`](https://nodejs.org/api/net.html) {% /anchor %} +- 🟡 +- Missing `net.createServer` `net.{get|set}DefaultAutoSelectFamily` `net.SocketAddress` `net.BlockList`. + +--- + +- {% anchor id="node_os" %} [`node:os`](https://nodejs.org/api/os.html) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_path" %} [`node:path`](https://nodejs.org/api/path.html) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_perf_hooks" %} [`node:perf_hooks`](https://nodejs.org/api/perf_hooks.html) {% /anchor %} +- 🟡 +- Only `perf_hooks.performance.now()` and `perf_hooks.performance.timeOrigin` are implemented. Recommended to use `performance` global instead of `perf_hooks.performance`. + +--- + +- {% anchor id="node_process" %} [`node:process`](https://nodejs.org/api/process.html) {% /anchor %} +- 🟡 +- See `Globals > process`. + +--- + +- {% anchor id="node_punycode" %} [`node:punycode`](https://nodejs.org/api/punycode.html) {% /anchor %} +- 🟢 +- Fully implemented. _Deprecated by Node.js._ + +--- + +- {% anchor id="node_querystring" %} [`node:querystring`](https://nodejs.org/api/querystring.html) {% /anchor %} +- 🟡 +- Missing `querystring.escape` and `querystring.unescape`. + +--- + +- {% anchor id="node_readline" %} [`node:readline`](https://nodejs.org/api/readline.html) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_repl" %} [`node:repl`](https://nodejs.org/api/repl.html) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_stream" %} [`node:stream`](https://nodejs.org/api/stream.html) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_string_decoder" %} [`node:string_decoder`](https://nodejs.org/api/string_decoder.html) {% /anchor %} +- 🟢 + +--- + +- {% anchor id="node_sys" %} [`node:sys`](https://nodejs.org/api/util.html) {% /anchor %} +- 🟡 +- See `node:util`. + +--- + +- {% anchor id="node_timers" %} [`node:timers`](https://nodejs.org/api/timers.html) {% /anchor %} +- 🟢 +- Recommended to use global `setTimeout`, et. al. instead. + +--- + +- {% anchor id="node_tls" %} [`node:tls`](https://nodejs.org/api/tls.html) {% /anchor %} +- 🟡 +- Missing `tls.Server` `tls.createServer` `tls.createSecurePair` `tls.checkServerIdentity` `tls.rootCertificates` + +--- + +- {% anchor id="node_trace_events" %} [`node:trace_events`](https://nodejs.org/api/tracing.html) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_tty" %} [`node:tty`](https://nodejs.org/api/tty.html) {% /anchor %} +- 🟡 +- Missing `tty.ReadStream` and `tty.WriteStream`. + +--- + +- {% anchor id="node_url" %} [`node:url`](https://nodejs.org/api/url.html) {% /anchor %} +- 🟡 +- Missing `url.domainTo{ASCII|Unicode}` `url.urlToHttpOptions`. Recommended to use `URL` and `URLSearchParams` globals instead. + +--- + +- {% anchor id="node_util" %} [`node:util`](https://nodejs.org/api/util.html) {% /anchor %} +- 🟡 +- Missing `util.MIMEParams` `util.MIMEType` `util.formatWithOptions()` `util.getSystemErrorMap()` `util.getSystemErrorName()` `util.parseArgs()` `util.stripVTControlCharacters()` `util.toUSVString()` `util.transferableAbortController()` `util.transferableAbortSignal()`. + +--- + +- {% anchor id="node_v8" %} [`node:v8`](https://nodejs.org/api/v8.html) {% /anchor %} +- 🔴 +- Not implemented or planned. For profiling, use `bun:jsc` instead. + +--- + +- {% anchor id="node_vm" %} [`node:vm`](https://nodejs.org/api/vm.html) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_wasi" %} [`node:wasi`](https://nodejs.org/api/wasi.html) {% /anchor %} +- 🟡 +- Partially implemented. + +--- + +- {% anchor id="node_worker_threads" %} [`node:worker_threads`](https://nodejs.org/api/worker_threads.html) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_zlib" %} [`node:zlib`](https://nodejs.org/api/zlib.html) {% /anchor %} +- 🟡 +- Missing `zlib.brotli*` + +{% /table %} +{% /block %} + +## Globals + +The table below lists all globals implemented by Node.js and Bun's current compatibility status. + +{% table %} + +--- + +- {% anchor id="node_abortcontroller" %} [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_abortsignal" %} [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_blob" %} [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_buffer" %} [`Buffer`](https://nodejs.org/api/buffer.html#class-buffer) {% /anchor %} +- 🟡 +- Incomplete implementation of `base64` and `base64url` encodings. + +--- + +- {% anchor id="node_bytelengthqueuingstrategy" %} [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_dirname" %} [`__dirname`](https://nodejs.org/api/globals.html#__dirname) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_filename" %} [`__filename`](https://nodejs.org/api/globals.html#__filename) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_atob" %} [`atob()`](https://developer.mozilla.org/en-US/docs/Web/API/atob) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_broadcastchannel" %} [`BroadcastChannel`](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_btoa" %} [`btoa()`](https://developer.mozilla.org/en-US/docs/Web/API/btoa) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_clearimmediate" %} [`clearImmediate()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearImmediate) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_clearinterval" %} [`clearInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearInterval) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_cleartimeout" %} [`clearTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearTimeout) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_compressionstream" %} [`CompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_console" %} [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_countqueuingstrategy" %} [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_crypto" %} [`Crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_crypto" %} [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/crypto) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_cryptokey" %} [`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_customevent" %} [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_decompressionstream" %} [`DecompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_event" %} [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_eventtarget" %} [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_exports" %} [`exports`](https://nodejs.org/api/globals.html#exports) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_fetch" %} [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_formdata" %} [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) {% /anchor %} +- 🟢 +- Fully implemented. Added in Bun 0.7.0. + +--- + +- {% anchor id="node_global" %} [`global`](https://nodejs.org/api/globals.html#global) {% /anchor %} +- 🟢 +- Implemented. This is an object containing all objects in the global namespace. It's rarely referenced directly, as its contents are available without an additional prefix, e.g. `__dirname` instead of `global.__dirname`. + +--- + +- {% anchor id="node_globalthis" %} [`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) {% /anchor %} +- 🟢 +- Aliases to `global`. + +--- + +- {% anchor id="node_headers" %} [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_messagechannel" %} [`MessageChannel`](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_messageevent" %} [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_messageport" %} [`MessagePort`](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_module" %} [`module`](https://nodejs.org/api/globals.html#module) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_performanceentry" %} [`PerformanceEntry`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_performancemark" %} [`PerformanceMark`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceMark) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_performancemeasure" %} [`PerformanceMeasure`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceMeasure) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_performanceobserver" %} [`PerformanceObserver`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_performanceobserverentrylist" %} [`PerformanceObserverEntryList`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserverEntryList) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_performanceresourcetiming" %} [`PerformanceResourceTiming`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_performance" %} [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/performance) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_process" %} [`process`](https://nodejs.org/api/process.html) {% /anchor %} +- 🟡 +- Missing `process.allowedNodeEnvironmentFlags` `process.channel()` `process.connected` `process.constrainedMemory()` `process.cpuUsage()` `process.debugPort` `process.disconnect()` `process.{get|set}ActiveResourcesInfo()` `process.{get|set}{uid|gid|egid|euid|groups}()` `process.hasUncaughtExceptionCaptureCallback` `process.initGroups()` `process.kill()` `process.listenerCount` `process.memoryUsage()` `process.report` `process.resourceUsage()` `process.setSourceMapsEnabled()` `process.send()`. + +--- + +- {% anchor id="node_queuemicrotask" %} [`queueMicrotask()`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_readablebytestreamcontroller" %} [`ReadableByteStreamController`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableByteStreamController) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_readablestream" %} [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_readablestreambyobreader" %} [`ReadableStreamBYOBReader`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBReader) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_readablestreambyobrequest" %} [`ReadableStreamBYOBRequest`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBRequest) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_readablestreamdefaultcontroller" %} [`ReadableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultController) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_readablestreamdefaultreader" %} [`ReadableStreamDefaultReader`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_require" %} [`require()`](https://nodejs.org/api/globals.html#require) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_response" %} [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_request" %} [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_setimmediate" %} [`setImmediate()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_setinterval" %} [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setInterval) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_settimeout" %} [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_structuredclone" %} [`structuredClone()`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_subtlecrypto" %} [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_domexception" %} [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_textdecoder" %} [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_textdecoderstream" %} [`TextDecoderStream`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoderStream) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_textencoder" %} [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_textencoderstream" %} [`TextEncoderStream`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoderStream) {% /anchor %} +- 🔴 +- Not implemented. + +--- + +- {% anchor id="node_transformstream" %} [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_transformstreamdefaultcontroller" %} [`TransformStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStreamDefaultController) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_url" %} [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_urlsearchparams" %} [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_webassembly" %} [`WebAssembly`](https://nodejs.org/api/globals.html#webassembly) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_writablestream" %} [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_writablestreamdefaultcontroller" %} [`WritableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultController) {% /anchor %} +- 🟢 +- Fully implemented. + +--- + +- {% anchor id="node_writablestreamdefaultwriter" %} [`WritableStreamDefaultWriter`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultWriter) {% /anchor %} +- 🟢 +- Fully implemented. + +{% /table %} diff --git a/docs/runtime/plugins.md b/docs/runtime/plugins.md new file mode 100644 index 000000000..c19ea9145 --- /dev/null +++ b/docs/runtime/plugins.md @@ -0,0 +1,245 @@ +{% callout %} +**Note** — Introduced in Bun v0.1.11. +{% /callout %} + +Bun's runtime can be extended to support additional file types using _plugins_. Plugins can intercept imports and perform custom loading logic: reading files, transpiling code, etc. They can be used to extend Bun's runtime with _loaders_ for additional file types. + +## 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#yamlPlugin.ts +import { plugin } from "bun"; + +plugin({ + name: "YAML loader", + setup(build) { + // implementation + }, +}); +``` + +To consume this plugin, import it at the top of your project's entrypoint, before any application code is imported. + +```ts#app.ts +import "./yamlPlugin.ts"; +import { config } from "./config.yml"; + +console.log(config); +``` + +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 + }), +); + +// application code +``` + +Bun's plugin API is based on [esbuild](https://esbuild.github.io/plugins). Only a subset 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()); + +import { renderToStaticMarkup } from "react-dom/server"; +import Foo from "./bar.mdx"; +console.log(renderToStaticMarkup(<Foo />)); +``` + +## Loaders + +<!-- The plugin logic is implemented in the `setup` function using the builder provided as the first argument (`build` in the example above). The `build` variable provides two methods: `onResolve` and `onLoad`. --> + +<!-- ## `onResolve` --> + +<!-- The `onResolve` method lets you intercept imports that match a particular regex and modify the resolution behavior, such as re-mapping the import to another file. In the simplest case, you can simply remap the matched import to a new path. + +```ts +import { plugin } from "bun"; + +plugin({ + name: "YAML loader", + setup(build) { + build.onResolve(); + // implementation + }, +}); +``` --> + +<!-- +Internally, Bun's transpiler automatically turns `plugin()` calls into separate files (at most 1 per file). This lets loaders activate before the rest of your application runs with zero configuration. --> + +Plugins are primarily used to extend Bun with loaders for additional file types. Let's look at a simple plugin that exposes envLet's look at a sample plugin that implements a loader for `.yaml` files. + +```ts#yamlPlugin.ts +import { plugin } from "bun"; + +plugin({ + name: "YAML", + async setup(build) { + const { load } = await import("js-yaml"); + const { readFileSync } = await import("fs"); + + // when a .yaml file is imported... + build.onLoad({ filter: /\.(yaml|yml)$/ }, (args) => { + + // read and parse the file + const text = readFileSync(args.path, "utf8"); + const exports = load(text) as Record<string, any>; + + // and returns it as a module + return { + exports, + loader: "object", // special loader for JS objects + }; + }); + }, +}); +``` + +With this plugin, data can be directly imported from `.yaml` files. + +{% codetabs %} + +```ts#index.ts +import "./yamlPlugin.ts" +import {name, releaseYear} from "./data.yml" + +console.log(name, releaseYear); +``` + +```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 special 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 extensions. + +{% table %} + +- Loader +- Extensions +- Output + +--- + +- `js` +- `.js` `.mjs` `.cjs` +- Transpile to JavaScript files + +--- + +- `jsx` +- `.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 + +--- + +- `object` +- — +- 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. Lets 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"); + const { readFileSync } = await import("fs"); + + // when a .svelte file is imported... + build.onLoad({ filter: /\.svelte$/ }, ({ path }) => { + + // read and compile it with the Svelte compiler + const file = readFileSync(path, "utf8"); + 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()); +``` + +## Reference + +```ts +namespace Bun { + function plugin(plugin: { name: string; setup: (build: PluginBuilder) => void }): void; +} + +type PluginBuilder = { + onLoad: ( + args: { filter: RegExp; namespace?: string }, + callback: (args: { path: string }) => { + loader?: "js" | "jsx" | "ts" | "tsx" | "json" | "yaml" | "object"; + contents?: string; + exports?: Record<string, any>; + }, + ) => void; +}; +``` + +The `onLoad` method optionally accepts a `namespace` in addition to the `filter` regex. This namespace will be be 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`. diff --git a/docs/runtime/streams.md b/docs/runtime/streams.md deleted file mode 100644 index 733b99799..000000000 --- a/docs/runtime/streams.md +++ /dev/null @@ -1,5 +0,0 @@ -# Streams in Bun - -Bun supports Web Streams ([`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) and [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) first and partially supports Node.js [`"stream"`](https://nodejs.org/api/stream.html). - -## `ReadableStream` diff --git a/docs/runtime/web-apis.md b/docs/runtime/web-apis.md new file mode 100644 index 000000000..ebe0e6041 --- /dev/null +++ b/docs/runtime/web-apis.md @@ -0,0 +1,300 @@ +Many 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), [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), and [`History`](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. + +## Globals + +{% table %} + +--- + +- 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) + +--- + +- 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) + +--- + +- 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) + +--- + +- Microtasks +- [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) + +--- + +- Errors +- [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError) [`ResolveError`](https://developer.mozilla.org/en-US/docs/Web/API/ResolveError) + [`BuildError`](https://developer.mozilla.org/en-US/docs/Web/API/BuildError) + +--- + +- 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) + +<!-- - 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. --> + +- Blob +- [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) + +--- + +- 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) + +--- + +- WebSockets +- [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) + +--- + +- 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) plus associated `*Reader`, `*Writer`, and `*Controller` classes. + +<!-- ## Globals + +{% table %} + +--- + +--- + +- [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console) + +--- + +- [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto) + +--- + +- [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/Performance) + +{% /table %} + +## Functions + +{% table %} + +- [`atob`](https://developer.mozilla.org/en-US/docs/Web/API/atob) + +--- + +- [`btoa`](https://developer.mozilla.org/en-US/docs/Web/API/btoa) + +--- + +- [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval) + +--- + +- [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout) + +--- + +- [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) + +--- + +- [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) + +--- + +- [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError) + +--- + +- [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) + +--- + +- [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) + +--- + +- [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/alert) +- Blocking. Prints the alert message to terminal and awaits `[ENTER]` before proceeding. + +--- + +- [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/confirm) +- Blocking. Prints confirmation message and awaits `[y/N]` input from user. Returns `true` if user entered `y` or `Y`, `false` otherwise. + +--- + +- [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/prompt) +- Blocking. Prints prompt message and awaits user input. Returns the user input as a string. + +--- + +- [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console) + +{% /table %} + +## Classes + +{% table %} + +--- + +- [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) + +--- + +- [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) + +--- + +- [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) + +--- + +- [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) and [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) + +--- + +--- + +- [`ShadowRealm`](https://github.com/tc39/proposal-shadowrealm) +- A ["better `eval`](https://2ality.com/2022/04/shadow-realms.html). Currently a Stage 3 TC39 proposal + +--- + +- [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) + +--- + +- [`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) + +--- + +- [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) + +--- + +- [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) + +--- + +- [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) + +--- + +- [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) + +--- + +- [`Loader`](https://developer.mozilla.org/en-US/docs/Web/API/Loader) + +--- + +- [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) + +--- + +- [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) + +--- + +- [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy) + +--- + +- [`ReadableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultController) + +--- + +- [`ReadableStreamDefaultReader`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader) + +--- + +- [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) + +--- + +- [`WritableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultController) + +--- + +- [`WritableStreamDefaultWriter`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultWriter) + +--- + +- [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) + +--- + +- [`TransformStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStreamDefaultController) + +--- + +- [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) + +--- + +- [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) + +--- + +- [`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey) + +--- + +- [`ResolveError`](https://developer.mozilla.org/en-US/docs/Web/API/ResolveError) + +--- + +- [`BuildError`](https://developer.mozilla.org/en-US/docs/Web/API/BuildError) + +{% /table %} --> diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 000000000..d104f3bd5 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,59 @@ +## Troubleshooting + +### Bun not running on an M1 (or Apple Silicon) + +If you see a message like this + +> [1] 28447 killed bun create next ./test + +It most likely means you’re running Bun’s x64 version on Apple Silicon. This happens if Bun is running via Rosetta. Rosetta is unable to emulate AVX2 instructions, which Bun indirectly uses. + +The fix is to ensure you installed a version of Bun built for Apple Silicon. + +### error: Unexpected + +If you see an error like this: + + + +It usually means the max number of open file descriptors is being explicitly set to a low number. By default, Bun requests the max number of file descriptors available (which on macOS, is something like 32,000). But, if you previously ran into ulimit issues with, e.g., Chokidar, someone on The Internet may have advised you to run `ulimit -n 8096`. + +That advice unfortunately **lowers** the hard limit to `8096`. This can be a problem in large repositories or projects with lots of dependencies. Chokidar (and other watchers) don’t seem to call `setrlimit`, which means they’re reliant on the (much lower) soft limit. + +To fix this issue: + +1. Remove any scripts that call `ulimit -n` and restart your shell. +2. Try again, and if the error still occurs, try setting `ulimit -n` to an absurdly high number, such as `ulimit -n 2147483646` +3. Try again, and if that still doesn’t fix it, open an issue + +### Unzip is required + +Unzip is required to install Bun on Linux. You can use one of the following commands to install `unzip`: + +#### Debian / Ubuntu / Mint + +```sh +$ sudo apt install unzip +``` + +#### RedHat / CentOS / Fedora + +```sh +$ sudo dnf install unzip +``` + +#### Arch / Manjaro + +```sh +$ sudo pacman -S unzip +``` + +#### OpenSUSE + +```sh +$ sudo zypper install unzip +``` + +### bun install is stuck + +Please run `bun install --verbose 2> logs.txt` and send them to me in Bun's discord. If you're on Linux, it would also be helpful if you run `sudo perf trace bun install --silent` and attach the logs. diff --git a/docs/typescript.md b/docs/typescript.md new file mode 100644 index 000000000..43fc2b322 --- /dev/null +++ b/docs/typescript.md @@ -0,0 +1,87 @@ +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. + +{% callout %} +**Note** — Similar to other build tools, Bun does not typecheck the files. Use [`tsc --noEmit`](https://www.typescriptlang.org/docs/handbook/compiler-options.html) (the official TypeScript CLI) if you're looking to catch static type errors. +{% /callout %} + +## Type definitions + +To install TypeScript definitions for Bun's built-in APIs, first install `bun-types`. + +```sh +$ bun add -d bun-types # dev dependency +``` + +Then include `"bun-types"` in the `compilerOptions.types` in your `tsconfig.json`: + +```json-diff + { + "compilerOptions": { ++ "types": ["bun-types"] + } + } +``` + +## Compiler options + +Bun's runtime implements [modern ECMAScript features](https://github.com/sudheerj/ECMAScript-features), like bigint literals, nullish coalescing, dynamic imports, `import.meta`, `globalThis`, ES modules, top-level await, and more. To use these features without seeing TypeScript errors in your IDE, your `tsconfig.json` needs to be properly configured. + +These are the recommended `compilerOptions` for a Bun project. + +```jsonc +{ + "compilerOptions": { + // enable latest features + "lib": ["esnext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "nodenext", + + // support JSX, CommonJS + "jsx": "preserve", // support JSX (value doesn't matter) + "allowJs": true, // allow importing `.js` from `.ts` + "esModuleInterop": true, // allow default imports for CommonJS modules + + // best practices + "strict": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + + // add Bun type definitions + "types": ["bun-types"] + } +} +``` + +If you use `bun init`, an appropriate `tsconfig.json` is automatically generated for you. + +## 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. + +Given the following `tsconfig.json`... + +```json +{ + "compilerOptions": { + "paths": { + "data": ["./data.ts"] + } + } +} +``` + +...the import from `"data"` will work as expected. + +{% codetabs %} + +```ts#index.ts +import { foo } from "data"; +console.log(foo); // => "Hello world!" +``` + +```ts#data.ts +export const foo = "Hello world!" +``` + +{% /codetabs %} diff --git a/docs/upgrading-webkit.md b/docs/upgrading-webkit.md index f9194fc15..e726fb1d8 100644 --- a/docs/upgrading-webkit.md +++ b/docs/upgrading-webkit.md @@ -1,5 +1,3 @@ -# Upgrading WebKit - Bun uses [a fork](https://github.com/oven-sh/WebKit) of WebKit with a small number of changes. It's important to periodically update WebKit for many reasons: @@ -9,24 +7,24 @@ It's important to periodically update WebKit for many reasons: - Compatibility - …and many more. -To upgrade, first find the commit in **bun's WebKit fork** (not bun!) between when we last upgraded and now. +To upgrade, first find the commit in **Bun's WebKit fork** (not Bun!) between when we last upgraded and now. ```bash -cd src/bun.js/WebKit # In the WebKit directory! not bun -git checkout $COMMIT +$ cd src/bun.js/WebKit # In the WebKit directory! not bun +$ git checkout $COMMIT ``` This is the main command to run: ```bash -git pull https://github.com/WebKit/WebKit.git main --no-rebase --allow-unrelated-histories -X theirs +$ git pull https://github.com/WebKit/WebKit.git main --no-rebase --allow-unrelated-histories -X theirs ``` Then, you will likely see some silly merge conflicts. Fix them and then run: ```bash # You might have to run this multiple times. -rm -rf WebKitBuild +$ rm -rf WebKitBuild # Go to Bun's directory! Not WebKit. cd ../../../../ |