diff options
author | 2023-04-26 20:07:30 -0700 | |
---|---|---|
committer | 2023-04-26 20:07:30 -0700 | |
commit | 8ba13f273c2fb4cd18e144f885a0c6b4b94e5880 (patch) | |
tree | cf9d42de2b840c0dcbfd578843507d2db1e36b8d | |
parent | 68ab71eb13687ebb0889d085bb9c76132e452f60 (diff) | |
download | bun-8ba13f273c2fb4cd18e144f885a0c6b4b94e5880.tar.gz bun-8ba13f273c2fb4cd18e144f885a0c6b4b94e5880.tar.zst bun-8ba13f273c2fb4cd18e144f885a0c6b4b94e5880.zip |
Add bundler documentation (#2753)
* WIP
* WIP
* WIP
* Document API
* Updates
* Polish bundler docs
* Tweaks
* Tweak
-rw-r--r-- | docs/bundler/intro.md | 75 | ||||
-rw-r--r-- | docs/bundler/loaders.md | 136 | ||||
-rw-r--r-- | docs/bundler/plugins.md (renamed from docs/runtime/plugins.md) | 42 | ||||
-rw-r--r-- | docs/cli/build.md | 969 | ||||
-rw-r--r-- | docs/cli/bunx.md | 5 | ||||
-rw-r--r-- | docs/nav.ts | 19 |
6 files changed, 1233 insertions, 13 deletions
diff --git a/docs/bundler/intro.md b/docs/bundler/intro.md new file mode 100644 index 000000000..5056c26a9 --- /dev/null +++ b/docs/bundler/intro.md @@ -0,0 +1,75 @@ +<!-- This document is a work in progress. It's not currently included in the actual docs. --> + +The goal of this document is to break down why bundling is necessary, how it works, and how the bundler became such a key part of modern JavaScript development. The content is not specific to Bun's bundler, but is rather aimed at anyone looking for a greater understanding of how bundlers work and, by extension, how most modern frameworks are implemented. + +## What is bundling + +With the adoption of ECMAScript modules (ESM), browsers can now resolve `import`/`export` statements in JavaScript files loaded via `<script>` tags. + +{% codetabs %} + +```html#index.html +<html> + <head> + <script type="module" src="/index.js" ></script> + </head> +</html> +``` + +```js#index.js +import {sayHello} from "./hello.js"; + +sayHello(); +``` + +```js#hello.js +export function sayHello() { + console.log("Hello, world!"); +} +``` + +{% /codetabs %} + +When a user visits this website, the files are loaded in the following order: + +{% image src="/images/module_loading_unbundled.png" /%} + +{% callout %} +**Relative imports** — Relative imports are resolved relative to the URL of the importing file. Because we're importing `./hello.js` from `/index.js`, the browser resolves it to `/hello.js`. If instead we'd imported `./hello.js` from `/src/index.js`, the browser would have resolved it to `/src/hello.js`. +{% /callout %} + +This approach works, it requires three round-trip HTTP requests before the browser is ready to render the page. On slow internet connections, this may add up to a non-trivial delay. + +This example is extremely simplistic. A modern app may be loading dozens of modules from `node_modules`, each consisting of hundrends of files. Loading each of these files with a separate HTTP request becomes untenable very quickly. While most of these requests will be running in parallel, the number of round-trip requests can still be very high; plus, there are limits on how many simultaneous requests a browser can make. + +{% callout %} +Some recent advances like modulepreload and HTTP/3 are intended to solve some of these problems, but at the moment bundling is still the most performant approach. +{% /callout %} + +The answer: bundling. + +## Entrypoints + +A bundler accepts an "entrypoint" to your source code (in this case, `/index.js`) and outputs a single file containing all of the code needed to run your app. If does so by parsing your source code, reading the `import`/`export` statements, and building a "module graph" of your app's dependencies. + +{% image src="/images/bundling.png" /%} + +We can now load `/bundle.js` from our `index.html` file and eliminate a round trip request, decreasing load times for our app. + +{% image src="/images/module_loading_bundled.png" /%} + +## Loaders + +Bundlers typically have some set of built-in "loaders". + +## Transpilation + +The JavaScript files above are just that: plain JavaScript. They can be directly executed by any modern browser. + +But modern tooling goes far beyond HTML, JavaScript, and CSS. JSX, TypeScript, and PostCSS/CSS-in-JS are all popular technologies that involve non-standard syntax that must be converted into vanilla JavaScript and CSS before if can be consumed by a browser. + +## Chunking + +## Module resolution + +## Plugins diff --git a/docs/bundler/loaders.md b/docs/bundler/loaders.md new file mode 100644 index 000000000..c8f067385 --- /dev/null +++ b/docs/bundler/loaders.md @@ -0,0 +1,136 @@ +The Bun bundler implements a set of default loaders out of the box. As a rule of thumb, the bundler and the runtime both support the same set of file types out of the box. + +`.js` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` `.jsx` `.toml` `.json` + +{% callout %} +The runtime also supports `.wasm` and `.node` binaries, which are not easily embedded in a bundle. These are effectively not supported by Bun's bundler. +{% /callout %} + +This document describes how these extensions map onto Bun's set of built-in _loaders_. Every loader has a name, such as `js`, `tsx`, or `json`. These names are used when building [plugins](/docs/bundler/plugins) that extend Bun with custom loaders. + +{% table %} + +- Loader +- Extensions +- Description + +--- + +- `js` +- `.cjs` `.mjs` +- **JavaScript.** Parses the code and applies a set if default transforms, like dead-code elimination, tree shaking, and environment variable inlining. Note that Bun does not attempt to down-convert syntax at the moment. + +--- + +- `jsx` +- `.js` `.jsx` +- **JavaScript + JSX.** Same as the `js` loader, but JSX syntax is supported. By default, JSX is downconverted to `createElement` syntax and a `jsx` factory is injected into the bundle. This can be configured using the relevant `jsx*` compiler options in `tsconfig.json`. + +--- + +- `ts` +- `.mts` `.cts` +- **TypeScript.** Strips out all TypeScript syntax, then behaves identically to the `js` loader. Bun does not perform typechecking. + +--- + +- `tsx` +- `.ts` `.tsx` +- **TypeScript + JSX**. Transpiles both TypeScript and JSX to vanilla JavaScript. + +--- + +- `json` +- `.json` +- **JSON**. JSON files are parsed and inlined into the bundle as a JavaScript object. + + ```ts + import pkg from "./package.json"; + pkg.name; // => "my-package" + ``` + + If a `.json` file is passed as an entrypoint, it will be converted to a `.js` with the parsed object as a default export. + + {% codetabs %} + + ```json#Input + { + "name": "John Doe", + "age": 35, + "email": "johndoe@example.com" + } + ``` + + ```js#Output + export default { + name: "John Doe", + age: 35, + email: "johndoe@example.com" + } + ``` + + {% /codetabs %} + +--- + +- `toml` +- `.toml` +- **TOML**. TOML files are parsed and inlined into the bundle as a JavaScript object. + + ```ts + import config from "./bunfig.toml"; + config.logLevel; // => "debug" + ``` + + If a `.toml` file is passed as an entrypoint, it will be converted to a `.js` with the parsed object as a default export. + + {% codetabs %} + + ```toml#Input + name = "John Doe" + age = 35 + email = "johndoe@example.com" + ``` + + ```js#Output + export default { + name: "John Doe", + age: 35, + email: "johndoe@example.com" + } + ``` + + {% /codetabs %} + +--- + +- `text` +- `.txt` +- **Text files**. 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!" + ``` + + If a `.txt` file is passed as an entrypoint, it will be converted to a `.js` with the contents of the file as a default export. + + {% codetabs %} + + ```txt#Input + Hello, world! + ``` + + ```js#Output + export default "Hello, world!"; + ``` + + {% /codetabs %} + +--- + +- `file` +- `.*` +- **File loader**. Any unrecognized file type is handled using the `file` loader. The file is copied into the `outdir` as-is. The name of the copied file is determined using the value of `naming.asset`. + +{% /table %} diff --git a/docs/runtime/plugins.md b/docs/bundler/plugins.md index 788bfb3ef..68d2b3107 100644 --- a/docs/runtime/plugins.md +++ b/docs/bundler/plugins.md @@ -2,29 +2,54 @@ **Note** — Introduced in Bun v0.1.11. {% /callout %} -Bun's runtime can be extended to support additional file types using _plugins_. Plugins can intercept imports and perform custom loading logic: reading files, transpiling code, etc. They can be used to extend Bun's runtime with _loaders_ for additional file types. +Bun provides a universal plugin API that can be used to extend both the _runtime_ and _bundler_. + +Plugins intercept imports and perform custom loading logic: reading files, transpiling code, etc. They can be used to add support for additional file types, like `.scss` or `.yaml`. In the context of Bun's bundler, plugins can be used to implement framework-level features like CSS extraction, macros, and client-server code co-location. ## Usage A plugin is defined as simple JavaScript object containing a `name` property and a `setup` function. Register a plugin with Bun using the `plugin` function. ```tsx#yamlPlugin.ts -import { plugin } from "bun"; +import type { BunPlugin } from "bun"; -plugin({ +const myPlugin: BunPlugin = { name: "YAML loader", setup(build) { // implementation }, +}; +``` + +This plugin can be passed into the `plugins` array when calling `Bun.build`. + +```ts +Bun.build({ + entrypoints: ["./app.ts"], + outdir: "./out", + plugins: [myPlugin], }); ``` +It can also be "registered" with the Bun runtime using the `Bun.plugin()` function. Once registered, the currently executing `bun` process will incorporate the plugin into its module resolution algorithm. + +```ts +Bun.plugin(myPlugin); +``` + 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 preload = ["./yamlPlugin.ts"] ``` +To preload files during `bun test`: + +```toml +[test] +preload = ["./loader.ts"] +``` + {% details summary="Usage without preload" %} Alternatively, you can import this file manually at the top of your project's entrypoint, before any application code is imported. @@ -243,15 +268,24 @@ namespace Bun { } type PluginBuilder = { + onResolve: ( + args: { filter: RegExp; namespace?: string }, + callback: (args: { path: string; importer: string }) => { + path: string; + namespace?: string; + } | void, + ) => void; onLoad: ( args: { filter: RegExp; namespace?: string }, callback: (args: { path: string }) => { - loader?: "js" | "jsx" | "ts" | "tsx" | "json" | "toml" | "object"; + loader?: Loader; contents?: string; exports?: Record<string, any>; }, ) => void; }; + +type Loader = "js" | "jsx" | "ts" | "tsx" | "json" | "toml" | "object"; ``` The `onLoad` method optionally accepts a `namespace` in addition to the `filter` regex. This namespace will be be used to prefix the import in transpiled code; for instance, a loader with a `filter: /\.yaml$/` and `namespace: "yaml:"` will transform an import from `./myfile.yaml` into `yaml:./myfile.yaml`. diff --git a/docs/cli/build.md b/docs/cli/build.md new file mode 100644 index 000000000..9ce6fddde --- /dev/null +++ b/docs/cli/build.md @@ -0,0 +1,969 @@ +{% callout %} +**Note** — Added in Bun v0.6.0 +{% /callout %} + +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 %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './build', +}); +``` + +```sh#CLI +$ bun build ./index.tsx --outdir ./build +``` + +{% /codetabs %} + +## 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(<Component message="Sup!" />) +``` + +```tsx#./Component.tsx +export function Component(props: {message: string}) { + return <p>{props.message}</p> +} +``` + +{% /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 %} + +```ts#JavaScript +Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out +``` + +{% /codetabs %} + +Let's break that down. + +- `entrypoints` — **Required.** An array of paths corresponding to the entrypoints of our application. In this case, we just have one. +- `outdir` — **Required.** The directory where output files will be written. + +Running this build will generate a new file `./out/index.js`. + +```ts +. +├── index.tsx +├── Component.tsx +└── out + └── index.js +``` + +It looks 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 $jsx and $createRoot functions are defined + + +// Component.tsx +function Component(props) { + return $jsx("p", { + children: props.message + }, undefined, false, undefined, this); +} + +// index.tsx +var rootNode = document.getElementById("root"); +var root = $createRoot(rootNode); +root.render($jsx(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 +<html> + <body> + <div id="root"></div> + <script type="module" src="/index.js"></script> + </body> +</div> +``` + +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!" + ``` + +--- + +- `.*` +- If the bundler encounters a file with an unsupported extension, it treats it as an _external file_. That means the import is converted into a path, and the referenced file is copied into the `outdir` as-is. + + {% codetabs %} + + ```ts#Build_file + Bun.build({ + entrypoints: ['./index.ts'], + outdir: './out', + origin: 'https://example.com', + }) + ``` + + ```ts#Input + import logo from "./logo.svg"; + console.log(logo); + ``` + + ```ts#Output + var logo = "./logo-ab237dfe.svg"; + console.log(logo); + ``` + + {% /codetabs %} + + By default, a hash is added to the file name to avoid collisions; this behavior can be overridden with the [`naming.asset`](#naming) option. + + If a value is provided for `origin`, the bundler will construct an absolute URL instead of using a relative path. + + {% codetabs %} + + ```ts-diff#Build_file + Bun.build({ + entrypoints: ['./index.ts'], + outdir: './out', + + origin: 'https://example.com', + }) + ``` + + ```ts-diff#Output + - var logo = "./logo-ab237dfe.svg"; + + var logo = "https://example.com/logo-ab237dfe.svg"; + console.log(logo); + ``` + + {% /codetabs %} + +{% /table %} + +The behavior described in this table can be overridden with [plugins](/docs/bundler/plugins). Refer to the [Bundler > Loaders](/docs/bundler/loaders) page for complete documentation on Bun's built-in loaders. + +## API + +### `entrypoints` + +**Required.** An array of paths corresponding to the entrypoints of our application. One bundle will be generated for each entrypoint. + +### `outdir` + +**Required.** The directory where output files will be written. + +### `target` + +The intended execution environment for the bundle. + +{% codetabs %} + +```ts#JavaScript +Bun.build({ + entrypoints: ['./index.ts'], + outdir: './out', + target: 'browser', // default +}) +``` + +```bash#CLI +$ bunx build --entrypoints ./index.ts --outdir ./out --target browser +``` + +{% /codetabs %} + +Depending on the target, Bun will apply different module resolution rules and optimizations. + +<!-- - Module resolution. For example, when bundling for the browser, Bun will prioritize 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`. --> + +{% 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. This + +--- + +- `node` +- For generating bundles that are intended to be run by Node.js. Prioritizes the `"node"` export condition when resolving imports. In the future, this will automatically polyfill the `Bun` global and other built-in `bun:*` modules, though this is not yet implemented. + +{% /table %} + +<!-- ### `module` + +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#Bun.build +Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + module: "esm", +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --module esm +``` + +{% /codetabs %} --> + +### `bundling` + +Whether to enable bundling. + +{% codetabs %} + +```ts#JavaScript +Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + bundling: true, // default +}) +``` + +```bash#CLI +# bundling is enabled by default +$ bun build ./index.tsx --outdir ./out +``` + +{% /codetabs %} + +Set to `false` to disable bundling. Instead, files will be transpiled and individually written to `outdir`. + +{% codetabs %} + +```ts#JavaScript +Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + bundling: false, +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --no-bundling +``` + +{% /codetabs %} + +### `splitting` + +Whether to enable code splitting. + +{% codetabs %} + +```ts#JavaScript +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 %} + +```ts#JavaScript +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 %} + +```ts#JavaScript +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. + +### `manifest` + +Whether to return a build manifest in the result of `Bun.build`. + +```ts +const result = await Bun.build({ + entrypoints: ["./index.tsx"], + outdir: "./out", + manifest: true, // default is true +}); + +console.log(result.manifest); +``` + +{% details summary="Manifest structure" %} + +The manifest has the following form: + +```ts +export type BuildManifest = { + inputs: { + [path: string]: { + output: { + path: string; + }; + imports: { + path: string; + kind: ImportKind; + external?: boolean; + }[]; + }; + }; + outputs: { + [path: string]: { + type: "chunk" | "entry-point" | "asset"; + inputs: { path: string }[]; + imports: { + path: string; + kind: ImportKind; + external?: boolean; + asset?: boolean; + }[]; + exports: string[]; + }; + }; +}; + +export type ImportKind = + | "entry-point" + | "import-statement" + | "require-call" + | "dynamic-import" + | "require-resolve" + | "import-rule" + | "url-token"; +``` + +{% /details %} + +### `sourcemap` + +Specifies the type of sourcemap to generate. + +{% codetabs %} + +```ts#JavaScript +Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + sourcemap: "inline", // default "none" +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --sourcemap=inline +``` + +{% /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 inside a `//# sourceMappingURL= ` comment. + +--- + +- `"external"` +- A separate `*.js.map` file is created alongside each `*.js` bundle. + +{% /table %} + +### `minify` + +Whether to enable minification. Default `false`. To enable minification: + +{% codetabs %} + +```ts#JavaScript +Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + minify: true, // default false +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --minify +``` + +{% /codetabs %} + +This will enable all minification options. To granularly enable certain minifications: + +{% codetabs %} + +```ts#JavaScript +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 %} + +<!-- ### `treeshaking` + +boolean; --> + +### `external` + +A list of import paths to consider _external_. Defaults to `[]`. + +{% codetabs %} + +```ts#JavaScript +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 %} + +```ts#JavaScript +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)); +``` + +### `naming` + +Customizes the generated file names. Defaults to `./[dir]/[name].[ext]`. + +{% codetabs %} + +```ts#JavaScript +Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + naming: "[dir]/[name].[ext]", // default +}) +``` + +```bash#CLI +n/a +``` + +{% /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 of these 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, e.g. `index` +- `[ext]` - The extension of the generated bundle, e.g. `js` +- `[hash]` - A hash of the bundle contents, e.g. `a1b2c3d4` +- `[dir]` - The relative path from the build [`root`](#root) to the parent directory of the file, e.g. `nested` + +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 %} + +```ts#JavaScript +Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + naming: '[dir]/[name]-[hash].[ext]', +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --naming [name]-[hash].[ext] +``` + +{% /codetabs %} + +This build would result in the following file structure: + +```txt +. +├── index.tsx +└── out + └── 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 %} + +```ts#JavaScript +Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + naming: { + entrypoint: '[dir]/[name]-[hash].[ext]', + chunk: '[dir]/[name]-[hash].[ext]', + asset: '[dir]/[name]-[hash].[ext]', + }, +}) +``` + +```bash#CLI +n/a +``` + +{% /codetabs %} + +### `root` + +The root directory of the project. + +{% codetabs %} + +```ts#JavaScript +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 %} + +```ts#JavaScript +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 %} + +```ts#JavaScript +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 +``` + +### `origin` + +Used to generate absolute asset URLs. + +{% codetabs %} + +```ts#JavaScript +Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + origin: 'https://cdn.example.com', // default is undefined +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --origin https://cdn.example.com +``` + +{% /codetabs %} + +When the bundler encounters an unknown file type, it defaults to using the `"file"` loader. This converts the import path to an absolute URL that can be referenced in the file. This is useful for referencing images, fonts, and other static assets. + +```tsx#Input +import logo from "./images/logo.svg"; + +export function Logo(){ + return <img src={logo} /> +} +``` + +In the absence of a plugin that overrides `*.svg` loading, the `logo` import will be converted to a relative path: + +```ts +var logo = "./logo.svg"; + +console.log(logo); +``` + +This is fine for local development, but in production, we may want these imports to correspond to absolute URLs. To do this, we can specify the `origin` option: + +{% codetabs %} + +```ts#JavaScript +Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + origin: 'https://cdn.mydomain.com', +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --origin https://cdn.mydomain.com +``` + +{% /codetabs %} + +With `origin` set to this value, the generated bundle will now be something like this: + +```ts-diff +- var logo = "./logo.svg"; ++ var logo = "https://cdn.mydomain.com/logo.svg"; + +console.log(logo); +``` + +## Reference + +```ts +Bun.build({ + entrypoints: string[]; // list of file path + outdir: string; // output directory + target?: "browser" | "bun" | "node"; // default: "browser" + bundling?: boolean, // default: false, transform instead of bundling + splitting?: boolean, // default true, enable code splitting + plugins?: BunPlugin[]; + manifest?: boolean; // whether to return manifest + external?: Array<string | RegExp>; + naming?: string | { + entrypoint?: string; + chunk?: string; + asset?: string; + }, // default './[dir]/[name].[ext]' + root?: string; // project root + origin?: string; // e.g. http://mydomain.com + minify?: boolean | { + identifiers?: boolean; + whitespace?: boolean; + syntax?: boolean; + }; +}); +``` + +<!-- +module?: "esm"; // later: "cjs", "iife" +loader?: { [k in string]: Loader }; +sourcemap?: "none" | "inline" | "external"; // default: "none" +treeshaking?: boolean; +--> diff --git a/docs/cli/bunx.md b/docs/cli/bunx.md index fe7bd80a2..5a70c1e70 100644 --- a/docs/cli/bunx.md +++ b/docs/cli/bunx.md @@ -56,16 +56,13 @@ By default, Bun respects shebangs. If an executable is marked with `#!/usr/bin/e $ bunx --bun my-cli ``` -{% callout %} -**Note** — The `--bun` flag must occur _before_ the executable name. Flags that appear _after_ the name are passed through to the executable. +The `--bun` flag must occur _before_ the executable name. Flags that appear _after_ the name are passed through to the executable. ```bash $ bunx --bun my-cli # good $ bunx my-cli --bun # bad ``` -{% /callout %} - <!-- ## Environment variables Bun automatically loads environment variables from `.env` files before running a file, script, or executable. The following files are checked, in order: diff --git a/docs/nav.ts b/docs/nav.ts index b3034a914..6bbbde529 100644 --- a/docs/nav.ts +++ b/docs/nav.ts @@ -102,7 +102,6 @@ export default { page("runtime/web-apis", "Web APIs", { description: `Bun implements an array of Web-standard APIs like fetch, URL, and WebSocket.`, }), - page("runtime/nodejs-apis", "Node.js compatibility", { description: `Bun aims for full Node.js compatibility. This page tracks the current compatibility status.`, }), @@ -125,10 +124,6 @@ export default { page("runtime/configuration", "Configuration", { description: `Bun's runtime is configurable with environment variables and the bunfig.toml config file.`, }), - page("runtime/plugins", "Plugins", { - description: `Implement custom loaders and module resolution logic with Bun's plugin system.`, - }), - page("runtime/framework", "Framework API", { disabled: true, description: @@ -158,6 +153,20 @@ export default { description: "Use `bun pm` to introspect your global module cache or project dependency tree.", }), + divider("Bundler"), + page("cli/build", "`Bun.build`", { + description: "Bundle code for comsumption in the browser with Bun's native bundler.", + }), + // page("bundler/intro", "How bundlers work", { + // description: "A visual introduction to bundling", + // }), + page("bundler/loaders", "Loaders", { + description: "Bun's built-in loaders for the bundler and runtime", + }), + page("bundler/plugins", "Plugins", { + description: `Implement custom loaders and module resolution logic with Bun's plugin system.`, + }), + divider("Test runner"), page("cli/test", "`bun test`", { description: "Bun's test runner uses Jest-compatible syntax but runs 100x faster.", |