Compiler API
Use createCompiler when your own script or build system should own the lifecycle. Vite and Next
share the same compiler behavior, but each created instance keeps its own records, queue, and
highlighter.
Create and close a compiler
import { createCompiler } from '@amamo/mdx'
import amamo from './amamo.config.mjs'
const compiler = await createCompiler(amamo)
try {
const result = await compiler.build()
console.log(result)
} finally {
await compiler.dispose()
}Creation normalizes the config and initializes Shiki when highlighting is enabled. The native
binding is loaded on the first build() or transform() that needs a native batch.
interface ICompiler {
build(): Promise<IBuildResult>
dispose(): Promise<void>
remove(file: string): Promise<number>
transform(file: string): Promise<ITransformResult>
}Operations on one compiler are serialized. Concurrent build() calls share one in-flight build;
incremental operations wait their turn.
build()
const result = await compiler.build()A full build:
- Walks every collection in deterministic order.
- Derives locale, slug, and key metadata and rejects duplicate keys.
- Reads every source and optional last-modified value.
- Reuses compatible cache records or runs the Rust/Shiki compile pipeline.
- Replaces the compiler's complete in-memory record set.
- Writes changed manifests and shared generated files, then prunes stale cache entries.
interface IBuildResult {
cached: number
compiled: number
discovered: number
outputsWritten: number
}outputsWritten counts files whose bytes changed. An unchanged warm build can report zero and leave
all output modification times untouched. Cache-record writes are not included in this count.
transform(file)
const result = await compiler.transform('/absolute/path/content/posts/hello.mdx')transform compiles one recognized collection file, replaces its record, rewrites affected shared
outputs, and prunes cache records no longer in the compiler's current state.
interface ITransformResult {
cached: boolean
code: string
map: null
outputsWritten: number
record: IDocumentRecord
}codeis the compiled JavaScript module body.mapis currently alwaysnull.record.frontmattercontains the complete validated frontmatter object.record.derivedcontains enabled reading-time and last-modified values.record.hashis the BLAKE3 digest of the source bytes.record.cacheKeyalso includes config, platform, path, and optional modification metadata.record.diagnosticscontains successful warnings such as missing media under thewarnpolicy.
Use absolute paths. Relative paths resolve from the process working directory, not automatically
from config.root.
Run
build()first when shared outputs must retain every document. Callingtransform()on a new compiler creates state containing only that transformed file.
remove(file)
const outputsWritten = await compiler.remove('/absolute/path/content/posts/deleted.mdx')remove deletes a record already known to this compiler, updates shared outputs, prunes the cache,
and returns the number of changed output files. An unknown path is a no-op returning 0.
dispose()
await compiler.dispose()Disposal releases compiler resources and Shiki. It is idempotent. Any later build, transform, or
remove call rejects with AMAMO_COMPILER_DISPOSED.
Errors and diagnostics
Different boundaries fail differently:
- Invalid object schemas or non-schema configuration data throw
TypeErrorwith anAMAMO_CONFIG_*code in the message. - Native parsing, schema, media, cache, and manifest failures become an error named
AmamoMdxErrorwith adiagnosticsarray. - Shiki setup and unknown-language failures are ordinary
Errorvalues. - Filesystem, watcher, and Next loader failures are ordinary
Errorvalues.
IDiagnostic is exported from @amamo/mdx:
interface IDiagnostic {
code: string
file?: string
hint?: string
message: string
range?: {
start: { line: number; column: number; offset: number }
end: { line: number; column: number; offset: number }
}
severity: 'error' | 'warning'
}The shape reserves range and hint, but they are currently omitted. A batch stops at the first
failing document or stage rather than aggregating every failure. Schema messages may quote submitted
frontmatter values.
Shared generated files
After a build, generatedDirectory contains:
collections.mjs— sorted arrays of{ derived, frontmatter, key, locale, slug, load }.collections.d.ts— a companion declaration output for the registry.index.json— source path, cache key, cache directory, and config fingerprint for the Next loader.
The registry's load() functions import the original MDX sources. A host bundler still needs the
Vite plugin, Next loader, or another compatible MDX transform.