Macros
bake
Run a function at build, serialize its result, inline it as a constant. Run at build, ship the answer, even in client code. The imports that the function used are dropped. Thus the server-only deps of the computation never stay behind.
import.meta.og.bake(fn) runs fn at build, serializes its result, and rewrites the call to that literal. At runtime, there is no function and no work: 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 the “run at build, ship the answer” trick. A nav tree computed from a corpus, a config object assembled from several files, an RSS payload, anything costly and unchanging: compute it one time when you build, not on each request, and not in the browser.
How it runs
At build, the macro bundles fn together with the imports that it uses. It executes the bundle in Node, serializes the result with devalue, and inlines it. devalue is the serializer. Thus the result can be far more than JSON: Date, Map, Set, RegExp, BigInt, typed arrays, and circular structures all survive.
Ship the answer, not the computation
When a function is baked, the imports that only fed it become dead. Thus bake removes them. If buildNav came from a server-only module, that import is fully gone from the output. The dependencies of the computation never stay in the graph, and the server guard of Kit has nothing to complain about. Imports still used in other parts of the module stay.
The contract
fn is self-contained, exactly like a Bun macro:
- It can use the imports and literals of the module, and
awaitfreely (asyncfunctions andPromise.alloperate). - It must not close over the other local variables of the module. The eval bundle carries the imports, not the surrounding scope. A reference to something that is not imported is a build error.
- It must not touch runtime-only modules:
$app/*, the browser, anything that exists only when the app runs. - Its result must be devalue-serializable. A function, a Promise, or a class instance in the result is a build error that names the fix. Bake makes data.
Each violation surfaces at build, with an [ogygia] message that names the file and line. Never a silent no-op.
Where it fits
bake operates in .ts/.js modules, and inside a .svelte <script>. It is the general form of the idea that the loaders apply to content, and that code/md apply to snippets: settle at build what cannot change between requests, and ship only the result.