Macros are indicated using import attribute syntax. If you haven’t seen this syntax before, it’s a
Stage 3 TC39 proposal that lets you attach additional metadata to import statements.
bun build
. The bundled file will be printed to stdout.
terminal
random
function occurs nowhere in the bundle. Instead, it is executed during bundling and function call (random()
) is replaced with the result of the function. Since the source code will never be included in the bundle, macros can safely perform privileged operations like reading from a database.
When to use macros
If you have several build scripts for small things where you would otherwise have a one-off build script, bundle-time code execution can be easier to maintain. It lives with the rest of your code, it runs with the rest of the build, it is automatically parallelized, 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
Bun Macros are import statements annotated using either:with { type: 'macro' }
— an import attribute, a Stage 3 ECMA Script proposalassert { type: 'macro' }
— an import assertion, an earlier incarnation of import attributes that has now been abandoned (but is already supported by a number of browsers and runtimes)
Security considerations
Macros must explicitly be imported with{ type: "macro" }
in order to be executed at bundle-time. These imports have no effect if they are not called, unlike regular JavaScript imports which may have side effects.
You can disable macros entirely by passing the --no-macros
flag to Bun. It produces a build error like this:
node_modules/**/*
. If a package attempts to invoke a macro, you’ll see an error like this:
node_modules
and invoke them.
Export condition “macro”
When shipping a library containing a macro to npm or another package registry, use the"macro"
export condition to provide a special version of your package exclusively for the macro environment.
package.json
./node_modules/my-package/index.js
, while the second will be resolved by Bun’s bundler to ./node_modules/my-package/index.macro.js
.
Execution
When Bun’s transpiler sees a macro import, it calls the function inside the transpiler using Bun’s JavaScript runtime and converts the return value from JavaScript into an AST node. These JavaScript functions are called at bundle-time, not runtime. Macros are executed synchronously in the transpiler during the visiting phase—before plugins and before the transpiler generates the AST. They are executed in the order they are imported. The transpiler will wait for the macro to finish executing before continuing. The transpiler will also await any Promise returned by a macro. Bun’s bundler is multi-threaded. As such, macros execute in parallel inside of multiple spawned JavaScript “workers”.Dead code elimination
The bundler performs dead code elimination after running and inlining macros. So given the following macro:Serializability
Bun’s transpiler needs to be able to serialize the result of the macro so it can be inlined into the AST. All JSON-compatible data structures are supported:Response
, Blob
, TypedArray
.
- TypedArray: Resolves to a base64-encoded string.
- Response: Bun will read the
Content-Type
and serialize accordingly; for instance, a Response with typeapplication/json
will be automatically parsed into an object andtext/plain
will be inlined as a string. Responses with an unrecognized or undefined type will be base-64 encoded. - Blob: As with Response, the serialization depends on the
type
property.
fetch
is Promise<Response>
, so it can be directly returned.
Arguments
Macros can accept inputs, but only in limited cases. The value must be statically known. For example, the following is not allowed:foo
is known at bundle-time (say, if it’s a constant or the result of another macro) then it’s allowed:
Examples
Embed latest git commit hash
getGitCommitHash
is replaced with the result of calling the function:
You’re probably thinking “Why not just 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
In this example, we make an outgoing HTTP request usingfetch()
, parse the HTML response using HTMLRewriter
, and return an object containing the title and meta tags–all at bundle-time.
extractMetaTags
function is erased at bundle-time and replaced with the result of the function call. This means that the fetch request happens at bundle-time, and the result is embedded in the bundle. Also, the branch throwing the error is eliminated since it’s unreachable.