Bun 提供一個通用的外掛程式 API,可用於擴充 執行時期 和 打包器。
外掛程式攔截匯入並執行自訂載入邏輯:讀取檔案、編譯程式碼等。它們可用於新增對其他檔案類型的支援,例如 .scss
或 .yaml
。在 Bun 的打包器中,外掛程式可用於實作架構層級功能,例如 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 > Loaders。
載入器 | 副檔名 | 輸出 |
---|---|---|
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
時可用,且尚未在套件組工具中支援,但你可以使用 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
。