From 9b6913e1a674ceb7f670f917fc355bb8758c6c72 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Mon, 29 May 2023 11:49:51 -0700 Subject: More/better docs for JSX, utils, binary data, streams, hashing, `bun test`, `Bun.serve` (#3005) * WIP * Updates * Document deepEquals * WIP * Update typeS * Update TLS docs for Bun.serve * Update types for tls * Draft binary data page. Add Streams page. * Update test runner docs * Add hashing, flesh out utils * Grammar * Update types * Fix * Add import.meta docs * Tee --- docs/api/binary-data.md | 988 ++++++++++++++++++++ docs/api/hashing.md | 139 +++ docs/api/http.md | 83 +- docs/api/import-meta.md | 52 ++ docs/api/streams.md | 172 ++++ docs/api/tcp.md | 33 +- docs/api/utils.md | 366 +++++++- docs/bundler/index.md | 1334 ++++++++++++++++++++++++++++ docs/bundler/loaders.md | 2 +- docs/bundler/migration.md | 1133 ----------------------- docs/bundler/plugins.md | 14 +- docs/bundler/vs-esbuild.md | 1133 +++++++++++++++++++++++ docs/cli/build.md | 1334 ---------------------------- docs/cli/test.md | 47 +- docs/ecosystem/react.md | 2 + docs/index.md | 7 +- docs/nav.ts | 39 +- docs/runtime/bun-apis.md | 79 +- docs/runtime/configuration.md | 116 ++- docs/runtime/jsx.md | 289 +++++- docs/runtime/modules.md | 2 +- docs/runtime/nodejs-apis.md | 4 +- docs/test/extending.md | 162 ---- docs/test/lifecycle.md | 81 ++ docs/test/snapshots.md | 15 + docs/test/writing.md | 48 +- packages/bun-types/buffer.d.ts | 1 + packages/bun-types/bun.d.ts | 8 +- packages/bun-types/globals.d.ts | 3 + packages/bun-types/tests/hashing.test-d.ts | 1 + packages/bun-types/tests/http.test-d.ts | 2 + packages/bun-types/tests/streams.test-d.ts | 19 + packages/bun-types/tests/tcp.test-d.ts | 19 + packages/bun-types/tests/tls.test-d.ts | 26 + packages/bun-types/tls.d.ts | 24 +- 35 files changed, 4960 insertions(+), 2817 deletions(-) create mode 100644 docs/api/binary-data.md create mode 100644 docs/api/hashing.md create mode 100644 docs/api/import-meta.md create mode 100644 docs/api/streams.md create mode 100644 docs/bundler/index.md delete mode 100644 docs/bundler/migration.md create mode 100644 docs/bundler/vs-esbuild.md delete mode 100644 docs/cli/build.md delete mode 100644 docs/test/extending.md create mode 100644 docs/test/lifecycle.md create mode 100644 docs/test/snapshots.md create mode 100644 packages/bun-types/tests/hashing.test-d.ts create mode 100644 packages/bun-types/tests/streams.test-d.ts diff --git a/docs/api/binary-data.md b/docs/api/binary-data.md new file mode 100644 index 000000000..71b02338f --- /dev/null +++ b/docs/api/binary-data.md @@ -0,0 +1,988 @@ +This page is intended as an introduction to working with binary data in JavaScript. Bun implements a number of data types and utilities for working with binary data, most of which are Web-standard. Any Bun-specific APIs will be noted as such. + +Below is a quick "cheat sheet" that doubles as a table of contents. Click an item in the left column to jump to that section. + +{% table %} + +--- + +- [`TypedArray`](#typedarray) +- A family of classes that provide an `Array`-like interface for interacting with binary data. Includes `Uint8Array`, `Uint16Array`, `Int8Array`, and more. + +--- + +- [`Buffer`](#buffer) +- A subclass of `Uint8Array` that implements a wide range of convenience methods. Unlike the other elements in this table, this is a Node.js API (which Bun implements). It can't be used in the browser. + +--- + +- [`DataView`](#dataview) +- A class that provides a `get/set` API for writing some number of bytes to an `ArrayBuffer` at a particular byte offset. Often used reading or writing binary protocols. + +--- + +- [`Blob`](#blob) +- A readonly blob of binary data usually representing a file. Has a MIME `type`, a `size`, and methods for converting to `ArrayBuffer`, `ReadableStream`, and string. + +--- + + + +- [`BunFile`](#bunfile) +- _Bun only_. A subclass of `Blob` that represents a lazily-loaded file on disk. Created with `Bun.file(path)`. + +{% /table %} + +## `ArrayBuffer` and views + +Until 2009, there was no language-native way to store and manipulate binary data in JavaScript. ECMAScript v5 introduced a range of new mechanisms for this. The most fundamental building block is `ArrayBuffer`, a simple data structure that represents a sequence of bytes in memory. + +```ts +// this buffer can store 8 bytes +const buf = new ArrayBuffer(8); +``` + +Despite the name, it isn't an array and supports none of the array methods and operators one might expect. In fact, there is no way to directly read or write values from an `ArrayBuffer`. There's very little you can do with one except check its size and create "slices" from it. + +```ts +const buf = new ArrayBuffer(8); + +buf.byteLength; // => 8 + +const slice = buf.slice(0, 4); // returns new ArrayBuffer +slice.byteLength; // => 4 +``` + +To do anything interesting we need a construct known as a "view". A view is a class that _wraps_ an `ArrayBuffer` instance and lets you read and manipulate the underlying data. There are two types of views: _typed arrays_ and `DataView`. + +### `DataView` + +The `DataView` class is a lower-level interface for reading and manipulating the data in an `ArrayBuffer`. + +Below we create a new `DataView` and set the first byte to 5. + +```ts +const buf = new ArrayBuffer(4); +// [0x0, 0x0, 0x0, 0x0] + +const dv = new DataView(buf); +dv.setUint8(0, 3); // write value 3 at byte offset 0 +dv.getUint8(0); // => 3 +// [0x11, 0x0, 0x0, 0x0] +``` + +Now lets write a `Uint16` at byte offset `1`. This requires two bytes. We're using the value `513`, which is `2 * 256 + 1`; in bytes, that's `00000010 00000001`. + +```ts +dv.setUint16(1, 513); +// [0x11, 0x10, 0x1, 0x0] + +console.log(dv.getUint16(1)); // => 513 +``` + +We've now assigned a value to the first three bytes in our underlying `ArrayBuffer`. Even though the second and third bytes were created using `setUint16()`, we can still read each of its component bytes using `getUint8()`. + +```ts +console.log(dv.getUint8(1)); // => 2 +console.log(dv.getUint8(2)); // => 1 +``` + +Attempting to write a value that requires more space than is available in the underlying `ArrayBuffer` will cuase an error. Below we attempt to write a `Float64` (which requires 8 bytes) at byte offset `0`, but there are only four total bytes in the buffer. + +```ts +dv.setFloat64(0, 3.1415); +// ^ RangeError: Out of bounds access +``` + +The following methods are available on `DataView`: + +{% table %} + +- Getters +- Setters + +--- + +- [`getBigInt64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigInt64) +- [`setBigInt64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigInt64) + +--- + +- [`getBigUint64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigUint64) +- [`setBigUint64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigUint64) + +--- + +- [`getFloat32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat32) +- [`setFloat32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat32) + +--- + +- [`getFloat64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat64) +- [`setFloat64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat64) + +--- + +- [`getInt16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt16) +- [`setInt16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt16) + +--- + +- [`getInt32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32) +- [`setInt32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt32) + +--- + +- [`getInt8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt8) +- [`setInt8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt8) + +--- + +- [`getUint16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint16) +- [`setUint16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint16) + +--- + +- [`getUint32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint32) +- [`setUint32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint32) + +--- + +- [`getUint8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint8) +- [`setUint8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint8) + +{% /table %} + +### `TypedArray` + +Typed arrays are a family of classes that provide an `Array`-like interface for interacting with data in an `ArrayBuffer`. Whereas a `DataView` lets you write numbers of varying size at a particular offset, a `TypedArray` interprets the underlying bytes as an array of numbers, each of a fixed size. + +{% callout %} +**Note** — It's common to refer to this family of classes collectively by their shared superclass `TypedArray`. This class as _internal_ to JavaScript; you can't directly create instances of it, and `TypedArray` is not defined in the global scope. Think of it as an `interface` or an abstract class. +{% /callout %} + +```ts +const buffer = new ArrayBuffer(3); +const arr = new Uint8Array(buffer); + +// contents are initialized to zero +console.log(arr); // Uint8Array(3) [0, 0, 0] + +// assign values like an array +arr[0] = 0; +arr[1] = 10; +arr[2] = 255; +arr[3] = 255; // no-op, out of bounds +``` + +While an `ArrayBuffer` is a generic sequence of bytes, these typed array classes interpret the bytes as an array of numbers of a given byte size. +The top row contains the raw bytes, and the later rows contain how these bytes will be interpreted when _viewed_ using different typed array classes. + +The following classes are typed arrays, along with a description of how they interpret the bytes in an `ArrayBuffer`: + +{% table %} + +- Class +- Description + +--- + +- [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) +- Every one (1) byte is interpreted as an unsigned 8-bit integer. Range 0 to 255. + +--- + +- [`Uint16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array) +- Every two (2) bytes are interpreted as an unsigned 16-bit integer. Range 0 to 65535. + +--- + +- [`Uint32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array) +- Every four (4) bytes are interpreted as an unsigned 32-bit integer. Range 0 to 4294967295. + +--- + +- [`Int8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array) +- Every one (1) byte is interpreted as a signed 8-bit integer. Range -128 to 127. + +--- + +- [`Int16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array) +- Every two (2) bytes are interpreted as a signed 16-bit integer. Range -32768 to 32767. + +--- + +- [`Int32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array) +- Every four (4) bytes are interpreted as a signed 32-bit integer. Range -2147483648 to 2147483647. + +--- + +- [`Float32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array) +- Every four (4) bytes are interpreted as a 32-bit floating point number. Range -3.4e38 to 3.4e38. + +--- + +- [`Float64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array) +- Every eight (8) bytes are interpreted as a 64-bit floating point number. Range -1.7e308 to 1.7e308. + +--- + +- [`BigInt64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array) +- Every eight (8) bytes are interpreted as an unsigned `BigInt`. Range -9223372036854775808 to 9223372036854775807 (though `BigInt` is capable of representing larger numbers). + +--- + +- [`BigUint64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array) +- Every eight (8) bytes are interpreted as an unsigned `BigInt`. Range 0 to 18446744073709551615 (though `BigInt` is capable of representing larger numbers). + +--- + +- [`Uint8ClampedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray) +- Same as `Uint8Array`, but automatically "clamps" to the range 0-255 when assigning a value to an element. + +{% /table %} + +The table below demonstrates how the bytes in an `ArrayBuffer` are interpreted when viewed using different typed array classes. + +{% table %} + +--- + +- `ArrayBuffer` +- `00000000` +- `00000001` +- `00000010` +- `00000011` +- `00000100` +- `00000101` +- `00000110` +- `00000111` + +--- + +- `Uint8Array` +- 0 +- 1 +- 2 +- 3 +- 4 +- 5 +- 6 +- 7 + +--- + +- `Uint16Array` +- 256 (`1 * 256 + 0`) {% colspan=2 %} +- 770 (`3 * 256 + 2`) {% colspan=2 %} +- 1284 (`5 * 256 + 4`) {% colspan=2 %} +- 1798 (`7 * 256 + 6`) {% colspan=2 %} + +--- + +- `Uint32Array` +- 50462976 {% colspan=4 %} +- 117835012 {% colspan=4 %} + +--- + +- `BigUint64Array` +- 506097522914230528n {% colspan=8 %} + +{% /table %} + +To create a typed array from a pre-defined `ArrayBuffer`: + +```ts +// create typed array from ArrayBuffer +const buf = new ArrayBuffer(10); +const arr = new Uint8Array(buf); + +arr[0] = 30; +arr[1] = 60; + +// all elements are initialized to zero +console.log(arr); // => Uint8Array(10) [ 30, 60, 0, 0, 0, 0, 0, 0, 0, 0 ]; +``` + +If we tried to instantiate a `Uint32Array` from this same `ArrayBuffer`, we'd get an error. + +```ts +const buf = new ArrayBuffer(10); +const arr = new Uint32Array(buf); +// ^ RangeError: ArrayBuffer length minus the byteOffset +// is not a multiple of the element size +``` + +A `Uint32` value requires four bytes (16 bits). Because the `ArrayBuffer` is 10 bytes long, there's no way to cleanly divide its contents into 4-byte chunks. + +To fix this, we can create a typed array over a particular "slice" of an `ArrayBuffer`. The `Uint16Array` below only "views" the _first_ 8 bytes of the underlying `ArrayBuffer`. To achieve these, we specify a `byteOffset` of `0` and a `length` of `2`, which indicates the number of `Uint32` numbers we want our array to hold. + +```ts +// create typed array from ArrayBuffer slice +const buf = new ArrayBuffer(10); +const arr = new Uint32Array(buf, 0, 2); + +/* + buf _ _ _ _ _ _ _ _ _ _ 10 bytes + arr [_______,_______] 2 4-byte elements +*/ + +arr.byteOffset; // 0 +arr.length; // 2 +``` + +You don't need to explicitly create an `ArrayBuffer` instance; you can instead directly specify a length in the typed array constructor: + +```ts +const arr2 = new Uint8Array(5); + +// all elements are initialized to zero +// => Uint8Array(5) [0, 0, 0, 0, 0] +``` + +Typed arrays can also be instantiated directly from an array of numbers, or another typed array: + +```ts +// from an array of numbers +const arr1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]); +arr1[0]; // => 0; +arr1[7]; // => 7; + +// from another typed array +const arr2 = new Uint8Array(arr); +``` + +Broadly speaking, typed arrays provide the same methods as regular arrays, with a few exceptions. For example, `push` and `pop` are not available on typed arrays, because they would require resizing the underlying `ArrayBuffer`. + +```ts +const arr = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]); + +// supports common array methods +arr.filter(n => n > 128); // Uint8Array(1) [255] +arr.map(n => n * 2); // Uint8Array(8) [0, 2, 4, 6, 8, 10, 12, 14] +arr.reduce((acc, n) => acc + n, 0); // 28 +arr.forEach(n => console.log(n)); // 0 1 2 3 4 5 6 7 +arr.every(n => n < 10); // true +arr.find(n => n > 5); // 6 +arr.includes(5); // true +arr.indexOf(5); // 5 +``` + +Refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) for more information on the properties and methods of typed arrays. + +### `Uint8Array` + +It's worth specifically highlighting `Uint8Array`, as it represents a classic "byte array"—a sequence of 8-bit unsigned integers between 0 and 255. This is the most common typed array you'll encounter in JavaScript. + +It is the return value of [`TextEncoder#encode`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder), and the input type of [`TextDecoder#decode`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder), two utility classes designed to translate strings and various binary encodings, most notably `"utf-8"`. + +```ts +const encoder = new TextEncoder(); +const bytes = encoder.encode("hello world"); +// => Uint8Array(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ] + +const decoder = new TextDecoder(); +const text = decoder.decode(bytes); +// => hello world +``` + +### `Buffer` + +Bun implements `Buffer`, a Node.js API for working with binary data that pre-dates the introduction of typed arrays in the JavaScript spec. It has since been re-implemented as a subclass of `Uint8Array`. It provides a wide range of methods, including several Array-like and `DataView`-like methods. + +```ts +const buf = Buffer.from("hello world"); +// => Buffer(16) [ 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 115, 116, 114, 105, 110, 103 ] + +buf.length; // => 11 +buf[0]; // => 104, ascii for 'h' +buf.writeUInt8(72, 0); // => ascii for 'H' + +console.log(buf.toString()); +// => Hello world +``` + +For complete documentation, refer to the [Node.js documentation](https://nodejs.org/api/buffer.html). + +## `Blob` + +`Blob` is a Web API commonly used for representing files. `Blob` was initially implemented in browsers (unlike `ArrayBuffer` which is part of JavaScript itself), but it is now supported in Node and Bun. + +It isn't common to directly create `Blob` instances. More often, you'll recieve instances of `Blob` from an external source (like an `` element in the browser) or library. That said, it is possible to create a `Blob` from one or more string or binary "blob parts". + +```ts +const blob = new Blob(["Hello"], { + type: "text/html", +}); + +blob.type; // => text/html +blob.size; // => 19 +``` + +These parts can be `string`, `ArrayBuffer`, `TypedArray`, `DataView`, or other `Blob` instances. The blob parts are concatenated together in the order they are provided. + +```ts +const blob = new Blob([ + "", + new Blob([""]), + new Uint8Array([104, 101, 108, 108, 111]), // "hello" in binary + "", +]); +``` + +The contents of a `Blob` can be asynchronously read in various formats. + +```ts +await blob.text(); // => hello +await blob.arrayBuffer(); // => ArrayBuffer (copies contents) +await blob.stream(); // => ReadableStream +``` + +### `BunFile` + +`BunFile` is a subclass of `Blob` used to represent a lazily-loaded file on disk. Like `File`, it adds a `name` and `lastModified` property. Unlike `File`, it does not require the file to be loaded into memory. + +```ts +const file = Bun.file("index.txt"); +// => BunFile +``` + +### `File` + +{% callout %} +Browser only. Experimental support in Node.js 20. +{% /callout %} + +[`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) is a subclass of `Blob` that adds a `name` and `lastModified` property. It's commonly used in the browser to represent files uploaded via a `` element. Node.js and Bun implement `File`. + +```ts +// on browser! +// + +const files = document.getElementById("file").files; +// => File[] +``` + +```ts +const file = new File(["Hello"], "index.html", { + type: "text/html", +}); +``` + +Refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Blob) for complete docs information. + +## Streams + +Streams are an important abstraction for working with binary data without loading it all into memory at once. They are commonly used for reading and writing files, sending and receiving network requests, and processing large amounts of data. + +Bun implements the Web APIs [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) and [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream). + +{% callout %} +Bun also implements the `node:stream` module, including [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams), [`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), and [`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). For complete documentation, refer to the Node.js docs. +{% /callout %} + +To create a simple readable stream: + +```ts +const stream = new ReadableStream({ + start(controller) { + controller.enqueue("hello"); + controller.enqueue("world"); + controller.close(); + }, +}); +``` + +The contents of this stream can be read chunk-by-chunk with `for await` syntax. + +```ts +for await (const chunk of stream) { + console.log(chunk); + // => "hello" + // => "world" +} +``` + +For a more complete discusson of streams in Bun, see [API > Streams](/docs/api/streams). + +## Conversion + +Converting from one binary format to another is a common task. This section is intended as a reference. + +### From `ArrayBuffer` + +Since `ArrayBuffer` stores the data that underlies other binary structures like `TypedArray`, the snippets below are not _converting_ from `ArrayBuffer` to another format. Instead, they are _creating_ a new instance using the data stored underlying data. + +#### To `TypedArray` + +```ts +new Uint8Array(buf); +``` + +#### To `DataView` + +```ts +new DataView(buf); +``` + +#### To `Buffer` + +```ts +// create Buffer over entire ArrayBuffer +Buffer.from(buf); + +// create Buffer over a slice of the ArrayBuffer +Buffer.from(buf, 0, 10); +``` + +#### To `string` + +```ts +new TextDecoder().decode(buf); +``` + +#### To `number[]` + +```ts +Array.from(new Uint8Array(buf)); +``` + +#### To `Blob` + +```ts +new Blob([buf], { type: "text/plain" }); +``` + + + +#### To `ReadableStream` + +The following snippet creates a `ReadableStream` and enqueues the entire `ArrayBuffer` as a single chunk. + +```ts +new ReadableStream({ + start(controller) { + controller.enqueue(buf); + controller.close(); + }, +}); +``` + +{% details summary="With chunking" %} +To stream the `ArrayBuffer` in chunks, use a `Uint8Array` view and enqueue each chunk. + +```ts +const view = new Uint8Array(buf); +const chunkSize = 1024; + +new ReadableStream({ + start(controller) { + for (let i = 0; i < view.length; i += chunkSize) { + controller.enqueue(view.slice(i, i + chunkSize)); + } + controller.close(); + }, +}); +``` + +{% /details %} + +### From `TypedArray` + +#### To `ArrayBuffer` + +This retrieves the underlying `ArrayBuffer`. Note that a `TypedArray` can be a view of a _slice_ of the underlying buffer, so the sizes may differ. + +```ts +arr.buffer; +``` + +#### To `DataView` + +To creates a `DataView` over the same byte range as the TypedArray. + +```ts +new DataView(arr.buffer, arr.byteOffset, arr.byteLength); +``` + +#### To `Buffer` + +```ts +Buffer.from(arr); +``` + +#### To `string` + +```ts +new TextDecoder().decode(arr); +``` + +#### To `number[]` + +```ts +Array.from(arr); +``` + +#### To `Blob` + +```ts +new Blob([arr.buffer], { type: "text/plain" }); +``` + + + +#### To `ReadableStream` + +```ts +new ReadableStream({ + start(controller) { + controller.enqueue(arr); + controller.close(); + }, +}); +``` + +{% details summary="With chunking" %} +To stream the `ArrayBuffer` in chunks, split the `TypedArray` into chunks and enqueue each one individually. + +```ts +new ReadableStream({ + start(controller) { + for (let i = 0; i < arr.length; i += chunkSize) { + controller.enqueue(arr.slice(i, i + chunkSize)); + } + controller.close(); + }, +}); +``` + +{% /details %} + +### From `DataView` + +#### To `ArrayBuffer` + +```ts +view.buffer; +``` + +#### To `TypedArray` + +Only works if the `byteLength` of the `DataView` is a multiple of the `BYTES_PER_ELEMENT` of the `TypedArray` subclass. + +```ts +new Uint8Array(view.buffer, view.byteOffset, view.byteLength); +new Uint16Array(view.buffer, view.byteOffset, view.byteLength / 2); +new Uint32Array(view.buffer, view.byteOffset, view.byteLength / 4); +// etc... +``` + +#### To `Buffer` + +```ts +Buffer.from(view.buffer, view.byteOffset, view.byteLength); +``` + +#### To `string` + +```ts +new TextDecoder().decode(view); +``` + +#### To `number[]` + +```ts +Array.from(view); +``` + +#### To `Blob` + +```ts +new Blob([view.buffer], { type: "text/plain" }); +``` + + + +#### To `ReadableStream` + +```ts +new ReadableStream({ + start(controller) { + controller.enqueue(view.buffer); + controller.close(); + }, +}); +``` + +{% details summary="With chunking" %} +To stream the `ArrayBuffer` in chunks, split the `DataView` into chunks and enqueue each one individually. + +```ts +new ReadableStream({ + start(controller) { + for (let i = 0; i < view.byteLength; i += chunkSize) { + controller.enqueue(view.buffer.slice(i, i + chunkSize)); + } + controller.close(); + }, +}); +``` + +{% /details %} + +### From `Buffer` + +#### To `ArrayBuffer` + +```ts +buf.buffer; +``` + +#### To `TypedArray` + +```ts +new Uint8Array(buf); +``` + +#### To `DataView` + +```ts +new DataView(buf.buffer, buf.byteOffset, buf.byteLength); +``` + +#### To `string` + +```ts +buf.toString(); +``` + +#### To `number[]` + +```ts +Array.from(buf); +``` + +#### To `Blob` + +```ts +new Blob([buf], { type: "text/plain" }); +``` + + + +#### To `ReadableStream` + +```ts +new ReadableStream({ + start(controller) { + controller.enqueue(buf); + controller.close(); + }, +}); +``` + +{% details summary="With chunking" %} +To stream the `ArrayBuffer` in chunks, split the `Buffer` into chunks and enqueue each one individually. + +```ts +new ReadableStream({ + start(controller) { + for (let i = 0; i < buf.length; i += chunkSize) { + controller.enqueue(buf.slice(i, i + chunkSize)); + } + controller.close(); + }, +}); +``` + +{% /details %} + +### From `Blob` + +#### To `ArrayBuffer` + +The `Blob` class provides a convenience method for this purpose. + +```ts +await blob.arrayBuffer(); +``` + +#### To `TypedArray` + +```ts +new Uint8Array(await blob.arrayBuffer()); +``` + +#### To `DataView` + +```ts +new DataView(await blob.arrayBuffer()); +``` + +#### To `Buffer` + +```ts +Buffer.from(await blob.arrayBuffer()); +``` + +#### To `string` + +```ts +await blob.text(); +``` + +#### To `number[]` + +```ts +Array.from(new Uint8Array(await blob.arrayBuffer())); +``` + +#### To `ReadableStream` + +```ts +blob.stream(); +``` + + + +### From `ReadableStream` + +It's common to use [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) as a convenient intermediate representation to make it easier to convert `ReadableStream` to other formats. + +```ts +stream; // ReadableStream + +const buffer = new Response(stream).arrayBuffer(); +``` + +However this approach is verbose and adds overhead that slows down overall performance unnecessarily. Bun implements a set of optimized convenience functions for converting `ReadableStream` various binary formats. + +#### To `ArrayBuffer` + +```ts +// with Response +new Response(stream).arrayBuffer(); + +// with Bun function +Bun.readableStreamToArrayBuffer(stream); +``` + +#### To `TypedArray` + +```ts +// with Response +const buf = await new Response(stream).arrayBuffer(); +new Uint8Array(buf); + +// with Bun function +new Uint8Array(Bun.readableStreamToArrayBuffer(stream)); +``` + +#### To `DataView` + +```ts +// with Response +const buf = await new Response(stream).arrayBuffer(); +new DataView(buf); + +// with Bun function +new DataView(Bun.readableStreamToArrayBuffer(stream)); +``` + +#### To `Buffer` + +```ts +// with Response +const buf = await new Response(stream).arrayBuffer(); +Buffer.from(buf); + +// with Bun function +Buffer.from(Bun.readableStreamToArrayBuffer(stream)); +``` + +#### To `string` + +```ts +// with Response +new Response(stream).text(); + +// with Bun function +await Bun.readableStreamToString(stream); +``` + +#### To `number[]` + +```ts +// with Response +const buf = await new Response(stream).arrayBuffer(); +Array.from(new Uint8Array(buf)); + +// with Bun function +Array.from(new Uint8Array(Bun.readableStreamToArrayBuffer(stream))); +``` + +Bun provides a utility for resolving a `ReadableStream` to an array of its chunks. Each chunk may be a string, typed array, or `ArrayBuffer`. + +```ts +// with Bun function +Bun.readableStreamToArray(stream); +``` + +#### To `Blob` + +```ts +new Response(stream).blob(); +``` + + + +#### To `ReadableStream` + +To split a `ReadableStream` into two streams that can be consumed independently: + +```ts +const [a, b] = stream.tee(); +``` + + diff --git a/docs/api/hashing.md b/docs/api/hashing.md new file mode 100644 index 000000000..58bb05034 --- /dev/null +++ b/docs/api/hashing.md @@ -0,0 +1,139 @@ +{% callout %} + +Bun implements the `createHash` and `createHmac` functions from [`node:crypto`](https://nodejs.org/api/crypto.html) in addition to the Bun-native APIs documented below. + +{% /callout %} + +## `Bun.hash` + +`Bun.hash` is a collection of utilities for _non-cryptographic_ hashing. Non-cryptographic hashing algorithms are optimized for speed of computation over collision-resistance or security. + +The standard `Bun.hash` functions uses [Wyhash](https://github.com/wangyi-fudan/wyhash) to generate a 64-bit hash from an input of arbitrary size. + +```ts +Bun.hash("some data here"); +// 976213160445840 +``` + +The input can be a string, `TypedArray`, `DataView`, `ArrayBuffer`, or `SharedArrayBuffer`. + +```ts +const arr = new Uint8Array([1, 2, 3, 4]); + +Bun.hash("some data here"); +Bun.hash(arr); +Bun.hash(arr.buffer); +Bun.hash(new DataView(arr.buffer)); +``` + +Optionally, an integer seed can be specified as the second parameter. + +```ts +Bun.hash("some data here", 1234); +// 1173484059023252 +``` + +Additional hashing algorithms are available as properties on `Bun.hash`. The API is the same for each. + +```ts +Bun.hash.wyhash("data", 1234); // equivalent to Bun.hash() +Bun.hash.crc32("data", 1234); +Bun.hash.adler32("data", 1234); +Bun.hash.cityHash32("data", 1234); +Bun.hash.cityHash64("data", 1234); +Bun.hash.murmur32v3("data", 1234); +Bun.hash.murmur64v2("data", 1234); +``` + +## `Bun.CryptoHasher` + +`Bun.CryptoHasher` is a general-purpose utility class that lets you incrementally compute a hash of string or binary data using a range of cryptographic hash algorithms. The following algorithms are supported: + +- `"blake2b256"` +- `"md4"` +- `"md5"` +- `"ripemd160"` +- `"sha1"` +- `"sha224"` +- `"sha256"` +- `"sha384"` +- `"sha512"` +- `"sha512-256"` + +```ts +const hasher = new Bun.CryptoHasher("sha256"); +hasher.update("hello world"); +hasher.digest(); +// Uint8Array(32) [ , , ... ] +``` + +Once initialized, data can be incrementally fed to to the hasher using `.update()`. This method accepts `string`, `TypedArray`, and `ArrayBuffer`. + +```ts +const hasher = new Bun.CryptoHasher(); + +hasher.update("hello world"); +hasher.update(new Uint8Array([1, 2, 3])); +hasher.update(new ArrayBuffer(10)); +``` + +If a `string` is passed, an optional second parameter can be used to specify the encoding (default `'utf-8'`). The following encodings are supported: + +{% table %} + +--- + +- Binary encodings +- `"base64"` `"base64url"` `"hex"` `"binary"` + +--- + +- Character encodings +- `"utf8"` `"utf-8"` `"utf16le"` `"latin1"` + +--- + +- Legacy character encodings +- `"ascii"` `"binary"` `"ucs2"` `"ucs-2"` + +{% /table %} + +```ts +hasher.update("hello world"); // defaults to utf8 +hasher.update("hello world", "hex"); +hasher.update("hello world", "base64"); +hasher.update("hello world", "latin1"); +``` + +After the data has been feed into the hasher, a final hash can be computed using `.digest()`. By default, this method returns a `Uint8Array` containing the hash. + +```ts +const hasher = new Bun.CryptoHasher(); +hasher.update("hello world"); + +hasher.digest(); +// => Uint8Array(32) [ 185, 77, 39, 185, 147, ... ] +``` + +The `.digest()` method can optionally return the hash as a string. To do so, specify an encoding: + +```ts +hasher.digest("base64"); +// => "uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" + +hasher.digest("hex"); +// => "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" +``` + +Alternatively, the method can write the hash into a pre-existing `TypedArray` instance. This may be desirable in some performance-sensitive applications. + +```ts +const arr = new Uint8Array(32); + +hasher.digest(arr); + +console.log(arr); +// => Uint8Array(32) [ 185, 77, 39, 185, 147, ... ] +``` + + diff --git a/docs/api/http.md b/docs/api/http.md index 3ebdafab9..567560c3c 100644 --- a/docs/api/http.md +++ b/docs/api/http.md @@ -1,19 +1,12 @@ +The page primarily documents the Bun-native `Bun.serve` API. Bun also implements [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and the Node.js [`http`](https://nodejs.org/api/http.html) and [`https`](https://nodejs.org/api/https.html) modules. + {% callout %} -**Note** — This page documents the `Bun.serve` API. This API is heavily optimized and represents the recommended way to build HTTP servers in Bun. Existing Node.js projects may use Bun's [nearly complete](/docs/runtime/nodejs-apis#node_http) implementation of the Node.js [`http`](https://nodejs.org/api/http.html) and [`https`](https://nodejs.org/api/https.html) modules. +These modules have been re-implemented to use Bun's fast internal HTTP infrastructure. Feel free to use these modules directly; frameworks like [Express](https://expressjs.com/) that depend on these modules should work out of the box. For granular compatibility information, see [Runtime > Node.js APIs](/docs/runtime/nodejs-apis). {% /callout %} -## Send a request (`fetch()`) - -Bun implements the Web `fetch` API for making HTTP requests. The `fetch` function is available in the global scope. +To start a high-performance HTTP server with a clean API, the recommended approach is [`Bun.serve`](#start-a-server-bun-serve). -```ts -const response = await fetch("https://bun.sh/manifest.json"); -const result = (await response.json()) as any; -console.log(result.icons[0].src); -// => "/logo-square.jpg" -``` - -## Start a server (`Bun.serve()`) +## `Bun.serve()` Start an HTTP server in Bun with `Bun.serve`. @@ -50,7 +43,7 @@ Bun.serve({ }); ``` -### Error handling +## Error handling To activate development mode, set `development: true`. By default, development mode is _enabled_ unless `NODE_ENV` is `production`. @@ -100,36 +93,72 @@ const server = Bun.serve({ server.stop(); ``` -### TLS +## 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`. +Bun supports TLS out of the box, powered by [OpenSSL](https://www.openssl.org/). Enable TLS by passing in a value for `key` and `cert`; 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 + + // can be string, BunFile, TypedArray, Buffer, or array thereof + key: Bun.file("./key.pem"), + cert: Bun.file("./cert.pem"), + + // passphrase, only required if key is encrypted + passphrase: "super-secret", +}); +``` + +The `key` and `cert` fields expect the _contents_ of your TLS key and certificate. This can be a string, `BunFile`, `TypedArray`, or `Buffer`. + +```ts +Bun.serve({ + fetch() {}, + + // BunFile + key: Bun.file("./key.pem"), + // Buffer + key: fs.readFileSync("./key.pem"), + // string + key: fs.readFileSync("./key.pem", "utf8"), + // array of above + key: [Bun.file('./key1.pem'), Bun.file('./key2.pem'] + }); ``` -The root CA and Diffie-Helman parameters can be configured as well. +{% callout %} + +**Note** — Earlier versions of Bun supported passing a file path as `keyFile` and `certFile`; this has been deprecated as of `v0.6.3`. + +{% /callout %} + +Optionally, you can override the trusted CA certificates by passing a value for `ca`. By default, the server will trust the list of well-known CAs curated by Mozilla. When `ca` is specified, the Mozilla list is overwritten. ```ts Bun.serve({ 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 + key: Bun.file("./key.pem"), // path to TLS key + cert: Bun.file("./cert.pem"), // path to TLS cert + ca: Bun.file("./ca.pem"), // path to root CA certificate +}); +``` + +To override Diffie-Helman parameters: + +```ts +Bun.serve({ + // ... + dhParamsFile: "./dhparams.pem", // path to Diffie Helman parameters }); ``` -### Hot reloading +## Hot reloading Thus far, the examples on this page have used the explicit `Bun.serve` API. Bun also supports an alternate syntax. @@ -153,7 +182,7 @@ $ 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 +## Streaming files To stream a file, return a `Response` object with a `BunFile` object as the body. @@ -191,7 +220,7 @@ Bun.serve({ }); ``` -### Benchmarks +## Benchmarks Below are Bun and Node.js implementations of a simple HTTP server that responds `Bun!` to each incoming `Request`. @@ -234,7 +263,7 @@ The `Bun.serve` server can handle roughly 2.5x more requests per second than Nod {% image width="499" alt="image" src="https://user-images.githubusercontent.com/709451/162389032-fc302444-9d03-46be-ba87-c12bd8ce89a0.png" /%} -### Reference +## Reference {% details summary="See TypeScript definitions" %} diff --git a/docs/api/import-meta.md b/docs/api/import-meta.md new file mode 100644 index 000000000..8e148fffa --- /dev/null +++ b/docs/api/import-meta.md @@ -0,0 +1,52 @@ +The `import.meta` object is a way for a module to access information about itself. It's part of the JavaScript language, but its contents are not standardized. Each "host" (browser, runtime, etc) is free to implement any properties it wishes on the `import.meta` object. + +Bun implements the following properties. + +```ts#/path/to/project/file.ts +import.meta.dir; // => "/path/to/project" +import.meta.file; // => "file.ts" +import.meta.path; // => "/path/to/project/file.ts" + +import.meta.main; // `true` if this file is directly executed by `bun run` + // `false` otherwise + +import.meta.resolveSync("zod") +// resolve an import specifier relative to the directory +``` + +{% table %} + +--- + +- `import.meta.dir` +- Absolute path to the directory containing the current fil, e.g. `/path/to/project`. Equivalent to `__dirname` in Node.js. + +--- + +- `import.meta.file` +- The name of the current file, e.g. `index.tsx`. Equivalent to `__filename` in Node.js. + +--- + +- `import.meta.path` +- Absolute path to the current file, e.g. `/path/to/project/index.tx`. + +--- + +- `import.meta.main` +- `boolean` Indicates whether the current file is the entrypoint to the current `bun` process. Is the file being directly executed by `bun run` or is it being imported? + +--- + +- `import.meta.resolve{Sync}` +- Resolve a module specifier (e.g. `"zod"` or `"./file.tsx`) to an absolute path. While file would be imported if the specifier were imported from this file? + + ```ts + import.meta.resolveSync("zod"); + // => "/path/to/project/node_modules/zod/index.ts" + + import.meta.resolveSync("./file.tsx"); + // => "/path/to/project/file.tsx" + ``` + +{% /table %} diff --git a/docs/api/streams.md b/docs/api/streams.md new file mode 100644 index 000000000..7f3e3bcb4 --- /dev/null +++ b/docs/api/streams.md @@ -0,0 +1,172 @@ +Streams are an important abstraction for working with binary data without loading it all into memory at once. They are commonly used for reading and writing files, sending and receiving network requests, and processing large amounts of data. + +Bun implements the Web APIs [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) and [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream). + +{% callout %} +Bun also implements the `node:stream` module, including [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams), [`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), and [`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). For complete documentation, refer to the [Node.js docs](https://nodejs.org/api/stream.html). +{% /callout %} + +To create a simple `ReadableStream`: + +```ts +const stream = new ReadableStream({ + start(controller) { + controller.enqueue("hello"); + controller.enqueue("world"); + controller.close(); + }, +}); +``` + +The contents of a `ReadableStream` can be read chunk-by-chunk with `for await` syntax. + +```ts +for await (const chunk of stream) { + console.log(chunk); + // => "hello" + // => "world" +} +``` + +For a more complete discusson of streams in Bun, see [API > Streams](/docs/api/streams). + +## Direct `ReadableStream` + +Bun implements an optimized version of `ReadableStream` that avoid unnecessary data copying & queue management logic. With a traditional `ReadableStream`, chunks of data are _enqueued_. Each chunk is copied into a queue, where it sits until the stream is ready to send more data. + +```ts +const stream = new ReadableStream({ + start(controller) { + controller.enqueue("hello"); + controller.enqueue("world"); + controller.close(); + }, +}); +``` + +With a direct `ReadableStream`, chunks of data are written directly to the stream. No queueing happens, and there's no need to clone the chunk data into memory. The `controller` API is updated to reflect this; instead of `.enqueue()` you call `.write`. + +```ts +const stream = new ReadableStream({ + type: "direct", + pull(controller) { + controller.write("hello"); + controller.write("world"); + }, +}); +``` + +When using a direct `ReadableStream`, all chunk queueing is handled by the destination. The consumer of the stream receives exactly what is passed to `controller.write()`, without any encoding or modification. + +## `Bun.ArrayBufferSink` + +The `Bun.ArrayBufferSink` class is a fast incremental writer for constructing an `ArrayBuffer` of unknown size. + +```ts +const sink = new Bun.ArrayBufferSink(); + +sink.write("h"); +sink.write("e"); +sink.write("l"); +sink.write("l"); +sink.write("o"); + +sink.end(); +// ArrayBuffer(5) [ 104, 101, 108, 108, 111 ] +``` + +To instead retrieve the data as a `Uint8Array`, pass the `asUint8Array` option to the constructor. + +```ts-diff +const sink = new Bun.ArrayBufferSink({ ++ asUint8Array: true +}); + +sink.write("h"); +sink.write("e"); +sink.write("l"); +sink.write("l"); +sink.write("o"); + +sink.end(); +// Uint8Array(5) [ 104, 101, 108, 108, 111 ] +``` + +The `.write()` method supports strings, typed arrays, `ArrayBuffer`, and `SharedArrayBuffer`. + +```ts +sink.write("h"); +sink.write(new Uint8Array([101, 108])); +sink.write(Buffer.from("lo").buffer); + +sink.end(); +``` + +Once `.end()` is called, no more data can be written to the `ArrayBufferSink`. However, in the context of buffering a stream, it's useful to continuously write data and periodically `.flush()` the contents (say, into a `WriteableStream`). To support this, pass `stream: true` to the constructor. + +```ts +const sink = new Bun.ArrayBufferSink({ + stream: true, +}); + +sink.write("h"); +sink.write("e"); +sink.write("l"); +sink.flush(); +// ArrayBuffer(5) [ 104, 101, 108 ] + +sink.write("l"); +sink.write("o"); +sink.flush(); +// ArrayBuffer(5) [ 108, 111 ] +``` + +The `.flush()` method returns the buffered data as an `ArrayBuffer` (or `Uint8Array` if `asUint8Array: true`) and clears internal buffer. + +To manually set the size of the internal buffer in bytes, pass a value for `highWaterMark`: + +```ts +const sink = new Bun.ArrayBufferSink({ + highWaterMark: 1024 * 1024, // 1 MB +}); +``` + +{% details summary="Reference" %} + +```ts +/** + * Fast incremental writer that becomes an `ArrayBuffer` on end(). + */ +export class ArrayBufferSink { + constructor(); + + start(options?: { + asUint8Array?: boolean; + /** + * Preallocate an internal buffer of this size + * This can significantly improve performance when the chunk size is small + */ + highWaterMark?: number; + /** + * On {@link ArrayBufferSink.flush}, return the written data as a `Uint8Array`. + * Writes will restart from the beginning of the buffer. + */ + stream?: boolean; + }): void; + + write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number; + /** + * Flush the internal buffer + * + * If {@link ArrayBufferSink.start} was passed a `stream` option, this will return a `ArrayBuffer` + * If {@link ArrayBufferSink.start} was passed a `stream` option and `asUint8Array`, this will return a `Uint8Array` + * Otherwise, this will return the number of bytes written since the last flush + * + * This API might change later to separate Uint8ArraySink and ArrayBufferSink + */ + flush(): number | Uint8Array | ArrayBuffer; + end(): ArrayBuffer | Uint8Array; +} +``` + +{% /details %} diff --git a/docs/api/tcp.md b/docs/api/tcp.md index cb6d2d783..bcf86d9a8 100644 --- a/docs/api/tcp.md +++ b/docs/api/tcp.md @@ -59,7 +59,7 @@ Bun.listen({ }); ``` -To enable TLS, pass a `tls` object containing `keyFile` and `certFile` properties. +To enable TLS, pass a `tls` object containing `key` and `cert` fields. ```ts Bun.listen({ @@ -69,13 +69,38 @@ Bun.listen({ data(socket, data) {}, }, tls: { - certFile: "cert.pem", - keyFile: "key.pem", + // can be string, BunFile, TypedArray, Buffer, or array thereof + key: Bun.file("./key.pem"), + cert: Bun.file("./cert.pem"), }, }); ``` -The result of `Bun.listen` is a server that conforms to the `TCPSocket` instance. +{% callout %} + +**Note** — Earlier versions of Bun supported passing a file path as `keyFile` and `certFile`; this has been deprecated as of `v0.6.3`. + +{% /callout %} + +The `key` and `cert` fields expect the _contents_ of your TLS key and certificate. This can be a string, `BunFile`, `TypedArray`, or `Buffer`. + +```ts +Bun.listen({ + // ... + tls: { + // BunFile + key: Bun.file("./key.pem"), + // Buffer + key: fs.readFileSync("./key.pem"), + // string + key: fs.readFileSync("./key.pem", "utf8"), + // array of above + key: [Bun.file('./key1.pem'), Bun.file('./key2.pem'] + }, +}); +``` + +The result of `Bun.listen` is a server that conforms to the `TCPSocket` interface. ```ts const server = Bun.listen({ diff --git a/docs/api/utils.md b/docs/api/utils.md index 59167b2b7..7797650f5 100644 --- a/docs/api/utils.md +++ b/docs/api/utils.md @@ -1,4 +1,47 @@ -## `Bun.sleep` +## `Bun.version` + +A `string` containing the version of the `bun` CLI that is currently running. + +```ts +Bun.version; +// => "0.6.4" +``` + +## `Bun.revision` + +The git commit of [Bun](https://github.com/oven-sh/bun) that was compiled to create the current `bun` CLI. + +```ts +Bun.revision; +// => "f02561530fda1ee9396f51c8bc99b38716e38296" +``` + +## `Bun.env` + +An alias for `process.env`. + +## `Bun.main` + +An absolute path to the entrypoint of the current program (the file that was executed with `bun run`). + +```ts#script.ts +Bun.main; +// /path/to/script.ts +``` + +This is particular useful for determining whether a script is being directly executed, as opposed to being imported by another script. + +```ts +if (import.meta.path === Bun.main) { + // this script is being directly executed +} else { + // this file is being imported from another script +} +``` + +This is analogous to the [`require.main = module` trick](https://stackoverflow.com/questions/6398196/detect-if-called-through-require-or-directly-by-command-line) in Node.js. + +## `Bun.sleep()` `Bun.sleep(ms: number)` (added in Bun v0.5.6) @@ -20,11 +63,33 @@ await Bun.sleep(oneSecondInFuture); console.log("hello one second later!"); ``` -## `Bun.which` +## `Bun.sleepSync()` + +`Bun.sleepSync(ms: number)` (added in Bun v0.5.6) + +A blocking synchronous version of `Bun.sleep`. + +```ts +console.log("hello"); +Bun.sleepSync(1000); // blocks thread for one second +console.log("hello one second later!"); +``` + +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. +Returns the path to an executable, similar to typing `which` in your terminal. ```ts const ls = Bun.which("ls"); @@ -51,11 +116,11 @@ const ls = Bun.which("ls", { console.log(ls); // null ``` -## `Bun.peek` +## `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. +Reads a promise's result without `await` or `.then`, but only if the promise has already fulfilled or rejected. ```ts import { peek } from "bun"; @@ -117,9 +182,9 @@ test("peek.status", () => { }); ``` -## `Bun.openInEditor` +## `Bun.openInEditor()` -Open a file in your default editor. Bun auto-detects your editor via the `$VISUAL` or `$EDITOR` environment variables. +Opens a file in your default editor. Bun auto-detects your editor via the `$VISUAL` or `$EDITOR` environment variables. ```ts const currentFile = import.meta.url; @@ -142,3 +207,290 @@ Bun.openInEditor(import.meta.url, { column: 5, }); ``` + +Bun.ArrayBufferSink; + +## `Bun.deepEquals()` + +Nestedly checks if two objects are equivalent. This is used internally by `expect().toEqual()` in `bun:test`. + +```ts +const foo = { a: 1, b: 2, c: { d: 3 } }; + +// true +Bun.deepEquals(foo, { a: 1, b: 2, c: { d: 3 } }); + +// false +Bun.deepEquals(foo, { a: 1, b: 2, c: { d: 4 } }); +``` + +A third boolean parameter can be used to enable "strict" mode. This is used by `expect().toStrictEqual()` in the test runner. + +```ts +const a = { entries: [1, 2] }; +const b = { entries: [1, 2], extra: undefined }; + +Bun.deepEquals(a, b); // => true +Bun.deepEquals(a, b, true); // => false +``` + +In strict mode, the following are considered unequal: + +```ts +// undefined values +Bun.deepEquals({}, { a: undefined }, true); // false + +// undefined in arrays +Bun.deepEquals(["asdf"], ["asdf", undefined], true); // false + +// sparse arrays +Bun.deepEquals([, 1], [undefined, 1], true); // false + +// object literals vs instances w/ same properties +class Foo { + a = 1; +} +Bun.deepEquals(new Foo(), { a: 1 }, true); // false +``` + +## `Bun.escapeHTML()` + +`Bun.escapeHTML(value: string | object | number | boolean): boolean` + +Escapes the following characters from an input string: + +- `"` becomes `"""` +- `&` becomes `"&"` +- `'` becomes `"'"` +- `<` becomes `"<"` +- `>` becomes `">"` + +This function is optimized for large input. On an M1X, it processes 480 MB/s - +20 GB/s, depending on how much data is being escaped and whether there is non-ascii +text. Non-string types will be converted to a string before escaping. + + + +## `Bun.fileURLToPath()` + +Converts a `file://` URL to an absolute path. + +```ts +const path = Bun.fileURLToPath(new URL("file:///foo/bar.txt")); +console.log(path); // "/foo/bar.txt" +``` + +## `Bun.pathToFileURL()` + +Converts an absolute path to a `file://` URL. + +```ts +const url = Bun.pathToFileURL("/foo/bar.txt"); +console.log(url); // "file:///foo/bar.txt" +``` + + + +## `Bun.gzipSync()` + +Compresses a `Uint8Array` using zlib's DEFLATE algorithm. + +```ts +const buf = Buffer.from("hello".repeat(100)); // Buffer extends Uint8Array +const compressed = Bun.gzipSync(buf); + +buf; // => Uint8Array(500) +compressed; // => Uint8Array(30) +``` + +Optionally, pass a parameters object as the second argument: + +{% details summary="zlib compression options"%} + +```ts +export type ZlibCompressionOptions = { + /** + * The compression level to use. Must be between `-1` and `9`. + * - A value of `-1` uses the default compression level (Currently `6`) + * - A value of `0` gives no compression + * - A value of `1` gives least compression, fastest speed + * - A value of `9` gives best compression, slowest speed + */ + level?: -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; + /** + * How much memory should be allocated for the internal compression state. + * + * A value of `1` uses minimum memory but is slow and reduces compression ratio. + * + * A value of `9` uses maximum memory for optimal speed. The default is `8`. + */ + memLevel?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; + /** + * The base 2 logarithm of the window size (the size of the history buffer). + * + * Larger values of this parameter result in better compression at the expense of memory usage. + * + * The following value ranges are supported: + * - `9..15`: The output will have a zlib header and footer (Deflate) + * - `-9..-15`: The output will **not** have a zlib header or footer (Raw Deflate) + * - `25..31` (16+`9..15`): The output will have a gzip header and footer (gzip) + * + * The gzip header will have no file name, no extra data, no comment, no modification time (set to zero) and no header CRC. + */ + windowBits?: + | -9 + | -10 + | -11 + | -12 + | -13 + | -14 + | -15 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 25 + | 26 + | 27 + | 28 + | 29 + | 30 + | 31; + /** + * Tunes the compression algorithm. + * + * - `Z_DEFAULT_STRATEGY`: For normal data **(Default)** + * - `Z_FILTERED`: For data produced by a filter or predictor + * - `Z_HUFFMAN_ONLY`: Force Huffman encoding only (no string match) + * - `Z_RLE`: Limit match distances to one (run-length encoding) + * - `Z_FIXED` prevents the use of dynamic Huffman codes + * + * `Z_RLE` is designed to be almost as fast as `Z_HUFFMAN_ONLY`, but give better compression for PNG image data. + * + * `Z_FILTERED` forces more Huffman coding and less string matching, it is + * somewhat intermediate between `Z_DEFAULT_STRATEGY` and `Z_HUFFMAN_ONLY`. + * Filtered data consists mostly of small values with a somewhat random distribution. + */ + strategy?: number; +}; +``` + +{% /details %} + +## `Bun.gunzipSync()` + +Uncompresses a `Uint8Array` using zlib's INFLATE algorithm. + +```ts +const buf = Buffer.from("hello".repeat(100)); // Buffer extends Uint8Array +const compressed = Bun.gunzipSync(buf); + +const dec = new TextDecoder(); +const uncompressed = Bun.inflateSync(compressed); +dec.decode(uncompressed); +// => "hellohellohello..." +``` + +## `Bun.deflateSync()` + +Compresses a `Uint8Array` using zlib's DEFLATE algorithm. + +```ts +const buf = Buffer.from("hello".repeat(100)); +const compressed = Bun.deflateSync(buf); + +buf; // => Uint8Array(25) +compressed; // => Uint8Array(10) +``` + +The second argument supports the same set of configuration options as [`Bun.gzipSync`](#bun.gzipSync). + +## `Bun.inflateSync()` + +Uncompresses a `Uint8Array` using zlib's INFLATE algorithm. + +```ts +const buf = Buffer.from("hello".repeat(100)); +const compressed = Bun.deflateSync(buf); + +const dec = new TextDecoder(); +const uncompressed = Bun.inflateSync(compressed); +dec.decode(uncompressed); +// => "hellohellohello..." +``` + +## `Bun.inspect()` + +Serializes an object to a `string` exactly as it would be printed by `console.log`. + +```ts +const obj = { foo: "bar" }; +const str = Bun.inspect(obj); +// => '{\nfoo: "bar" \n}' + +const arr = new Uint8Array([1, 2, 3]); +const str = Bun.inspect(arr); +// => "Uint8Array(3) [ 1, 2, 3 ]" +``` + +## `Bun.nanoseconds()` + +Returns the number of nanoseconds since the current `bun` process started, as a `number`. Useful for high-precision timing and benchmarking. + +```ts +Bun.nanoseconds(); +// => 7288958 +``` + +## `Bun.readableStreamTo*()` + +Bun implements a set of convenience functions for asynchronously consuming the body of a `ReadableStream` and converting it to various binary formats. + +```ts +const stream = (await fetch("https://bun.sh")).body; +stream; // => ReadableStream + +await Bun.readableStreamToArrayBuffer(stream); +// => ArrayBuffer + +await Bun.readableStreamToBlob(stream); +// => Blob + +await Bun.readableStreamToJSON(stream); +// => object + +await Bun.readableStreamToText(stream); +// => string + +// returns all chunks as an array +await Bun.readableStreamToArray(stream); +// => unknown[] +``` + +## `Bun.resolveSync()` + +Resolves a file path or module specifier using Bun's internal module resolution algorithm. The first argument is the path to resolve, and the second argument is the "root". If no match is found, an `Error` is thrown. + +```ts +Bun.resolveSync("./foo.ts", "/path/to/project"); +// => "/path/to/project/foo.ts" + +Bun.resolveSync("zod", "/path/to/project"); +// => "/path/to/project/node_modules/zod/index.ts" +``` + +To resolve relative to the current working directory, pass `process.cwd` or `"."` as the root. + +```ts +Bun.resolveSync("./foo.ts", process.cwd()); +Bun.resolveSync("./foo.ts", "/path/to/project"); +``` + +To resolve relative to the directory containing the current file, pass `import.meta.dir`. + +```ts +Bun.resolveSync("./foo.ts", import.meta.dir); +``` diff --git a/docs/bundler/index.md b/docs/bundler/index.md new file mode 100644 index 000000000..e0cd36651 --- /dev/null +++ b/docs/bundler/index.md @@ -0,0 +1,1334 @@ +Bun's fast native bundler is now in beta. It can be used via the `bun build` CLI command or the `Bun.build()` JavaScript API. + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './build', +}); +``` + +```sh#CLI +$ bun build ./index.tsx --outdir ./build +``` + +{% /codetabs %} + +It's fast. The numbers below represent performance on esbuild's [three.js benchmark](https://github.com/oven-sh/bun/tree/main/bench/bundle). + +{% image src="/images/bundler-speed.png" caption="Bundling 10 copies of three.js from scratch, with sourcemaps and minification" /%} + +## Why bundle? + +The bundler is a key piece of infrastructure in the JavaScript ecosystem. As a brief overview of why bundling is so important: + +- **Reducing HTTP requests.** A single package in `node_modules` may consist of hundreds of files, and large applications may have dozens of such dependencies. Loading each of these files with a separate HTTP request becomes untenable very quickly, so bundlers are used to convert our application source code into a smaller number of self-contained "bundles" that can be loaded with a single request. +- **Code transforms.** Modern apps are commonly built with languages or tools like TypeScript, JSX, and CSS modules, all of which must be converted into plain JavaScript and CSS before they can be consumed by a browser. The bundler is the natural place to configure these transformations. +- **Framework features.** Frameworks rely on bundler plugins & code transformations to implement common patterns like file-system routing, client-server code co-location (think `getServerSideProps` or Remix loaders), and server components. + +Let's jump into the bundler API. + +## Basic example + +Let's build our first bundle. You have the following two files, which implement a simple client-side rendered React app. + +{% codetabs %} + +```tsx#./index.tsx +import * as ReactDOM from 'react-dom/client'; +import {Component} from "./Component" + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render() +``` + +```tsx#./Component.tsx +export function Component(props: {message: string}) { + return

{props.message}

+} +``` + +{% /codetabs %} + +Here, `index.tsx` is the "entrypoint" to our application. Commonly, this will be a script that performs some _side effect_, like starting a server or—in this case—initializing a React root. Because we're using TypeScript & JSX, we need to bundle our code before it can be sent to the browser. + +To create our bundle: + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out +``` + +{% /codetabs %} + +For each file specified in `entrypoints`, Bun will generate a new bundle. This bundle will be written to disk in the `./out` directory (as resolved from the current working directory). After running the build, the file system looks like this: + +```ts +. +├── index.tsx +├── Component.tsx +└── out + └── index.js +``` + +The contents of `out/index.js` will look something like this: + +```js#out/index.js +// ... +// ~20k lines of code +// including the contents of `react-dom/client` and all its dependencies +// this is where the $jsxDEV and $createRoot functions are defined + + +// Component.tsx +function Component(props) { + return $jsxDEV("p", { + children: props.message + }, undefined, false, undefined, this); +} + +// index.tsx +var rootNode = document.getElementById("root"); +var root = $createRoot(rootNode); +root.render($jsxDEV(Component, { + message: "Sup!" +}, undefined, false, undefined, this)); +``` + +{% details summary="Tutorial: Run this file in your browser" %} +We can load this file in the browser to see our app in action. Create an `index.html` file in the `out` directory: + +```bash +$ touch out/index.html +``` + +Then paste the following contents into it: + +```html + + +
+ + + +``` + +Then spin up a static file server serving the `out` directory: + +```bash +$ bunx serve out +``` + +Visit `http://localhost:5000` to see your bundled app in action. + +{% /details %} + +## Content types + +Like the Bun runtime, the bundler supports an array of file types out of the box. The following table breaks down the bundler's set of standard "loaders". Refer to [Bundler > File types](/docs/runtime/loaders) for full documentation. + +{% table %} + +- Extensions +- Details + +--- + +- `.js` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` +- Uses Bun's built-in transpiler to parse the file and transpile TypeScript/JSX syntax to vanilla JavaScript. The bundler executes a set of default transforms, including dead code elimination, tree shaking, and environment variable inlining. At the moment Bun does not attempt to down-convert syntax; if you use recently ECMAScript syntax, that will be reflected in the bundled code. + +--- + +- `.json` +- JSON files are parsed and inlined into the bundle as a JavaScript object. + + ```ts + import pkg from "./package.json"; + pkg.name; // => "my-package" + ``` + +--- + +- `.toml` +- TOML files are parsed and inlined into the bundle as a JavaScript object. + + ```ts + import config from "./bunfig.toml"; + config.logLevel; // => "debug" + ``` + +--- + +- `.txt` +- The contents of the text file are read and inlined into the bundle as a string. + + ```ts + import contents from "./file.txt"; + console.log(contents); // => "Hello, world!" + ``` + +--- + +- `.node` `.wasm` +- These files are supported by the Bun runtime, but during bundling they are treated as [assets](#assets). + +{% /table %} + +### Assets + +If the bundler encounters an import with an unrecognized extension, it treats the imported file as an _external file_. The referenced file is copied as-is into `outdir`, and the import is resolved as a _path_ to the file. + +{% codetabs %} + +```ts#Input +// bundle entrypoint +import logo from "./logo.svg"; +console.log(logo); +``` + +```ts#Output +// bundled output +var logo = "./logo-ab237dfe.svg"; +console.log(logo); +``` + +{% /codetabs %} + +{% callout %} +The exact behavior of the file loader is also impacted by [`naming`](#naming) and [`publicPath`](#publicpath). +{% /callout %} + +Refer to the [Bundler > Loaders](/docs/bundler/loaders#file) page for more complete documentation on the file loader. + +### Plugins + +The behavior described in this table can be overridden or extended with [plugins](/docs/bundler/plugins). Refer to the [Bundler > Loaders](/docs/bundler/plugins) page for complete documentation. + +## API + +### `entrypoints` + +**Required.** An array of paths corresponding to the entrypoints of our application. One bundle will be generated for each entrypoint. + +{% codetabs group="a" %} + +```ts#JavaScript +const result = await Bun.build({ + entrypoints: ["./index.ts"], +}); +// => { success: boolean, outputs: BuildArtifact[], logs: BuildMessage[] } +``` + +```bash#CLI +$ bun build --entrypoints ./index.ts +# the bundle will be printed to stdout +# +``` + +{% /codetabs %} + +### `outdir` + +The directory where output files will be written. + +{% codetabs group="a" %} + +```ts#JavaScript +const result = await Bun.build({ + entrypoints: ['./index.ts'], + outdir: './out' +}); +// => { success: boolean, outputs: BuildArtifact[], logs: BuildMessage[] } +``` + +```bash#CLI +$ bun build --entrypoints ./index.ts --outdir ./out +# a summary of bundled files will be printed to stdout +``` + +{% /codetabs %} + +If `outdir` is not passed to the JavaScript API, bundled code will not be written to disk. Bundled files are returned in an array of `BuildArtifact` objects. These objects are Blobs with extra properties; see [Outputs](#outputs) for complete documentation. + +```ts +const result = await Bun.build({ + entrypoints: ["./index.ts"], +}); + +for (const result of result.outputs) { + // Can be consumed as blobs + await result.text(); + + // Bun will set Content-Type and Etag headers + new Response(result); + + // Can be written manually, but you should use `outdir` in this case. + Bun.write(path.join("out", result.path), result); +} +``` + +When `outdir` is set, the `path` property on a `BuildArtifact` will be the absolute path to where it was written to. + +### `target` + +The intended execution environment for the bundle. + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.ts'], + outdir: './out', + target: 'browser', // default +}) +``` + +```bash#CLI +$ bun build --entrypoints ./index.ts --outdir ./out --target browser +``` + +{% /codetabs %} + +Depending on the target, Bun will apply different module resolution rules and optimizations. + + + +{% table %} + +--- + +- `browser` +- _Default._ For generating bundles that are intended for execution by a browser. Prioritizes the `"browser"` export condition when resolving imports. An error will be thrown if any Node.js or Bun built-ins are imported or used, e.g. `node:fs` or `Bun.serve`. + +--- + +- `bun` +- For generating bundles that are intended to be run by the Bun runtime. In many cases, it isn't necessary to bundle server-side code; you can directly execute the source code without modification. However, bundling your server code can reduce startup times and improve running performance. + + All bundles generated with `target: "bun"` are marked with a special `// @bun` pragma, which indicates to the Bun runtime that there's no need to re-transpile the file before execution. + + If any entrypoints contains a Bun shebang (`#!/usr/bin/env bun`) the bundler will default to `target: "bun"` instead of `"browser`. + +--- + +- `node` +- For generating bundles that are intended to be run by Node.js. Prioritizes the `"node"` export condition when resolving imports, and outputs `.mjs`. In the future, this will automatically polyfill the `Bun` global and other built-in `bun:*` modules, though this is not yet implemented. + +{% /table %} + +{% callout %} + +{% /callout %} + +### `format` + +Specifies the module format to be used in the generated bundles. + +Currently the bundler only supports one module format: `"esm"`. Support for `"cjs"` and `"iife"` are planned. + +{% codetabs %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + format: "esm", +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --format esm +``` + +{% /codetabs %} + + + +### `splitting` + +Whether to enable code splitting. + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + splitting: false, // default +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --splitting +``` + +{% /codetabs %} + +When `true`, the bundler will enable _code splitting_. When multiple entrypoints both import the same file, module, or set of files/modules, it's often useful to split the shared code into a separate bundle. This shared bundle is known as a _chunk_. Consider the following files: + +{% codetabs %} + +```ts#entry-a.ts +import { shared } from './shared.ts'; +``` + +```ts#entry-b.ts +import { shared } from './shared.ts'; +``` + +```ts#shared.ts +export const shared = 'shared'; +``` + +{% /codetabs %} + +To bundle `entry-a.ts` and `entry-b.ts` with code-splitting enabled: + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./entry-a.ts', './entry-b.ts'], + outdir: './out', + splitting: true, +}) +``` + +```bash#CLI +$ bun build ./entry-a.ts ./entry-b.ts --outdir ./out --splitting +``` + +{% /codetabs %} + +Running this build will result in the following files: + +```txt +. +├── entry-a.tsx +├── entry-b.tsx +├── shared.tsx +└── out + ├── entry-a.js + ├── entry-b.js + └── chunk-2fce6291bf86559d.js + +``` + +The generated `chunk-2fce6291bf86559d.js` file contains the shared code. To avoid collisions, the file name automatically includes a content hash by default. This can be customized with [`naming`](#naming). + +### `plugins` + +A list of plugins to use during bundling. + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + plugins: [/* ... */], +}) +``` + +```bash#CLI +n/a +``` + +{% /codetabs %} + +Bun implements a univeral plugin system for both Bun's runtime and bundler. Refer to the [plugin documentation](/docs/bundler/plugins) for complete documentation. + + + +### `sourcemap` + +Specifies the type of sourcemap to generate. + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + sourcemap: "external", // default "none" +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --sourcemap=external +``` + +{% /codetabs %} + +{% table %} + +--- + +- `"none"` +- _Default._ No sourcemap is generated. + +--- + +- `"inline"` +- A sourcemap is generated and appended to the end of the generated bundle as a base64 payload. + + ```ts + // + + //# sourceMappingURL=data:application/json;base64, + ``` + +--- + +- `"external"` +- A separate `*.js.map` file is created alongside each `*.js` bundle. + +{% /table %} + +{% callout %} + +Generated bundles contain a [debug id](https://sentry.engineering/blog/the-case-for-debug-ids) that can be used to associate a bundle with its corresponding sourcemap. This `debugId` is added as a comment at the bottom of the file. + +```ts +// + +//# debugId= +``` + +The associated `*.js.map` sourcemap will be a JSON file containing an equivalent `debugId` property. + +{% /callout %} + +### `minify` + +Whether to enable minification. Default `false`. + +{% callout %} +When targeting `bun`, identifiers will be minified by default. +{% /callout %} + +To enable all minification options: + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + minify: true, // default false +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --minify +``` + +{% /codetabs %} + +To granularly enable certain minifications: + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + minify: { + whitespace: true, + identifiers: true, + syntax: true, + }, +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --minify-whitespace --minify-identifiers --minify-syntax +``` + +{% /codetabs %} + + + +### `external` + +A list of import paths to consider _external_. Defaults to `[]`. + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + external: ["lodash", "react"], // default: [] +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --external lodash --external react +``` + +{% /codetabs %} + +An external import is one that will not be included in the final bundle. Instead, the `import` statement will be left as-is, to be resolved at runtime. + +For instance, consider the following entrypoint file: + +```ts#index.tsx +import _ from "lodash"; +import {z} from "zod"; + +const value = z.string().parse("Hello world!") +console.log(_.upperCase(value)); +``` + +Normally, bundling `index.tsx` would generate a bundle containing the entire source code of the `"zod"` package. If instead, we want to leave the `import` statement as-is, we can mark it as external: + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + external: ['zod'], +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --external zod +``` + +{% /codetabs %} + +The generated bundle will look something like this: + +```js#out/index.js +import {z} from "zod"; + +// ... +// the contents of the "lodash" package +// including the `_.upperCase` function + +var value = z.string().parse("Hello world!") +console.log(_.upperCase(value)); +``` + +To mark all imports as external, use the wildcard `*`: + +{% codetabs %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + external: ['*'], +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --external '*' +``` + +{% /codetabs %} + +### `naming` + +Customizes the generated file names. Defaults to `./[dir]/[name].[ext]`. + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + naming: "[dir]/[name].[ext]", // default +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --entry-naming [dir]/[name].[ext] +``` + +{% /codetabs %} + +By default, the names of the generated bundles are based on the name of the associated entrypoint. + +```txt +. +├── index.tsx +└── out + └── index.js +``` + +With multiple entrypoints, the generated file hierarchy will reflect the directory structure of the entrypoints. + +```txt +. +├── index.tsx +└── nested + └── index.tsx +└── out + ├── index.js + └── nested + └── index.js +``` + +The names and locations of the generated files can be customized with the `naming` field. This field accepts a template string that is used to generate the filenames for all bundles corresponding to entrypoints. where the following tokens are replaced with their corresponding values: + +- `[name]` - The name of the entrypoint file, without the extension. +- `[ext]` - The extension of the generated bundle. +- `[hash]` - A hash of the bundle contents. +- `[dir]` - The relative path from the build root to the parent directory of the file. + +For example: + +{% table %} + +- Token +- `[name]` +- `[ext]` +- `[hash]` +- `[dir]` + +--- + +- `./index.tsx` +- `index` +- `js` +- `a1b2c3d4` +- `""` (empty string) + +--- + +- `./nested/entry.ts` +- `entry` +- `js` +- `c3d4e5f6` +- `"nested"` + +{% /table %} + +We can combine these tokens to create a template string. For instance, to include the hash in the generated bundle names: + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + naming: 'files/[dir]/[name]-[hash].[ext]', +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --entry-naming [name]-[hash].[ext] +``` + +{% /codetabs %} + +This build would result in the following file structure: + +```txt +. +├── index.tsx +└── out + └── files + └── index-a1b2c3d4.js +``` + +When a `string` is provided for the `naming` field, it is used only for bundles _that correspond to entrypoints_. The names of [chunks](#splitting) and copied assets are not affected. Using the JavaScript API, separate template strings can be specified for each type of generated file. + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + naming: { + // default values + entry: '[dir]/[name].[ext]', + chunk: '[name]-[hash].[ext]', + asset: '[name]-[hash].[ext]', + }, +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --entry-naming "[dir]/[name].[ext]" --chunk-naming "[name]-[hash].[ext]" --asset-naming "[name]-[hash].[ext]" +``` + +{% /codetabs %} + +### `root` + +The root directory of the project. + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./pages/a.tsx', './pages/b.tsx'], + outdir: './out', + root: '.', +}) +``` + +```bash#CLI +n/a +``` + +{% /codetabs %} + +If unspecified, it is computed to be the first common ancestor of all entrypoint files. Consider the following file structure: + +```txt +. +└── pages + └── index.tsx + └── settings.tsx +``` + +We can build both entrypoints in the `pages` directory: + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./pages/index.tsx', './pages/settings.tsx'], + outdir: './out', +}) +``` + +```bash#CLI +$ bun build ./pages/index.tsx ./pages/settings.tsx --outdir ./out +``` + +{% /codetabs %} + +This would result in a file structure like this: + +```txt +. +└── pages + └── index.tsx + └── settings.tsx +└── out + └── index.js + └── settings.js +``` + +Since the `pages` directory is the first common ancestor of the entrypoint files, it is considered the project root. This means that the generated bundles live at the top level of the `out` directory; there is no `out/pages` directory. + +This behavior can be overridden by specifying the `root` option: + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./pages/index.tsx', './pages/settings.tsx'], + outdir: './out', + root: '.', +}) +``` + +```bash#CLI +$ bun build ./pages/index.tsx ./pages/settings.tsx --outdir ./out --root . +``` + +{% /codetabs %} + +By specifying `.` as `root`, the generated file structure will look like this: + +```txt +. +└── pages + └── index.tsx + └── settings.tsx +└── out + └── pages + └── index.js + └── settings.js +``` + +### `publicPath` + +A prefix to be appended to any import paths in bundled code. + + + +In many cases, generated bundles will contain no `import` statements. After all, the goal of bundling is to combine all of the code into a single file. However there are a number of cases with the generated bundles will contain `import` statements. + +- **Asset imports** — When importing an unrecognized file type like `*.svg`, the bundler defers to the [`file` loader](/docs/bundler/loaders#file), which copies the file into `outdir` as is. The import is converted into a variable +- **External modules** — Files and modules can be marked as [`external`](#external), in which case they will not be included in the bundle. Instead, the `import` statement will be left in the final bundle. +- **Chunking**. When [`splitting`](#splitting) is enabled, the bundler may generate separate "chunk" files that represent code that is shared among multiple entrypoints. + +In any of these cases, the final bundles may contain paths to other files. By default these imports are _relative_. Here is an example of a simple asset import: + +{% codetabs %} + +```ts#Input +import logo from './logo.svg'; +console.log(logo); +``` + +```ts#Output +// logo.svg is copied into +// and hash is added to the filename to prevent collisions +var logo = './logo-a7305bdef.svg'; +console.log(logo); +``` + +{% /codetabs %} + +Setting `publicPath` will prefix all file paths with the specified value. + +{% codetabs group="a" %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + publicPath: 'https://cdn.example.com/', // default is undefined +}) +``` + +```bash#CLI +n/a +``` + +{% /codetabs %} + +The output file would now look something like this. + +```ts-diff#Output +- var logo = './logo-a7305bdef.svg'; ++ var logo = 'https://cdn.example.com/logo-a7305bdef.svg'; +``` + +### `define` + +A map of global identifiers to be replaced at build time. Keys of this object are identifier names, and values are JSON strings that will be inlined. + +{% callout } +This is not needed to inline `process.env.NODE_ENV`, as Bun does this automatically. +{% /callout %} + +{% codetabs %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + define: { + STRING: JSON.stringify("value"), + "nested.boolean": "true", + }, +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --define 'STRING="value"' --define "nested.boolean=true" +``` + +{% /codetabs %} + +### `loader` + +A map of file extensions to [built-in loader names](https://bun.sh/docs/bundler/loaders#built-in-loaders). This can be used to quickly customize how certain file files are loaded. + +{% codetabs %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + loader: { + ".png": "dataurl", + ".txt": "file", + }, +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --loader .png:dataurl --loader .txt:file +``` + +{% /codetabs %} + +## Outputs + +The `Bun.build` function returns a `Promise`, defined as: + +```ts +interface BuildOutput { + outputs: BuildArtifact[]; + success: boolean; + logs: Array; // see docs for details +} + +interface BuildArtifact extends Blob { + kind: "entry-point" | "chunk" | "asset" | "sourcemap"; + path: string; + loader: Loader; + hash: string | null; + sourcemap: BuildArtifact | null; +} +``` + +The `outputs` array contains all the files that were generated by the build. Each artifact implements the `Blob` interface. + +```ts +const build = Bun.build({ + /* */ +}); + +for (const output of build.outputs) { + await output.arrayBuffer(); // => ArrayBuffer + await output.text(); // string +} +``` + +Each artifact also contains the following properties: + +{% table %} + +--- + +- `kind` +- What kind of build output this file is. A build generates bundled entrypoints, code-split "chunks", sourcemaps, and copied assets (like images). + +--- + +- `path` +- Absolute path to the file on disk + +--- + +- `loader` +- The loader was used to interpret the file. See [Bundler > Loaders](/docs/bundler/loaders) to see how Bun maps file extensions to the appropriate built-in loader. + +--- + +- `hash` +- The hash of the file contents. Always defined for assets. + +--- + +- `sourcemap` +- The sourcemap file corresponding to this file, if generated. Only defined for entrypoints and chunks. + +{% /table %} + +Similar to `BunFile`, `BuildArtifact` objects can be passed directly into `new Response()`. + +```ts +const build = Bun.build({ + /* */ +}); + +const artifact = build.outputs[0]; + +// Content-Type header is automatically set +return new Response(artifact); +``` + +The Bun runtime implements special pretty-printing of `BuildArtifact` object to make debugging easier. + +{% codetabs %} + +```ts#Build_script +// build.ts +const build = Bun.build({/* */}); + +const artifact = build.outputs[0]; +console.log(artifact); +``` + +```sh#Shell_output +$ bun run build.ts +BuildArtifact (entry-point) { + path: "./index.js", + loader: "tsx", + kind: "entry-point", + hash: "824a039620219640", + Blob (114 bytes) { + type: "text/javascript;charset=utf-8" + }, + sourcemap: null +} +``` + +{% /codetabs %} + +### Executables + +Bun supports "compiling" a JavaScript/TypeScript entrypoint into a standalone executable. This executable contains a copy of the Bun binary. + +```sh +$ bun build ./cli.tsx --outfile mycli --compile +$ ./mycli +``` + +Refer to [Bundler > Executables](/docs/bundler/executables) for complete documentation. + +## Logs and errors + +`Bun.build` only throws if invalid options are provided. Read the `success` property to determine if the build was successful; the `logs` property will contain additional details. + +```ts +const result = await Bun.build({ + entrypoints: ["./index.tsx"], + outdir: "./out", +}); + +if (!result.success) { + console.error("Build failed"); + for (const message of result.logs) { + // Bun will pretty print the message object + console.error(message); + } +} +``` + +Each message is either a `BuildMessage` or `ResolveMessage` object, which can be used to trace what problems happened in the build. + +```ts +class BuildMessage { + name: string; + position?: Position; + message: string; + level: "error" | "warning" | "info" | "debug" | "verbose"; +} + +class ResolveMessage extends BuildMessage { + code: string; + referrer: string; + specifier: string; + importKind: ImportKind; +} +``` + +If you want to throw an error from a failed build, consider passing the logs to an `AggregateError`. If uncaught, Bun will pretty-print the contained messages nicely. + +```ts +if (!result.success) { + throw new AggregateError(result.logs, "Build failed"); +} +``` + +## Reference + +```ts +interface Bun { + build(options: BuildOptions): Promise; +} + +interface BuildOptions { + entrypoints: string[]; // required + outdir?: string; // default: no write (in-memory only) + format?: "esm"; // later: "cjs" | "iife" + target?: "browser" | "bun" | "node"; // "browser" + splitting?: boolean; // true + plugins?: BunPlugin[]; // [] // See https://bun.sh/docs/bundler/plugins + loader?: { [k in string]: Loader }; // See https://bun.sh/docs/bundler/loaders + manifest?: boolean; // false + external?: string[]; // [] + sourcemap?: "none" | "inline" | "external"; // "none" + root?: string; // computed from entrypoints + naming?: + | string + | { + entry?: string; // '[dir]/[name].[ext]' + chunk?: string; // '[name]-[hash].[ext]' + asset?: string; // '[name]-[hash].[ext]' + }; + publicPath?: string; // e.g. http://mydomain.com/ + minify?: + | boolean // false + | { + identifiers?: boolean; + whitespace?: boolean; + syntax?: boolean; + }; +} + +interface BuildOutput { + outputs: BuildArtifact[]; + success: boolean; + logs: Array; +} + +interface BuildArtifact extends Blob { + path: string; + loader: Loader; + hash?: string; + kind: "entry-point" | "chunk" | "asset" | "sourcemap"; + sourcemap?: BuildArtifact; +} + +type Loader = "js" | "jsx" | "ts" | "tsx" | "json" | "toml" | "file" | "napi" | "wasm" | "text"; + +interface BuildOutput { + outputs: BuildArtifact[]; + success: boolean; + logs: Array; +} + +declare class ResolveMessage { + readonly name: "ResolveMessage"; + readonly position: Position | null; + readonly code: string; + readonly message: string; + readonly referrer: string; + readonly specifier: string; + readonly importKind: + | "entry_point" + | "stmt" + | "require" + | "import" + | "dynamic" + | "require_resolve" + | "at" + | "at_conditional" + | "url" + | "internal"; + readonly level: "error" | "warning" | "info" | "debug" | "verbose"; + + toString(): string; +} +``` + + diff --git a/docs/bundler/loaders.md b/docs/bundler/loaders.md index b06f07175..2dc5103df 100644 --- a/docs/bundler/loaders.md +++ b/docs/bundler/loaders.md @@ -221,7 +221,7 @@ If a value is specified for `publicPath`, the import will use value as a prefix {% /table %} {% callout %} -The location and file name of the copied file is determined by the value of [`naming.asset`](/docs/cli/build#naming). +The location and file name of the copied file is determined by the value of [`naming.asset`](/docs/bundler#naming). {% /callout %} This loader is copied into the `outdir` as-is. The name of the copied file is determined using the value of `naming.asset`. diff --git a/docs/bundler/migration.md b/docs/bundler/migration.md deleted file mode 100644 index 1bf9d52dc..000000000 --- a/docs/bundler/migration.md +++ /dev/null @@ -1,1133 +0,0 @@ -{% callout %} -**Note** — Available in the Bun v0.6.0 nightly. Run `bun upgrade --canary` to try it out. -{% /callout %} - -Bun's bundler API is inspired heavily by [esbuild](https://esbuild.github.io/). Migrating to Bun's bundler from esbuild should be relatively painless. This guide will briefly explain why you might consider migrating to Bun's bundler and provide a side-by-side API comparison reference for those who are already familiar with esbuild's API. - -There are a few behavioral differences to note. - -- **Bundling by default**. Unlike esbuild, Bun _always bundles by default_. This is why the `--bundle` flag isn't necessary in the Bun example. To transpile each file individually, use [`Bun.Transpiler`](/docs/api/transpiler). -- **It's just a bundler**. Unlike esbuild, Bun's bundler does not include a built-in development server or file watcher. It's just a bundler. The bundler is intended for use in conjunction with `Bun.serve` and other runtime APIs to achieve the same effect. As such, all options relating to HTTP/file watching are not applicable. - -## Performance - -This is the simplest reason to migrate to Bun's bundler. With an performance-minded API inspired by esbuild coupled with the extensively optimized Zig-based JS/TS parser, Bun's bundler is roughly 50% faster than esbuild on most benchmarks. - -IMAGE HERE - -## CLI API - -Bun and esbuild both provide a command-line interface. - -```bash -$ esbuild --outdir=out --bundle -$ bun build --outdir=out -``` - -In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Other flags like `--outdir ` do accept an argument; these flags can be written as `--outdir out` or `--outdir=out`. Some flags like `--define` can be specified several times: `--define foo=bar --define bar=baz`. - -{% table %} - -- `esbuild` -- `bun build` - ---- - -- `--bundle` -- n/a -- Bun always bundles, use `--no-bundle` to disable this behavior. - ---- - -- `--define:K=V` -- `--define K=V` -- Small syntax difference; no colon. - - ```bash - $ esbuild --define:foo=bar - $ bun build --define foo=bar - ``` - ---- - -- `--external:` -- `--external ` -- Small syntax difference; no colon. - - ```bash - $ esbuild --external:react - $ bun build --external react - ``` - ---- - -- `--format` -- `--format` -- Bun only supports `"esm"` currently but other module formats are planned. esbuild defaults to `"iife"`. - ---- - -- `--loader:.ext=loader` -- `--loader .ext:loader` -- Bun supports a different set of built-in loaders than esbuild; see [Bundler > Loaders](/docs/bundler/loaders) for a complete reference. The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not yet implemented. - - The syntax for `--loader` is slightly different. - - ```bash - $ esbuild app.ts --bundle --loader:.svg=text - $ bun build app.ts --loader .svg:text - ``` - ---- - -- `--minify` -- `--minify` -- No differences - ---- - -- `--outdir` -- `--outdir` -- No differences - ---- - -- `--outfile` -- `--outfile` - ---- - -- `--packages` -- n/a -- Not supported - ---- - -- `--platform` -- `--target` -- Renamed to `--target` for consistency with tsconfig. Does not support `neutral`. - ---- - -- `--serve` -- n/a -- Not applicable - ---- - -- `--sourcemap` -- `--sourcemap` -- No differences - ---- - -- `--splitting` -- `--splitting` -- No differences - ---- - -- `--target` -- n/a -- No supported. Bun's bundler performs no syntactic downleveling at this time. - ---- - -- `--watch` -- n/a -- Not applicable - ---- - -- `--allow-overwrite` -- n/a -- Overwriting is never allowed - ---- - -- `--analyze` -- n/a -- Not supported - ---- - -- `--asset-names` -- `--asset-naming` -- Renamed for consistency with `naming` in JS API - ---- - -- `--banner` -- n/a -- Not supported - ---- - -- `--certfile` -- n/a -- Not applicable - ---- - -- `--charset=utf8` -- n/a -- Not supported - ---- - -- `--chunk-names` -- `--chunk-naming` -- Renamed for consistency with `naming` in JS API - ---- - -- `--color` -- n/a -- Always enabled - ---- - -- `--drop` -- n/a -- Not supported - ---- - -- `--entry-names` -- `--entry-naming` -- Renamed for consistency with `naming` in JS API - ---- - -- `--footer` -- n/a -- Not supported - ---- - -- `--global-name` -- n/a -- Not applicable, Bun does not support `iife` output at this time - ---- - -- `--ignore-annotations` -- n/a -- Not supported - ---- - -- `--inject` -- n/a -- Not supported - ---- - -- `--jsx` -- `--jsx-runtime ` -- Supports `"automatic"` (uses `jsx` transform) and `"classic"` (uses `React.createElement`) - ---- - -- `--jsx-dev` -- n/a -- Bun reads `compilerOptions.jsx` from `tsconfig.json` to determine a default. If `compilerOptions.jsx` is `"react-jsx"`, or if `NODE_ENV=production`, Bun will use the `jsx` transform. Otherwise, it uses `jsxDEV`. For any to Bun uses `jsxDEV`. The bundler does not support `preserve`. - ---- - -- `--jsx-factory` -- `--jsx-factory` - ---- - -- `--jsx-fragment` -- `--jsx-fragment` - ---- - -- `--jsx-import-source` -- `--jsx-import-source` - ---- - -- `--jsx-side-effects` -- n/a -- JSX is always assumed to be side-effect-free - ---- - -- `--keep-names` -- n/a -- Not supported - ---- - -- `--keyfile` -- n/a -- Not applicable - ---- - -- `--legal-comments` -- n/a -- Not supported - ---- - -- `--log-level` -- n/a -- Not supported. This can be set in `bunfig.toml` as `logLevel`. - ---- - -- `--log-limit` -- n/a -- Not supported - ---- - -- `--log-override:X=Y` -- n/a -- Not supported - ---- - -- `--main-fields` -- n/a -- Not supported - ---- - -- `--mangle-cache` -- n/a -- Not supported - ---- - -- `--mangle-props` -- n/a -- Not supported - ---- - -- `--mangle-quoted` -- n/a -- Not supported - ---- - -- `--metafile` -- n/a -- Not supported - ---- - -- `--minify-whitespace` -- `--minify-whitespace` - ---- - -- `--minify-identifiers` -- `--minify-identifiers` - ---- - -- `--minify-syntax` -- `--minify-syntax` - ---- - -- `--out-extension` -- n/a -- Not supported - ---- - -- `--outbase` -- `--root` - ---- - -- `--preserve-symlinks` -- n/a -- Not supported - ---- - -- `--public-path` -- `--public-path` - ---- - -- `--pure` -- n/a -- Not supported - ---- - -- `--reserve-props` -- n/a -- Not supported - ---- - -- `--resolve-extensions` -- n/a -- Not supported - ---- - -- `--servedir` -- n/a -- Not applicable - ---- - -- `--source-root` -- n/a -- Not supported - ---- - -- `--sourcefile` -- n/a -- Not supported. Bun does not support `stdin` input yet. - ---- - -- `--sourcemap` -- `--sourcemap` -- No differences - ---- - -- `--sources-content` -- n/a -- Not supported - ---- - -- `--supported` -- n/a -- Not supported - ---- - -- `--tree-shaking` -- n/a -- Always `true` - ---- - -- `--tsconfig` -- `--tsconfig-override` - ---- - -- `--version` -- n/a -- Run `bun --version` to see the version of Bun. - -{% /table %} - -## JavaScript API - -{% table %} - -- `esbuild.build()` -- `Bun.build()` - ---- - -- `absWorkingDir` -- n/a -- Always set to `process.cwd()` - ---- - -- `alias` -- n/a -- Not supported - ---- - -- `allowOverwrite` -- n/a -- Always `false` - ---- - -- `assetNames` -- `naming.asset` -- Uses same templating syntax as esbuild, but `[ext]` must be included explicitly. - - ```ts - Bun.build({ - entrypoints: ["./index.tsx"], - naming: { - asset: "[name].[ext]", - }, - }); - ``` - ---- - -- `banner` -- n/a -- Not supported - ---- - -- `bundle` -- n/a -- Always `true`. Use [`Bun.Transpiler`](/docs/api/transpiler) to transpile without bundling. - ---- - -- `charset` -- n/a -- Not supported - ---- - -- `chunkNames` -- `naming.chunk` -- Uses same templating syntax as esbuild, but `[ext]` must be included explicitly. - - ```ts - Bun.build({ - entrypoints: ["./index.tsx"], - naming: { - chunk: "[name].[ext]", - }, - }); - ``` - ---- - -- `color` -- n/a -- Bun returns logs in the `logs` property of the build result. - ---- - -- `conditions` -- n/a -- Not supported. Export conditions priority is determined by `target`. - ---- - -- `define` -- `define` - ---- - -- `drop` -- n/a -- Not supported - ---- - -- `entryNames` -- `naming` or `naming.entry` -- Bun supports a `naming` key that can either be a string or an object. Uses same templating syntax as esbuild, but `[ext]` must be included explicitly. - - ```ts - Bun.build({ - entrypoints: ["./index.tsx"], - // when string, this is equivalent to entryNames - naming: "[name].[ext]", - - // granular naming options - naming: { - entry: "[name].[ext]", - asset: "[name].[ext]", - chunk: "[name].[ext]", - }, - }); - ``` - ---- - -- `entryPoints` -- `entrypoints` -- Capitalization difference - ---- - -- `external` -- `external` -- No differences - ---- - -- `footer` -- n/a -- Not supported - ---- - -- `format` -- `format` -- Only supports `"esm"` currently. Support for `"cjs"` and `"iife"` is planned. - ---- - -- `globalName` -- n/a -- Not supported - ---- - -- `ignoreAnnotations` -- n/a -- Not supported - ---- - -- `inject` -- n/a -- Not supported - ---- - -- `jsx` -- `jsx` -- Not supported in JS API, configure in `tsconfig.json` - ---- - -- `jsxDev` -- `jsxDev` -- Not supported in JS API, configure in `tsconfig.json` - ---- - -- `jsxFactory` -- `jsxFactory` -- Not supported in JS API, configure in `tsconfig.json` - ---- - -- `jsxFragment` -- `jsxFragment` -- Not supported in JS API, configure in `tsconfig.json` - ---- - -- `jsxImportSource` -- `jsxImportSource` -- Not supported in JS API, configure in `tsconfig.json` - ---- - -- `jsxSideEffects` -- `jsxSideEffects` -- Not supported in JS API, configure in `tsconfig.json` - ---- - -- `keepNames` -- n/a -- Not supported - ---- - -- `legalComments` -- n/a -- Not supported - ---- - -- `loader` -- `loader` -- Bun supports a different set of built-in loaders than esbuild; see [Bundler > Loaders](/docs/bundler/loaders) for a complete reference. The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not yet implemented. - ---- - -- `logLevel` -- n/a -- Not supported - ---- - -- `logLimit` -- n/a -- Not supported - ---- - -- `logOverride` -- n/a -- Not supported - ---- - -- `mainFields` -- n/a -- Not supported - ---- - -- `mangleCache` -- n/a -- Not supported - ---- - -- `mangleProps` -- n/a -- Not supported - ---- - -- `mangleQuoted` -- n/a -- Not supported - ---- - -- `metafile` -- n/a -- Not supported - - - ---- - -- `minify` -- `minify` -- In Bun, `minify` can be a boolean or an object. - - ```ts - Bun.build({ - entrypoints: ['./index.tsx'], - // enable all minification - minify: true - - // granular options - minify: { - identifiers: true, - syntax: true, - whitespace: true - } - }) - ``` - ---- - -- `minifyIdentifiers` -- `minify.identifiers` -- See `minify` - ---- - -- `minifySyntax` -- `minify.syntax` -- See `minify` - ---- - -- `minifyWhitespace` -- `minify.whitespace` -- See `minify` - ---- - -- `nodePaths` -- n/a -- Not supported - ---- - -- `outExtension` -- n/a -- Not supported - ---- - -- `outbase` -- `root` -- Different name - ---- - -- `outdir` -- `outdir` -- No differences - ---- - -- `outfile` -- `outfile` -- No differences - ---- - -- `packages` -- n/a -- Not supported, use `external` - ---- - -- `platform` -- `target` -- Supports `"bun"`, `"node"` and `"browser"` (the default). Does not support `"neutral"`. - ---- - -- `plugins` -- `plugins` -- Bun's plugin API is a subset of esbuild's. Some esbuild plugins will work out of the box with Bun. - ---- - -- `preserveSymlinks` -- n/a -- Not supported - ---- - -- `publicPath` -- `publicPath` -- No differences - ---- - -- `pure` -- n/a -- Not supported - ---- - -- `reserveProps` -- n/a -- Not supported - ---- - -- `resolveExtensions` -- n/a -- Not supported - ---- - -- `sourceRoot` -- n/a -- Not supported - ---- - -- `sourcemap` -- `sourcemap` -- Supports `"inline"`, `"external"`, and `"none"` - ---- - -- `sourcesContent` -- n/a -- Not supported - ---- - -- `splitting` -- `splitting` -- No differences - ---- - -- `stdin` -- n/a -- Not supported - ---- - -- `supported` -- n/a -- Not supported - ---- - -- `target` -- n/a -- No support for syntax downleveling - ---- - -- `treeShaking` -- n/a -- Always `true` - ---- - -- `tsconfig` -- n/a -- Not supported - ---- - -- `write` -- n/a -- Set to `true` if `outdir`/`outfile` is set, otherwise `false` - ---- - -{% /table %} - -## Plugin API - -Bun's plugin API is designed to be esbuild compatible. Bun doesn't support esbuild's entire plugin API surface, but the core functionality is implemented. Many third-party `esbuild` plugins will work out of the box with Bun. - -{% callout %} -Long term, we aim for feature parity with esbuild's API, so if something doesn't work please file an issue to help us prioritize. - -{% /callout %} - -Plugins in Bun and esbuild are defined with a `builder` object. - -```ts -import type { BunPlugin } from "bun"; - -const myPlugin: BunPlugin = { - name: "my-plugin", - setup(builder) { - // define plugin - }, -}; -``` - -The `builder` object provides some methods for hooking into parts of the bundling process. Bun implements `onResolve` and `onLoad`; it does not yet implement the esbuild hooks `onStart`, `onEnd`, and `onDispose`, and `resolve` utilities. `initialOptions` is partially implemented, being read-only and only having a subset of esbuild's options; use [`config`](/docs/bundler/plugins#reading-bunbuilds-config) (same thing but with Bun's `BuildConfig` format) instead. - -```ts -import type { BunPlugin } from "bun"; -const myPlugin: BunPlugin = { - name: "my-plugin", - setup(builder) { - builder.onResolve( - { - /* onResolve.options */ - }, - args => { - return { - /* onResolve.results */ - }; - }, - ); - builder.onLoad( - { - /* onLoad.options */ - }, - args => { - return { - /* onLoad.results */ - }; - }, - ); - }, -}; -``` - -### `onResolve` - -#### `options` - -{% table %} - -- 🟢 -- `filter` - ---- - -- 🟢 -- `namespace` - -{% /table %} - -#### `arguments` - -{% table %} - -- 🟢 -- `path` - ---- - -- 🟢 -- `importer` - ---- - -- 🔴 -- `namespace` - ---- - -- 🔴 -- `resolveDir` - ---- - -- 🔴 -- `kind` - ---- - -- 🔴 -- `pluginData` - -{% /table %} - -#### `results` - -{% table %} - -- 🟢 -- `namespace` - ---- - -- 🟢 -- `path` - ---- - -- 🔴 -- `errors` - ---- - -- 🔴 -- `external` - ---- - -- 🔴 -- `pluginData` - ---- - -- 🔴 -- `pluginName` - ---- - -- 🔴 -- `sideEffects` - ---- - -- 🔴 -- `suffix` - ---- - -- 🔴 -- `warnings` - ---- - -- 🔴 -- `watchDirs` - ---- - -- 🔴 -- `watchFiles` - -{% /table %} - -### `onLoad` - -#### `options` - -{% table %} - ---- - -- 🟢 -- `filter` - ---- - -- 🟢 -- `namespace` - -{% /table %} - -#### `arguments` - -{% table %} - ---- - -- 🟢 -- `path` - ---- - -- 🔴 -- `namespace` - ---- - -- 🔴 -- `suffix` - ---- - -- 🔴 -- `pluginData` - -{% /table %} - -#### `results` - -{% table %} - ---- - -- 🟢 -- `contents` - ---- - -- 🟢 -- `loader` - ---- - -- 🔴 -- `errors` - ---- - -- 🔴 -- `pluginData` - ---- - -- 🔴 -- `pluginName` - ---- - -- 🔴 -- `resolveDir` - ---- - -- 🔴 -- `warnings` - ---- - -- 🔴 -- `watchDirs` - ---- - -- 🔴 -- `watchFiles` - -{% /table %} diff --git a/docs/bundler/plugins.md b/docs/bundler/plugins.md index 37f8ce66e..4e0fafee5 100644 --- a/docs/bundler/plugins.md +++ b/docs/bundler/plugins.md @@ -31,6 +31,16 @@ Bun.build({ }); ``` + + +## `--preload` + To consume this plugin, add this file to the `preload` option in your [`bunfig.toml`](/docs/runtime/configuration). Bun automatically loads the files/modules specified in `preload` before running a file. ```toml @@ -74,7 +84,7 @@ plugin( // application code ``` -Bun's plugin API is based on [esbuild](https://esbuild.github.io/plugins). Only [a subset](/docs/bundler/migration#plugin-api) of the esbuild API is implemented, but some esbuild plugins "just work" in Bun, like the official [MDX loader](https://mdxjs.com/packages/esbuild/): +Bun's plugin API is based on [esbuild](https://esbuild.github.io/plugins). Only [a subset](/docs/bundler/vs-esbuild#plugin-api) of the esbuild API is implemented, but some esbuild plugins "just work" in Bun, like the official [MDX loader](https://mdxjs.com/packages/esbuild/): ```jsx import { plugin } from "bun"; @@ -268,7 +278,7 @@ console.log(mySvelteComponent.render()); ## Reading `Bun.build`'s config -Plugins can read and write to the [build config](/docs/cli/build#api) with `build.config`. +Plugins can read and write to the [build config](/docs/bundler#api) with `build.config`. ```ts Bun.build({ diff --git a/docs/bundler/vs-esbuild.md b/docs/bundler/vs-esbuild.md new file mode 100644 index 000000000..5ccde1a1f --- /dev/null +++ b/docs/bundler/vs-esbuild.md @@ -0,0 +1,1133 @@ +{% callout %} +**Note** — Available in Bun v0.6.0 and later. +{% /callout %} + +Bun's bundler API is inspired heavily by [esbuild](https://esbuild.github.io/). Migrating to Bun's bundler from esbuild should be relatively painless. This guide will briefly explain why you might consider migrating to Bun's bundler and provide a side-by-side API comparison reference for those who are already familiar with esbuild's API. + +There are a few behavioral differences to note. + +- **Bundling by default**. Unlike esbuild, Bun _always bundles by default_. This is why the `--bundle` flag isn't necessary in the Bun example. To transpile each file individually, use [`Bun.Transpiler`](/docs/api/transpiler). +- **It's just a bundler**. Unlike esbuild, Bun's bundler does not include a built-in development server or file watcher. It's just a bundler. The bundler is intended for use in conjunction with `Bun.serve` and other runtime APIs to achieve the same effect. As such, all options relating to HTTP/file watching are not applicable. + +## Performance + +With an performance-minded API coupled with the extensively optimized Zig-based JS/TS parser, Bun's bundler is 1.75x faster than esbuild on esbuild's [three.js benchmark](https://github.com/oven-sh/bun/tree/main/bench/bundle). + +{% image src="/images/bundler-speed.png" caption="Bundling 10 copies of three.js from scratch, with sourcemaps and minification" /%} + +## CLI API + +Bun and esbuild both provide a command-line interface. + +```bash +$ esbuild --outdir=out --bundle +$ bun build --outdir=out +``` + +In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Other flags like `--outdir ` do accept an argument; these flags can be written as `--outdir out` or `--outdir=out`. Some flags like `--define` can be specified several times: `--define foo=bar --define bar=baz`. + +{% table %} + +- `esbuild` +- `bun build` + +--- + +- `--bundle` +- n/a +- Bun always bundles, use `--no-bundle` to disable this behavior. + +--- + +- `--define:K=V` +- `--define K=V` +- Small syntax difference; no colon. + + ```bash + $ esbuild --define:foo=bar + $ bun build --define foo=bar + ``` + +--- + +- `--external:` +- `--external ` +- Small syntax difference; no colon. + + ```bash + $ esbuild --external:react + $ bun build --external react + ``` + +--- + +- `--format` +- `--format` +- Bun only supports `"esm"` currently but other module formats are planned. esbuild defaults to `"iife"`. + +--- + +- `--loader:.ext=loader` +- `--loader .ext:loader` +- Bun supports a different set of built-in loaders than esbuild; see [Bundler > Loaders](/docs/bundler/loaders) for a complete reference. The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not yet implemented. + + The syntax for `--loader` is slightly different. + + ```bash + $ esbuild app.ts --bundle --loader:.svg=text + $ bun build app.ts --loader .svg:text + ``` + +--- + +- `--minify` +- `--minify` +- No differences + +--- + +- `--outdir` +- `--outdir` +- No differences + +--- + +- `--outfile` +- `--outfile` + +--- + +- `--packages` +- n/a +- Not supported + +--- + +- `--platform` +- `--target` +- Renamed to `--target` for consistency with tsconfig. Does not support `neutral`. + +--- + +- `--serve` +- n/a +- Not applicable + +--- + +- `--sourcemap` +- `--sourcemap` +- No differences + +--- + +- `--splitting` +- `--splitting` +- No differences + +--- + +- `--target` +- n/a +- No supported. Bun's bundler performs no syntactic downleveling at this time. + +--- + +- `--watch` +- n/a +- Not applicable + +--- + +- `--allow-overwrite` +- n/a +- Overwriting is never allowed + +--- + +- `--analyze` +- n/a +- Not supported + +--- + +- `--asset-names` +- `--asset-naming` +- Renamed for consistency with `naming` in JS API + +--- + +- `--banner` +- n/a +- Not supported + +--- + +- `--certfile` +- n/a +- Not applicable + +--- + +- `--charset=utf8` +- n/a +- Not supported + +--- + +- `--chunk-names` +- `--chunk-naming` +- Renamed for consistency with `naming` in JS API + +--- + +- `--color` +- n/a +- Always enabled + +--- + +- `--drop` +- n/a +- Not supported + +--- + +- `--entry-names` +- `--entry-naming` +- Renamed for consistency with `naming` in JS API + +--- + +- `--footer` +- n/a +- Not supported + +--- + +- `--global-name` +- n/a +- Not applicable, Bun does not support `iife` output at this time + +--- + +- `--ignore-annotations` +- n/a +- Not supported + +--- + +- `--inject` +- n/a +- Not supported + +--- + +- `--jsx` +- `--jsx-runtime ` +- Supports `"automatic"` (uses `jsx` transform) and `"classic"` (uses `React.createElement`) + +--- + +- `--jsx-dev` +- n/a +- Bun reads `compilerOptions.jsx` from `tsconfig.json` to determine a default. If `compilerOptions.jsx` is `"react-jsx"`, or if `NODE_ENV=production`, Bun will use the `jsx` transform. Otherwise, it uses `jsxDEV`. For any to Bun uses `jsxDEV`. The bundler does not support `preserve`. + +--- + +- `--jsx-factory` +- `--jsx-factory` + +--- + +- `--jsx-fragment` +- `--jsx-fragment` + +--- + +- `--jsx-import-source` +- `--jsx-import-source` + +--- + +- `--jsx-side-effects` +- n/a +- JSX is always assumed to be side-effect-free + +--- + +- `--keep-names` +- n/a +- Not supported + +--- + +- `--keyfile` +- n/a +- Not applicable + +--- + +- `--legal-comments` +- n/a +- Not supported + +--- + +- `--log-level` +- n/a +- Not supported. This can be set in `bunfig.toml` as `logLevel`. + +--- + +- `--log-limit` +- n/a +- Not supported + +--- + +- `--log-override:X=Y` +- n/a +- Not supported + +--- + +- `--main-fields` +- n/a +- Not supported + +--- + +- `--mangle-cache` +- n/a +- Not supported + +--- + +- `--mangle-props` +- n/a +- Not supported + +--- + +- `--mangle-quoted` +- n/a +- Not supported + +--- + +- `--metafile` +- n/a +- Not supported + +--- + +- `--minify-whitespace` +- `--minify-whitespace` + +--- + +- `--minify-identifiers` +- `--minify-identifiers` + +--- + +- `--minify-syntax` +- `--minify-syntax` + +--- + +- `--out-extension` +- n/a +- Not supported + +--- + +- `--outbase` +- `--root` + +--- + +- `--preserve-symlinks` +- n/a +- Not supported + +--- + +- `--public-path` +- `--public-path` + +--- + +- `--pure` +- n/a +- Not supported + +--- + +- `--reserve-props` +- n/a +- Not supported + +--- + +- `--resolve-extensions` +- n/a +- Not supported + +--- + +- `--servedir` +- n/a +- Not applicable + +--- + +- `--source-root` +- n/a +- Not supported + +--- + +- `--sourcefile` +- n/a +- Not supported. Bun does not support `stdin` input yet. + +--- + +- `--sourcemap` +- `--sourcemap` +- No differences + +--- + +- `--sources-content` +- n/a +- Not supported + +--- + +- `--supported` +- n/a +- Not supported + +--- + +- `--tree-shaking` +- n/a +- Always `true` + +--- + +- `--tsconfig` +- `--tsconfig-override` + +--- + +- `--version` +- n/a +- Run `bun --version` to see the version of Bun. + +{% /table %} + +## JavaScript API + +{% table %} + +- `esbuild.build()` +- `Bun.build()` + +--- + +- `absWorkingDir` +- n/a +- Always set to `process.cwd()` + +--- + +- `alias` +- n/a +- Not supported + +--- + +- `allowOverwrite` +- n/a +- Always `false` + +--- + +- `assetNames` +- `naming.asset` +- Uses same templating syntax as esbuild, but `[ext]` must be included explicitly. + + ```ts + Bun.build({ + entrypoints: ["./index.tsx"], + naming: { + asset: "[name].[ext]", + }, + }); + ``` + +--- + +- `banner` +- n/a +- Not supported + +--- + +- `bundle` +- n/a +- Always `true`. Use [`Bun.Transpiler`](/docs/api/transpiler) to transpile without bundling. + +--- + +- `charset` +- n/a +- Not supported + +--- + +- `chunkNames` +- `naming.chunk` +- Uses same templating syntax as esbuild, but `[ext]` must be included explicitly. + + ```ts + Bun.build({ + entrypoints: ["./index.tsx"], + naming: { + chunk: "[name].[ext]", + }, + }); + ``` + +--- + +- `color` +- n/a +- Bun returns logs in the `logs` property of the build result. + +--- + +- `conditions` +- n/a +- Not supported. Export conditions priority is determined by `target`. + +--- + +- `define` +- `define` + +--- + +- `drop` +- n/a +- Not supported + +--- + +- `entryNames` +- `naming` or `naming.entry` +- Bun supports a `naming` key that can either be a string or an object. Uses same templating syntax as esbuild, but `[ext]` must be included explicitly. + + ```ts + Bun.build({ + entrypoints: ["./index.tsx"], + // when string, this is equivalent to entryNames + naming: "[name].[ext]", + + // granular naming options + naming: { + entry: "[name].[ext]", + asset: "[name].[ext]", + chunk: "[name].[ext]", + }, + }); + ``` + +--- + +- `entryPoints` +- `entrypoints` +- Capitalization difference + +--- + +- `external` +- `external` +- No differences + +--- + +- `footer` +- n/a +- Not supported + +--- + +- `format` +- `format` +- Only supports `"esm"` currently. Support for `"cjs"` and `"iife"` is planned. + +--- + +- `globalName` +- n/a +- Not supported + +--- + +- `ignoreAnnotations` +- n/a +- Not supported + +--- + +- `inject` +- n/a +- Not supported + +--- + +- `jsx` +- `jsx` +- Not supported in JS API, configure in `tsconfig.json` + +--- + +- `jsxDev` +- `jsxDev` +- Not supported in JS API, configure in `tsconfig.json` + +--- + +- `jsxFactory` +- `jsxFactory` +- Not supported in JS API, configure in `tsconfig.json` + +--- + +- `jsxFragment` +- `jsxFragment` +- Not supported in JS API, configure in `tsconfig.json` + +--- + +- `jsxImportSource` +- `jsxImportSource` +- Not supported in JS API, configure in `tsconfig.json` + +--- + +- `jsxSideEffects` +- `jsxSideEffects` +- Not supported in JS API, configure in `tsconfig.json` + +--- + +- `keepNames` +- n/a +- Not supported + +--- + +- `legalComments` +- n/a +- Not supported + +--- + +- `loader` +- `loader` +- Bun supports a different set of built-in loaders than esbuild; see [Bundler > Loaders](/docs/bundler/loaders) for a complete reference. The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not yet implemented. + +--- + +- `logLevel` +- n/a +- Not supported + +--- + +- `logLimit` +- n/a +- Not supported + +--- + +- `logOverride` +- n/a +- Not supported + +--- + +- `mainFields` +- n/a +- Not supported + +--- + +- `mangleCache` +- n/a +- Not supported + +--- + +- `mangleProps` +- n/a +- Not supported + +--- + +- `mangleQuoted` +- n/a +- Not supported + +--- + +- `metafile` +- n/a +- Not supported + + + +--- + +- `minify` +- `minify` +- In Bun, `minify` can be a boolean or an object. + + ```ts + Bun.build({ + entrypoints: ['./index.tsx'], + // enable all minification + minify: true + + // granular options + minify: { + identifiers: true, + syntax: true, + whitespace: true + } + }) + ``` + +--- + +- `minifyIdentifiers` +- `minify.identifiers` +- See `minify` + +--- + +- `minifySyntax` +- `minify.syntax` +- See `minify` + +--- + +- `minifyWhitespace` +- `minify.whitespace` +- See `minify` + +--- + +- `nodePaths` +- n/a +- Not supported + +--- + +- `outExtension` +- n/a +- Not supported + +--- + +- `outbase` +- `root` +- Different name + +--- + +- `outdir` +- `outdir` +- No differences + +--- + +- `outfile` +- `outfile` +- No differences + +--- + +- `packages` +- n/a +- Not supported, use `external` + +--- + +- `platform` +- `target` +- Supports `"bun"`, `"node"` and `"browser"` (the default). Does not support `"neutral"`. + +--- + +- `plugins` +- `plugins` +- Bun's plugin API is a subset of esbuild's. Some esbuild plugins will work out of the box with Bun. + +--- + +- `preserveSymlinks` +- n/a +- Not supported + +--- + +- `publicPath` +- `publicPath` +- No differences + +--- + +- `pure` +- n/a +- Not supported + +--- + +- `reserveProps` +- n/a +- Not supported + +--- + +- `resolveExtensions` +- n/a +- Not supported + +--- + +- `sourceRoot` +- n/a +- Not supported + +--- + +- `sourcemap` +- `sourcemap` +- Supports `"inline"`, `"external"`, and `"none"` + +--- + +- `sourcesContent` +- n/a +- Not supported + +--- + +- `splitting` +- `splitting` +- No differences + +--- + +- `stdin` +- n/a +- Not supported + +--- + +- `supported` +- n/a +- Not supported + +--- + +- `target` +- n/a +- No support for syntax downleveling + +--- + +- `treeShaking` +- n/a +- Always `true` + +--- + +- `tsconfig` +- n/a +- Not supported + +--- + +- `write` +- n/a +- Set to `true` if `outdir`/`outfile` is set, otherwise `false` + +--- + +{% /table %} + +## Plugin API + +Bun's plugin API is designed to be esbuild compatible. Bun doesn't support esbuild's entire plugin API surface, but the core functionality is implemented. Many third-party `esbuild` plugins will work out of the box with Bun. + +{% callout %} +Long term, we aim for feature parity with esbuild's API, so if something doesn't work please file an issue to help us prioritize. + +{% /callout %} + +Plugins in Bun and esbuild are defined with a `builder` object. + +```ts +import type { BunPlugin } from "bun"; + +const myPlugin: BunPlugin = { + name: "my-plugin", + setup(builder) { + // define plugin + }, +}; +``` + +The `builder` object provides some methods for hooking into parts of the bundling process. Bun implements `onResolve` and `onLoad`; it does not yet implement the esbuild hooks `onStart`, `onEnd`, and `onDispose`, and `resolve` utilities. `initialOptions` is partially implemented, being read-only and only having a subset of esbuild's options; use [`config`](/docs/bundler/plugins#reading-bunbuilds-config) (same thing but with Bun's `BuildConfig` format) instead. + +```ts +import type { BunPlugin } from "bun"; +const myPlugin: BunPlugin = { + name: "my-plugin", + setup(builder) { + builder.onResolve( + { + /* onResolve.options */ + }, + args => { + return { + /* onResolve.results */ + }; + }, + ); + builder.onLoad( + { + /* onLoad.options */ + }, + args => { + return { + /* onLoad.results */ + }; + }, + ); + }, +}; +``` + +### `onResolve` + +#### `options` + +{% table %} + +- 🟢 +- `filter` + +--- + +- 🟢 +- `namespace` + +{% /table %} + +#### `arguments` + +{% table %} + +- 🟢 +- `path` + +--- + +- 🟢 +- `importer` + +--- + +- 🔴 +- `namespace` + +--- + +- 🔴 +- `resolveDir` + +--- + +- 🔴 +- `kind` + +--- + +- 🔴 +- `pluginData` + +{% /table %} + +#### `results` + +{% table %} + +- 🟢 +- `namespace` + +--- + +- 🟢 +- `path` + +--- + +- 🔴 +- `errors` + +--- + +- 🔴 +- `external` + +--- + +- 🔴 +- `pluginData` + +--- + +- 🔴 +- `pluginName` + +--- + +- 🔴 +- `sideEffects` + +--- + +- 🔴 +- `suffix` + +--- + +- 🔴 +- `warnings` + +--- + +- 🔴 +- `watchDirs` + +--- + +- 🔴 +- `watchFiles` + +{% /table %} + +### `onLoad` + +#### `options` + +{% table %} + +--- + +- 🟢 +- `filter` + +--- + +- 🟢 +- `namespace` + +{% /table %} + +#### `arguments` + +{% table %} + +--- + +- 🟢 +- `path` + +--- + +- 🔴 +- `namespace` + +--- + +- 🔴 +- `suffix` + +--- + +- 🔴 +- `pluginData` + +{% /table %} + +#### `results` + +{% table %} + +--- + +- 🟢 +- `contents` + +--- + +- 🟢 +- `loader` + +--- + +- 🔴 +- `errors` + +--- + +- 🔴 +- `pluginData` + +--- + +- 🔴 +- `pluginName` + +--- + +- 🔴 +- `resolveDir` + +--- + +- 🔴 +- `warnings` + +--- + +- 🔴 +- `watchDirs` + +--- + +- 🔴 +- `watchFiles` + +{% /table %} diff --git a/docs/cli/build.md b/docs/cli/build.md deleted file mode 100644 index e0cd36651..000000000 --- a/docs/cli/build.md +++ /dev/null @@ -1,1334 +0,0 @@ -Bun's fast native bundler is now in beta. It can be used via the `bun build` CLI command or the `Bun.build()` JavaScript API. - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './build', -}); -``` - -```sh#CLI -$ bun build ./index.tsx --outdir ./build -``` - -{% /codetabs %} - -It's fast. The numbers below represent performance on esbuild's [three.js benchmark](https://github.com/oven-sh/bun/tree/main/bench/bundle). - -{% image src="/images/bundler-speed.png" caption="Bundling 10 copies of three.js from scratch, with sourcemaps and minification" /%} - -## Why bundle? - -The bundler is a key piece of infrastructure in the JavaScript ecosystem. As a brief overview of why bundling is so important: - -- **Reducing HTTP requests.** A single package in `node_modules` may consist of hundreds of files, and large applications may have dozens of such dependencies. Loading each of these files with a separate HTTP request becomes untenable very quickly, so bundlers are used to convert our application source code into a smaller number of self-contained "bundles" that can be loaded with a single request. -- **Code transforms.** Modern apps are commonly built with languages or tools like TypeScript, JSX, and CSS modules, all of which must be converted into plain JavaScript and CSS before they can be consumed by a browser. The bundler is the natural place to configure these transformations. -- **Framework features.** Frameworks rely on bundler plugins & code transformations to implement common patterns like file-system routing, client-server code co-location (think `getServerSideProps` or Remix loaders), and server components. - -Let's jump into the bundler API. - -## Basic example - -Let's build our first bundle. You have the following two files, which implement a simple client-side rendered React app. - -{% codetabs %} - -```tsx#./index.tsx -import * as ReactDOM from 'react-dom/client'; -import {Component} from "./Component" - -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render() -``` - -```tsx#./Component.tsx -export function Component(props: {message: string}) { - return

