aRustyDev / aRustyDev/mdbook-htmx
docs(mdbook): backend render explained
- Dominant language
- Rust
- Stars
- 0
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
# RenderContext Reference
This document describes the `RenderContext` JSON structure that MDBook passes to backend renderers via stdin.
## Overview
When MDBook invokes a backend renderer, it serializes the book's state to JSON and passes it via stdin. The backend reads this `RenderContext` to understand what to render and how.
```rust
// Reading RenderContext in Rust
let ctx: RenderContext = serde_json::from_reader(io::stdin())?;
```
---
## Top-Level Structure
```json
{
"version": "0.4.40",
"root": "/absolute/path/to/book",
"book": { ... },
"config": { ... },
"destination": "/absolute/path/to/book/book/htmx"
}
```
| Field | Type | Description |
|-------|------|-------------|
| `version` | string | MDBook version (semver) |
| `root` | string | Absolute path to book root directory |
| `book` | object | Book content structure |
| `config` | object | Full book.toml configuration |
| `destination` | string | Output directory for this renderer |
---
## Book Structure (`book`)
The `book` object contains the parsed book content:
```json
{
"book": {
"sections": [
{
"Chapter": {
"name": "Introduction",
"content": "# Introduction\n\nWelcome to the book...",
"number": [1],
"sub_items": [...],
"path": "introduction.md",
"source_path": "introduction.md",
"parent_names": []
}
},
{
"Separator": null
},
{
"PartTitle": "Advanced Topics"
}
],
"__non_exhaustive": null
}
}
```
### Section Types
| Type | Description |
|------|-------------|
| `Chapter` | A chapter with content |
| `Separator` | Visual separator in TOC |
| `PartTitle` | Part title (section grouping) |
---
## Chapter Object
Each chapter contains:
```json
{
"Chapter": {
"name": "Getting Started",
"content": "---\nauth:\n access: public\n---\n\n# Getting Started\n\nThis chapter...",
"number": [1, 1],
"sub_items": [
{
"Chapter": {
"name": "Installation",
"content": "# Installation\n\n...",
"number": [1, 1, 1],
"sub_items": [],
"path": "getting-started/installation.md",
"source_path": "getting-started/installation.md",
"parent_names": ["Getting Started"]
}
}
],
"path": "getting-started.md",
"source_path": "getting-started.md",
"parent_names": []
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Chapter title (from SUMMARY.md) |
| `content` | string | Full markdown content (including frontmatter) |
| `number` | int[] | Chapter numbering (e.g., [1, 2] = Chapter 1.2) |
| `sub_items` | array | Nested chapters |
| `path` | string \| null | Relative path to source file (null for drafts) |
| `source_path` | string \| null | Original source path |
| `parent_names` | string[] | Names of parent chapters |
### Draft Chapters
Draft chapters have `path: null`:
```json
{
"Chapter": {
"name": "Coming Soon",
"content": "",
"number": null,
"sub_items": [],
"path": null,
"source_path": null,
"parent_names": []
}
}
```
---
## Configuration (`config`)
The full `book.toml` configuration:
```json
{
"config": {
"book": {
"title": "My Documentation",
"authors": ["John Doe"],
"description": "Complete reference",
"src": "src",
"language": "en"
},
"build": {
"build-dir": "book",
"create-missing": true,
"use-default-preprocessors": true,
"extra-watch-dirs": []
},
"output": {
"html": {
"theme": null,
"default-theme": "light",
"preferred-dark-theme": "navy"
},
"htmx": {
"version": "1.0",
"htmx-version": "2.0.4",
"boost": true,
"swap-strategy": "innerHTML",
"target": "#content",
"push-url": true,
"template-engine": "tera",
"output-mode": "both",
"navigation": {
"sidebar-collapse": true,
"breadcrumbs": true
},
"authz": {
"enabled": true,
"frontmatter-key": "auth",
"default-access": "public"
},
"search": {
"enabled": true,
"index-format": "json",
"heading-split-level": 3
},
"assets": {
"hash-files": true,
"copy-htmx": true
},
"scopes": {
"enabled": true,
"available": ["all", "developers", "managers"],
"default": "all"
}
}
},
"preprocessor": {
"links": {}
}
}
}
```
### Accessing Backend Config
```rust
// Get htmx-specific configuration
let htmx_config: HtmxConfig = ctx
.config
.get_deserialized_opt("output.htmx")?
.unwrap_or_default();
```
---
## Complete Example
```json
{
"version": "0.4.40",
"root": "/home/user/my-book",
"book": {
"sections": [
{
"Chapter": {
"name": "Introduction",
"content": "# Introduction\n\nWelcome to My Documentation.\n\n## What You'll Learn\n\n- Feature A\n- Feature B\n",
"number": [1],
"sub_items": [],
"path": "introduction.md",
"source_path": "introduction.md",
"parent_names": []
}
},
{
"Separator": null
},
{
"PartTitle": "Getting Started"
},
{
"Chapter": {
"name": "Installation",
"content": "---\nauth:\n access: public\nscopes: [developers]\n---\n\n# Installation\n\n## Prerequisites\n\n- Rust 1.70+\n- Cargo\n\n## Steps\n\n```bash\ncargo install mdbook-htmx\n```\n",
"number": [2],
"sub_items": [
{
"Chapter": {
"name": "Linux",
"content": "# Linux Installation\n\n...",
"number": [2, 1],
"sub_items": [],
"path": "installation/linux.md",
"source_path": "installation/linux.md",
"parent_names": ["Installation"]
}
},
{
"Chapter": {
"name": "macOS",
"content": "# macOS Installation\n\n...",
"number": [2, 2],
"sub_items": [],
"path": "installation/macos.md",
"source_path": "installation/macos.md",
"parent_names": ["Installation"]
}
}
],
"path": "installation.md",
"source_path": "installation.md",
"parent_names": []
}
},
{
"Separator": null
},
{
"PartTitle": "Advanced"
},
{
"Chapter": {
"name": "Admin Guide",
"content": "---\nauth:\n access: roles\n roles: [admin]\nscopes: [sre]\n---\n\n# Admin Guide\n\nThis section is for administrators only.\n",
"number": [3],
"sub_items": [],
"path": "admin-guide.md",
"source_path": "admin-guide.md",
"parent_names": []
}
}
],
"__non_exhaustive": null
},
"config": {
"book": {
"title": "My Documentation",
"authors": ["John Doe"],
"description": "Complete platform reference",
"src": "src",
"language": "en"
},
"build": {
"build-dir": "book",
"create-missing": true,
"use-default-preprocessors": true
},
"output": {
"htmx": {
"version": "1.0",
"boost": true,
"authz": {
"enabled": true
},
"search": {
"enabled": true
},
"scopes": {
"enabled": true,
"available": ["all", "developers", "sre"]
}
}
}
},
"destination": "/home/user/my-book/book/htmx"
}
```
---
## Rust Types
```rust
use mdbook::renderer::RenderContext;
use mdbook::book::{Book, BookItem, Chapter};
use mdbook::config::Config;
use std::path::PathBuf;
// RenderContext fields
pub struct RenderContext {
pub version: String,
pub root: PathBuf,
pub book: Book,
pub config: Config,
pub destination: PathBuf,
}
// Iterating chapters
fn process_book(ctx: &RenderContext) {
for item in ctx.book.iter() {
match item {
BookItem::Chapter(chapter) => {
println!("Chapter: {}", chapter.name);
if let Some(path) = &chapter.path {
println!(" Path: {}", path.display());
}
}
BookItem::Separator => {
println!("--- Separator ---");
}
BookItem::PartTitle(title) => {
println!("Part: {}", title);
}
}
}
}
// Recursive chapter iteration
fn walk_chapters(items: &[BookItem], f: &mut F)
where
F: FnMut(&Chapter),
{
for item in items {
if let BookItem::Chapter(chapter) = item {
f(chapter);
walk_chapters(&chapter.sub_items, f);
}
}
}
```
---
## TypeScript Types
For server-side implementations:
```typescript
interface RenderContext {
version: string;
root: string;
book: Book;
config: Config;
destination: string;
}
interface Book {
sections: BookItem[];
}
type BookItem =
| { Chapter: Chapter }
| { Separator: null }
| { PartTitle: string };
interface Chapter {
name: string;
content: string;
number: number[] | null;
sub_items: BookItem[];
path: string | null;
source_path: string | null;
parent_names: string[];
}
interface Config {
book: BookConfig;
build: BuildConfig;
output: {
htmx?: HtmxConfig;
html?: HtmlConfig;
};
}
```
---
## Useful Patterns
### Extract All Chapters
```rust
fn collect_chapters(book: &Book) -> Vec<&Chapter> {
fn collect_recursive<'a>(items: &'a [BookItem], result: &mut Vec<&'a Chapter>) {
for item in items {
if let BookItem::Chapter(chapter) = item {
result.push(chapter);
collect_recursive(&chapter.sub_items, result);
}
}
}
let mut chapters = Vec::new();
collect_recursive(&book.sections, &mut chapters);
chapters
}
```
### Build Navigation Tree
```rust
fn build_nav_tree(book: &Book) -> NavTree {
fn build_node(item: &BookItem) -> Option {
match item {
BookItem::Chapter(ch) => Some(NavNode {
title: ch.name.clone(),
path: ch.path.clone(),
children: ch.sub_items.iter().filter_map(build_node).collect(),
}),
_ => None,
}
}
NavTree {
nodes: book.sections.iter().filter_map(build_node).collect(),
}
}
```
### Filter by Scope
```rust
fn chapters_for_scope<'a>(
chapters: &'a [&Chapter],
scope: &str,
) -> Vec<&'a Chapter> {
chapters
.iter()
.filter(|ch| {
let (fm, _) = extract_frontmatter(&ch.content).unwrap_or_default();
fm.scopes.contains(&scope.to_string()) || fm.scopes.contains(&"all".to_string())
})
.copied()
.collect()
}
```
---
## Related Documentation
- [Build Process](./build-process.md)
- [ADR-0012: MDBook Renderer Trait Implementation](../adr/0012-mdbook-renderer-trait-implementation.md)
- [MDBook Backend Development Guide](https://rust-lang.github.io/mdBook/for_developers/backends.html)
Contributor guide
Assessment
This issue has not been assessed yet.