Skip to content

Renderer API Reference

Rendering Pipeline Reference
Reference
[1]

Rendering Pipeline

The rendering pipeline transforms a cell’s raw source string into rendered output (HTML string or virtual DOM nodes). It is designed as a chain of composable stages so plugins can hook in at any point.

[2]
graph TD
    S(source string) --> PRE[Pre-process]
    PRE -->|plugins transform| P[Parse]
    P -->|markdown-it| AST[AST Transforms]
    AST -->|plugins modify tokens| R[Render]
    R -->|tokens to HTML| POST[Post-process]
    POST -->|sanitize/highlight| O(rendered output)

    style S fill:transparent,stroke-dasharray: 5 5
    style O fill:transparent,stroke-dasharray: 5 5
Mermaid not available. Import mermaid and expose it as globalThis.mermaid.
[3]

Pipeline Stages

1. Pre-process

Plugins register preprocess(source, cell) hooks. Use cases: expand shortcodes, strip front-matter, inject variables.

2. Parse

Default parser: markdown-it in CommonMark mode with HTML, linkify, and typographer enabled.

3. AST Transforms

Use cases: LaTeX ($...$ → KaTeX), Mermaid (fenced blocks → SVG placeholders), table enhancements, embed mount points.

4. Render

Multiple renderers per cell type — first non-null result wins.

5. Post-process

Use cases: sanitization (DOMPurify), syntax highlighting (Shiki/Prism), link rewriting, image lazy loading.

[4]
// Parser interface
interface MarkdownParser {
  parse(source: string): Token[];
  render(tokens: Token[]): string;
  use(plugin: MarkdownParserPlugin): void;
}

// AST Transform
type ASTTransformer = (tokens: Token[], cell: Cell) => Token[];

// Cell Renderer
interface CellRenderer {
  id: string;
  cellTypes: CellType[];
  renderToHTML?(tokens: Token[], cell: Cell): string;
  renderToVDOM?(tokens: Token[], cell: Cell): unknown;
  priority?: number;
}

// Post-processor
type PostProcessor = (html: string, cell: Cell) => string;
[5]
// RenderPipeline class
class RenderPipeline {
  constructor(parser?: MarkdownParser);

  addPreprocessor(id: string, fn: (s: string, c: Cell) => string, priority?: number): void;
  addASTTransformer(id: string, fn: ASTTransformer, priority?: number): void;
  addRenderer(renderer: CellRenderer): void;
  addPostprocessor(id: string, fn: PostProcessor, priority?: number): void;
  remove(id: string): void;

  render(cell: Cell): RenderedCell;
  renderAll(notebook: Notebook): RenderedCell[];
}

interface RenderedCell {
  cellId: string;
  html: string;
  renderTime: number;  // ms
  cached: boolean;
}
[6]

Caching Strategy

Content-addressed LRU cache (default: 200 entries):

  • Cache key = hash(cell.type + cell.source + relevantMetadata) using FNV-1a
  • Cell source change → invalidate that cell only
  • Plugin registered/unregistered → clear entire cache
  • Theme changed → clear entire cache
[7]
interface RenderCache {
  get(key: string): string | null;
  set(key: string, html: string): void;
  has(key: string): boolean;
  invalidate(key: string): void;
  clear(): void;
  size: number;
  maxSize: number;
}
[8]
// Incremental Rendering — for large notebooks (100+ cells)
interface IncrementalRenderer {
  markDirty(cellId: string): void;
  setViewport(startIndex: number, endIndex: number): void;
  flush(): RenderedCell[];
  getCachedRender(cellId: string): string | null;
}

// 1. Dirty tracking — cell:updated marks only that cell dirty
// 2. Viewport rendering — only visible cells render immediately
// 3. Background rendering — off-screen cells use requestIdleCallback
[9]

Markdown-it Plugins

Plugin Purpose
markdown-it-footnote Footnote syntax [^1]
markdown-it-task-lists Checkbox lists - [x]
markdown-it-anchor Heading anchors for TOC
markdown-it-toc Table of contents generation
markdown-it-attrs Custom attributes {.class #id}
markdown-it-container Custom containers :::name
markdown-it-sub Subscript ~sub~
markdown-it-sup Superscript ^sup^
markdown-it-mark Highlighted text ==mark==

All optional and loaded only if the user opts in (tree-shakeable).

[10]

Sanitization

Rendered HTML passes through DOMPurify with a permissive scientific config. Sanitization can be disabled per-cell via metadata.trusted = true for embed cells that need full HTML access.

[11]
// Syntax Highlighting interface
interface SyntaxHighlighter {
  highlight(code: string, language: string): string | Promise<string>;
  languages(): string[];
  loadLanguage(lang: string): Promise<void>;
}

// Default: Shiki (TextMate grammars, same as VS Code)
// Alternative: lighter Prism.js adapter also available

Integrated under the Sci DNA / VeloSci Ecosystem