Bun 提供了一个通用的插件 API,可用于扩展运行时和bundler。
插件拦截导入并执行自定义加载逻辑:读取文件、转换代码等。它们可用于添加对其他文件类型(如 .scss
或 .yaml
)的支持。在 Bun 的 bundler 的上下文中,插件可用于实现框架级功能,如 CSS 提取、宏和客户端-服务器代码并置。
用法
插件被定义为包含 name
属性和 setup
函数的简单 JavaScript 对象。使用 plugin
函数向 Bun 注册插件。
import { plugin, type BunPlugin } from "bun";
const myPlugin: BunPlugin = {
name: "Custom loader",
setup(build) {
// implementation
},
};
plugin(myPlugin);
插件必须在任何其他代码运行之前加载!要实现此目的,请在 bunfig.toml
中使用 preload
选项。Bun 会在运行文件之前自动加载 preload
中指定的文件/模块。
preload = ["./myPlugin.ts"]
要在 bun test
之前预加载文件
[test]
preload = ["./myPlugin.ts"]
第三方插件
根据惯例,供消费的第三方插件应导出一个工厂函数,该函数接受一些配置并返回一个插件对象。
import { plugin } from "bun";
import fooPlugin from "bun-plugin-foo";
plugin(
fooPlugin({
// configuration
}),
);
Bun 的插件 API 松散基于 esbuild。仅实现了 esbuild API 的 子集,但一些 esbuild 插件在 Bun 中“直接可用”,例如官方 MDX 加载器
import { plugin } from "bun";
import mdx from "@mdx-js/esbuild";
plugin(mdx());
加载器
插件主要用于使用其他文件类型的加载器扩展 Bun。我们来看一个简单的插件,它为 .yaml
文件实现了加载器。
import { plugin } from "bun";
await plugin({
name: "YAML",
async setup(build) {
const { load } = await import("js-yaml");
// when a .yaml file is imported...
build.onLoad({ filter: /\.(yaml|yml)$/ }, async (args) => {
// read and parse the file
const text = await Bun.file(args.path).text();
const exports = load(text) as Record<string, any>;
// and returns it as a module
return {
exports,
loader: "object", // special loader for JS objects
};
});
},
});
在 preload
中注册此文件
preload = ["./yamlPlugin.ts"]
注册插件后,可以直接导入 .yaml
和 .yml
文件。
import data from "./data.yml"
console.log(data);
name: Fast X
releaseYear: 2023
请注意,返回的对象具有 loader
属性。这会告诉 Bun 应使用其哪个内部加载器来处理结果。即使我们正在为 .yaml
实现加载器,结果仍必须是 Bun 的内置加载器之一可以理解的。它一直都是加载器。
在这种情况下,我们使用 "object"
——一个内置加载器(供插件使用),它将纯 JavaScript 对象转换为等效的 ES 模块。支持 Bun 的任何内置加载器;Bun 在内部使用这些相同的加载器来处理各种文件。下表是一个快速参考;有关完整文档,请参阅 Bundler > 加载器。
加载器 | 扩展名 | 输出 |
---|---|---|
js | .mjs .cjs | 编译为 JavaScript 文件 |
jsx | .js .jsx | 转换 JSX,然后编译 |
ts | .ts .mts .cts | 转换 TypeScript,然后编译 |
tsx | .tsx | 转换 TypeScript、JSX,然后编译 |
toml | .toml | 使用 Bun 的内置 TOML 解析器进行解析 |
json | .json | 使用 Bun 的内置 JSON 解析器进行解析 |
napi | .node | 导入一个原生 Node.js 插件 |
wasm | .wasm | 导入一个原生 Node.js 插件 |
object | none | 一个专门用于插件的加载器,它将一个普通的 JavaScript 对象转换为一个等效的 ES 模块。对象中的每个键对应一个具名导出。 |
加载一个 YAML 文件很有用,但插件支持的不仅仅是数据加载。我们来看一个允许 Bun 导入 *.svelte
文件的插件。
import { plugin } from "bun";
await plugin({
name: "svelte loader",
async setup(build) {
const { compile } = await import("svelte/compiler");
// when a .svelte file is imported...
build.onLoad({ filter: /\.svelte$/ }, async ({ path }) => {
// read and compile it with the Svelte compiler
const file = await Bun.file(path).text();
const contents = compile(file, {
filename: path,
generate: "ssr",
}).js.code;
// and return the compiled source code as "js"
return {
contents,
loader: "js",
};
});
},
});
注意:在生产环境中,你可能希望缓存编译后的输出并包含额外的错误处理。
从 build.onLoad
返回的对象包含 contents
中的编译后的源代码,并指定 "js"
作为其加载器。这告诉 Bun 将返回的 contents
视为一个 JavaScript 模块,并使用 Bun 的内置 js
加载器对其进行转换。
有了这个插件,Svelte 组件现在可以直接导入和使用。
import "./sveltePlugin.ts";
import MySvelteComponent from "./component.svelte";
console.log(MySvelteComponent.render());
虚拟模块
此功能目前仅在运行时使用 Bun.plugin
,并且还不受 bundler 支持,但你可以使用 onResolve
和 onLoad
来模拟此行为。
要在运行时创建虚拟模块,请在 Bun.plugin
的 setup
函数中使用 builder.module(specifier, callback)
。
例如
import { plugin } from "bun";
plugin({
name: "my-virtual-module",
setup(build) {
build.module(
// The specifier, which can be any string - except a built-in, such as "buffer"
"my-transpiled-virtual-module",
// The callback to run when the module is imported or required for the first time
() => {
return {
contents: "console.log('hello world!')",
loader: "js",
};
},
);
build.module("my-object-virtual-module", () => {
return {
exports: {
foo: "bar",
},
loader: "object",
};
});
},
});
// Sometime later
// All of these work
import "my-transpiled-virtual-module";
require("my-transpiled-virtual-module");
await import("my-transpiled-virtual-module");
require.resolve("my-transpiled-virtual-module");
import { foo } from "my-object-virtual-module";
const object = require("my-object-virtual-module");
await import("my-object-virtual-module");
require.resolve("my-object-virtual-module");
覆盖现有模块
你还可以使用 build.module
覆盖现有模块。
import { plugin } from "bun";
build.module("my-object-virtual-module", () => {
return {
exports: {
foo: "bar",
},
loader: "object",
};
});
require("my-object-virtual-module"); // { foo: "bar" }
await import("my-object-virtual-module"); // { foo: "bar" }
build.module("my-object-virtual-module", () => {
return {
exports: {
baz: "quix",
},
loader: "object",
};
});
require("my-object-virtual-module"); // { baz: "quix" }
await import("my-object-virtual-module"); // { baz: "quix" }
读取配置
插件可以使用 build.config
读取和写入 构建配置。
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}`);
},
},
],
});
参考
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";
除了 filter
正则表达式外,onLoad
方法还可以选择接受一个 namespace
。此命名空间将用于在转换后的代码中为导入添加前缀;例如,一个 filter: /\.yaml$/
和 namespace: "yaml:"
的加载器将把来自 ./myfile.yaml
的导入转换为 yaml:./myfile.yaml
。