Pipeline Stages
Complete interface reference for every stage in the tbdocs build pipeline. Each section covers one module: its entry-point signature, the data it reads from prior stages, the data it writes for subsequent stages, and every exported symbol.
For design rationale and narrative descriptions, see tbdocs Builder. To add a new stage or markdown-it plugin, see Extending the Builder.
- Data model
- Pre-phase:
mermaid.mjs - Phase 1:
discover.mjs - Phase 2: COMPUTE
- Phase 3:
render.mjs - Phase 4:
template.mjsandcompress.mjs - Phase 5:
write.mjs - Phase 6: Auxiliaries
- Phase 7:
offline.mjs - Phase 8:
pdf.mjs+book.mjs - Phase 12:
serve.mjs - Shared helpers
tbdocs.mjs— orchestrator- See Also
Data model
The pipeline passes two mutable data structures through every stage.
Page objects (pages[])
discover creates one page object per .md or .html source file with parseable YAML frontmatter. Subsequent stages add new fields; no stage removes or renames a field set by an earlier one. Later stages can safely assume all fields from earlier stages are present.
| Field | Added by | Type | Description |
|---|---|---|---|
srcPath | Phase 1 | string | Absolute filesystem path of the source file. |
srcRel | Phase 1 | string | POSIX-style path relative to srcRoot, e.g. Reference/Core/Dim.md. |
ext | Phase 1 | string | Lowercase file extension: .md or .html. |
frontmatter | Phase 1 | object | Parsed YAML frontmatter. All frontmatter keys are accessible here (e.g. frontmatter.title, frontmatter.parent, frontmatter.nav_order). |
rawContent | Phase 1 | string | Body text after the frontmatter block. |
permalink | Phase 1 | string | URL path, taken from frontmatter.permalink or derived from srcRel. |
destPath | Phase 1 | string | Filesystem path within the output root, e.g. Reference/Core/Dim.html. |
layoutDefault | Phase 1 | boolean | true when frontmatter has no explicit layout: key. |
imageScope | Phase 1 | boolean | true when srcRel contains an Images/ segment. Phase 3 uses this to validate image paths. |
navPath | Phase 2 (nav) | string | Slash-joined nav chain: grand_parent / parent / title. Set only on pages with a non-empty title. |
navLevels | Phase 2 (nav) | object | Positional indices in the sidebar tree. Phase 4 uses this to generate the per-page activation CSS. |
breadcrumbs | Phase 2 (nav) | Page[] | Ancestor chain from the root to the current page, nearest-first. |
children | Phase 2 (nav) | Page[] | Immediate child pages in nav order. |
seoTitle | Phase 2 (seo) | string | HTML-stripped, whitespace-collapsed page title for <title> and og:title. |
seoFullTitle | Phase 2 (seo) | string | "<seoTitle> -- <siteTitle>" for non-home pages; equals seoTitle on the home page. |
seoCanonical | Phase 2 (seo) | string | Absolute canonical URL (scheme + host + baseurl + permalink). |
seoIsHome | Phase 2 (seo) | boolean | true when the page’s permalink is a known home-page URL (e.g. /). |
renderedContent | Phase 3 | string | HTML body produced by markdown-it. Not yet wrapped in the site layout. |
html | Phase 4 | string | Complete HTML document, ready to write to disk. Absent on layout: book-combined pages, which Phase 8 owns. |
Site object (site)
Built at the end of Phase 2 and passed unchanged to every subsequent phase.
| Field | Type | Description |
|---|---|---|
config | object | Parsed _config.yml, with CLI overrides (--baseurl, --url) already applied. |
navTree | object | Top-level nav hierarchy produced by nav.mjs. |
seoSiteTitle | string | Rendered site title from config.title. |
seoLogoUrl | string | Absolute URL of the site logo. |
buildInfo | object | { commit: string, commitDate: string } from git. Both fall back to "unknown" outside a git repository. |
bookData | object|null | Parsed _data/book.yml with chapter selectors resolved to Page references. null when the file is absent. See Book Configuration. |
data | object | All _data/*.yml files keyed by basename: data.book, data.contributors, and so on. |
markdown | MarkdownIt | Shared markdown-it instance, built once during Phase 2 setup and reused by Phase 2’s SEO pass and Phase 3’s render pass. |
Static files (staticFiles[])
Also produced by Phase 1. Every file that is not a page — images, fonts, prebuilt CSS/JS, and any .md/.html file without frontmatter — becomes a static file object. This array does not grow after Phase 1.
| Field | Type | Description |
|---|---|---|
srcPath | string | Absolute source path. |
srcRel | string | POSIX path relative to srcRoot. |
destRel | string | Relative path within the output root (currently the same as srcRel). |
size | number | File size in bytes at discovery time. |
Pre-phase: mermaid.mjs
Runs before Phase 1 so any freshly regenerated .svg files appear in Phase 1’s static-file inventory.
Entry point
regenerateMermaid(srcRoot: string): Promise<{
processed: number,
regenerated: number,
failed: number,
setupSkipped?: true,
}>
Enumerates <srcRoot>/assets/images/mmd/*.mmd, compares modification times against the .svg sibling at the same path, and drives puppeteer + the mermaid package directly to render each stale .mmd into its .svg. One browser launch covers the whole batch. The call is a no-op when no .mmd files are stale.
The render runs in an in-page page.evaluate that dynamic-imports mermaid.esm.mjs via a request-intercept origin (https://tbdocs-mermaid.invalid). The intercept maps requests back to node_modules/mermaid/dist/; the origin trick is needed because Chromium blocks the import() chain that mermaid.esm.mjs triggers when loaded over file://. The IIFE bundle (mermaid.min.js) would sidestep that constraint but inlines + minifies past the patched dagre chunk (see Mermaid Dagre Patches), so the ESM path with the intercept is the only one that keeps the patch effective.
Two failure-mode distinctions:
- Setup failure (
puppeteer/mermaidnot installed, Chrome runtime missing) returns{ ..., setupSkipped: true }, warns once, and leaves on-disk SVGs intact. The orchestrator does not flip the exit code. - Per-diagram render failure (broken
.mmd, mermaid render throws) does not abort the batch — the loop continues so every broken diagram surfaces in one run, the previous SVG is retained for each failed diagram, and the orchestrator flipsprocess.exitCode = 1based on thefailedcount.
Reads: <srcRoot>/assets/images/mmd/*.mmd and their .svg siblings; node_modules/mermaid/dist/** (resolved via import.meta.url-rooted createRequire).
Writes: .svg files alongside stale .mmd sources.
All exports
| Symbol | Signature | Description |
|---|---|---|
regenerateMermaid | (srcRoot) → Promise<{ processed, regenerated, failed, setupSkipped? }> | Main entry point. |
Phase 1: discover.mjs
Traverses the source tree and produces the pages and staticFiles arrays consumed by every later phase.
Entry point
discover(srcRoot: string): Promise<{ pages: Page[], staticFiles: StaticFile[] }>
Runs a single fast-glob call over srcRoot with the hardcoded IGNORE exclude list (underscore-prefixed directories, prebuilt theme assets, toolchain files). For each .md or .html file, attempts to parse YAML frontmatter. Files with parseable frontmatter become page objects; everything else becomes static file objects. Pages are sorted by basename (mirroring Jekyll’s reader); static files by relative path.
Reads: source files under srcRoot.
Writes (page fields): srcPath, srcRel, ext, frontmatter, rawContent, permalink, destPath, layoutDefault, imageScope.
All exports
| Symbol | Signature | Description |
|---|---|---|
discover | (srcRoot) → Promise<{ pages, staticFiles }> | Main entry point. |
Phase 2: COMPUTE
Phase 2 runs several modules in sequence (with captureBuildInfo running in parallel). Together they build the site object and add nav, SEO, and book-chapter data to each page.
nav.mjs
Computes the navigation tree from each page’s title, parent, and grand_parent frontmatter keys. The only Phase 2 substep that can abort the build — it throws on orphan or ambiguous parent: declarations.
Entry point
computeNav(pages: Page[], config: object): { navTree: object }
Runs six substeps in sequence: nav-path, nav-integrity check, shared-state build (byTitle / byParentTitle maps, topLevel list, orderedChildren map), nav-tree, nav-levels, breadcrumbs, children. Returns the nav tree for the site object; writes nav-related fields directly onto each page object.
Reads: frontmatter.title, frontmatter.parent, frontmatter.grand_parent, frontmatter.nav_order, frontmatter.nav_exclude, page.permalink, config.nav_sort, config.case_insensitive.
Writes (page fields): navPath, navLevels, breadcrumbs, children.
All exports
| Symbol | Signature | Description |
|---|---|---|
computeNav | (pages, config) → { navTree } | Main entry point. |
seo.mjs
Pre-computes SEO metadata for every page and for the site as a whole.
Entry point
precomputeSeo(pages: Page[], config: object, markdown: MarkdownIt): { seoSiteTitle: string, seoLogoUrl: string }
For each page, runs the title through renderTitle (markdown-it render → strip HTML → collapse whitespace → escape HTML entities) and writes four SEO fields to the page object. Returns seoSiteTitle and seoLogoUrl for the site object. Requires the shared markdown-it instance to be built via createMarkdownIt before this call.
Reads: frontmatter.title, frontmatter.permalink, page.permalink, config.title, config.url, config.baseurl, config.logo, site.markdown.
Writes (page fields): seoTitle, seoFullTitle, seoCanonical, seoIsHome.
All exports
| Symbol | Signature | Description |
|---|---|---|
precomputeSeo | (pages, config, markdown) → { seoSiteTitle, seoLogoUrl } | Main entry point. |
renderTitle | (text: string, markdown: MarkdownIt) → string | Runs one title string through the full markdown-it + strip-HTML pipeline. |
stripHtml | (s: string) → string | Strips all HTML tags from a string. Re-exported for search.mjs. |
absoluteUrl | (input: string, config: object) → string | Resolves a root-relative path to an absolute URL using config.url and config.baseurl. Re-exported for sitemap.mjs and redirects.mjs. |
relativeUrl | (input: string, config: object) → string | Prepends config.baseurl to a root-relative path. |
book.mjs — Phase 2 half
Resolves the _data/book.yml chapter selectors to concrete Page arrays so Phase 8 has no further page lookups to do.
Phase 2 entry point
resolveBookChapters(bookData: object | null, pages: Page[]): void
Iterates over every entry in bookData.front_matter and bookData.parts (and their chapters sub-arrays), resolves each selector to a Page[], and stores the result as entry._chapters. Pre-resolves landing_page and foreword_page URLs to their Page references. Operates in-place; returns nothing. See Book Configuration for the selector schema.
Reads: bookData (loaded by data.mjs), page.permalink, page.navPath.
Writes: entry._chapters on each bookData entry (not a page field). Sets _landing and _foreword references on entries that declare landing_page: / foreword_page:.
Phase 8’s assembleBook lives in the same module; see Phase 8 below.
All exports
| Symbol | Signature | Description |
|---|---|---|
loadBookData | (srcRoot: string) → Promise<object|null> | Back-compat wrapper that loads _data/book.yml directly. Prefer data.mjs instead. |
resolveBookChapters | (bookData, pages) → void | Phase 2 entry point. |
sortByNavOrder | (input: Page[]) → Page[] | Sorts a page array: index pages (URLs ending in /) first, then by nav_order ascending with title as tie-breaker, then alphabetically by title. |
chapterAnchorFromUrl | (url: string, fallbackTitle?: string) → string | Converts a page URL to the ch-… anchor slug used for in-book cross-references. |
bookChapterTransform | (body: string, baseurl: string, headingShiftN: number, chapterAnchor: string) → string | Applies all per-chapter body transforms to a rendered HTML string: baseurl-prefix stripping, <details> / <summary> unwrapping, whitespace wrapping for pagedjs page breaks, heading-level shift, and chapter-anchor prefixing. |
assembleBook | (site: object, pages: Page[]) → string | Phase 8 entry point. Returns the assembled book.html string. |
rewriteBookHrefs | (html: string, site: object, pages: Page[]) → string | Rewrites intra-book absolute href="/X" references to in-page href="#ch-X" fragment anchors. |
build-info.mjs
Captures git commit hash and date for the PDF title page.
Entry point
captureBuildInfo(): Promise<{ commit: string, commitDate: string }>
Issues two parallel git shell-outs (rev-parse --short HEAD and log -1 --format=%cs). Falls back to "unknown" on any failure, so the build never aborts outside a git repository. The orchestrator launches this promise immediately after Phase 1 so the shell-outs overlap with the CPU-bound nav computation.
Reads: local git repository state.
Writes: nothing to pages (result returned directly to the orchestrator).
All exports
| Symbol | Signature | Description |
|---|---|---|
captureBuildInfo | () → Promise<{ commit, commitDate }> | Main entry point. |
data.mjs
Loads every _data/*.yml file under srcRoot into a flat object.
Entry point
loadData(srcRoot: string): Promise<object>
Returns a plain object keyed by file basename without extension: _data/book.yml → { book: … }, _data/contributors.yml → { contributors: … }. Returns {} when the _data/ directory is absent. The orchestrator stores the result at site.data and also exposes site.data.book as site.bookData.
Reads: <srcRoot>/_data/*.yml.
Writes: nothing to pages (result returned directly).
All exports
| Symbol | Signature | Description |
|---|---|---|
loadData | (srcRoot: string) → Promise<object> | Main entry point. |
Phase 2 setup — shared markdown-it instance
Before Phase 2 completes, the orchestrator builds the shared markdown-it instance that both Phase 2’s SEO pass and Phase 3’s render pass reuse. Three functions from render.mjs are called in sequence:
const highlighter = await initHighlighter({ copyButton: boolean });
const linkTables = buildLinkTables(pages);
const markdown = createMarkdownIt({ highlighter, linkTables, baseurl, staticFiles });
These functions are documented under Phase 3 below since they are defined in render.mjs. They are called here during Phase 2 only to allow the SEO pass to share the same configured pipeline.
Phase 3: render.mjs
Renders every page’s rawContent to HTML via markdown-it.
Entry point
renderPhase(pages: Page[], site: object, staticFiles?: StaticFile[]): Promise<void>
Renders each page’s rawContent to page.renderedContent using the shared site.markdown instance. Skips pages with layout: book-combined (Phase 8 owns those).
Reads: page.rawContent, page.frontmatter, page.imageScope, site.markdown, site.config.baseurl, staticFiles (for image-path validation).
Writes (page fields): renderedContent.
All exports
| Symbol | Signature | Description |
|---|---|---|
renderPhase | (pages, site, staticFiles?) → Promise<void> | Main entry point. |
createMarkdownIt | ({ highlighter, linkTables, baseurl, staticFiles }) → MarkdownIt | Configures and returns a markdown-it instance with all plugins and render-rule overrides applied. See Extending the Builder for how to add a plugin here. |
initHighlighter | ({ copyButton?: boolean }) → Promise<{ render, themeCss }> | Initialises Shiki with the bundled twinBASIC grammar (delegates to highlight.mjs internally). render(code, lang) returns highlighted HTML; themeCss is the generated tb-highlight.css string or null when no theme was loaded. |
buildLinkTables | (pages: Page[]) → { byPath, byUrl, byRedirect } | Builds lookup tables keyed by srcRel, permalink, and redirect_from entries. Used by the relative-links plugin to resolve in-source [X](Y.md) links to absolute URLs at render time. |
kramdownSlug | (text: string) → string | Converts heading text to a kramdown-compatible anchor slug: lowercase, strip non-word characters, deduplicate with -1, -2, and so on. |
rewriteAdmonitions | (src: string) → string | Pre-render text pass: converts GFM > [!NOTE] / [!IMPORTANT] / [!WARNING] / [!TIP] / [!CAUTION] blocks to the markdown-alert markdown-alert-<type> class structure. |
Phase 4: template.mjs and compress.mjs
Phase 4 wraps each page’s renderedContent in the full site layout and then compresses the resulting HTML.
template.mjs
Entry point
templatePhase(pages: Page[], site: object): Promise<void>
Pre-computes the per-build static sidebar HTML once, then wraps each page’s renderedContent in the just-the-docs layout via direct JS template-literal concatenation (no template engine). Calls compressHtml on each page’s output before storing the result. Skips layout: book-combined pages.
Reads: all page fields set by Phases 1–3, all site fields.
Writes (page fields): html.
All exports
| Symbol | Signature | Description |
|---|---|---|
templatePhase | (pages, site) → Promise<void> | Main entry point. |
navActivationCss | (page: Page) → string | Generates the per-page <style id="jtd-nav-activation"> block from page.navLevels. Phase 12’s dev server calls this when patching in the SSE reload script. |
injectAnchorHeadings | (html: string) → string | Adds <a class="anchor-heading"> next to every heading that has an id attribute. |
compress.mjs
templatePhase calls compressHtml internally. The function is also exported for standalone use.
Entry point
compressHtml(html: string): string
Splits on <pre>…</pre> blocks, collapses ASCII whitespace in the non-<pre> segments to a single space, and trims. Uses the explicit character class [ \t\n\r\f\v]+ rather than \s to preserve non-breaking spaces in -based indentation.
All exports
| Symbol | Signature | Description |
|---|---|---|
compressHtml | (html: string) → string | Compresses whitespace outside <pre> blocks. |
Phase 5: write.mjs
Materialises the in-memory page set and static files to disk.
Entry point
writePhase(
pages: Page[],
staticFiles: StaticFile[],
{
destRoot: string,
dryRun?: boolean,
generatedAssets?: { rel: string, content: string }[],
baseurl?: string
}
): Promise<{ pages: { written, skipped }, theme: { copied }, staticFiles: { copied } }>
Clears then recreates destRoot, then runs three operations in parallel: writes each page.html to its destPath; copies builder/assets/ to <destRoot>/assets/ (with a CSS url() baseurl rewrite for non-empty baseurls); and copies every staticFiles[] entry. Skips pages where page.html is undefined.
Reads: page.html, page.destPath, staticFile.srcPath, staticFile.destRel.
Writes: <destRoot>/** (the online tree).
All exports
| Symbol | Signature | Description |
|---|---|---|
writePhase | (pages, staticFiles, opts) → Promise<stats> | Main entry point. |
WRITE_LIMIT | 64 | Concurrency ceiling for runLimited. Phases 6, 7, and 8 pass this value to their own runLimited calls for consistent I/O throttling. |
isUnderProject | (destRoot: string) → boolean | Returns true only when destRoot is a descendant of the project root. Used by Phases 7 and 8 as a guard against destructive --dest values. |
mkdirRec | (dir: string) → Promise<void> | Recursive mkdir with an in-flight deduplication cache. Shared by Phases 6, 7, and 8. |
runLimited | <T>(items: T[], limit: number, fn: (T) → Promise<any>) → Promise<void> | Runs fn on each item with at most limit concurrent operations. |
writeFileMkdirp | (filePath: string, content: string|Buffer) → Promise<void> | Writes content to filePath, creating parent directories as needed. |
safeWrite | (dest: string, fn: () → Promise<any>) → Promise<void> | Wraps a write callback and re-throws with dest in the error message if the callback throws. |
Phase 6: Auxiliaries
Phase 6 runs three writers concurrently. None writes to page objects; all write to <destRoot>/.
redirects.mjs
Entry point
writeRedirects(pages: Page[], site: object, destRoot: string): Promise<{ written: number }>
For each page with a redirect_from: frontmatter entry, writes one HTML stub per source URL. Each stub uses <script>location=…</script>, <meta http-equiv="refresh">, a <link rel="canonical">, <meta name="robots" content="noindex">, and a visible <a> fallback for no-script/no-meta-refresh environments.
Reads: page.frontmatter.redirect_from, page.permalink, site.config.
Writes: redirect stub HTML files under <destRoot>/.
All exports
| Symbol | Signature | Description |
|---|---|---|
writeRedirects | (pages, site, destRoot) → Promise<{ written }> | Main entry point. |
deriveRedirectStubs | (pages, site) → Array<{ from, to, destPath }> | Pure derivation of the stub list without writing to disk. Exported so offline.mjs can read the list without re-running the derivation. |
sitemap.mjs
Entry point
writeSitemap(pages: Page[], site: object, destRoot: string): Promise<{ entries: number }>
Filters pages by jekyll-sitemap rules (drops sitemap: false and /404.html), sorts absolute URLs alphabetically for byte-identical re-runs, and emits sitemap.xml. Also writes robots.txt with a Sitemap: reference.
Reads: page.permalink, page.frontmatter.sitemap, site.config.
Writes: <destRoot>/sitemap.xml, <destRoot>/robots.txt.
All exports
| Symbol | Signature | Description |
|---|---|---|
writeSitemap | (pages, site, destRoot) → Promise<{ entries }> | Main entry point. |
deriveSitemapUrls | (pages, site) → string[] | Returns the sorted list of absolute URLs that would appear in the sitemap, without writing to disk. |
extractSitemapUrls | (xml: string) → string[] | Parses an existing sitemap.xml string and extracts its <loc> values. Useful for diffing two builds. |
renderRobotsTxt | (config: object) → string | Produces the robots.txt content string. |
search.mjs
Entry point
writeSearchData(pages: Page[], site: object, destRoot: string): Promise<{ entries: number }>
Splits each titled, non-search_exclude page by headings, emits one search-index entry per heading-bounded section, and writes a Lunr-compatible JSON index.
Reads: page.renderedContent, page.frontmatter.title, page.frontmatter.search_exclude, page.permalink, page.seoTitle, site.config.
Writes: <destRoot>/assets/js/search-data.json.
All exports
| Symbol | Signature | Description |
|---|---|---|
writeSearchData | (pages, site, destRoot) → Promise<{ entries }> | Main entry point. |
deriveSearchEntries | (pages, site) → object[] | Returns the search-index entry array without writing to disk. |
Phase 7: offline.mjs
Mirrors <destRoot>/ to <destRoot>-offline/, rewriting every URL to a page-relative path so the tree opens under file://.
Entry point
writeOffline(
pages: Page[],
staticFiles: StaticFile[],
site: object,
destRoot: string,
{ auxStats?: object, profileOffline?: boolean }
): Promise<{ html, css, redirects, statics, assets, excluded, unresolved }>
Reads every file written by Phases 5 and 6, rewrites absolute URLs to relative paths, and writes to <destRoot>-offline/. Patches just-the-docs.js via AST (acorn) to replace navLink and initSearch with offline-compatible implementations. Wraps search-data.json as an inline window.SEARCH_DATA assignment.
Reads: all files under <destRoot> (online tree), auxStats.redirects (redirect stub list from Phase 6).
Writes: all files to <destRoot>-offline/.
All exports
| Symbol | Signature | Description |
|---|---|---|
writeOffline | (pages, staticFiles, site, destRoot, opts) → Promise<stats> | Main entry point. |
buildOfflineState | (pages, staticFiles, site, destRoot, { stubs? }) → Promise<OfflineState> | Constructs the state object (site-path set, resolution caches, per-directory nav caches) used by all offline derivation functions. |
deriveOfflinePage | (page: Page, state: OfflineState) → string | Rewrites one page’s HTML for offline use. |
deriveOfflineRedirect | (stub, state: OfflineState) → string | Rewrites a redirect stub’s HTML for offline use. |
deriveOfflineCss | (cssIn: string, themeRel: string, state: OfflineState) → string | Rewrites url() references in a CSS file to page-relative paths. |
deriveOfflineJtdJs | (src: string) → string | Patches just-the-docs.js via AST: replaces navLink and initSearch with offline-compatible implementations. A parse failure at build time is a signal that re-extraction produced unreadable source. |
deriveOfflineSearchDataJs | (jsonBytes: Buffer) → string | Wraps search-data.json as window.SEARCH_DATA = … and minifies it. |
Phase 8: pdf.mjs + book.mjs
Produces the sparse <destRoot>-pdf/ tree that render-book.mjs renders into a PDF.
Entry point
writePdf(
pages: Page[],
staticFiles: StaticFile[],
site: object,
destRoot: string,
{ tolerateMissingImages?: boolean }
): Promise<{ bookBytes, css, images, missing }>
Calls book.mjs’s assembleBook(site, pages) to produce book.html, copies print.css and tb-highlight.css, and collects every image referenced in book.html. Reports missing images as build errors by default; --tolerate-missing-images downgrades them to warnings.
Reads: site.bookData (with chapter selectors resolved by Phase 2’s resolveBookChapters), all pages’ page.html, staticFiles.
Writes: <destRoot>-pdf/book.html, <destRoot>-pdf/*.css, image copies in <destRoot>-pdf/.
book.mjs Phase 8 entry point
assembleBook(site: object, pages: Page[]): string
Traverses site.bookData, emits a title page, then iterates over front_matter and parts in order. For each chapter, calls bookChapterTransform to apply the five body transforms. Then runs rewriteBookHrefs to convert intra-book absolute hrefs to #ch-… fragment anchors. Returns the complete book.html HTML string.
All exports (pdf.mjs)
| Symbol | Signature | Description |
|---|---|---|
writePdf | (pages, staticFiles, site, destRoot, opts) → Promise<stats> | Main entry point. |
deriveBookOutputs | (pages, site) → { bookHtml: string, images: string[] } | Pure-compute version: returns the assembled HTML and image-path list without writing to disk. |
extractImagePaths | (html: string) → string[] | Extracts all image src / href paths from an HTML string. |
For book.mjs exports, see Phase 2 book.mjs above.
Phase 12: serve.mjs
The long-lived dev server, activated by tbdocs --serve. This is a separate lifecycle from the one-shot build; it skips Phase 7 (offline) and Phase 8 (PDF).
Entry point
runServe(opts: BuildOpts): Promise<void>
Runs an initial one-shot online build (Phases pre, 1–5), then starts an HTTP server on opts.port (default 4000), a recursive source-tree watcher, and an SSE endpoint at /_tbdocs/reload. A 300 ms debounce fires a rebuild on file changes. On rebuild success, a reload event is sent to every connected browser tab.
All exports
| Symbol | Signature | Description |
|---|---|---|
runServe | (opts: BuildOpts) → Promise<void> | Main entry point. BuildOpts is the same object accepted by runBuild. |
Shared helpers
paths.mjs
All exports
| Symbol | Signature | Description |
|---|---|---|
permalinkToDestPath | (permalink: string) → string | Converts a permalink URL to a destination file path. / → index.html; /foo/ → foo/index.html; paths with .html, .htm, or .xml extensions are kept as-is; all other paths get .html appended. Used by Phase 1 and Phase 6. |
highlight-theme.mjs
Called internally by initHighlighter in highlight.mjs; not normally called directly by other stages.
All exports
| Symbol | Signature | Description |
|---|---|---|
loadHighlightTheme | (themesDir?: string) → Promise<{ scopeToClass, css }> | Reads Light.theme and Dark.theme, groups TextMate-scope tokens by their (light-props, dark-props) pair, assigns one CSS class per unique pair, and returns the scope-to-class lookup and the generated tb-highlight.css content. |
tbdocs.mjs — orchestrator
The orchestrator sequences all stages and assembles the site object. It is not a stage itself.
All exports
| Symbol | Signature | Description |
|---|---|---|
runBuild | (opts: BuildOpts) → Promise<{ pages, staticFiles, site, destRoot }> | Runs the full pipeline (pre-phase, Phases 1–8). Returns the final state so external harnesses can chain additional work. |
makeTimer | () → { lap(label: string): void, summary(): string } | Lightweight lap timer. lap(label) records elapsed milliseconds since the last lap; summary() returns all laps as a "label=Nms …" string. |
BuildOpts fields:
| Field | Default | Description |
|---|---|---|
src | "docs" | Source root, relative to cwd. |
dest | null | Destination root. Defaults to <src>/_site. |
baseurl | null | Overrides config.baseurl. |
url | null | Overrides config.url. |
dryRun | false | Skip all filesystem writes. |
skipOffline | null | Skip Phase 7. null reads also_build_offline from _config.yml. |
skipPdf | null | Skip Phase 8. null reads also_build_pdf from _config.yml. |
tolerateMissingImages | false | Downgrade missing-image errors to warnings in Phase 8. |
profileOffline | false | Emit per-substep timings for Phase 7 in the console output. |
serve | false | Start Phase 12 instead of the one-shot build. |
port | 4000 | HTTP port for Phase 12. |
See Also
- tbdocs Builder – architecture overview and narrative design rationale.
- Book Configuration –
_data/book.ymlkey reference. - Extending the Builder – tutorial for adding a new stage or markdown-it plugin.