diff options
Diffstat (limited to '')
-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 |
3 files changed, 249 insertions, 4 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`. |