{props.message}

-} -``` - -{% /codetabs %} - -Here, `index.tsx` is the "entrypoint" to our application. Commonly, this will be a script that performs some _side effect_, like starting a server or—in this case—initializing a React root. Because we're using TypeScript & JSX, we need to bundle our code before it can be sent to the browser. - -To create our bundle: - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out -``` - -{% /codetabs %} - -For each file specified in `entrypoints`, Bun will generate a new bundle. This bundle will be written to disk in the `./out` directory (as resolved from the current working directory). After running the build, the file system looks like this: - -```ts -. -├── index.tsx -├── Component.tsx -└── out - └── index.js -``` - -The contents of `out/index.js` will look something like this: - -```js#out/index.js -// ... -// ~20k lines of code -// including the contents of `react-dom/client` and all its dependencies -// this is where the $jsxDEV and $createRoot functions are defined - - -// Component.tsx -function Component(props) { - return $jsxDEV("p", { - children: props.message - }, undefined, false, undefined, this); -} - -// index.tsx -var rootNode = document.getElementById("root"); -var root = $createRoot(rootNode); -root.render($jsxDEV(Component, { - message: "Sup!" -}, undefined, false, undefined, this)); -``` - -{% details summary="Tutorial: Run this file in your browser" %} -We can load this file in the browser to see our app in action. Create an `index.html` file in the `out` directory: - -```bash -$ touch out/index.html -``` - -Then paste the following contents into it: - -```html - - -
- - - -``` - -Then spin up a static file server serving the `out` directory: - -```bash -$ bunx serve out -``` - -Visit `http://localhost:5000` to see your bundled app in action. - -{% /details %} - -## Content types - -Like the Bun runtime, the bundler supports an array of file types out of the box. The following table breaks down the bundler's set of standard "loaders". Refer to [Bundler > File types](/docs/runtime/loaders) for full documentation. - -{% table %} - -- Extensions -- Details - ---- - -- `.js` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` -- Uses Bun's built-in transpiler to parse the file and transpile TypeScript/JSX syntax to vanilla JavaScript. The bundler executes a set of default transforms, including dead code elimination, tree shaking, and environment variable inlining. At the moment Bun does not attempt to down-convert syntax; if you use recently ECMAScript syntax, that will be reflected in the bundled code. - ---- - -- `.json` -- JSON files are parsed and inlined into the bundle as a JavaScript object. - - ```ts - import pkg from "./package.json"; - pkg.name; // => "my-package" - ``` - ---- - -- `.toml` -- TOML files are parsed and inlined into the bundle as a JavaScript object. - - ```ts - import config from "./bunfig.toml"; - config.logLevel; // => "debug" - ``` - ---- - -- `.txt` -- The contents of the text file are read and inlined into the bundle as a string. - - ```ts - import contents from "./file.txt"; - console.log(contents); // => "Hello, world!" - ``` - ---- - -- `.node` `.wasm` -- These files are supported by the Bun runtime, but during bundling they are treated as [assets](#assets). - -{% /table %} - -### Assets - -If the bundler encounters an import with an unrecognized extension, it treats the imported file as an _external file_. The referenced file is copied as-is into `outdir`, and the import is resolved as a _path_ to the file. - -{% codetabs %} - -```ts#Input -// bundle entrypoint -import logo from "./logo.svg"; -console.log(logo); -``` - -```ts#Output -// bundled output -var logo = "./logo-ab237dfe.svg"; -console.log(logo); -``` - -{% /codetabs %} - -{% callout %} -The exact behavior of the file loader is also impacted by [`naming`](#naming) and [`publicPath`](#publicpath). -{% /callout %} - -Refer to the [Bundler > Loaders](/docs/bundler/loaders#file) page for more complete documentation on the file loader. - -### Plugins - -The behavior described in this table can be overridden or extended with [plugins](/docs/bundler/plugins). Refer to the [Bundler > Loaders](/docs/bundler/plugins) page for complete documentation. - -## API - -### `entrypoints` - -**Required.** An array of paths corresponding to the entrypoints of our application. One bundle will be generated for each entrypoint. - -{% codetabs group="a" %} - -```ts#JavaScript -const result = await Bun.build({ - entrypoints: ["./index.ts"], -}); -// => { success: boolean, outputs: BuildArtifact[], logs: BuildMessage[] } -``` - -```bash#CLI -$ bun build --entrypoints ./index.ts -# the bundle will be printed to stdout -# -``` - -{% /codetabs %} - -### `outdir` - -The directory where output files will be written. - -{% codetabs group="a" %} - -```ts#JavaScript -const result = await Bun.build({ - entrypoints: ['./index.ts'], - outdir: './out' -}); -// => { success: boolean, outputs: BuildArtifact[], logs: BuildMessage[] } -``` - -```bash#CLI -$ bun build --entrypoints ./index.ts --outdir ./out -# a summary of bundled files will be printed to stdout -``` - -{% /codetabs %} - -If `outdir` is not passed to the JavaScript API, bundled code will not be written to disk. Bundled files are returned in an array of `BuildArtifact` objects. These objects are Blobs with extra properties; see [Outputs](#outputs) for complete documentation. - -```ts -const result = await Bun.build({ - entrypoints: ["./index.ts"], -}); - -for (const result of result.outputs) { - // Can be consumed as blobs - await result.text(); - - // Bun will set Content-Type and Etag headers - new Response(result); - - // Can be written manually, but you should use `outdir` in this case. - Bun.write(path.join("out", result.path), result); -} -``` - -When `outdir` is set, the `path` property on a `BuildArtifact` will be the absolute path to where it was written to. - -### `target` - -The intended execution environment for the bundle. - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.ts'], - outdir: './out', - target: 'browser', // default -}) -``` - -```bash#CLI -$ bun build --entrypoints ./index.ts --outdir ./out --target browser -``` - -{% /codetabs %} - -Depending on the target, Bun will apply different module resolution rules and optimizations. - - - -{% table %} - ---- - -- `browser` -- _Default._ For generating bundles that are intended for execution by a browser. Prioritizes the `"browser"` export condition when resolving imports. An error will be thrown if any Node.js or Bun built-ins are imported or used, e.g. `node:fs` or `Bun.serve`. - ---- - -- `bun` -- For generating bundles that are intended to be run by the Bun runtime. In many cases, it isn't necessary to bundle server-side code; you can directly execute the source code without modification. However, bundling your server code can reduce startup times and improve running performance. - - All bundles generated with `target: "bun"` are marked with a special `// @bun` pragma, which indicates to the Bun runtime that there's no need to re-transpile the file before execution. - - If any entrypoints contains a Bun shebang (`#!/usr/bin/env bun`) the bundler will default to `target: "bun"` instead of `"browser`. - ---- - -- `node` -- For generating bundles that are intended to be run by Node.js. Prioritizes the `"node"` export condition when resolving imports, and outputs `.mjs`. In the future, this will automatically polyfill the `Bun` global and other built-in `bun:*` modules, though this is not yet implemented. - -{% /table %} - -{% callout %} - -{% /callout %} - -### `format` - -Specifies the module format to be used in the generated bundles. - -Currently the bundler only supports one module format: `"esm"`. Support for `"cjs"` and `"iife"` are planned. - -{% codetabs %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - format: "esm", -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out --format esm -``` - -{% /codetabs %} - - - -### `splitting` - -Whether to enable code splitting. - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - splitting: false, // default -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out --splitting -``` - -{% /codetabs %} - -When `true`, the bundler will enable _code splitting_. When multiple entrypoints both import the same file, module, or set of files/modules, it's often useful to split the shared code into a separate bundle. This shared bundle is known as a _chunk_. Consider the following files: - -{% codetabs %} - -```ts#entry-a.ts -import { shared } from './shared.ts'; -``` - -```ts#entry-b.ts -import { shared } from './shared.ts'; -``` - -```ts#shared.ts -export const shared = 'shared'; -``` - -{% /codetabs %} - -To bundle `entry-a.ts` and `entry-b.ts` with code-splitting enabled: - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./entry-a.ts', './entry-b.ts'], - outdir: './out', - splitting: true, -}) -``` - -```bash#CLI -$ bun build ./entry-a.ts ./entry-b.ts --outdir ./out --splitting -``` - -{% /codetabs %} - -Running this build will result in the following files: - -```txt -. -├── entry-a.tsx -├── entry-b.tsx -├── shared.tsx -└── out - ├── entry-a.js - ├── entry-b.js - └── chunk-2fce6291bf86559d.js - -``` - -The generated `chunk-2fce6291bf86559d.js` file contains the shared code. To avoid collisions, the file name automatically includes a content hash by default. This can be customized with [`naming`](#naming). - -### `plugins` - -A list of plugins to use during bundling. - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - plugins: [/* ... */], -}) -``` - -```bash#CLI -n/a -``` - -{% /codetabs %} - -Bun implements a univeral plugin system for both Bun's runtime and bundler. Refer to the [plugin documentation](/docs/bundler/plugins) for complete documentation. - - - -### `sourcemap` - -Specifies the type of sourcemap to generate. - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - sourcemap: "external", // default "none" -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out --sourcemap=external -``` - -{% /codetabs %} - -{% table %} - ---- - -- `"none"` -- _Default._ No sourcemap is generated. - ---- - -- `"inline"` -- A sourcemap is generated and appended to the end of the generated bundle as a base64 payload. - - ```ts - // - - //# sourceMappingURL=data:application/json;base64, - ``` - ---- - -- `"external"` -- A separate `*.js.map` file is created alongside each `*.js` bundle. - -{% /table %} - -{% callout %} - -Generated bundles contain a [debug id](https://sentry.engineering/blog/the-case-for-debug-ids) that can be used to associate a bundle with its corresponding sourcemap. This `debugId` is added as a comment at the bottom of the file. - -```ts -// - -//# debugId= -``` - -The associated `*.js.map` sourcemap will be a JSON file containing an equivalent `debugId` property. - -{% /callout %} - -### `minify` - -Whether to enable minification. Default `false`. - -{% callout %} -When targeting `bun`, identifiers will be minified by default. -{% /callout %} - -To enable all minification options: - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - minify: true, // default false -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out --minify -``` - -{% /codetabs %} - -To granularly enable certain minifications: - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - minify: { - whitespace: true, - identifiers: true, - syntax: true, - }, -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out --minify-whitespace --minify-identifiers --minify-syntax -``` - -{% /codetabs %} - - - -### `external` - -A list of import paths to consider _external_. Defaults to `[]`. - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - external: ["lodash", "react"], // default: [] -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out --external lodash --external react -``` - -{% /codetabs %} - -An external import is one that will not be included in the final bundle. Instead, the `import` statement will be left as-is, to be resolved at runtime. - -For instance, consider the following entrypoint file: - -```ts#index.tsx -import _ from "lodash"; -import {z} from "zod"; - -const value = z.string().parse("Hello world!") -console.log(_.upperCase(value)); -``` - -Normally, bundling `index.tsx` would generate a bundle containing the entire source code of the `"zod"` package. If instead, we want to leave the `import` statement as-is, we can mark it as external: - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - external: ['zod'], -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out --external zod -``` - -{% /codetabs %} - -The generated bundle will look something like this: - -```js#out/index.js -import {z} from "zod"; - -// ... -// the contents of the "lodash" package -// including the `_.upperCase` function - -var value = z.string().parse("Hello world!") -console.log(_.upperCase(value)); -``` - -To mark all imports as external, use the wildcard `*`: - -{% codetabs %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - external: ['*'], -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out --external '*' -``` - -{% /codetabs %} - -### `naming` - -Customizes the generated file names. Defaults to `./[dir]/[name].[ext]`. - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - naming: "[dir]/[name].[ext]", // default -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out --entry-naming [dir]/[name].[ext] -``` - -{% /codetabs %} - -By default, the names of the generated bundles are based on the name of the associated entrypoint. - -```txt -. -├── index.tsx -└── out - └── index.js -``` - -With multiple entrypoints, the generated file hierarchy will reflect the directory structure of the entrypoints. - -```txt -. -├── index.tsx -└── nested - └── index.tsx -└── out - ├── index.js - └── nested - └── index.js -``` - -The names and locations of the generated files can be customized with the `naming` field. This field accepts a template string that is used to generate the filenames for all bundles corresponding to entrypoints. where the following tokens are replaced with their corresponding values: - -- `[name]` - The name of the entrypoint file, without the extension. -- `[ext]` - The extension of the generated bundle. -- `[hash]` - A hash of the bundle contents. -- `[dir]` - The relative path from the build root to the parent directory of the file. - -For example: - -{% table %} - -- Token -- `[name]` -- `[ext]` -- `[hash]` -- `[dir]` - ---- - -- `./index.tsx` -- `index` -- `js` -- `a1b2c3d4` -- `""` (empty string) - ---- - -- `./nested/entry.ts` -- `entry` -- `js` -- `c3d4e5f6` -- `"nested"` - -{% /table %} - -We can combine these tokens to create a template string. For instance, to include the hash in the generated bundle names: - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - naming: 'files/[dir]/[name]-[hash].[ext]', -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out --entry-naming [name]-[hash].[ext] -``` - -{% /codetabs %} - -This build would result in the following file structure: - -```txt -. -├── index.tsx -└── out - └── files - └── index-a1b2c3d4.js -``` - -When a `string` is provided for the `naming` field, it is used only for bundles _that correspond to entrypoints_. The names of [chunks](#splitting) and copied assets are not affected. Using the JavaScript API, separate template strings can be specified for each type of generated file. - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - naming: { - // default values - entry: '[dir]/[name].[ext]', - chunk: '[name]-[hash].[ext]', - asset: '[name]-[hash].[ext]', - }, -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out --entry-naming "[dir]/[name].[ext]" --chunk-naming "[name]-[hash].[ext]" --asset-naming "[name]-[hash].[ext]" -``` - -{% /codetabs %} - -### `root` - -The root directory of the project. - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./pages/a.tsx', './pages/b.tsx'], - outdir: './out', - root: '.', -}) -``` - -```bash#CLI -n/a -``` - -{% /codetabs %} - -If unspecified, it is computed to be the first common ancestor of all entrypoint files. Consider the following file structure: - -```txt -. -└── pages - └── index.tsx - └── settings.tsx -``` - -We can build both entrypoints in the `pages` directory: - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./pages/index.tsx', './pages/settings.tsx'], - outdir: './out', -}) -``` - -```bash#CLI -$ bun build ./pages/index.tsx ./pages/settings.tsx --outdir ./out -``` - -{% /codetabs %} - -This would result in a file structure like this: - -```txt -. -└── pages - └── index.tsx - └── settings.tsx -└── out - └── index.js - └── settings.js -``` - -Since the `pages` directory is the first common ancestor of the entrypoint files, it is considered the project root. This means that the generated bundles live at the top level of the `out` directory; there is no `out/pages` directory. - -This behavior can be overridden by specifying the `root` option: - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./pages/index.tsx', './pages/settings.tsx'], - outdir: './out', - root: '.', -}) -``` - -```bash#CLI -$ bun build ./pages/index.tsx ./pages/settings.tsx --outdir ./out --root . -``` - -{% /codetabs %} - -By specifying `.` as `root`, the generated file structure will look like this: - -```txt -. -└── pages - └── index.tsx - └── settings.tsx -└── out - └── pages - └── index.js - └── settings.js -``` - -### `publicPath` - -A prefix to be appended to any import paths in bundled code. - - - -In many cases, generated bundles will contain no `import` statements. After all, the goal of bundling is to combine all of the code into a single file. However there are a number of cases with the generated bundles will contain `import` statements. - -- **Asset imports** — When importing an unrecognized file type like `*.svg`, the bundler defers to the [`file` loader](/docs/bundler/loaders#file), which copies the file into `outdir` as is. The import is converted into a variable -- **External modules** — Files and modules can be marked as [`external`](#external), in which case they will not be included in the bundle. Instead, the `import` statement will be left in the final bundle. -- **Chunking**. When [`splitting`](#splitting) is enabled, the bundler may generate separate "chunk" files that represent code that is shared among multiple entrypoints. - -In any of these cases, the final bundles may contain paths to other files. By default these imports are _relative_. Here is an example of a simple asset import: - -{% codetabs %} - -```ts#Input -import logo from './logo.svg'; -console.log(logo); -``` - -```ts#Output -// logo.svg is copied into -// and hash is added to the filename to prevent collisions -var logo = './logo-a7305bdef.svg'; -console.log(logo); -``` - -{% /codetabs %} - -Setting `publicPath` will prefix all file paths with the specified value. - -{% codetabs group="a" %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - publicPath: 'https://cdn.example.com/', // default is undefined -}) -``` - -```bash#CLI -n/a -``` - -{% /codetabs %} - -The output file would now look something like this. - -```ts-diff#Output -- var logo = './logo-a7305bdef.svg'; -+ var logo = 'https://cdn.example.com/logo-a7305bdef.svg'; -``` - -### `define` - -A map of global identifiers to be replaced at build time. Keys of this object are identifier names, and values are JSON strings that will be inlined. - -{% callout } -This is not needed to inline `process.env.NODE_ENV`, as Bun does this automatically. -{% /callout %} - -{% codetabs %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - define: { - STRING: JSON.stringify("value"), - "nested.boolean": "true", - }, -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out --define 'STRING="value"' --define "nested.boolean=true" -``` - -{% /codetabs %} - -### `loader` - -A map of file extensions to [built-in loader names](https://bun.sh/docs/bundler/loaders#built-in-loaders). This can be used to quickly customize how certain file files are loaded. - -{% codetabs %} - -```ts#JavaScript -await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', - loader: { - ".png": "dataurl", - ".txt": "file", - }, -}) -``` - -```bash#CLI -$ bun build ./index.tsx --outdir ./out --loader .png:dataurl --loader .txt:file -``` - -{% /codetabs %} - -## Outputs - -The `Bun.build` function returns a `Promise`, defined as: - -```ts -interface BuildOutput { - outputs: BuildArtifact[]; - success: boolean; - logs: Array; // see docs for details -} - -interface BuildArtifact extends Blob { - kind: "entry-point" | "chunk" | "asset" | "sourcemap"; - path: string; - loader: Loader; - hash: string | null; - sourcemap: BuildArtifact | null; -} -``` - -The `outputs` array contains all the files that were generated by the build. Each artifact implements the `Blob` interface. - -```ts -const build = Bun.build({ - /* */ -}); - -for (const output of build.outputs) { - await output.arrayBuffer(); // => ArrayBuffer - await output.text(); // string -} -``` - -Each artifact also contains the following properties: - -{% table %} - ---- - -- `kind` -- What kind of build output this file is. A build generates bundled entrypoints, code-split "chunks", sourcemaps, and copied assets (like images). - ---- - -- `path` -- Absolute path to the file on disk - ---- - -- `loader` -- The loader was used to interpret the file. See [Bundler > Loaders](/docs/bundler/loaders) to see how Bun maps file extensions to the appropriate built-in loader. - ---- - -- `hash` -- The hash of the file contents. Always defined for assets. - ---- - -- `sourcemap` -- The sourcemap file corresponding to this file, if generated. Only defined for entrypoints and chunks. - -{% /table %} - -Similar to `BunFile`, `BuildArtifact` objects can be passed directly into `new Response()`. - -```ts -const build = Bun.build({ - /* */ -}); - -const artifact = build.outputs[0]; - -// Content-Type header is automatically set -return new Response(artifact); -``` - -The Bun runtime implements special pretty-printing of `BuildArtifact` object to make debugging easier. - -{% codetabs %} - -```ts#Build_script -// build.ts -const build = Bun.build({/* */}); - -const artifact = build.outputs[0]; -console.log(artifact); -``` - -```sh#Shell_output -$ bun run build.ts -BuildArtifact (entry-point) { - path: "./index.js", - loader: "tsx", - kind: "entry-point", - hash: "824a039620219640", - Blob (114 bytes) { - type: "text/javascript;charset=utf-8" - }, - sourcemap: null -} -``` - -{% /codetabs %} - -### Executables - -Bun supports "compiling" a JavaScript/TypeScript entrypoint into a standalone executable. This executable contains a copy of the Bun binary. - -```sh -$ bun build ./cli.tsx --outfile mycli --compile -$ ./mycli -``` - -Refer to [Bundler > Executables](/docs/bundler/executables) for complete documentation. - -## Logs and errors - -`Bun.build` only throws if invalid options are provided. Read the `success` property to determine if the build was successful; the `logs` property will contain additional details. - -```ts -const result = await Bun.build({ - entrypoints: ["./index.tsx"], - outdir: "./out", -}); - -if (!result.success) { - console.error("Build failed"); - for (const message of result.logs) { - // Bun will pretty print the message object - console.error(message); - } -} -``` - -Each message is either a `BuildMessage` or `ResolveMessage` object, which can be used to trace what problems happened in the build. - -```ts -class BuildMessage { - name: string; - position?: Position; - message: string; - level: "error" | "warning" | "info" | "debug" | "verbose"; -} - -class ResolveMessage extends BuildMessage { - code: string; - referrer: string; - specifier: string; - importKind: ImportKind; -} -``` - -If you want to throw an error from a failed build, consider passing the logs to an `AggregateError`. If uncaught, Bun will pretty-print the contained messages nicely. - -```ts -if (!result.success) { - throw new AggregateError(result.logs, "Build failed"); -} -``` - -## Reference - -```ts -interface Bun { - build(options: BuildOptions): Promise; -} - -interface BuildOptions { - entrypoints: string[]; // required - outdir?: string; // default: no write (in-memory only) - format?: "esm"; // later: "cjs" | "iife" - target?: "browser" | "bun" | "node"; // "browser" - splitting?: boolean; // true - plugins?: BunPlugin[]; // [] // See https://bun.sh/docs/bundler/plugins - loader?: { [k in string]: Loader }; // See https://bun.sh/docs/bundler/loaders - manifest?: boolean; // false - external?: string[]; // [] - sourcemap?: "none" | "inline" | "external"; // "none" - root?: string; // computed from entrypoints - naming?: - | string - | { - entry?: string; // '[dir]/[name].[ext]' - chunk?: string; // '[name]-[hash].[ext]' - asset?: string; // '[name]-[hash].[ext]' - }; - publicPath?: string; // e.g. http://mydomain.com/ - minify?: - | boolean // false - | { - identifiers?: boolean; - whitespace?: boolean; - syntax?: boolean; - }; -} - -interface BuildOutput { - outputs: BuildArtifact[]; - success: boolean; - logs: Array; -} - -interface BuildArtifact extends Blob { - path: string; - loader: Loader; - hash?: string; - kind: "entry-point" | "chunk" | "asset" | "sourcemap"; - sourcemap?: BuildArtifact; -} - -type Loader = "js" | "jsx" | "ts" | "tsx" | "json" | "toml" | "file" | "napi" | "wasm" | "text"; - -interface BuildOutput { - outputs: BuildArtifact[]; - success: boolean; - logs: Array; -} - -declare class ResolveMessage { - readonly name: "ResolveMessage"; - readonly position: Position | null; - readonly code: string; - readonly message: string; - readonly referrer: string; - readonly specifier: string; - readonly importKind: - | "entry_point" - | "stmt" - | "require" - | "import" - | "dynamic" - | "require_resolve" - | "at" - | "at_conditional" - | "url" - | "internal"; - readonly level: "error" | "warning" | "info" | "debug" | "verbose"; - - toString(): string; -} -``` - - diff --git a/docs/cli/test.md b/docs/cli/test.md index ecae45377..e76f37cdc 100644 --- a/docs/cli/test.md +++ b/docs/cli/test.md @@ -1,4 +1,10 @@ -Bun ships with a built-in test runner. +Bun ships with a fast built-in test runner. Tests are executed with the Bun runtime, and support the following features. + +- TypeScript and JSX +- Snapshot testing +- Lifecycle hooks +- Watch mode with `--watch` +- Script pre-loading with `--preload` ## Run tests @@ -29,34 +35,39 @@ You can filter the set of tests to run by passing additional positional argument $ bun test ... ``` -## Snapshot testing - -Snapshots are supported by `bun test`. First, write a test using the `.toMatchSnapshot()` matcher: - -```ts -import { test, expect } from "bun:test"; +The test runner runs all tests in a single process. It loads all `--preload` scripts (see [Lifecycle](/docs/test/lifecycle) for details), then runs all tests. If a test fails, the test runner will exit with a non-zero exit code. -test("snap", () => { - expect("foo").toMatchSnapshot(); -}); -``` +## Watch mode -Then generate snapshots with the following command: +Similar to `bun run`, you can pass the `--watch` flag to `bun test` to watch for changes and re-run tests. ```bash -bun test --update-snapshots +$ bun test --watch ``` -Snapshots will be stored in a `__snapshots__` directory next to the test file. +## Lifecycle hooks -## Watch mode +Bun supports the following lifecycle hooks: -Similar to `bun run`, you can pass the `--watch` flag to `bun test` to watch for changes and re-run tests. +| Hook | Description | +| ------------ | --------------------------- | +| `beforeAll` | Runs once before all tests. | +| `beforeEach` | Runs before each test. | +| `afterEach` | Runs after each test. | +| `afterAll` | Runs once after all tests. | -```bash -$ bun test --watch +These hooks can be define inside test files, or in a separate file that is preloaded with the `--preload` flag. + +```ts +$ bun test --preload ./setup.ts ``` +See [Test > Lifecycle](/docs/test/lifecycle) for complete documentation. + +## Snapshot testing + +Snapshots are supported by `bun test`. See [Test > Snapshots](/docs/test/snapshots) for complete documentation. + ## Performance Bun's test runner is fast. diff --git a/docs/ecosystem/react.md b/docs/ecosystem/react.md index 84f1a4536..02f8a5dc5 100644 --- a/docs/ecosystem/react.md +++ b/docs/ecosystem/react.md @@ -61,3 +61,5 @@ Bun.serve({ }, }); ``` + +React `18.3` and later includes an [SSR optimization](https://github.com/facebook/react/pull/25597) that takes advantage of Bun's "direct" `ReadableStream` implementation. diff --git a/docs/index.md b/docs/index.md index 69df001f5..3fb4c8850 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,9 +9,10 @@ $ bun run index.tsx # TS and JSX supported out of the box ​​The `bun​` command-line tool also implements a test runner, script runner, and Node.js-compatible package manager, all significantly faster than existing tools and usable in existing Node.js projects with little to no changes necessary. ```bash -$ bun test # run tests $ bun run start # run the `start` script $ bun install ​ # install a package +$ bun build ./index.tsx # bundle a project +$ bun test # run tests $ bunx cowsay "Hello, world!" # execute a package ``` @@ -26,7 +27,7 @@ Get started with one of the quick links below, or read on to learn more about Bu {% arrowbutton href="/docs/quickstart" text="Do the quickstart" /%} {% arrowbutton href="/docs/cli/install" text="Install a package" /%} {% arrowbutton href="/docs/templates" text="Use a project template" /%} -{% arrowbutton href="/docs/cli/build" text="Bundle code for production" /%} +{% arrowbutton href="/docs/bundler" text="Bundle code for production" /%} {% 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" /%} @@ -63,7 +64,7 @@ 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. +- **ESM & CommonJS compatibility**. The world is moving towards ES modules (ESM), but millions of packages on npm still require CommonJS. Bun recommends ES modules, but supports CommonJS. - **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. diff --git a/docs/nav.ts b/docs/nav.ts index 8ddced819..057f901bd 100644 --- a/docs/nav.ts +++ b/docs/nav.ts @@ -90,9 +90,9 @@ export default { page("runtime/typescript", "TypeScript", { description: `Bun can directly execute TypeScript files without additional configuration.`, }), - // page("runtime/jsx", "JSX", { - // description: `Bun can directly execute TypeScript files without additional configuration.`, - // }), + page("runtime/jsx", "JSX", { + description: `Bun can directly execute TypeScript files without additional configuration.`, + }), // page("runtime/apis", "APIs", { // description: `Bun is a new JavaScript runtime designed to be a faster, leaner, more modern replacement for Node.js.`, // }), @@ -154,7 +154,7 @@ export default { }), divider("Bundler"), - page("cli/build", "`Bun.build`", { + page("bundler", "`Bun.build`", { description: "Bundle code for comsumption in the browser with Bun's native bundler.", }), // page("bundler/intro", "How bundlers work", { @@ -169,7 +169,7 @@ export default { page("bundler/executables", "Executables", { description: "Compile a TypeScript or JavaScript file to a standalone cross-platform executable", }), - page("bundler/migration", "Migration", { + page("bundler/vs-esbuild", "vs esbuild", { description: `Guides for migrating from other bundlers to Bun.`, }), @@ -181,8 +181,11 @@ export default { description: "Write your tests using Jest-like expect matchers, plus setup/teardown hooks, snapshot testing, and more", }), - page("test/extending", "Extending the test runner", { - description: "Add lifecycle hooks to your tests that run before/after each test, or before/after all tests.", + page("test/lifecycle", "Lifecycle hooks", { + description: "Add lifecycle hooks to your tests that run before/after each test or test run", + }), + page("test/snapshots", "Snapshots", { + description: "Add lifecycle hooks to your tests that run before/after each test or test run", }), page("test/hot", "Watch mode", { description: "Reload your tests automatically on change.", @@ -221,33 +224,45 @@ export default { }), divider("API"), - page("api/http", "HTTP", { + page("api/http", "HTTP server", { description: `Bun implements Web-standard fetch, plus a Bun-native API for building fast HTTP servers.`, }), // "`Bun.serve`"), page("api/websockets", "WebSockets", { description: `Bun supports server-side WebSockets with on-the-fly compression, TLS support, and a Bun-native pubsub API.`, }), // "`Bun.serve`"), - page("api/tcp", "TCP Sockets", { - description: `Bun's native API implements Web-standard TCP Sockets, plus a Bun-native API for building fast TCP servers.`, - }), // "`Bun.{listen|connect}`"), + page("api/binary-data", "Binary data", { + description: `How to represent and manipulate binary data in Bun.`, + }), // "`Bun.serve`"), + page("api/streams", "Streams", { + description: `Reading, writing, and manipulating streams of data in Bun.`, + }), // "`Bun.serve`"), page("api/file-io", "File I/O", { description: `Read and write files fast with Bun's heavily optimized file system API.`, }), // "`Bun.write`"), + page("api/import-meta", "import.meta", { + description: `Module-scoped metadata and utilities`, + }), // "`bun:sqlite`"), page("api/sqlite", "SQLite", { description: `The fastest SQLite driver for JavaScript is baked directly into Bun.`, }), // "`bun:sqlite`"), page("api/file-system-router", "FileSystemRouter", { description: `Resolve incoming HTTP requests against a local file system directory with Bun's fast, Next.js-compatible router.`, }), // "`Bun.FileSystemRouter`"), + page("api/tcp", "TCP sockets", { + description: `Bun's native API implements Web-standard TCP Sockets, plus a Bun-native API for building fast TCP servers.`, + }), // "`Bun.{listen|connect}`") page("api/globals", "Globals", { description: `Bun implements a range of Web APIs, Node.js APIs, and Bun-native APIs that are available in the global scope.`, }), // "`Bun.write`"), - page("api/spawn", "Spawn", { + page("api/spawn", "Child processes", { description: `Spawn sync and async child processes with easily configurable input and output streams.`, }), // "`Bun.spawn`"), page("api/transpiler", "Transpiler", { description: `Bun exposes its internal transpiler as a pluggable API.`, }), // "`Bun.Transpiler`"), + page("api/hashing", "Hashing", { + description: `Native support for a range of fast hashing algorithms.`, + }), // "`Bun.serve`"), page("api/console", "Console", { description: `Bun implements a Node.js-compatible \`console\` object with colorized output and deep pretty-printing.`, }), // "`Node-API`"), diff --git a/docs/runtime/bun-apis.md b/docs/runtime/bun-apis.md index 0cfdf61fb..c4cd534e7 100644 --- a/docs/runtime/bun-apis.md +++ b/docs/runtime/bun-apis.md @@ -1,4 +1,16 @@ -Bun implements a set of native 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. +Bun implements a set of native APIs on the `Bun` global object and through a number of built-in modules. These APIs are heavily optimized and represent the canonical "Bun-native" way to implement some common functionality. + +Bun strives to implement standard Web APIs wherever possible. Bun introduces new APIs primarily for server-side tasks where no standard exists, such as file I/O and starting an HTTP server. In these cases, Bun's approach still builds atop standard APIs like `Blob`, `URL`, and `Request`. + +```ts +Bun.serve({ + fetch(req: Request) { + return new Response("Success!"); + }, +}); +``` + +Click the link in the right column to jump to the associated documentation. {% table %} @@ -7,38 +19,53 @@ Bun implements a set of native APIs on the `Bun` global object and through a num --- -- [HTTP](/docs/api/http) -- `Bun.serve` +- HTTP server +- [`Bun.serve`](/docs/api/http#bun-serve) + +--- + +- Bundler +- [`Bun.build`](/docs/bundler) + +--- + +- File I/O +- [`Bun.file`](/docs/api/file-io#reading-files-bun-file) [`Bun.write`](/docs/api/file-io#writing-files-bun-write) --- -- [File I/O](/docs/api/file-io) -- `Bun.file` `Bun.write` +- Child processes +- [`Bun.spawn`](/docs/api/spawn#spawn-a-process-bun-spawn) [`Bun.spawnSync`](/docs/api/spawn#blocking-api-bun-spawnsync) --- -- [Processes](/docs/api/spawn) -- `Bun.spawn` `Bun.spawnSync` +- TCP +- [`Bun.listen`](/docs/api/tcp#start-a-server-bun-listen) [`Bun.connect`](/docs/api/tcp#start-a-server-bun-listen) --- -- [TCP](/docs/api/tcp) -- `Bun.listen` `Bun.connect` +- Transpiler +- [`Bun.Transpiler`](/docs/api/transpiler) --- -- [Transpiler](/docs/api/transpiler) -- `Bun.Transpiler` +- Routing +- [`Bun.FileSystemRouter`](/docs/api/file-system-router) --- -- [Routing](/docs/api/file-system-router) -- `Bun.FileSystemRouter` +- HTML Rewriting +- [`HTMLRewriter`](/docs/api/html-rewriter) --- -- [HTMLRewriter](/docs/api/html-rewriter) -- `HTMLRewriter` +- Hashing +- [`Bun.hash`](/docs/api/hashing#bun-hash) [`Bun.CryptoHasher`](/docs/api/hashing#bun-cryptohasher) + +--- + +- import.meta +- [`import.meta`](/docs/api/import-meta) --- @@ -47,29 +74,27 @@ Bun implements a set of native APIs on the `Bun` global object and through a num --- --> -- [Utils](/docs/api/utils) -- `Bun.peek` `Bun.which` +- SQLite +- [`bun:sqlite`](/docs/api/sqlite) --- -- [SQLite](/docs/api/sqlite) -- `bun:sqlite` +- FFI +- [`bun:ffi`](/docs/api/ffi) --- -- [FFI](/docs/api/ffi) -- `bun:ffi` +- Testing +- [`bun:test`](/docs/cli/test) --- -- [Testing](/docs/api/test) -- `bun:test` +- Node-API +- [`Node-API`](/docs/api/node-api) --- -- [Node-API](/docs/api/node-api) -- `Node-API` - ---- +- Utilities +- [`Bun.version`](/docs/api/utils#bun-version) [`Bun.revision`](/docs/api/utils#bun-revision) [`Bun.env`](/docs/api/utils#bun-env) [`Bun.main`](/docs/api/utils#bun-main) [`Bun.sleep()`](/docs/api/utils#bun-sleep) [`Bun.sleepSync()`](/docs/api/utils#bun-sleepsync) [`Bun.which()`](/docs/api/utils#bun-which) [`Bun.peek()`](/docs/api/utils#bun-peek) [`Bun.openInEditor()`](/docs/api/utils#bun-openineditor) [`Bun.deepEquals()`](/docs/api/utils#bun-deepequals) [`Bun.escapeHTML()`](/docs/api/utils#bun-escapehtlm) [`Bun.enableANSIColors()`](/docs/api/utils#bun-enableansicolors) [`Bun.fileURLToPath()`](/docs/api/utils#bun-fileurltopath) [`Bun.pathToFileURL()`](/docs/api/utils#bun-pathtofileurl) [`Bun.gzipSync()`](/docs/api/utils#bun-gzipsync) [`Bun.gunzipSync()`](/docs/api/utils#bun-gunzipsync) [`Bun.deflateSync()`](/docs/api/utils#bun-deflatesync) [`Bun.inflateSync()`](/docs/api/utils#bun-inflatesync) [`Bun.inspect()`](/docs/api/utils#bun-inspect) [`Bun.nanoseconds()`](/docs/api/utils#bun-nanoseconds) [`Bun.readableStreamTo*()`](/docs/api/utils#bun-readablestreamto) [`Bun.resolveSync()`](/docs/api/utils#bun-resolvesync) {% /table %} diff --git a/docs/runtime/configuration.md b/docs/runtime/configuration.md index 9ea896edb..83a6ae37f 100644 --- a/docs/runtime/configuration.md +++ b/docs/runtime/configuration.md @@ -19,7 +19,77 @@ If both a global and local `bunfig` are detected, the results are shallow-merged - `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`: Bun occasionally requires a directory to store intermediate assets during bundling or other operations. If unset, `TMPDIR` defaults to the platform-specific temporary directory (on Linux, `/tmp` and on macOS `/private/tmp`). -## Configure `bun install` +## Runtime + +```toml +# scripts to run before `bun run`ning a file or script +# useful for registering plugins +preload = ["./preload.ts"] + +# equivalent to corresponding tsconfig compilerOptions +jsx = "react" +jsxFactory = "h" +jsxFragment = "Fragment" +jsxImportSource = "react" + +# Set a default framework to use +# By default, Bun will look for an npm package like `bun-framework-${framework}`, followed by `${framework}` +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" } + +[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" +# - "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 +``` + +### Debugging + +```toml +[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" +``` + +## Test runner + +```toml +[test] +# setup scripts to run before all test files +preload = ["./setup.ts"] +``` + +## Package manager 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/runtime/configuration). @@ -120,7 +190,9 @@ save = true print = "yarn" ``` -## Configure `bun dev` +## Dev server (`bun dev`) + +{% The `bun dev` command is likely to change soon and will likely be deprecated in an upcoming release. We recommend %} Here is an example: @@ -128,17 +200,6 @@ Here is an example: # 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" @@ -154,33 +215,4 @@ entryPoints = ["./app/index.ts"] # 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/runtime/jsx.md b/docs/runtime/jsx.md index 1ace6e367..ae0a319db 100644 --- a/docs/runtime/jsx.md +++ b/docs/runtime/jsx.md @@ -12,14 +12,291 @@ function Component(props: {message: string}) { console.log(); ``` -Bun implements special logging for JSX to make debugging easier. +## Configuration -```bash -$ bun run react.tsx - +Bun reads your `tsconfig.json` or `jsconfig.json` configuration files to determines how to perform the JSX transform internally. To avoid using either of these, the following options can also be defined in [`bunfig.json`](/docs/runtime/configuration). + +The following compiler options are respected. + +### [`jsx`](https://www.typescriptlang.org/tsconfig#jsx) + +How JSX constructs are transformed into vanilla JavaScript internally. The table below lists the possible values of `jsx`, along with their transpilation of the following simple JSX component: + +```tsx +Hello +``` + +{% table %} + +- Compiler options +- Transpiled output + +--- + +- ```json + { + "jsx": "react" + } + ``` + +- ```tsx + import { createElement } from "react"; + createElement("Box", { width: 5 }, "Hello"); + ``` + +--- + +- ```json + { + "jsx": "react-jsx" + } + ``` + +- ```tsx + import { jsx } from "react/jsx-runtime"; + jsx("Box", { width: 5 }, "Hello"); + ``` + +--- + +- ```json + { + "jsx": "react-jsxdev" + } + ``` + +- ```tsx + import { jsxDEV } from "react/jsx-dev-runtime"; + jsxDEV("Box", { width: 5, children: "Hello" }, undefined, false, undefined, this); + ``` + + The `jsxDEV` variable name is a convention used by React. The `DEV` suffix is a visible way to indicate that the code is intended for use in development. The development version of React is slowers and includes additional validity checks & debugging tools. + +--- + +- ```json + { + "jsx": "preserve" + } + ``` + +- ```tsx + // JSX is not transpiled + // "preserve" is not supported by Bun currently + Hello + ``` + +{% /table %} + + + +### [`jsxFactory`](https://www.typescriptlang.org/tsconfig#jsxFactory) + +{% callout %} +**Note** — Only applicable when `jsx` is `react`. +{% /callout %} + +The function name used to represent JSX constructs. Default value is `"createElement"`. This is useful for libraries like [Preact](https://preactjs.com/) that use a different function name (`"h"`). + +{% table %} + +- Compiler options +- Transpiled output + +--- + +- ```json + { + "jsx": "react", + "jsxFactory": "h" + } + ``` + +- ```tsx + import { createhElement } from "react"; + h("Box", { width: 5 }, "Hello"); + ``` + +{% /table %} + +### [`jsxFragmentFactory`](https://www.typescriptlang.org/tsconfig#jsxFragmentFactory) + +{% callout %} +**Note** — Only applicable when `jsx` is `react`. +{% /callout %} + +The function name used to represent [JSX fragments](https://react.dev/reference/react/Fragment) such as `<>Hello`; only applicable when `jsx` is `react`. Default value is `"Fragment"`. + +{% table %} + +- Compiler options +- Transpiled output + +--- + +- ```json + { + "jsx": "react", + "jsxFactory": "myjsx", + "jsxFragmentFactory": "MyFragment" + } + ``` + +- ```tsx + // input + <>Hello; + + // output + import { myjsx, MyFragment } from "react"; + createElement("Box", { width: 5 }, "Hello"); + ``` + +{% /table %} + +### [`jsxImportSource`](https://www.typescriptlang.org/tsconfig#jsxImportSource) + +{% callout %} +**Note** — Only applicable when `jsx` is `react-jsx` or `react-jsxdev`. +{% /callout %} + +The module from which the component factory function (`createElement`, `jsx`, `jsxDEV`, etc) will be imported. Default value is `"react"`. This will typically be necessary when using a component library like Preact. + +{% table %} + +- Compiler options +- Transpiled output + +--- + +- ```jsonc + { + "jsx": "react" + // jsxImportSource is not defined + // default to "react" + } + ``` + +- ```tsx + import { jsx } from "react/jsx-runtime"; + jsx("Box", { width: 5, children: "Hello" }); + ``` + +--- + +- ```jsonc + { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } + ``` + +- ```tsx + import { jsx } from "preact/jsx-runtime"; + jsx("Box", { width: 5, children: "Hello" }); + ``` + +--- + +- ```jsonc + { + "jsx": "react-jsxdev", + "jsxImportSource": "preact" + } + ``` + +- ```tsx + // /jsx-runtime is automatically appended + import { jsxDEV } from "preact/jsx-dev-runtime"; + jsxDEV("Box", { width: 5, children: "Hello" }, undefined, false, undefined, this); + ``` + +{% /table %} + +### JSX pragma + +All of these values can be set on a per-file basis using _pragmas_. A pragma is a special comment that sets a compiler option in a particular file. + +{% table %} + +- Pragma +- Equivalent config + +--- + +- ```ts + // @jsx h + ``` + +- ```jsonc + { + "jsxFactory": "h" + } + ``` + +--- + +- ```ts + // @jsxFrag MyFragment + ``` +- ```jsonc + { + "jsxFragmentFactory": "MyFragment" + } + ``` + +--- + +- ```ts + // @jsxImportSource preact + ``` +- ```jsonc + { + "jsxImportSource": "preact" + } + ``` + +{% /table %} + +## Logging + +Bun implements special logging for JSX to make debugging easier. Given the following file: + +```tsx#index.tsx +import { Stack, UserCard } from "./components"; + +console.log( + + + + +); ``` - +``` diff --git a/docs/runtime/modules.md b/docs/runtime/modules.md index 6c33f7336..da9bc9253 100644 --- a/docs/runtime/modules.md +++ b/docs/runtime/modules.md @@ -121,7 +121,7 @@ Bun respects subpath [`"exports"`](https://nodejs.org/api/packages.html#subpath- "name": "foo", "exports": { ".": "./index.js", - "./package.json": "./package.json" # subpath + "./package.json": "./package.json" // subpath } } ``` diff --git a/docs/runtime/nodejs-apis.md b/docs/runtime/nodejs-apis.md index a4256077c..9a7cf070a 100644 --- a/docs/runtime/nodejs-apis.md +++ b/docs/runtime/nodejs-apis.md @@ -104,8 +104,8 @@ This page is updated regularly to reflect compatibility status of the latest ver --- - {% anchor id="node_https" %} [`node:https`](https://nodejs.org/api/https.html) {% /anchor %} -- 🟡 -- See `node:http`. +- 🟢 +- Fully implemented. --- diff --git a/docs/test/extending.md b/docs/test/extending.md deleted file mode 100644 index 385957693..000000000 --- a/docs/test/extending.md +++ /dev/null @@ -1,162 +0,0 @@ -Like the runtime, `bun:test` also supports `--preload` scripts. These scripts are loaded before any tests are run. This is useful for setting up test fixtures, mocking, and configuring the test environment. - -{% codetabs %} - -```ts#preloaded.ts -import { beforeAll, beforeEach, afterEach, afterAll } from "bun:test"; - -beforeAll(() => { - console.log("beforeAll"); -}); - -beforeEach(() => { - console.log("beforeEach"); -}); - -afterEach(() => { - console.log("afterEach"); -}); - -afterAll(() => { - console.log("afterAll"); -}); -``` - -{% /codetabs %} - -Test file: - -```ts -import { expect, test } from "bun:test"; - -test("1 + 1", () => { - expect(1 + 1).toEqual(2); - console.log("1 + 1"); -}); -``` - -Run the test with `--preload`: - -```sh -$ bun test --preload=preloaded.ts -``` - -It outputs: - -```sh -beforeAll -beforeEach -1 + 1 -afterEach -afterAll -``` - -## Configuration - -To save yourself from having to type `--preload` every time you run tests, you can add it to your `bunfig.toml`: - -```toml -[test] -preload = ["./preloaded.ts"] -``` - -## List of lifecycle hooks - -The following lifecycle hooks are available in `--preload`: - -| Hook | Description | -| ------------ | --------------------------- | -| `beforeAll` | Runs once before all tests. | -| `beforeEach` | Runs before each test. | -| `afterEach` | Runs after each test. | -| `afterAll` | Runs once after all tests. | - -Calling `expect`, `test`, or any other test function inside a lifecycle hook will throw an error. Calling `test` inside `beforeAll`, `afterAll`, `beforeEach` or `afterEach` will also throw an error. - -You can use `console.log` or any other function otherwise inside a lifecycle hook. - -We haven't implemented timer simulation, test isolation, or `Math.random` mocking yet. If you need these features, please [open an issue](https://bun.sh/issues). - -### The lifecycle of bun:test - -The test runner is a single process that runs all tests. It loads all `--preload` scripts, then runs all tests. If a test fails, the test runner will exit with a non-zero exit code. - -Before running each test, it transpiles the source code and all dependencies into vanilla JavaScript using Bun's transpiler and module resolver. This means you can use TypeScript, JSX, ESM, and CommonJS in your tests. - -#### Globals - -Like Jest, you can use `describe`, `test`, `expect`, and other functions without importing them. - -But unlike Jest, they are not globals. They are imported from `bun:test` and are exclusively available in test files or when preloading scripts. - -```ts -typeof globalThis.describe; // "undefined" -typeof describe; // "function" -``` - -This works via a transpiler integration in Bun. When `describe`, `expect`, `it`, etc are used in a test file, the transpiler auto-imports from `bun:test`. This transpiler plugin is only enabled inside test files and when preloading scripts. If you try to use Jest globals in other files, you will get an error. - -Every `describe`, `test`, and `expect` is scoped to the current test file. Importing from `"bun:test"` creates a new scope. This means you can't use `describe` from one test file in another test file because belong to different scopes. - -## Loaders & Resolvers - -{% note %} -Plugin support is not implemented yet. **There is a bug and this feature is not working**. -{% /note %} - -`bun:test` supports the same plugin API as bun's runtime and bun's bundler. See [Plugins](/docs/bundler/plugins#usage) for more information. - -## Example loader - -{% codetabs %} - -```ts#loader.ts -import { plugin } from 'bun'; - -plugin({ - name: 'My loader', - setup(build) { - build.onResolve({ filter: /\.txt$/ }, (args) => { - return { - path: args.path, - namespace: 'my-loader', - }; - }); - - build.onLoad({ filter: /my-loader:.txt$/ }, (args) => { - return { - contents: 'Hello world!', - loader: 'text', - }; - }); - }, -}); -``` - -{% /codetabs %} - -Now in your test file, you can import `.txt` files: - -```ts#my-test.test.ts -import { expect, test } from "bun:test"; -import text from "./hello.txt"; - -test("text is 'Hello world!'", () => { - expect(text).toEqual("Hello world!"); -}); -``` - -To run the test, you need to add `loader.ts` to `preload`: - -```toml -[test] -preload = ["loader.ts"] -``` - -Or you can pass --preload to the command line: - -```sh -$ bun test --preload=loader.ts -``` - -TODO: `expect.extend` diff --git a/docs/test/lifecycle.md b/docs/test/lifecycle.md new file mode 100644 index 000000000..ccb7ce85d --- /dev/null +++ b/docs/test/lifecycle.md @@ -0,0 +1,81 @@ +The test runner supports the following lifecycle hooks. This is useful for loading test fixtures, mocking data, and configuring the test environment. + +| Hook | Description | +| ------------ | --------------------------- | +| `beforeAll` | Runs once before all tests. | +| `beforeEach` | Runs before each test. | +| `afterEach` | Runs after each test. | +| `afterAll` | Runs once after all tests. | + +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`. The _scope_ is determined by where the hook is defined. + +To scope the hooks to a particular `describe` block: + +```ts +import { describe, beforeAll } from "bun:test"; + +describe("test group", () => { + beforeAll(() => { + // setup + }); + + // tests... +}); +``` + +To scope the hooks to a test file: + +```ts +import { describe, beforeAll } from "bun:test"; + +describe("test group", () => { + beforeAll(() => { + // setup + }); + + // tests... +}); +``` + +To scope the hooks to an entire multi-file test run, define the hooks in a separate file. + +```ts#setup.ts +import { beforeAll, afterAll } from "bun:test"; + +beforeAll(() => { + // global setup +}); + +afterAll(() => { + // global teardown +}); +``` + +Then use `--preload` to run the setup script before any test files. + +```ts +bun test --preload ./setup.ts +``` + +To avoid typing `--preload` every time you run tests, it can be added to your `bunfig.toml`: + +```toml +[test] +preload = ["./setup.ts"] +``` diff --git a/docs/test/snapshots.md b/docs/test/snapshots.md new file mode 100644 index 000000000..46df217ce --- /dev/null +++ b/docs/test/snapshots.md @@ -0,0 +1,15 @@ +Snapshot tests are written using the `.toMatchSnapshot()` matcher: + +```ts +import { test, expect } from "bun:test"; + +test("snap", () => { + expect("foo").toMatchSnapshot(); +}); +``` + +The first time this test is run, the argument to `expect` will be serialized and written to a special snapshot file in a `__snapshots__` directory alongside the test file. On future runs, the argument is compared against the snapshot on disk. Snapshots can be re-generated with the following command: + +```bash +$ bun test --update-snapshots +``` diff --git a/docs/test/writing.md b/docs/test/writing.md index b79f88a0f..46773c1d2 100644 --- a/docs/test/writing.md +++ b/docs/test/writing.md @@ -12,6 +12,17 @@ test("2 + 2", () => { }); ``` +{% details summary="Jest-style globals" %} +As in Jest, you can use `describe`, `test`, `expect`, and other functions without importing them. Unlike Jest, they are not injected into the global scope. Instead, the Bun transpiler will automatically inject an import from `bun:test` internally. + +```ts +typeof globalThis.describe; // "undefined" +typeof describe; // "function" +``` + +This transpiler integration only occurs during `bun test`, and only for test files & preloaded scripts. In practice there's no significant difference to the end user. +{% /details %} + Tests can be grouped into suites with `describe`. ```ts#math.test.ts @@ -52,7 +63,7 @@ test("2 * 2", done => { }); ``` -Skip individual tests with `test.skip`. +Skip individual tests with `test.skip`. These tests will not be run. ```ts import { expect, test } from "bun:test"; @@ -63,41 +74,14 @@ test.skip("wat", () => { }); ``` -## Setup and teardown - -Perform per-test setup and teardown logic with `beforeEach` and `afterEach`. +Mark a test as a todo with `test.todo`. These tests _will_ be run, and the test runner will expect them to fail. If they pass, you will be prompted to mark it as a regular test. ```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 - db = initializeDatabase(); +test.todo("fix this", () => { + myTestFunction(); }); - -afterAll(() => { - // close connection - db.close(); -}); - -// tests... ``` ## Matchers @@ -106,6 +90,8 @@ Bun implements the following matchers. Full Jest compatibility is on the roadmap {% table %} +--- + - 🟢 - [`.not`](https://jestjs.io/docs/expect#not) diff --git a/packages/bun-types/buffer.d.ts b/packages/bun-types/buffer.d.ts index 5a0dd071a..eb7335871 100644 --- a/packages/bun-types/buffer.d.ts +++ b/packages/bun-types/buffer.d.ts @@ -547,6 +547,7 @@ declare module "buffer" { type: "Buffer"; data: number[]; }; + /** * Returns `true` if both `buf` and `otherBuffer` have exactly the same bytes,`false` otherwise. Equivalent to `buf.compare(otherBuffer) === 0`. * diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 7336d731d..287bd4686 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -667,6 +667,10 @@ declare module "bun" { * A UNIX timestamp indicating when the file was last modified. */ lastModified: number; + /** + * The name or path of the file, as specified in the constructor. + */ + name?: number; } /** @@ -1668,7 +1672,7 @@ declare module "bun" { * * @deprecated since v0.6.3 - Use `key: Bun.file(path)` instead. */ - keyFile: string; + keyFile?: string; /** * File path to a TLS certificate * @@ -1676,7 +1680,7 @@ declare module "bun" { * * @deprecated since v0.6.3 - Use `cert: Bun.file(path)` instead. */ - certFile: string; + certFile?: string; /** * Passphrase for the TLS key diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts index a0efdc983..47ccb85e7 100644 --- a/packages/bun-types/globals.d.ts +++ b/packages/bun-types/globals.d.ts @@ -2388,6 +2388,9 @@ interface UnderlyingSource { cancel?: UnderlyingSourceCancelCallback; pull?: UnderlyingSourcePullCallback; start?: UnderlyingSourceStartCallback; + /** + * Mode "bytes" is not currently supported. + */ type?: undefined; } diff --git a/packages/bun-types/tests/hashing.test-d.ts b/packages/bun-types/tests/hashing.test-d.ts new file mode 100644 index 000000000..bd9222541 --- /dev/null +++ b/packages/bun-types/tests/hashing.test-d.ts @@ -0,0 +1 @@ +Bun.hash.wyhash("asdf", 1234); diff --git a/packages/bun-types/tests/http.test-d.ts b/packages/bun-types/tests/http.test-d.ts index aee0b3703..c78ab80c2 100644 --- a/packages/bun-types/tests/http.test-d.ts +++ b/packages/bun-types/tests/http.test-d.ts @@ -31,3 +31,5 @@ const sp = new URLSearchParams("q=foo&bar=baz"); for (const q of sp) { console.log(q); } + +fetch; diff --git a/packages/bun-types/tests/streams.test-d.ts b/packages/bun-types/tests/streams.test-d.ts new file mode 100644 index 000000000..b894dd9fe --- /dev/null +++ b/packages/bun-types/tests/streams.test-d.ts @@ -0,0 +1,19 @@ +new ReadableStream({ + start(controller) { + controller.enqueue("hello"); + controller.enqueue("world"); + controller.close(); + }, +}); + +new ReadableStream({ + type: "direct", + pull(controller) { + controller.write("hello"); + controller.write("world"); + controller.close(); + }, + cancel() { + // called if stream.cancel() is called + }, +}); diff --git a/packages/bun-types/tests/tcp.test-d.ts b/packages/bun-types/tests/tcp.test-d.ts index f951163d6..92c765ff6 100644 --- a/packages/bun-types/tests/tcp.test-d.ts +++ b/packages/bun-types/tests/tcp.test-d.ts @@ -86,6 +86,25 @@ Bun.listen({ }, }); +Bun.listen({ + data: { arg: "asdf" }, + socket: { + data(socket) { + socket.data.arg.toLowerCase(); + }, + open() { + console.log("asdf"); + }, + }, + hostname: "adsf", + port: 324, + tls: { + cert: "asdf", + key: Bun.file("adsf"), + ca: Buffer.from("asdf"), + }, +}); + Bun.listen({ data: { arg: "asdf" }, socket: { diff --git a/packages/bun-types/tests/tls.test-d.ts b/packages/bun-types/tests/tls.test-d.ts index 5cf243e6c..fb2aa7d9b 100644 --- a/packages/bun-types/tests/tls.test-d.ts +++ b/packages/bun-types/tests/tls.test-d.ts @@ -1,6 +1,32 @@ import tls from "node:tls"; +tls.getCiphers()[0]; + +tls.connect({ + host: "localhost", + port: 80, + ca: "asdf", + cert: "path to cert", +}); + +tls.connect({ + host: "localhost", + port: 80, + ca: Bun.file("asdf"), + cert: Bun.file("path to cert"), + ciphers: "adsf", +}); + +tls.connect({ + host: "localhost", + port: 80, + ca: Buffer.from("asdf"), + cert: Buffer.from("asdf"), +}); + tls.connect({ host: "localhost", port: 80, + ca: new Uint8Array([1, 2, 3]), + cert: new Uint8Array([1, 2, 3]), }); diff --git a/packages/bun-types/tls.d.ts b/packages/bun-types/tls.d.ts index a156bc309..386b4e9d6 100644 --- a/packages/bun-types/tls.d.ts +++ b/packages/bun-types/tls.d.ts @@ -861,7 +861,13 @@ declare module "tls" { * the well-known CAs curated by Mozilla. Mozilla's CAs are completely * replaced when CAs are explicitly specified using this option. */ - ca?: string | Buffer | BunFile | Array | undefined; + ca?: + | string + | Buffer + | TypedArray + | BunFile + | Array + | undefined; /** * Cert chains in PEM format. One cert chain should be provided per * private key. Each cert chain should consist of the PEM formatted @@ -873,7 +879,13 @@ declare module "tls" { * intermediate certificates are not provided, the peer will not be * able to validate the certificate, and the handshake will fail. */ - cert?: string | Buffer | BunFile | Array | undefined; + cert?: + | string + | Buffer + | TypedArray + | BunFile + | Array + | undefined; /** * Colon-separated list of supported signature algorithms. The list * can contain digest algorithms (SHA256, MD5 etc.), public key @@ -931,7 +943,13 @@ declare module "tls" { * object.passphrase is optional. Encrypted keys will be decrypted with * object.passphrase if provided, or options.passphrase if it is not. */ - key?: string | Buffer | BunFile | Array | undefined; + key?: + | string + | Buffer + | BunFile + | TypedArray + | Array + | undefined; /** * Name of an OpenSSL engine to get private key from. Should be used * together with privateKeyIdentifier. -- cgit v1.2.3