Skip to main content
宏是在打包时运行的 JavaScript 函数。它们的返回值会直接内联到你的打包产物中。 作为一个示例,考虑这个返回随机数的函数。
random.ts
这是一个普通文件中的普通函数,但你可以将其用作宏:
cli.tsx
宏使用导入属性(import attribute)语法标记,这是 TC39 Stage 3 提案,用于向 import 语句附加额外元数据。
使用 bun build 打包该文件。打包后的文件会打印到标准输出。
terminal
random 函数的源代码不会出现在打包产物中。相反,它在打包期间运行,调用(random())被替换为其结果。由于源代码永远不会包含在打包产物中,因此宏可以安全地执行特权操作,比如读取数据库。

何时使用宏

对于那些你本来会编写一次性构建脚本的小事情,打包时代码执行更容易维护。它与你其余代码生活在一起,与构建一起运行,自动并行化,并且如果失败了,构建也会失败。 但如果你发现自己在打包时运行了大量代码,不妨考虑运行一个服务器。

导入属性

宏是用以下任一方式注解的 import 语句:
  • with { type: 'macro' }——导入属性,一个 Stage 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 节点。 宏在转译器的访问(visiting)阶段同步运行,在插件之前、在转译器生成 AST 之前。它们按导入的顺序运行。转译器会等待每个宏完成后再继续,并 await 宏返回的任何 Promise。 Bun 的打包器是多线程的,因此宏在多个生成的 JavaScript “worker” 中并行执行。

死代码消除

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

可序列化性

Bun 的转译器必须能够序列化宏的结果,以便将其内联到 AST 中。所有兼容 JSON 的数据结构都支持:
macro.ts
宏可以是异步的,或者返回 Promise 实例。Bun 的转译器会 await 该 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 请求在打包时发生,结果嵌入到打包产物中。抛出错误的分支也会被消除,因为它是不可达的。