Skip to content

Macros

bake

Run a function at build time, serialize its result and inline it as a constant, even in client code. The imports the function used are dropped, so the computation's server-only dependencies never stay behind.

On this page

import.meta.og.bake(fn) runs fn at build time, serializes its result, and rewrites the call to that literal value. At runtime there is no function and no work left, only the answer.

import { buildNav } from './nav';

const nav = import.meta.og.bake(() => buildNav());
// becomes, at build:
// const nav = { items: [ … the whole computed tree … ] };

This is useful for anything expensive that never changes between requests, such as a nav tree computed from a corpus, a config object assembled from several files, or an RSS payload. You compute it once when you build instead of on every request or in the browser.

How it runs

At build time the macro bundles fn together with the imports it uses, executes that bundle in Node, serializes the result with devalue, and inlines it. Because devalue is the serializer, the result can be much more than JSON: Date, Map, Set, RegExp, BigInt, typed arrays and circular structures all survive.

Ship the answer, not the computation

Once a function is baked, any import that only fed it is dead code, so bake removes it. If buildNav came from a server-only module, that import disappears from the output entirely, so the computation’s dependencies never stay in the graph and Kit’s server guard has nothing to object to. Imports that the rest of the module still uses are kept.

The contract

fn must be self-contained, in the same way as a Bun macro:

  • It can use the module’s imports and literals, and it can await freely (async functions and Promise.all work).
  • It must not close over the module’s other local variables, because the evaluation bundle carries the imports but not the surrounding scope. A reference to anything that is not imported is a build error.
  • It must not touch runtime-only modules: $app/*, the browser, or anything else that exists only while the app runs.
  • Its result must be serializable by devalue. A function, a Promise or a class instance in the result is a build error that names the fix, because bake produces data.

Each violation is reported at build time with an [ogygia] message that names the file and line, rather than silently doing nothing.

Where it fits

bake works in .ts/.js modules and inside a .svelte <script>. It is the general form of what the loaders do for content and what code/md do for snippets: settle at build time whatever cannot change between requests, and ship only the result.