random.ts
cli.tsx
Macros are marked with import attribute syntax, a Stage 3 TC39 proposal for attaching additional metadata to import
statements.
bun build. Bun prints the bundled file to stdout.
terminal
random function occurs nowhere in the bundle. Instead, the function runs during bundling and Bun replaces the call (random()) with its result. Since the source code is never included in the bundle, macros can safely perform privileged operations like reading from a database.
When to use macros
For small things you would otherwise write a one-off build script for, bundle-time code execution can be easier to maintain. It lives with the rest of your code and runs with the rest of the build. Bun parallelizes it automatically, and if it fails, the build fails too. If you find yourself running a lot of code at bundle-time though, consider running a server instead.Import attributes
Macros are import statements annotated with either:with { type: 'macro' }— an import attribute, a Stage 3 ECMAScript proposalassert { type: 'macro' }— an import assertion, an earlier incarnation of import attributes that has now been abandoned (but several browsers and runtimes already support it)
Security considerations
You must explicitly import a macro with{ type: "macro" } for it to run at bundle-time. These imports have no effect unless you call them, unlike regular JavaScript imports which may have side effects.
You can disable macros entirely with the --no-macros flag. It produces a build error like this:
node_modules/**/* invoke macros. If a package attempts to invoke a macro, you’ll see an error like this:
node_modules and invoke them.
cli.tsx
Export condition “macro”
When shipping a library containing a macro to npm or another package registry, use the"macro" export condition to provide a version of your package exclusively for the macro environment.
package.json
index.ts
./node_modules/my-package/index.js; Bun’s bundler resolves the second to ./node_modules/my-package/index.macro.js.
Execution
When Bun’s transpiler sees a macro import, it calls the function using Bun’s JavaScript runtime and converts the return value into an AST node. Macros run synchronously in the transpiler during the visiting phase, before plugins and before the transpiler generates the AST. They run in the order they are imported. The transpiler waits for each macro to finish before continuing, and awaits any Promise a macro returns. Bun’s bundler is multi-threaded, so macros execute in parallel in multiple spawned JavaScript “workers”.Dead code elimination
The bundler performs dead code elimination after running and inlining macros. Given the following macro:returnFalse.ts
index.ts
Serializability
Bun’s transpiler must be able to serialize the result of the macro to inline it into the AST. All JSON-compatible data structures are supported:macro.ts
macro.ts
Response and Blob.
- Response: Bun reads the
Content-Typeand serializes accordingly. For example, it parses a Response with typeapplication/jsoninto an object and inlinestext/plainas a string. Bun base64-encodes Responses with an unrecognized or undefined type. - Blob: As with Response, the serialization depends on the
typeproperty.
fetch is Promise<Response>, so a macro can return it directly.
macro.ts
macro.ts
Arguments
Macros can accept inputs, but only in limited cases. The value must be statically known. For example, the following is not allowed:index.ts
foo is known at bundle-time (say, if it’s a constant or the result of another macro), then the call is allowed:
index.ts
Examples
Embed latest git commit hash
getGitCommitHash.ts
getGitCommitHash call with the result of calling the function:
You’re probably thinking “Why not use
process.env.GIT_COMMIT_HASH?” Well, you can do that too. But can you do this
with an environment variable?Make fetch() requests at bundle-time
This example makes an outgoing HTTP request withfetch(), parses the HTML response with HTMLRewriter, and returns an object containing the title and meta tags, all at bundle-time.
meta.ts
extractMetaTags function at bundle-time and replaces it with the result of the function call. The fetch request happens at bundle-time, and Bun embeds the result in the bundle. Bun also eliminates the branch throwing the error since it’s unreachable.