--- title: 'Build-time modules' slug: compiled description: 'Name a module *.compiled.ts to run it at build time and inline its exports, so the code that produced them never ships.' --- ## Build-time modules A module named `*.compiled.ts` (or `.compiled.js`) runs while your app compiles. The bundler replaces it with its exported values, so the module, and everything it imports, is left out of the bundle. ```ts // src/demos/url/sources.compiled.ts import { loadSources } from '../../components/utils.ts'; import { files } from './files.ts'; export const sources = await loadSources(files); ``` ```svelte ``` What the bundle holds is the finished value, not the code: ```js const __mochi_export_0__ = [{ label: 'App.svelte', html: '
' }]; export { __mochi_export_0__ as sources }; ``` `loadSources` and `files` are gone, and so is anything they imported — which is the point: a syntax highlighter, a markdown parser, or a database client used only to produce the value stops being a runtime dependency. The suffix is the whole convention, like [`.server.ts`](/docs/server-only-imports/). Top-level `await` works; the build awaits it. Import the module with the extension (`./sources.compiled.ts`) from a `.svelte` or `.md` file, or from a `.ts` module one of them imports. Only the component bundler replaces it: imported from the server entry (`routes.ts`, an API handler), it runs as a plain module, and `moduleRef()` throws. ### What a build-time module may export Anything [devalue](https://github.com/sveltejs/devalue) can serialize: plain data, `Date`, `Map`, `Set`, `RegExp`, `BigInt`, `undefined`, cycles, and repeated references. Functions, class instances, and promises are a compile error naming the export: ``` src/lib/table.compiled.ts exports "rows", which cannot be inlined: Cannot stringify a function at rows[0].format. ``` Each export is serialized on its own, so an object shared between two exports is inlined twice. ### Returning components with `moduleRef()` A component cannot be serialized. `moduleRef()` marks a module to import instead, and the build turns each marker into a real `import`: ```ts // src/lib/docComponents.compiled.ts import { moduleRef } from 'mochi-framework'; import type { Component } from 'svelte'; import { loadDocs } from './docs'; export const docComponents: Record = Object.fromEntries((await loadDocs()).map((d) => [d.slug, moduleRef(`../../docs/${d.filename}`)])); ``` becomes: ```js import __mochi_ref_0__ from '../../docs/10-intro.md'; const __mochi_export_0__ = { intro: __mochi_ref_0__ }; export { __mochi_export_0__ as docComponents }; ``` Specifiers resolve relative to the `.compiled.ts` file. This replaces generating a barrel file into your source tree before every build, and the map stays typed against the real module. A build-time module cannot `import` a `.svelte`, `.md`, or `.svelte.ts` file itself — it runs outside the bundler, where Svelte files have no loader. The build rejects the import and points at `moduleRef()`. ### In dev The module is evaluated at build time in dev too, and re-evaluated on every rebuild. An edit to the module or to anything it imports rebuilds the pages that use it. A file it only _reads_ — a directory of markdown, say — is outside the import graph: adding or removing a source or data file (`.md`, `.svelte`, `.ts`, `.json`, …) rebuilds every build-time module, while an edit to one shows up on the next rebuild of a page that imports the module. Each dev evaluation gets fresh instances of the module's own imports, but the running server keeps its own. A memo that must be shared between the two, say a parsed-docs cache the build-time module fills and a route reads, has to live on `globalThis` — use `pinGlobal(key, create)` from `mochi-framework`. ### What the build reports `mochi-framework build` prints every module it inlined: ``` Build-time modules ┌ ✦ src/demos/url/sources.compiled.ts └ ✦ src/lib/docComponents.compiled.ts 2 build-time modules inlined ``` The value is inlined wherever the module is imported. Inside a `mochi:hydrate` island it is therefore sent to the browser, so never export a secret from a build-time module a hydrated component imports.