Plugin performance notes
Reproducible baseline measurements for plugin lifecycle snapshots.
The plugin lifecycle protects configuration, pages, and results with immutable snapshots. That protection has a measurable cost because each plugin receives a fresh context snapshot. Run the repository baseline with:
The script measures five snapshots of a 1,000-page catalog and eight
sequential pageTransformed plugins over a 100-page subset. A representative
run on the development machine reported:
These values are a baseline, not a performance promise; timing and heap usage vary with the runtime and machine. The result shows that page-hook context snapshots are the dominant cost for large catalogs and many plugins. The current implementation therefore keeps the stronger immutable contract and does not silently replace it with a mutable or selectively protected context. Any future selective-snapshot or read-only-context optimization must preserve the public immutability guarantee and update this measurement.
Development reloads additionally have a regression test that performs eight consecutive reloads and verifies that the catalog remains one page and hook counts remain bounded. This checks lifecycle state isolation; it is not a claim that JavaScript heap usage is constant under every host/runtime.
Reproducible memory evidence
Run the long-running evidence probe with:
The probe creates a temporary 100-page consumer, performs 100 development
reloads, forces garbage collection when the Bun runtime exposes it, records
heap/RSS samples, and writes V8 heap snapshots plus report.json to a
retained directory under /tmp/folio-plugin-evidence-*. The temporary consumer
itself is removed after the run and is never written to the repository.
The recorded run on the development machine produced these facts:
- development hooks were called exactly
configResolved: 101,buildStart: 101, andpageTransformed: 10100for 100 pages and 100 reloads; - the catalog remained exactly 100 pages on every reload;
- after forced garbage collection, server
heapUsedchanged from37,482,159to33,116,140bytes (-4,366,019), while RSS changed by+6,524,928bytes; - samples after the initial warm-up stayed around 33–40 MB heap rather than growing linearly with reload count;
- the production build completed and called the plugin once for
configResolved, once forbuildStart, and 100 times forpageTransformed.
The production build process changed RSS by +836,321,280 bytes in this run.
That is an observation about the complete Vite/Shiki build process, not proof
of a plugin-only leak; the probe therefore reports it without treating it as
an unexplained plugin regression. The heap snapshots provide inspectable
artifacts and a name-inventory diff, but a retained-path review in Chrome
DevTools is still required before claiming a mathematical proof that no host
or dependency retains memory.
The retained-path review was then performed against the latest snapshot pair
with a heap-graph traversal from (GC roots), excluding weak edges. The
after-snapshot contained one reachable FolioBuildSession, which was the
current session. The current catalog was retained through that session's
lifecycle -> pages path. The additional lifecycle-manager object found in
the after-snapshot was unreachable from GC roots through strong edges, so it
was eligible for collection rather than being retained by the reload loop.
No second reachable session or reload-proportional session chain was found.
This is evidence for the tested 100-reload scenario, not a universal guarantee
about arbitrary host integrations.
The browser HMR probe (bun run test:browser-hmr) performs 25 consecutive
MDX/plugin reloads and forces browser garbage collection before each sample.
The final run stayed at 2 documents, 270 DOM nodes, and 38 event listeners
throughout the sampled sequence. The final JS heap was 19,127,336 bytes
versus 18,987,688 before the stress sequence (+139,648 bytes). It also
verified the final rendered heading and document title, so the memory check is
coupled to real browser behavior rather than a server-only lifecycle
assertion.