Skip to content

Iterate README renderer pipeline#462

Merged
kaste merged 5 commits into
gh-pagesfrom
cache-readme-rendering
Jul 22, 2026
Merged

Iterate README renderer pipeline#462
kaste merged 5 commits into
gh-pagesfrom
cache-readme-rendering

Conversation

@kaste

@kaste kaste commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator
  • Reduce README renderer memory footprint and runtime

Obviously we had a bug here, and memory growth was 600kB per rendered README.
It is important to tackle this besides the #461 because we always have fresh/stale
states at some point. And with that growth, we quickly can't produce the initial
render without going out-of-memory.

But that over cached clearly as it only invalidates the cache if the source, the
raw markdown text, changed. Obviously, our feature set, the renderer itself,
can change and must invalidate the previously rendered files.

The third commit tries to tackle that.

Finally, use the feature set in the CI pipeline.

Convenient: also check the cached READMEs during dev. No surprises if we ever hack on the renderer again.

Closes #461

kaste and others added 5 commits July 22, 2026 12:04
Avoid the jsdom DOMParser and DOMPurify string sanitization paths in
render_readmes.mjs. Those paths retained substantial DOM state during a
cold README render and grew heap roughly linearly with package count.

Profiling the old implementation on 4,892 READMEs showed a peak working
set around 3.5 GB and a runtime around 30 seconds. The patched version
uses detached template fragments for post-processing and asks DOMPurify
for a DOM fragment before serializing it back to HTML.

The same cold render now peaks around 550 MB and runs in about 18
seconds. That is roughly 84% less peak memory and 39% faster for the
current corpus. The remaining growth is small enough that 10k packages
should stay well below the default Node heap limit, instead of risking an
out-of-memory failure around 6k-7k.

Security behavior is preserved: DOMPurify is still the final sanitizer,
with the same default policy. Regression checks rendered all 4,892
README files byte-for-byte identically to the existing
readmes_rendered.json, and static/readme-renderer.test.mjs passed.
The readme rendering is slow and memory intensive, but there's no reason
to redo all that work every time. We now store a hash of the text used
to render the readme inside readmes_renderes.json to avoid re-rendering.
Keep the per-README cache hash focused on the source README text, and
record the renderer context once in readmes_rendered.json as the
__environment entry.

The renderer environment hash includes render_readmes.mjs, the shared
README renderer, this cache helper, and package-lock.json. If any of
those inputs change, the prerender file is treated as coming from a
changed rendering environment and all entries are re-computed.
Restore readmes_rendered.json before running the build so
render_readmes.mjs can reuse unchanged entries across workflow runs.

Use a broad rolling Actions cache key and leave cache correctness to the
script's per-entry hashes.
Check the rendered README environment marker when Eleventy starts. If the
prerendered file was built with a different renderer environment, discard
it instead of injecting stale cached HTML into package pages.

This keeps renderer hacking honest: local builds and the dev server fall
back to the existing browser fetch/render path until
readmes_rendered.json is regenerated with the current renderer.
@kaste
kaste enabled auto-merge July 22, 2026 11:45
@kaste
kaste merged commit 4267770 into gh-pages Jul 22, 2026
2 checks passed
@kaste
kaste deleted the cache-readme-rendering branch July 22, 2026 11:46
@kaste kaste mentioned this pull request Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants