Skip to content

Plugins

The pipeline takes remarkPlugins and rehypePlugins:

lib/docs.ts
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';

export const docs = createDocsRoute({
  contentDir: 'content/docs',
  remarkPlugins: [remarkMath],
  rehypePlugins: [rehypeKatex],
});

Where they run, and why there

remarkPlugins run before link resolution, so what they emit is folded, contained and asserted exactly like authored markdown. A plugin that generates a [link](./other.md) gets the same treatment as one you typed.

rehypePlugins run after heading ids exist and before Shiki, so a fence is still the author's text rather than a tree of token spans.

That second position is the whole reason the slots exist. A frozen pipeline had two apparent escape hatches and both were useless: frozen.use() throws, and frozen().use(plugin) appends, so the plugin runs after Shiki and sees <span class="line"> where the code used to be.

Excluding a language

Some fences are not code. A Mermaid block wants to reach the browser as text, for a client-side renderer to pick up:

createDocsRenderer({ excludeLangs: ['mermaid'] });

The block keeps its surface — background, border, padding, horizontal scroll and a tabindex so a keyboard can scroll it — and is deliberately left unframed, with no copy button.

Errors you can branch on

Every failure carries a code from a nineteen-member union, exported so a switch over it can be exhaustive:

import { isDocsError } from '@waveso/docs/errors';

try {
  await docs.renderAll();
} catch (error) {
  if (isDocsError(error) && error.code === 'broken-link') {
    // the message names the file and the link
  }
  throw error;
}

DocsErrorCode is a union of string literals, not string, so a typo in that comparison is a compile error rather than a branch that silently never runs.

Next: the reference.