Skip to main content
宏是会在打包时运行的 JavaScript 函数。它们的返回值会直接内联到你的打包文件中。 作为一个简单示例,考虑下面这个返回随机数的函数。
random.ts
这是一个普通文件中的普通函数,但你可以将它用作宏:
cli.tsx
宏使用导入属性语法进行标记。这是一项处于第 3 阶段的 TC39 提案,用于向导入语句附加额外的元数据。
使用 bun build 打包该文件。打包后的文件会输出到 stdout。
terminal
random 函数的源代码不会出现在打包文件中的任何位置。相反,它会在打包过程中运行,并且调用(random())会被替换为其结果。由于源代码从未包含在打包文件中,因此宏可以安全地执行读取数据库等需要特权的操作。

何时使用宏

对于那些原本需要编写一次性构建脚本来完成的小任务,在打包时执行代码可能更易于维护。它与其余代码共存,与其余构建过程一同运行,会自动并行执行,而且如果它失败,构建也会失败。 不过,如果你发现自己在打包时运行大量代码,考虑改为运行服务器。

导入属性

宏是带有以下注解之一的导入语句:
  • with { type: 'macro' } — 导入属性,一项处于第 3 阶段的 ECMAScript 提案
  • assert { type: 'macro' } — 导入断言,这是导入属性的早期形式,现已被弃用(但已得到许多浏览器和运行时的支持)

安全注意事项

必须使用 { type: "macro" } 显式导入宏,才能在打包时运行。如果未调用这些宏导入,它们不会产生任何影响;这与可能产生副作用的常规 JavaScript 导入不同。 你可以使用 --no-macros 标志完全禁用宏。它会产生如下构建错误:
为减少恶意包的潜在攻击面,宏不能从 node_modules/**/* 内部调用。如果包试图调用宏,会看到如下错误:
你的应用代码仍然可以从 node_modules 引入宏并调用它们。
cli.tsx

导出条件 “macro”

当将包含宏的库发布到 npm 或其他包注册表时,使用 "macro" 导出条件,为宏环境提供专用的包版本。
package.json
这个配置允许用户使用相同的导入标识符在运行时或打包时消费你的包:
index.ts
第一个导入解析为 ./node_modules/my-package/index.js;Bun 的打包器会将第二个导入解析为 ./node_modules/my-package/index.macro.js

执行

当 Bun 的转译器看到宏导入时,它会使用 Bun 的 JavaScript 运行时调用该函数,并将返回值转换为 AST 节点。 宏会在访问阶段由转译器同步运行,此时插件尚未运行,转译器也尚未生成 AST。宏按照导入顺序运行。转译器会等待每个宏完成后再继续,并等待宏返回的任何 Promise。 Bun 的打包器是多线程的,因此宏会在多个生成的 JavaScript“工作线程”中并行执行。

死代码消除

打包器会在运行并内联宏之后执行死代码消除。给定以下宏:
returnFalse.ts
……在启用 minify 语法选项的情况下,打包以下文件会生成一个空的 bundle。
index.ts

可序列化性

Bun 的转译器必须能够序列化宏的结果,以便将其内联到 AST 中。所有与 JSON 兼容的数据结构都受支持:
macro.ts
宏可以是异步的,也可以返回 Promise 实例。Bun 的转译器会等待 Promise,并将结果内联。
macro.ts
转译器实现了特殊逻辑,用于序列化 ResponseBlob 等常见数据格式。
  • Response:Bun 会读取 Content-Type 并据此进行序列化;例如,类型为 application/json 的 Response 会被解析为对象,而 text/plain 会被内联为字符串。类型无法识别或未定义的 Response 会被编码为 base64。
  • Blob:与 Response 一样,序列化方式取决于 type 属性。
fetch 返回的是 Promise<Response>,可以直接返回。
macro.ts
函数和大多数类的实例(之前列出的实例除外)都无法序列化。
macro.ts

参数

宏可以接受输入,但仅限于有限情况。参数值必须是静态已知的。例如,以下用法不被允许:
index.ts
但是,如果 foo 的值在打包时已知(例如,它是一个常量或另一个宏的结果),那么这种用法就是允许的:
index.ts
其输出为:

示例

嵌入最新 git 提交哈希

getGitCommitHash.ts
构建时,getGitCommitHash 调用会被替换为调用该函数的结果:
你可能在想 “为什么不直接用 process.env.GIT_COMMIT_HASH?”可以用,但你能用环境变量做下面这个吗?

在打包时执行 fetch() 请求

此示例使用 fetch() 发出 HTTP 请求,使用 HTMLRewriter 解析 HTML 响应,并在打包时返回一个包含标题和 meta 标签的对象。
meta.ts
extractMetaTags 函数会在打包时被移除,并替换为函数调用的结果:fetch 请求会在打包时执行,结果会被嵌入到打包产物中。由于抛出错误的分支不可达,因此也会被移除。