Plugins and scripts

How the site loads and gates its own JavaScript, including the benchmarks plugin and body-end scripts

The site adds a few scripts of its own to Docsy’s, under assets/js/, loaded three ways:

  • Plugins, through Docsy’s loader
  • Hook scripts, through the body-end hook (inventory) and the head-end hook (one script, the Kapa widget’s pre-initialization)
  • Shortcode scripts, by the shortcode of the feature that needs them: the registry search form and the config-types accordion, for example

Hooks and shortcodes load a script through the script.html partial: Hugo’s js.Build bundles each file, and production builds also minify, fingerprint and add subresource integrity.

Plugins

Site plugins follow Docsy’s custom-script contract under the otel- prefix. One that renders markup of its own is gated by a shortcode that owns the markup and marks the page, so the gate follows the content into every language that serves it; the shim reads the mark. A shortcode in included content marks the included page, not the page that includes it (Page flags in included content).

Benchmarks plugin

The benchmarks shortcode charts a repository’s benchmark history, the data.js that github-action-benchmark publishes on GitHub Pages (its shape: the action’s source types). The shortcode names the data; three optional arguments say how to read it. With data_url alone, each benchmark gets a chart of its own, drawn on the commits it has data for (benchmark names must be unique within a commit):

{{< benchmarks data_url="param:otel-benchmarks.data_url" >}}

With group_separator, each chart carries several benchmarks as series, which is how the Collector benchmarks page reads its data:

{{< benchmarks data_url="param:otel-benchmarks.data_url" filter="10kDPS,10kSPS" group_separator="/" series_separator=" - " >}}

With group_separator, charts share the data set’s commit axis, so their series line up, and get a legend and a series name in the tooltip; without it, each chart has neither, its title naming the one series.

  • data_url: the action’s data.js, as https://… or as param:NAME, a page param holding it, so that the page’s prose can link the same URL through _param
  • filter (optional): keep a bench whose extra contains any of the comma-separated strings
  • group_separator (optional): split extra into a group, joined to the bench name as the chart’s title, and a series name; an extra without the separator is both
    • series_separator (optional): trim the series name at its first occurrence; without group_separator there is no series name, and the argument does nothing

The arguments’ contract, validation included, is the shortcode’s doc comment. Links and text belong in the page’s prose, where each language writes its own; the data link is also an agent’s path to the numbers, since the shortcode’s Markdown output is empty.

The otel-benchmarks plugin behind the shortcode is a fork of the action’s stock renderer. Its parts:

  • Shortcode, layouts/_shortcodes/benchmarks.html: emits the chart container and records the data URL for the shim and companion; each use renders a chart set where it stands.
  • Markdown variant, benchmarks.md beside it: empty, so the chart markup stays out of the page’s Markdown output (Agent support).
  • Shim, layouts/_partials/scripts/plugins/otel-benchmarks_docsy-shim.html (Adjust a plugin per page): disables the plugin on pages without the shortcode, which keeps the remote data script off every other page (Security).
  • Companion, layouts/_partials/scripts/plugins/otel-benchmarks.html (Plugin files): vendors Chart.js on the entry’s version pin, then loads the data file. The data stays a live remote script so the charts update between site builds, which a fixed integrity hash would forbid.
  • Script, assets/js/plugins/otel-benchmarks/ (Plugin files): the renderer, index.js, and its helpers for the legend, the content a point shows in the tooltip and the popover, and the popover itself.
  • Stylesheet, assets/scss/plugins/otel-benchmarks.scss (Plugin files): the charts’ layout, scoped to the container. The inline copies keep the stock page’s static/css/benchmarks.css, whose bare main, button and footer selectors also reach Docsy’s chrome.
  • Registration, config/_default/hugo.yaml (Configuration reference): one site-level entry, since the shim decides per page.
  • Tests, tests/public/otel-benchmarks.test.mjs: delivery on each page’s built copies; its PAGES table is the inventory of the pages on the plugin and of the localized copies still inline, whose conversion #12266 tracks.

Body-end scripts

ScriptPagesPurpose
blog-sidebar-entry-opener.jsBlog index and pager pages (acts on the first)Opens the two newest year groups in the sidebar
imageZoom.jsAllClick-to-zoom for content images
navScroll.jsAllMeant to scroll the sidebar’s active entry into view; inert today, since the active state is applied after it runs
tracing.jsNetlify builds, when unpausedWebsite telemetry, paused

Blog sidebar entry opener

blog-sidebar-entry-opener.js is the one body-end script gated to a page and the one that acts on the sidebar.

Gating to the blog index

The body-end hook renders on every page, so the opener is gated there on the page’s .Path, which is the same in every locale: {{ if eq .Path "/blog" }}.

A .Path gate can’t single out the first page of a paginated list. Hugo renders a list’s pager pages from the one section page, so .Path, .RelPermalink and .Pages are identical on /blog/ and /blog/page/2/, and the current slice is known only to the pager (.Paginator, or what .Paginate returned). Calling .Paginator on a list page that Docsy’s template leaves unpaginated, such as an empty blog, paginates it and publishes a page/1/ alias. The opener therefore loads on every pager page and skips them at runtime, by testing location.pathname for /page/.

Acting on the sidebar

The site uses Docsy’s cached sidebar (ui.sidebar_cache_limit: 1): one nav per sidebar root, shipped hidden (d-none) with no active state. Docsy’s chrome-nav.js hydrates it at DOMContentLoaded: it applies the active state and reveals the nav. The blog index, like every section’s landing page, carries that nav inline in both build kinds, so a body-end script there runs during parsing, before the reveal, and can set sidebar state before the sidebar appears; the opener checks its two year groups then. In a lean build, other pages carry a placeholder instead, which chrome-nav.js fills after fetching the nav, too late for a body-end script. With JavaScript disabled, the sidebar stays hidden.