Module Odoc_extension_registrySource
Odoc Extension Registry
This module provides a minimal registry for odoc tag extensions. It is kept separate to avoid circular dependencies between odoc_document and odoc_extension_api.
Page Resources
Resources that an extension requests to be injected into the HTML <head>. The shell plugin is responsible for rendering these; the behaviour described below applies to the docsite shell shipped with odoc-docsite.
Execution timing
On the initial full page load every resource listed by every extension present on that page is emitted into <head> in order. External scripts (Js_url) are loaded via a normal <script src="…"> tag and execute when the browser fetches them. Inline scripts (Js_inline) execute synchronously in document order.
SPA (single-page app) navigation
The docsite shell intercepts link clicks and fetches pages via fetch() instead of a full reload. After swapping the page content it reconciles <head> resources:
Js_url/Css_url: Compared by resolved absolute URL. Resources already present in the live document are skipped; new ones are appended to<head>and (for scripts) theironloadevents are awaited before continuing.Js_inline: Each inline script is stamped at HTML-generation time with adata-spa-inlineattribute containing a hash of its content. On SPA navigation the shell checks whether a<script>with the samedata-spa-inlinevalue already exists in<head>. If so it is not re-executed. This means inline scripts run exactly once across SPA navigations — the first time a page carrying that script is visited.Css_inline: Injected into<head>on every navigation (CSS is additive and idempotent, so duplicates are harmless).
Guidance for extension authors
- Prefer
Js_urlfor library code (e.g. the mermaid runtime). The deduplication-by-URL ensures it is loaded exactly once. - Use
Js_inlinefor one-time initialisation that must run after the library is loaded (e.g. registering a global observer). It will execute once; on subsequent SPA navigations the library and observer are still alive. - If your extension needs to re-process content on every SPA navigation, set up a
MutationObserveror listen for the"popstate"event in yourJs_inlineinit script rather than relying on the script being re-executed. - Do not gate initialisation on
DOMContentLoadedinside aJs_inlinescript — that event does not re-fire during SPA navigation.
Content of a support file: either inline string or path to copy from disk
type support_file = {filename : string;(*Relative path, e.g., "extensions/admonition.css"
*)content : Odoc_extension_registry.support_file_content;
}Support files that extensions want to output
type asset = {asset_filename : string;(*Filename for the asset, e.g., "diagram-1.png"
*)asset_content : bytes;(*Binary content
*)
}Binary asset generated by an extension (e.g., rendered PNG)
type option_doc = {opt_name : string;(*Option name, e.g., "width"
*)opt_description : string;(*What the option does
*)opt_default : string option;(*Default value if any
*)
}Documentation for an extension option
type extension_info = {info_kind : [ `Tag | `Code_block ];(*Type of extension
*)info_prefix : string;(*The prefix this extension handles
*)info_description : string;(*Short description of what it does
*)info_options : Odoc_extension_registry.option_doc list;(*Supported options
*)info_example : string option;(*Example usage
*)
}Documentation/metadata for an extension
type 'block extension_result = {content : 'block;overrides : (string * string) list;resources : Odoc_extension_registry.resource list;assets : Odoc_extension_registry.asset list;(*Binary assets to write alongside the HTML output. Use
*)__ODOC_ASSET__filename__placeholder in content to reference.
}Result of processing a custom tag. We use a record with a polymorphic content type that gets instantiated with the actual Block.t by odoc_document.
type 'block handler =
string ->
Comment.nestable_block_element Location_.with_location list ->
'block Odoc_extension_registry.extension_result optionType of handler functions stored in the registry. The handler takes a tag name and content, returns an optional result. If None, the tag is handled by the default mechanism.
The registry stores handlers indexed by prefix
Registered prefixes for listing
Support files registered by extensions
Extract the prefix from a tag name (part before the first dot)
Code Block Handlers
Similar to custom tag handlers, but for code blocks like {@dot[...]}. Handlers can transform code blocks based on language and metadata.
Metadata for code blocks, extracted from parser AST
type 'block code_block_handler =
Odoc_extension_registry.code_block_meta ->
string ->
'block Odoc_extension_registry.extension_result optionType of code block handler functions. Takes metadata and code content, returns optional transformed result.
Registry for code block handlers, indexed by language prefix
Registered code block prefixes
val register_code_block_handler :
prefix:string ->
'block Odoc_extension_registry.code_block_handler ->
unitval find_code_block_handler :
prefix:string ->
'block Odoc_extension_registry.code_block_handler optionExtract the prefix from a language tag (part before the first dot)
Extension Documentation
Extensions can register documentation that describes their options and usage. This is displayed by odoc extensions.
Registry for extension documentation
Link Handlers
Extensions can register a link handler that runs during the linking phase with access to the cross-reference environment. The handler receives the tag name, the environment (as Obj.t to avoid a dependency on odoc_xref2 here), and already-resolved content. It returns transformed content.
type link_handler =
string ->
Stdlib.Obj.t ->
Comment.nestable_block_element Location_.with_location list ->
Comment.nestable_block_element Location_.with_location list