From abc3c74024d5f724948abdf4b54c7facfc5d45c1 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 30 Jun 2026 13:00:36 -0700 Subject: [PATCH] fix(emcn): keep Prism grammar registrations in bundle, never throw on missing grammar Consumers import { highlight, languages } from the @sim/emcn barrel, which re-exported Prism's highlight straight from prismjs. Bundlers resolved that passthrough directly from prismjs and skipped prism.ts's module body, dropping the side-effect grammar registrations so languages.json (etc.) were undefined at runtime. Prism then threw 'The language "json" has no grammar.', crashing the start-block file[] input format field and every other workflow-editor code highlighter. Own highlight as a local wrapper so the registrations stay in the dependency graph, and degrade to escaped plaintext when a grammar is missing instead of throwing. --- packages/emcn/src/components/code/prism.ts | 28 +++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/emcn/src/components/code/prism.ts b/packages/emcn/src/components/code/prism.ts index 0aebddbf6ae..9d7b7b3c635 100644 --- a/packages/emcn/src/components/code/prism.ts +++ b/packages/emcn/src/components/code/prism.ts @@ -1,4 +1,4 @@ -import { highlight, languages } from 'prismjs' +import { type Grammar, languages, highlight as prismHighlight } from 'prismjs' import 'prismjs/components/prism-javascript' import 'prismjs/components/prism-python' import 'prismjs/components/prism-json' @@ -10,10 +10,32 @@ import 'prismjs/components/prism-json' * shared `Prism.languages` registry), which marks any module that statically * imports them as having side effects and therefore non-tree-shakeable. Keeping * them here — rather than in `code.tsx` — ensures Prism only enters bundles that - * actually import `highlight`/`languages`, instead of every consumer of the - * shared `@sim/emcn` barrel (which re-exports `Code`). + * actually import these utilities, instead of every consumer of the shared + * `@sim/emcn` barrel (which re-exports `Code`). * * `code.tsx` itself never imports this module statically; it loads it lazily via * dynamic `import()` on first highlight. + * + * `highlight` is a local wrapper rather than a re-export of Prism's `highlight`. + * A bare re-export lets bundlers resolve the binding straight from `prismjs` and + * skip this module's body, dropping the grammar registrations above so + * `languages.json` (etc.) become `undefined` at runtime. Owning the function + * keeps the registrations in the dependency graph and lets us degrade to escaped + * plaintext when a grammar is missing instead of throwing. + */ + +function escapeHtml(text: string): string { + return text.replace(/&/g, '&').replace(//g, '>') +} + +/** + * Highlights `code` with the given Prism `grammar`, returning HTML markup. + * Falls back to escaped plaintext when `grammar` is undefined so a missing or + * unregistered language never throws `The language "" has no grammar.`. */ +function highlight(code: string, grammar: Grammar | undefined, language: string): string { + if (!grammar) return escapeHtml(code) + return prismHighlight(code, grammar, language) +} + export { highlight, languages }