diff options
author | 2023-08-07 14:02:43 -0700 | |
---|---|---|
committer | 2023-08-07 14:02:43 -0700 | |
commit | 0b183beb51367004795d8a431eb06bb2fa4f8250 (patch) | |
tree | 86440cc6a1d809c3f383c07173afda1ffcf1dc5f /docs | |
parent | 5ce393aab815f38ce9594d8a7d481a608ee8524c (diff) | |
download | bun-0b183beb51367004795d8a431eb06bb2fa4f8250.tar.gz bun-0b183beb51367004795d8a431eb06bb2fa4f8250.tar.zst bun-0b183beb51367004795d8a431eb06bb2fa4f8250.zip |
Improve plugin docs
Diffstat (limited to 'docs')
-rw-r--r-- | docs/bundler/plugins.md | 310 | ||||
-rw-r--r-- | docs/nav.ts | 3 | ||||
-rw-r--r-- | docs/runtime/plugins.md | 276 |
3 files changed, 283 insertions, 306 deletions
diff --git a/docs/bundler/plugins.md b/docs/bundler/plugins.md index 1cb22432b..2b52ae7da 100644 --- a/docs/bundler/plugins.md +++ b/docs/bundler/plugins.md @@ -6,15 +6,17 @@ Bun provides a universal plugin API that can be used to extend both the _runtime 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. +For more complete documentation of the Plugin API, see [Runtime > Plugins](/docs/runtime/plugins). + ## 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 +```tsx#myPlugin.ts import type { BunPlugin } from "bun"; const myPlugin: BunPlugin = { - name: "YAML loader", + name: "Custom loader", setup(build) { // implementation }, @@ -30,307 +32,3 @@ Bun.build({ 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 -import {plugin} from "bun"; - -plugin(myPlugin); -``` --> - -## `--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 -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. - -```ts#app.ts -import "./yamlPlugin.ts"; -import { config } from "./config.yml"; - -console.log(config); -``` - -{% /details %} - -## Third-party plugins - -By convention, third-party plugins intended for consumption should export a factory function that accepts some configuration and returns a plugin object. - -```ts -import { plugin } from "bun"; -import fooPlugin from "bun-plugin-foo"; - -plugin( - fooPlugin({ - // configuration - }), -); - -// application code -``` - -Bun's plugin API is based on [esbuild](https://esbuild.github.io/plugins). Only [a subset](/docs/bundler/vs-esbuild#plugin-api) of the esbuild API is implemented, but some esbuild plugins "just work" in Bun, like the official [MDX loader](https://mdxjs.com/packages/esbuild/): - -```jsx -import { plugin } from "bun"; -import mdx from "@mdx-js/esbuild"; - -plugin(mdx()); - -import { renderToStaticMarkup } from "react-dom/server"; -import Foo from "./bar.mdx"; -console.log(renderToStaticMarkup(<Foo />)); -``` - -## Loaders - -<!-- The plugin logic is implemented in the `setup` function using the builder provided as the first argument (`build` in the example above). The `build` variable provides two methods: `onResolve` and `onLoad`. --> - -<!-- ## `onResolve` --> - -<!-- The `onResolve` method lets you intercept imports that match a particular regex and modify the resolution behavior, such as re-mapping the import to another file. In the simplest case, you can simply remap the matched import to a new path. - -```ts -import { plugin } from "bun"; - -plugin({ - name: "YAML loader", - setup(build) { - build.onResolve(); - // implementation - }, -}); -``` --> - -<!-- -Internally, Bun's transpiler automatically turns `plugin()` calls into separate files (at most 1 per file). This lets loaders activate before the rest of your application runs with zero configuration. --> - -Plugins are primarily used to extend Bun with loaders for additional file types. Let's look at a simple plugin that implements a loader for `.yaml` files. - -```ts#yamlPlugin.ts -import { plugin } from "bun"; - -plugin({ - name: "YAML", - async setup(build) { - const { load } = await import("js-yaml"); - const { readFileSync } = await import("fs"); - - // when a .yaml file is imported... - build.onLoad({ filter: /\.(yaml|yml)$/ }, (args) => { - - // read and parse the file - const text = readFileSync(args.path, "utf8"); - const exports = load(text) as Record<string, any>; - - // and returns it as a module - return { - exports, - loader: "object", // special loader for JS objects - }; - }); - }, -}); -``` - -With this plugin, data can be directly imported from `.yaml` files. - -{% codetabs %} - -```ts#index.ts -import "./yamlPlugin.ts" -import {name, releaseYear} from "./data.yml" - -console.log(name, releaseYear); -``` - -```yaml#data.yml -name: Fast X -releaseYear: 2023 -``` - -{% /codetabs %} - -Note that the returned object has a `loader` property. This tells Bun which of its internal loaders should be used to handle the result. Even though we're implementing a loader for `.yaml`, the result must still be understandable by one of Bun's built-in loaders. It's loaders all the way down. - -In this case we're using `"object"`—a built-in loader (intended for use by plugins) that converts a plain JavaScript object to an equivalent ES module. Any of Bun's built-in loaders are supported; these same loaders are used by Bun internally for handling files of various kinds. The table below is a quick reference; refer to [Bundler > Loaders](/docs/bundler/loaders) for complete documentation. - -{% table %} - -- Loader -- Extensions -- Output - ---- - -- `js` -- `.mjs` `.cjs` -- Transpile to JavaScript files - ---- - -- `jsx` -- `.js` `.jsx` -- Transform JSX then transpile - ---- - -- `ts` -- `.ts` `.mts` `cts` -- Transform TypeScript then transpile - ---- - -- `tsx` -- `.tsx` -- Transform TypeScript, JSX, then transpile - ---- - -- `toml` -- `.toml` -- Parse using Bun's built-in TOML parser - ---- - -- `json` -- `.json` -- Parse using Bun's built-in JSON parser - ---- - -- `napi` -- `.node` -- Import a native Node.js addon - ---- - -- `wasm` -- `.wasm` -- Import a native Node.js addon - ---- - -- `object` -- _none_ -- A special loader intended for plugins that converts a plain JavaScript object to an equivalent ES module. Each key in the object corresponds to a named export. - -{% /callout %} - -Loading a YAML file is useful, but plugins support more than just data loading. Let's look at a plugin that lets Bun import `*.svelte` files. - -```ts#sveltePlugin.ts -import { plugin } from "bun"; - -await plugin({ - name: "svelte loader", - async setup(build) { - const { compile } = await import("svelte/compiler"); - const { readFileSync } = await import("fs"); - - // when a .svelte file is imported... - build.onLoad({ filter: /\.svelte$/ }, ({ path }) => { - - // read and compile it with the Svelte compiler - const file = readFileSync(path, "utf8"); - const contents = compile(file, { - filename: path, - generate: "ssr", - }).js.code; - - // and return the compiled source code as "js" - return { - contents, - loader: "js", - }; - }); - }, -}); -``` - -> Note: in a production implementation, you'd want to cache the compiled output and include additional error handling. - -The object returned from `build.onLoad` contains the compiled source code in `contents` and specifies `"js"` as its loader. That tells Bun to consider the returned `contents` to be a JavaScript module and transpile it using Bun's built-in `js` loader. - -With this plugin, Svelte components can now be directly imported and consumed. - -```js -import "./sveltePlugin.ts"; -import MySvelteComponent from "./component.svelte"; - -console.log(mySvelteComponent.render()); -``` - -## Reading the config - -Plugins can read and write to the [build config](/docs/bundler#api) with `build.config`. - -```ts -Bun.build({ - entrypoints: ["./app.ts"], - outdir: "./dist", - sourcemap: "external", - plugins: [ - { - name: "demo", - setup(build) { - console.log(build.config.sourcemap); // "external" - - build.config.minify = true; // enable minification - - // `plugins` is readonly - console.log(`Number of plugins: ${build.config.plugins.length}`); - }, - }, - ], -}); -``` - -## Reference - -```ts -namespace Bun { - function plugin(plugin: { - name: string; - setup: (build: PluginBuilder) => void; - }): void; -} - -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?: Loader; - contents?: string; - exports?: Record<string, any>; - }, - ) => void; - config: BuildConfig; -}; - -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/nav.ts b/docs/nav.ts index ff74e16c3..4b3a67ec0 100644 --- a/docs/nav.ts +++ b/docs/nav.ts @@ -110,6 +110,9 @@ export default { page("runtime/nodejs-apis", "Node.js compatibility", { description: `Bun aims for full Node.js compatibility. This page tracks the current compatibility status.`, }), + page("runtime/plugins", "Plugins", { + description: `Implement custom loaders and module resolution logic with Bun's plugin system.`, + }), // page("runtime/nodejs", "Node.js compatibility", { // description: `Track the status of Bun's API compatibility with Node.js.`, diff --git a/docs/runtime/plugins.md b/docs/runtime/plugins.md new file mode 100644 index 000000000..39eea3278 --- /dev/null +++ b/docs/runtime/plugins.md @@ -0,0 +1,276 @@ +{% callout %} +**Note** — Introduced in Bun v0.1.11. +{% /callout %} + +Bun provides a universal plugin API that can be used to extend both the _runtime_ and [_bundler_](/docs/bundler). + +Plugins intercept imports and perform custom loading logic: reading files, transpiling code, etc. They can be used to add support for additional file types, like `.scss` or `.yaml`. In the context of Bun's bundler, plugins can be used to implement framework-level features like CSS extraction, macros, and client-server code co-location. + +## Usage + +A plugin is defined as simple JavaScript object containing a `name` property and a `setup` function. Register a plugin with Bun using the `plugin` function. + +```tsx#myPlugin.ts +import { plugin, type BunPlugin } from "bun"; + +const myPlugin: BunPlugin = { + name: "Custom loader", + setup(build) { + // implementation + }, +}; +``` + +Plugins have to be registered before any other code runs! To achieve this, use 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 = ["./myPlugin.ts"] +``` + +To preload files before `bun test`: + +```toml +[test] +preload = ["./myPlugin.ts"] +``` + +## Third-party plugins + +By convention, third-party plugins intended for consumption should export a factory function that accepts some configuration and returns a plugin object. + +```ts +import { plugin } from "bun"; +import fooPlugin from "bun-plugin-foo"; + +plugin( + fooPlugin({ + // configuration + }), +); +``` + +Bun's plugin API is 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"; +import mdx from "@mdx-js/esbuild"; + +plugin(mdx()); +``` + +## Loaders + +Plugins are primarily used to extend Bun with loaders for additional file types. Let's look at a simple plugin that implements a loader for `.yaml` files. + +```ts#yamlPlugin.ts +import { plugin } from "bun"; + +plugin({ + name: "YAML", + async setup(build) { + const { load } = await import("js-yaml"); + const { readFileSync } = await import("fs"); + + // when a .yaml file is imported... + build.onLoad({ filter: /\.(yaml|yml)$/ }, (args) => { + + // read and parse the file + const text = readFileSync(args.path, "utf8"); + const exports = load(text) as Record<string, any>; + + // and returns it as a module + return { + exports, + loader: "object", // special loader for JS objects + }; + }); + }, +}); +``` + +With this plugin, data can be directly imported from `.yaml` files. + +{% codetabs %} + +```ts#index.ts +import "./yamlPlugin.ts" +import {name, releaseYear} from "./data.yml" + +console.log(name, releaseYear); +``` + +```yaml#data.yml +name: Fast X +releaseYear: 2023 +``` + +{% /codetabs %} + +Note that the returned object has a `loader` property. This tells Bun which of its internal loaders should be used to handle the result. Even though we're implementing a loader for `.yaml`, the result must still be understandable by one of Bun's built-in loaders. It's loaders all the way down. + +In this case we're using `"object"`—a built-in loader (intended for use by plugins) that converts a plain JavaScript object to an equivalent ES module. Any of Bun's built-in loaders are supported; these same loaders are used by Bun internally for handling files of various kinds. The table below is a quick reference; refer to [Bundler > Loaders](/docs/bundler/loaders) for complete documentation. + +{% table %} + +- Loader +- Extensions +- Output + +--- + +- `js` +- `.mjs` `.cjs` +- Transpile to JavaScript files + +--- + +- `jsx` +- `.js` `.jsx` +- Transform JSX then transpile + +--- + +- `ts` +- `.ts` `.mts` `cts` +- Transform TypeScript then transpile + +--- + +- `tsx` +- `.tsx` +- Transform TypeScript, JSX, then transpile + +--- + +- `toml` +- `.toml` +- Parse using Bun's built-in TOML parser + +--- + +- `json` +- `.json` +- Parse using Bun's built-in JSON parser + +--- + +- `napi` +- `.node` +- Import a native Node.js addon + +--- + +- `wasm` +- `.wasm` +- Import a native Node.js addon + +--- + +- `object` +- _none_ +- A special loader intended for plugins that converts a plain JavaScript object to an equivalent ES module. Each key in the object corresponds to a named export. + +{% /callout %} + +Loading a YAML file is useful, but plugins support more than just data loading. Let's look at a plugin that lets Bun import `*.svelte` files. + +```ts#sveltePlugin.ts +import { plugin } from "bun"; + +await plugin({ + name: "svelte loader", + async setup(build) { + const { compile } = await import("svelte/compiler"); + const { readFileSync } = await import("fs"); + + // when a .svelte file is imported... + build.onLoad({ filter: /\.svelte$/ }, ({ path }) => { + + // read and compile it with the Svelte compiler + const file = readFileSync(path, "utf8"); + const contents = compile(file, { + filename: path, + generate: "ssr", + }).js.code; + + // and return the compiled source code as "js" + return { + contents, + loader: "js", + }; + }); + }, +}); +``` + +> Note: in a production implementation, you'd want to cache the compiled output and include additional error handling. + +The object returned from `build.onLoad` contains the compiled source code in `contents` and specifies `"js"` as its loader. That tells Bun to consider the returned `contents` to be a JavaScript module and transpile it using Bun's built-in `js` loader. + +With this plugin, Svelte components can now be directly imported and consumed. + +```js +import "./sveltePlugin.ts"; +import MySvelteComponent from "./component.svelte"; + +console.log(mySvelteComponent.render()); +``` + +## Reading the config + +Plugins can read and write to the [build config](/docs/bundler#api) with `build.config`. + +```ts +Bun.build({ + entrypoints: ["./app.ts"], + outdir: "./dist", + sourcemap: "external", + plugins: [ + { + name: "demo", + setup(build) { + console.log(build.config.sourcemap); // "external" + + build.config.minify = true; // enable minification + + // `plugins` is readonly + console.log(`Number of plugins: ${build.config.plugins.length}`); + }, + }, + ], +}); +``` + +## Reference + +```ts +namespace Bun { + function plugin(plugin: { + name: string; + setup: (build: PluginBuilder) => void; + }): void; +} + +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?: Loader; + contents?: string; + exports?: Record<string, any>; + }, + ) => void; + config: BuildConfig; +}; + +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`. |