第5章 unplugin-macros 插件使用指南:支持 Vite、Rollup、Webpack 等构建工具
unplugin-macros
宏(Macros)是一种在打包时运行 JavaScript 函数的机制。这些函数或变量返回的值会被直接内联到你的代码包中。
安装
# npm
npm i -D unplugin-macros
# jsr
npx jsr add -D @unplugin/macros
Vite
// vite.config.ts
import Macros from 'unplugin-macros/vite'
export default defineConfig({
plugins: [Macros()],
})
Rollup
// rollup.config.js
import Macros from 'unplugin-macros/rollup'
export default {
plugins: [Macros()],
}
esbuild(要求 esbuild >= 0.15)
// esbuild.config.js
import { build } from 'esbuild'
build({
plugins: [require('unplugin-macros/esbuild')()],
})
Webpack
// webpack.config.js
module.exports = {
/* ... */
plugins: [require('unplugin-macros/webpack')()],
}
用法
// main.js
import { buildTime, getRandom } from './macros.js' with { type: 'macro' }
getRandom() // 构建时会被替换为一个随机数
buildTime // 构建时会被替换为时间戳
// macros.js
export function getRandom() {
return Math.random()
}
export const buildTime = Date.now()
宏的指定符由运行器(runner)解析。默认运行器遵循 Node.js 的 ESM 解析规则,因此相对路径必须包含文件扩展名,例如 './macros.js',而非 './macros'。
函数参数
可以将函数值作为参数传递给宏。函数必须是被隔离的(不能引用外部标识符):
// main.js
import { transform } from './macros.js' with { type: 'macro' }
transform(() => 42)
transform(async () => {
const os = await import('node:os')
return os.endianness()
})
更多内容请参阅 Bun Macros。
MacroContext
每个宏调用都会接收一个 MacroContext 作为 this。其中最常用的字段如下:
| 字段 | 说明 |
|---|---|
| id | 正在被转换文件的绝对路径。 |
| source | 文件的完整源代码。 |
| ast.call | 本次宏调用的 CallExpression AST 节点(await / tagged template 会被解包)。 |
| ast.program | 整个文件的 Program AST。 |
| emitFile | 输出额外的打包资源。 |
| unpluginContext | 底层 unplugin 构建上下文,实验性功能,可能会变更。 |
ast.call 包含调用处的源码偏移量(start, end),这足以构建感知调用位置的宏,而无需支付运行时堆栈遍历的成本:
// macros.ts
import path from 'node:path'
import type { MacroContext } from 'unplugin-macros'
export function $callsite(this: MacroContext): string {
const before = this.source.slice(0, this.ast.call.start)
const line = before.split('\n').length
const column = this.ast.call.start - (before.lastIndexOf('\n') + 1)
return `${path.basename(this.id)}:${line}:${column}`
}
// main.ts
import { $callsite } from './macros.ts' with { type: 'macro' }
console.log($callsite()) // → 'main.ts:3:12'
TypeScript
TypeScript 5.3 及以上版本支持 Import Attributes 语法。
ESLint
ESLint v9.14.0 支持 Import Attributes 语法。
运行器(Runners)
运行器负责解析和执行宏模块。内置了两个运行器,它们解析宏指定符的方式相同——遵循 Node.js 的 ESM 解析规则,因此相对路径必须包含文件扩展名('./macros.ts',而非 './macros')。在开发模式下,编辑宏模块——或它导入的任何内容——都会使其失效。
nativeRunner(默认)
在 Node.js 原生 ESM 加载器上运行宏。不会进行预先打包或转译,这使得它成为成本最低的选项,且无需额外依赖。TypeScript 依赖 Node 的原生类型剥离功能,因此:不支持不可擦除的语法(enum, namespace, 参数属性);无法加载 node_modules 中作为 .ts 发布的宏模块;宏模块内不支持别名、tsconfig paths、JSX 或非 JS 导入。
import { nativeRunner } from 'unplugin-macros'
import Macros from 'unplugin-macros/vite'
Macros({ runner: nativeRunner() })
unrunRunner
在执行之前,使用 unrun (Rolldown) 对每个宏模块进行打包,因此 Rolldown 理解的一切都能正常工作:包括 enum 和其他不可擦除的 TypeScript 语法、JSX、宏模块内无扩展名的导入、tsconfig paths,以及通过 inputOptions 配置的别名或插件。需要安装 unrun,它是一个可选的 peer dependency。
import { unrunRunner } from 'unplugin-macros'
import Macros from 'unplugin-macros/vite'
Macros({
runner: unrunRunner({
inputOptions: { resolve: { alias: { '~': './src' } } },
}),
})
自定义运行器
任何符合 MacroRunner 接口的对象都可以作为运行器——这是针对 jiti 或 tsx 等加载器的逃生舱:
import path from 'node:path'
import { createJiti } from 'jiti'
import Macros from 'unplugin-macros/vite'
const jiti = createJiti(import.meta.url)
Macros({
runner: {
resolve: (source, importer) =>
jiti.esmResolve(source, { parentURL: path.dirname(importer) }),
import: (resolved) => jiti.import(resolved),
},
})
init(仅在真正找到宏时懒加载调用一次)、invalidate 和 close 是可选的。
选项
请参阅文档。
致谢
感谢 Bun Macros。
赞助商
许可证
MIT License © 2023-PRESENT Kevin Deng