You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[lexical-code-prism] Production client builds throw Prism is not defined #9857
Apps bundling @lexical/code-prism for production with Rolldown/Vite throw Prism is not defined at startup when a route that highlights code loads.
packages/lexical-code-prism/src/FacadePrism.ts loads the core with a bare import 'prismjs' (CommonJS) followed by the language components (plain scripts that read the bare Prism global). The bare import gives bundlers nothing to order on, so the CommonJS init can sink past the component scripts: they evaluate with no global Prism. The module then reads the result through globalThis.Prism || window.Prism, which is likewise unset.
Minimal shape that throws (Vite/Rolldown production build, single route chunk):
The shared-chunk variant (language scripts in an earlier-evaluating chunk than the core's require call) throws unconditionally. Reordering so the core initializes first, plus reading it through the module namespace so bundlers must initialize it ahead of the module body, fixes both shapes.
I have a fix incoming as a PR: namespace-import the core first and read the export from the namespace (keeping the pre-existing-global adoption behavior).
Found the failing layout, with a repro. Posting it here so the follow-up has something to point its test at.
The layout that throws on main
A Vite/Rolldown production build with aggressive tree-shaking — treeshake.moduleSideEffects: false — using the single-chunk facade shape from this issue (bare import 'prismjs', plain-script language components, result read through globalThis.Prism).
Bare import: the entire core and all language components are shaken out. The output bundle is 0.87 kB vs 20.18 kB — all that remains is a dead globalThis.Prism; read, so Prism is unset and clike is missing. The import 'prismjs' is eliminated because nothing in the graph uses its value.
Reproduced both with raw rolldown 1.3.1 (treeshake: {moduleSideEffects: false}) and with Vite 8.3.7 (same flag via build.rolldownOptions.treeshake), prismjs 1.30.0, single route chunk. This also explains why the pinned-bundler matrix in #9858 saw nothing: under default tree-shaking the bare import is kept, and both forms evaluate identically.
The shared-chunk shape (a component-only chunk evaluating before the core chunk) throws identically before and after — verified on Vite 8.3.7, esbuild 0.28.2, and rolldown 1.3.1. A namespace import can't pull the core init into an earlier chunk, so that shape needs app-level ordering (core imported in the entry/shared chunk), not a FacadePrism change. Saying this plainly so the follow-up isn't expected to cover it.
Proposed follow-up
Keep-both, as suggested on the revert: the bare import 'prismjs' first (preserves the eager order that #9877 restored for loaders that defer named requires — Metro inlineRequires, Babel lazy, and similar setups), then import * as PrismCore from 'prismjs' to read the value (survives the DCE layout above — I verified keep-both passes it too). With a regression test built on this layout: build the facade with moduleSideEffects: false and assert the clike grammar is present. Happy to put that PR up.
Activity
[lexical-code-prism] Bug Fix: initialize the Prism core before its la…
nbozidarevic commentedon Nov 11, 2026
Found the failing layout, with a repro. Posting it here so the follow-up has something to point its test at.
The layout that throws on main
A Vite/Rolldown production build with aggressive tree-shaking —
treeshake.moduleSideEffects: false— using the single-chunk facade shape from this issue (bareimport 'prismjs', plain-script language components, result read throughglobalThis.Prism).globalThis.Prism;read, soPrismis unset andclikeis missing. Theimport 'prismjs'is eliminated because nothing in the graph uses its value.globalThis.Prism ?? PrismCorereads the binding, so the core is a used value import and survives. Full core + components, evaluates clean.Reproduced both with raw rolldown 1.3.1 (
treeshake: {moduleSideEffects: false}) and with Vite 8.3.7 (same flag viabuild.rolldownOptions.treeshake), prismjs 1.30.0, single route chunk. This also explains why the pinned-bundler matrix in #9858 saw nothing: under default tree-shaking the bare import is kept, and both forms evaluate identically.What #9858 does not cover
The shared-chunk shape (a component-only chunk evaluating before the core chunk) throws identically before and after — verified on Vite 8.3.7, esbuild 0.28.2, and rolldown 1.3.1. A namespace import can't pull the core init into an earlier chunk, so that shape needs app-level ordering (core imported in the entry/shared chunk), not a FacadePrism change. Saying this plainly so the follow-up isn't expected to cover it.
Proposed follow-up
Keep-both, as suggested on the revert: the bare
import 'prismjs'first (preserves the eager order that #9877 restored for loaders that defer named requires — MetroinlineRequires, Babellazy, and similar setups), thenimport * as PrismCore from 'prismjs'to read the value (survives the DCE layout above — I verified keep-both passes it too). With a regression test built on this layout: build the facade withmoduleSideEffects: falseand assert theclikegrammar is present. Happy to put that PR up.