进阶 unplugin.unjs.io 2026-10-08 10:00:35 · 6 阅读

第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

评论 (0)