mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 03:48:56 +00:00
Improve plugin docs
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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.`,
|
||||
|
||||
276
docs/runtime/plugins.md
Normal file
276
docs/runtime/plugins.md
Normal file
@@ -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`.
|
||||
Reference in New Issue
Block a user