Summary
Today, importing @openzeppelin/ui-components/code-view statically registers all highlight.js grammars in one module. Bundlers cannot drop unused languages based on the runtime language prop. Make grammar loading tree-shakeable (or otherwise optional per language) so apps that only need a subset do not pay for every grammar.
Related: add TypeScript / JavaScript / Solidity — #226. Context from CodeView in #225.
Current behaviour
// highlight.ts — all grammars are static imports
const lowlight = createLowlight({ bash, ini, json, markdown, rust });
- App never imports
./code-view: highlighter is correctly excluded (subpath boundary).
- App imports CodeView once: every registered grammar ships, regardless of which
language values are used.
Documented contract: tokenization is synchronous in render, memoized on [source, language].
Why this is non-trivial
Per-language tree-shaking needs either lazy import() (async first paint) or opt-in side-effect imports (awkward DX). Both are deliberate API/invariant changes, not a half-day cleanup.
Options to evaluate (Design)
| Approach |
Tree-shake? |
Trade-off |
Lazy grammar import() + cache; plaintext/skeleton until ready |
Yes |
Breaks pure sync first highlight; needs loading path + invariant/docs rewrite |
Opt-in side-effect entrypoints (e.g. code-view/languages/rust) |
Yes |
Hosts must import every language they use; easy to miss |
| Consumer-registered grammars |
Yes |
Larger API; fights closed private registry |
Preference to explore first: lazy load + one-frame plaintext (or last-good) fallback — cleanest product shape if we accept a contract change.
Acceptance criteria
Out of scope
Motivation
As we add TypeScript, JavaScript, and especially Solidity (#226), the shared ./code-view chunk grows for every consumer. Tree-shaking keeps that expansion from punishing apps that only need a subset.
Summary
Today, importing
@openzeppelin/ui-components/code-viewstatically registers all highlight.js grammars in one module. Bundlers cannot drop unused languages based on the runtimelanguageprop. Make grammar loading tree-shakeable (or otherwise optional per language) so apps that only need a subset do not pay for every grammar.Related: add TypeScript / JavaScript / Solidity — #226. Context from CodeView in #225.
Current behaviour
./code-view: highlighter is correctly excluded (subpath boundary).languagevalues are used.Documented contract: tokenization is synchronous in render, memoized on
[source, language].Why this is non-trivial
Per-language tree-shaking needs either lazy
import()(async first paint) or opt-in side-effect imports (awkward DX). Both are deliberate API/invariant changes, not a half-day cleanup.Options to evaluate (Design)
import()+ cache; plaintext/skeleton until readycode-view/languages/rust)Preference to explore first: lazy load + one-frame plaintext (or last-good) fallback — cleanest product shape if we accept a contract change.
Acceptance criteria
rust+plaintextdoes not include unused grammar modules in a production bundle (proven with a packed-consumer or rollup/vite size check).Out of scope
Motivation
As we add TypeScript, JavaScript, and especially Solidity (#226), the shared
./code-viewchunk grows for every consumer. Tree-shaking keeps that expansion from punishing apps that only need a subset.