@amamo/mdx
On This Page

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

TypeScript
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.

TypeScript
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()

TypeScript
const result = await compiler.build()

A full build:

  1. Walks every collection in deterministic order.
  2. Derives locale, slug, and key metadata and rejects duplicate keys.
  3. Reads every source and optional last-modified value.
  4. Reuses compatible cache records or runs the Rust/Shiki compile pipeline.
  5. Replaces the compiler's complete in-memory record set.
  6. Writes changed manifests and shared generated files, then prunes stale cache entries.
TypeScript
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)

TypeScript
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.

TypeScript
interface ITransformResult {
  cached: boolean
  code: string
  map: null
  outputsWritten: number
  record: IDocumentRecord
}
  • code is the compiled JavaScript module body.
  • map is currently always null.
  • record.frontmatter contains the complete validated frontmatter object.
  • record.derived contains enabled reading-time and last-modified values.
  • record.hash is the BLAKE3 digest of the source bytes.
  • record.cacheKey also includes config, platform, path, and optional modification metadata.
  • record.diagnostics contains successful warnings such as missing media under the warn policy.

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. Calling transform() on a new compiler creates state containing only that transformed file.

remove(file)

TypeScript
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()

TypeScript
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 TypeError with an AMAMO_CONFIG_* code in the message.
  • Native parsing, schema, media, cache, and manifest failures become an error named AmamoMdxError with a diagnostics array.
  • Shiki setup and unknown-language failures are ordinary Error values.
  • Filesystem, watcher, and Next loader failures are ordinary Error values.

IDiagnostic is exported from @amamo/mdx:

TypeScript
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.

Copyright © 2026 白熱.