Publish the `Prism` global before its grammars load (fixes docs pages 500ing) (#13692)
* Publish the Prism global before its grammars load
Every https://gradio.app/docs/* page rendered for a moment and then replaced
itself with "500 Internal Error", because hydration threw
`ReferenceError: Prism is not defined` (#13677).
`prismjs/components/*` and `prism-svelte` are side-effect scripts: each does
`Prism.languages.x = ...` against a bare global and declares no imports of its
own, so nothing in the module graph orders them after prismjs. As *static*
imports the bundler is free to hoist them into a chunk that evaluates before the
`globalThis.Prism = Prism` statement that was supposed to precede them — and
rolldown compounds it by emitting prismjs as a lazily-invoked CommonJS factory,
so importing it does not initialise it either. The live chunk shows exactly
that: prismjs's factory closes and `Prism.languages.python={...}` runs on the
very next statement, with the global never assigned anywhere in the file.
The grammars in that chunk are python and typescript only, which identifies the
source as ParamViewer — rendered on every docs page — rather than the website's
own highlighter. `js/_website/src/lib/prism.ts` had the same defect though, and
it was live too: in the built html-gallery chunk its grammars sat ~10kB ahead of
its global assignment.
Dynamic imports are not hoisted, so both now assign the global first and await
the grammars after. ParamViewer gates on a promise (component init is sync, and
`highlight` already fell back to plain text) while prism.ts awaits at module
scope, keeping `highlight()` synchronous for the server `load` functions that
render the guides and changelog.
Verified against a real `VERCEL=1 vite build` of the website. Before: the build
reproduces the failing chunk byte-for-byte (D3k0kEOB.js, 22248 bytes, grammars
at offset 18830, no global). After: that chunk is gone, and every
grammar-bearing chunk is lazy-only with no static importer, so none can execute
ahead of the global.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Register the Prism grammars before ParamViewer renders
Publishing the global first stopped the `ReferenceError`, but the docs pages
still came out unhighlighted on a hard load, as @hysts found on the preview:
every parameter type rendered as plain text and only recovered after navigating
away and back.
prismjs schedules its own `highlightAll()` on a `requestAnimationFrame` as soon
as it loads. The grammar chunks are dynamic imports, so that pass runs before
they arrive, and `highlightElement` with no grammar does
`element.innerHTML = encode(element.textContent)` — wiping the server's
highlighting *and* the hydration comments the `{@html}` blocks claimed during
hydration. The derived does recompute once the grammars land, but it repaints
into nodes that are no longer in the document, so the page stays plain.
Reproduced against the real component (highlighted SSR markup as served by the
preview, hydrate, prism's auto pass, then the grammars): before, the type ends
as `value: str | Callable | None`; after, the token spans survive.
So don't need a second paint: await the grammars in `<script module>` instead of
gating a `$derived` on them. The component cannot render until they are
registered, so the client's first render agrees with the server's. The `try`
also stops a failed chunk from becoming an unhandled rejection — `highlight`
already falls back to plain text without a grammar.
This costs one round trip in front of hydration, since dynamic imports are not
preloaded; client-side navigation is unaffected as the module evaluates once.
Also drops `js/_website/vite-plugin-patch-paramviewer.ts`, dead since the
default import: its first replacement only matched `import * as Prism`, and its
second duplicates the guard already in `highlight`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>