Version imprimable multipages. Cliquer ici pour imprimer.
Design
1 - Script loading
Docsy loads its body-end JavaScript through
_partials/scripts.html: a small dispatcher over per-feature
sub-partials under _partials/scripts/. (Head-side JS, such as
theme initialization and analytics, is emitted by _partials/head.html and is
out of scope here.)
Loading mechanisms
Before 0.18, scripts.html mixed a few sub-partial dispatches (MarkMap,
Mermaid, KaTeX) with the other mechanisms’ logic inline. The decomposition moved
every mechanism out of the dispatcher into sub-partials without changing the
default rendered output; the 0.18 plugin conversions then moved the first
integrations onto the plugin loop:
- Static theme scripts, emitted as plain script tags:
deflate.js(PlantUML),prism.js. - The main bundle: Bootstrap plus the theme’s core and feature scripts
(search, PlantUML, draw.io; dark mode and ScrollSpy when enabled),
concatenated into
main.js(scripts/main-bundle.html), minified and fingerprinted in production. A site param picks which search script is bundled,search.jsoroffline-search.js. - Theme plugins: markmap, tab-pane persistence, and click-to-copy ride the plugin loop as theme-default registry entries, their legacy params aliased for a deprecation cycle (implementation notes).
- Pinned CDN tags with inline configuration: Algolia DocSearch.
- Build-time remote fetches: KaTeX, whose CSS and fonts are copied and re-served as local assets; Mermaid, whose pinned version is validated at build time while the browser imports the module straight from the CDN; and the MarkMap autoloader, vendored at build time and served same-origin with SRI (its runtime libraries still load from the CDN, at versions the autoloader pins).
Gating lives at two levels. The dispatcher gates PlantUML (site param) and
Mermaid and KaTeX (.Page.Store flags); the plugin loop’s pageGate carries
the same page-flag pattern for markmap (hasMarkmap) and tab-pane persistence
(hasTabs), while the remaining sub-partials gate internally (Algolia search
configuration, Prism, search bundle choice, dark mode, ScrollSpy).
The dispatcher as a seam
The decomposition has two design consequences:
- Independent overrides: each sub-partial resolves through Hugo’s union file
system, so a site can replace one sub-partial by shadowing one file instead of
copying all of
scripts.html. - A landing point: the dispatcher is where the plugin loop plugged in, and where the first built-in integrations (markmap, tab-pane persistence, click-to-copy) converted onto the loop in 0.18 (#2789).
Override points
- Every sub-partial the dispatcher routes to under
_partials/scripts/. _partials/algolia/head.htmland_partials/scripts/algolia.html: real partials as of 0.18, replacing inlinedefines whose documented override paths did not work (the internal template namesalgolia/headandalgolia/scriptsno longer exist).- Per plugin: the script asset
assets/js/plugins/NAME.js, its companion partial, and its companion stylesheet (implementation notes).
The plugin loop
scripts/plugins.html emits each eligible plugin registered in
params.docsy.plugins. pageGate generalizes the Mermaid/KaTeX page-flag
pattern. For the registry contract, shape guards, and build details, see the
implementation notes.
Two ordering decisions:
- Companions before the script: a plugin’s companion partial and stylesheet emit before its script tag, so a synchronous plugin script can rely on companion markup and styles being present.
- Body-end CSS (interim placement): the companion stylesheet’s
<link>is emitted where the loop runs (at the end of<body>), not in<head>, becausepageGatereads.Page.Storeflags that are only reliable after content render. Moving companion CSS into the head is a possible later refinement, and has to solve that constraint or gated CSS silently drops (#2789).
Print output: print layouts render descendant pages into one page, so they
merge descendant .Page.Store flags onto the print page
(_partials/print/page-flags.html) before the dispatcher runs; page-gated
scripts reach print output through that merge.
Related pages
- Implementation: script loading
- Quality: script loading: the test nets that pin this behavior
2 - Semantic classes
td- CSS classesFor what semantic classes are and the consumer contract (public td- classes
and state attributes), see Semantic classes in the user guide.
Naming
New semantic classes use td--prefixed light BEM (td-block__element), with
modifier suffixes reserved for variants, following the pattern of existing
names like td-sidebar-nav--search-disabled. Other pre-existing td- names
remain until a component’s migration renames or removes them; a migration may
also keep pre-existing names unchanged (the breadcrumb kept td-breadcrumbs).
State styling
When migrating a component, style each state through a semantic attribute, never
a state class. Reuse the ARIA state attribute the markup already exposes for
assistive technology when one applies: keying styling on it keeps visual and
accessibility state inseparable by construction. For a state with no ARIA home,
introduce a data-td-* attribute and announce it in the component’s upgrade
post.
Skins
A skin binds the semantic classes to a styling source: in CSS only, never in markup. The current skin binds to Bootstrap:
- Component styling binds by reference:
@extend .breadcrumb-style rules, so styling tracks the installed Bootstrap version instead of drifting as a vendored copy. - State rules are written out against Bootstrap’s component CSS variables
(
--bs-*by default), since Bootstrap defines these components’ state styling in compound selectors (like.breadcrumb-item.active), which@extendcan’t reference. Each written-out rule carries aBS mirror: FILE SELECTORcomment; from the repo root,grep -rn 'BS mirror:' theme/assets/scss/td/inventories the mirrored rule bodies to re-check on a Bootstrap upgrade.