# Precompiling

> Compile your templates to JavaScript at build time.

Precompiling turns the templates in your codebase into JavaScript modules when you build your site. They render the same HTML, with the same errors, but faster. See the [benchmarks](https://kilnlang.com/docs/benchmarks/).

Kiln never generates code while your site runs, so precompiled templates work everywhere, including Cloudflare Workers.

## Precompiling at build time

Call `precompile` on an environment for each template, and save what it returns as a `.js` file. Do this in a build script or a bundler plugin.

```ts
import { mkdir, writeFile } from "node:fs/promises";
import { dirname } from "node:path";
import { createKiln } from "@lucidkiln/core";
import { fileLoader } from "@lucidkiln/core/node";
import { options } from "./src/kiln-options.ts";

const loader = fileLoader({ root: "src/templates" });
const kiln = createKiln({ ...options, loader });

for (const name of await loader.list()) {
	const file = `build/templates/${name.replace(/\.kiln$/, ".js")}`;
	await mkdir(dirname(file), { recursive: true });
	await writeFile(file, await kiln.precompile(name));
}
```

`precompile` throws a [`KilnSyntaxError`](https://kilnlang.com/docs/api/errors/#error-types) if the template has a problem, so a broken template fails the build instead of a page.

## Rendering precompiled templates

Import the modules and give them to `memoryLoader` in place of the template source. Everything else stays the same.

```ts
import { createKiln, memoryLoader } from "@lucidkiln/core";
import layout from "./build/templates/layouts/base.js";
import home from "./build/templates/pages/home.js";
import { options } from "./kiln-options.ts";

const kiln = createKiln({
	...options,
	loader: memoryLoader({ "layouts/base": layout, "pages/home": home }),
});

const html = await kiln.render("pages/home", { title: "Home" });
```

With Vite, `import.meta.glob` can import every module at once:

```ts
import type { PrecompiledTemplate } from "@lucidkiln/core";

const modules = import.meta.glob<PrecompiledTemplate>(
	"./build/templates/**/*.js",
	{ eager: true, import: "default" },
);
const templates = Object.fromEntries(
	Object.entries(modules).map(([path, module]) => [
		path.slice("./build/templates/".length, -".js".length),
		module,
	]),
);
const kiln = createKiln({ ...options, loader: memoryLoader(templates) });
```

Your own loaders can return a precompiled module from `load` too, in place of the source.

## Use the same options

A precompiled template is built for the options it was compiled with. Create the environment that renders it with the same `autoescape` setting, and the same functions, filters and tests. If something the module depends on is different, such as a built-in filter you replaced with your own, or the generated-code format is incompatible, rendering it fails with a `precompiled-mismatch` error. Precompile again to fix it.

Build and render with the same exact version of `@lucidkiln/core`, and regenerate your modules whenever you upgrade it, including patch releases. Generated JavaScript and its runtime interface are build artifacts, not a stable public API. Kiln checks a format version and rejects known mismatches, but that check does not guarantee that modules from different package versions are compatible. Source templates and the documented public API follow semantic versioning.

## Only precompile templates you trust

Precompile the templates that live in your codebase, which you review like the rest of your code. Templates written by other people, such as editors in a CMS or AI agents, should load as source. Kiln renders source without generating code, with the same [safety rules and limits](https://kilnlang.com/docs/api/safety/).

One environment can render both: a loader can return precompiled modules for your own templates and source for everything else.

## What a module holds

A precompiled module holds the template's source and the JavaScript Kiln generated from it. Kiln reads the source when it loads the module, so errors point at the right line, and `check` and `analyze` work as they do for source.
