Static documentation
Parse, transform, sanitize, and highlight before shipping the HTML artifact.
- No Markdown parser in the browser.
- No highlighter in the browser.
- Fast first render and predictable output.
Build a small, secure, and complete documentation system by compiling Markdown into semantic HTML, retaining only the runtime features your SPA actually needs.
Use one public document contract with two execution modes: generation-time compilation for normal SPA pages and an optional browser compiler for dynamic Markdown.
Parse, transform, sanitize, and highlight before shipping the HTML artifact.
Use markdown-it behind a stable adapter with a small semantic document model.
Adopt unified, remark, and rehype when tree processing becomes a platform capability.
markdown-it for runtime rendering. Generation-time compiler for normal documents.A trustworthy renderer separates parsing, document analysis, HTML generation, sanitization, and progressive enhancement.
flowchart LR A[Markdown source] --> B[Normalize] B --> C[Block parser] C --> D[Inline parser] D --> E[Tokens or AST] E --> F[Analyze document] E --> G[Render HTML] F --> H[Outline and search] F --> I[Links and provenance] G --> J[Sanitize] J --> K[Trusted DOM insertion] K --> L[Progressive enhancements]
A CommonMark-compliant parser can still generate dangerous HTML when raw HTML, custom plugins, or unsafe URLs are allowed.
The smallest parser is not automatically the smallest system. Conformance, extension behavior, security, and maintenance dominate the real cost.
| Approach | Strength | Constraint | Best use | Fit |
|---|---|---|---|---|
| Custom regex | Minimal initial code. | Breaks on nesting, references, escaping, and pathological input. | Trusted, intentionally constrained snippets. | Avoid |
| Custom state machine | Total control over syntax and metadata. | Large conformance and test burden. | Deliberately small house dialect. | Niche |
| Marked | Fast, low-level, browser friendly. | Sanitization is external. Less structural than AST pipelines. | Simple Markdown viewers. | Good |
| markdown-it | Extensible rule engine, useful tokens, safe defaults. | Token stream rather than canonical mdast. | Dynamic SPA rendering. | Best default |
| micromark | Precise low-level tokenizer and extension model. | Infrastructure, not a complete application renderer. | Custom AST pipelines. | Foundation |
| unified stack | Rich mdast/hast transformation ecosystem. | More modules and plugin-order complexity. | Content platforms and multi-output compilers. | Advanced |
| markdown-wasm | Compact, fast, CommonMark-oriented conversion. | Less flexible for custom semantics and transforms. | Read-only conversion. | Lean |
| Markdoc / MDX | Typed components and application-native content. | New dialect or framework coupling. | Dedicated content products. | Platform choice |
Use an AST when the system needs to understand, transform, validate, or republish the document—not merely render it.
Hide the parser implementation behind one document model. This allows a future move from markdown-it tokens to mdast without rewriting the SPA.
export interface CompiledDocument {
html: string | TrustedHTML;
outline: OutlineItem[];
codeBlocks: CodeBlockInfo[];
diagrams: DiagramInfo[];
links: LinkInfo[];
textIndex: string;
diagnostics: Diagnostic[];
provenance: ProvenanceRecord[];
}Treat highlighting as a separate compiler stage. Prefer explicit language labels and load only the grammars your documents use.
Small core. Explicit languages. Predictable token classes.
Runtime default
Broad support and automatic detection, with possible false matches.
Good alternative
VS Code-quality grammars and themes. Higher browser cost.
Premium or build-time
Structural and incremental parsing. Excessive for display-only code.
Editor features
const document = compileMarkdown(source, {
rawHtml: false,
gfm: true,
highlight: "build-time",
languages: ["ts", "json", "bash", "html", "css"]
});
await emitSpaHtml(document);const allowedLanguages = new Set([
"text", "html", "css", "js", "ts",
"json", "bash", "yaml", "sql",
"markdown", "http", "graphql", "hcl"
]);A lightweight renderer stays coherent by documenting what it supports, how extensions are encoded, and what content is prohibited.
```ts title="policy-engine.ts" lines="3,7-11" copy
export function evaluatePolicy(input: PolicyInput) {
return policySet.evaluate(input);
}
```No single library provides the whole control plane. Each stage constrains a different class of risk.
const policy = window.trustedTypes?.createPolicy("spa-markdown", {
createHTML: value => DOMPurify.sanitize(value)
});
container.innerHTML = policy
? policy.createHTML(renderedHtml)
: DOMPurify.sanitize(renderedHtml);The strongest optimization is architectural: do not ship parsers, highlighters, sanitizers, or diagram engines to pages that do not need them.
Start with a narrow contract and strong tests. Add AST infrastructure only after concrete content-processing requirements appear.
Define supported CommonMark and GFM behavior, extension syntax, URL rules, limits, and prohibited constructs.
Wrap markdown-it. Produce semantic HTML plus outline, links, code blocks, diagrams, search text, diagnostics, and provenance.
Add build-time Prism, runtime copy controls, table wrappers, heading anchors, search, TOC, and lazy Mermaid.
Add DOMPurify, URL policy, Trusted Types, CommonMark/GFM fixtures, fuzz cases, accessibility checks, and performance budgets.
Approximately three to five engineer weeks for a complete, tested, and secure implementation. A focused internal v1 can be delivered in roughly seven to twelve engineering days.
Keep parser-specific concerns behind an adapter. Keep security, analysis, rendering, and progressive enhancement independently testable.
markdown-runtime/
├── compiler/
│ ├── compile.ts
│ ├── normalize.ts
│ ├── parser.ts
│ ├── renderer.ts
│ └── document-model.ts
├── analysis/
│ ├── outline.ts
│ ├── links.ts
│ ├── code-blocks.ts
│ ├── search-index.ts
│ ├── diagnostics.ts
│ └── provenance.ts
├── security/
│ ├── sanitize.ts
│ ├── url-policy.ts
│ ├── trusted-types.ts
│ └── limits.ts
├── enhancements/
│ ├── code-highlight.ts
│ ├── code-controls.ts
│ ├── mermaid.ts
│ ├── footnotes.ts
│ └── callouts.ts
├── static/
│ ├── prehighlight.ts
│ ├── emit-html.ts
│ └── emit-manifest.ts
└── tests/
├── commonmark/
├── gfm/
├── security/
├── snapshots/
├── accessibility/
└── performance/The guide prioritizes standards and official project documentation. Links are grouped by the claim families they support.