MUI Docs Infra

Warning

This is an internal project, and is not intended for public use. No support or stability guarantees are provided.

Code Provider

The CodeProvider component provides client-side functions for fetching source code and highlighting it. It's designed for cases where you need to render code blocks or demos based on client-side state or dynamic content loading. It also provides heavy processing functions that are moved from individual components to the context for better performance and code splitting.

CodeProvider implements the Props Context Layering pattern by providing heavy functions via context that can't be serialized across the server-client boundary.

Features

  • Client-side syntax highlighting with support for multiple languages using Starry Night
  • Dynamic code fetching from external sources or APIs
  • Context-based API for sharing highlighting functions across components
  • Custom loading functions for different code sources
  • Browser-only execution with SSR safety
  • Heavy function provision for code processing (parsing, transforming, loading)

When to Use CodeProvider

Use CodeProvider when you need:

  • Dynamic code loading from external sources or APIs
  • Client-side code highlighting without build-time optimization
  • Custom code fetching logic for different data sources
  • Shared highlighting context across multiple components

Note

If you need interactive code editing with shared state management, use the CodeControllerContext instead.

Basic Usage

The simplest way to use CodeProvider is to wrap components that need client-side highlighting:

Base Code Provider

example.js
console.log('Hello, world!');
import * as React from 'react';
import { Code } from './CodeBlock';

export function BasicCode() {
  return (
    <Code fileName="example.js">{`console.log('Hello, world!');`}</Code>
  );
}

Advanced Features

Fetch Demo Code Provider

For more dynamic use cases, you can provide custom loading functions that fetch code from external sources:

import * as React from 'react';
import { CodeProviderGitHub } from './CodeProviderGitHub';
import { DemoCheckboxBasic } from './demo-basic';

export function Docs() {
  return (
    <CodeProviderGitHub>
      <DemoCheckboxBasic />
    </CodeProviderGitHub>
  );
}

Recursive variant: walking imports on demand

Recursive Fetch Demo Code Provider

The previous demo asks loadVariantMeta to enumerate every file a variant exposes up front. The recursive variant flips that around: loadCodeMeta only resolves the variant entry points, and loadSource parses each file with parseImportsAndComments and reports the relative imports as extraFiles. The framework then calls loadSource for each newly discovered file, so the import graph is walked one node at a time.

import * as React from 'react';
import { CodeProviderGitHub } from './CodeProviderGitHub';
import { DemoCheckboxBasic } from './demo-basic';

export function Docs() {
  return (
    <CodeProviderGitHub>
      <DemoCheckboxBasic />
    </CodeProviderGitHub>
  );
}

Choosing between the two approaches

ConcernEager (fetch-demo)Recursive (fetch-demo-recursive)
Loader surfaceloadCodeMeta, loadVariantMeta, loadSourceloadCodeMeta, loadSource
File discoveryOne directory listing per variant via the Contents APIImports are parsed inside loadSource; only files actually imported are fetched
Network shapeMany small Contents API calls up front, then raw fetches in parallelA short serial chain (entry → its imports → their imports), but no Contents API per file
Source assumptionsWorks even when sources don't have static imports (handy for assets, JSON, generated bundles)Requires the host to ship parseable JS/TS/MDX/CSS so parseImportsAndComments can see imports
Best forDemos that expose a known set of files (e.g. index.tsx + a sibling styles.css)Demos with deeper, evolving import trees where listing every file up front is awkward
Stale riskEach variant points at a tree URL; ref drift between variants is possible if you don't pin itPinning the entry to a commit SHA cascades to every recursively discovered file

In practice the eager variant is the right starting point for documentation that mirrors a small, hand-curated set of files. Reach for the recursive variant once a demo grows beyond a couple of files or starts importing helpers from sibling directories — at that point the savings from on-demand resolution and SHA-pinned caching outweigh the extra request round-trips.

Integration with CodeHighlighter

CodeProvider works seamlessly with CodeHighlighter for dynamic content:

import { CodeProvider } from '@mui/internal-docs-infra/CodeProvider';
import { CodeHighlighter } from '@mui/internal-docs-infra/CodeHighlighter';
import { CodeContent } from './CodeContent';

function DynamicCodeExample() {
  return (
    <CodeProvider>
      <CodeHighlighter Content={CodeContent}>{`console.log("Dynamic code!");`}</CodeHighlighter>
    </CodeProvider>
  );
}

Custom Loading Functions

You can provide custom functions for loading code from different sources:

import { CodeProvider } from '@mui/internal-docs-infra/CodeProvider';
import type { LoadCodeMeta, LoadSource } from '@mui/internal-docs-infra/CodeHighlighter';

const loadCodeFromApi: LoadCodeMeta = async (url: string) => {
  const response = await fetch(`/api/code/${encodeURIComponent(url)}`);
  return response.json();
};

const loadSourceFromGitHub: LoadSource = async (url: string) => {
  const response = await fetch(url.replace('file://', 'https://raw.githubusercontent.com/'));
  const source = await response.text();
  return { source };
};

function MyApp() {
  return (
    <CodeProvider loadCodeMeta={loadCodeFromApi} loadSource={loadSourceFromGitHub}>
      {/* Your components */}
    </CodeProvider>
  );
}

Types

Provides client-side functions for fetching source code and highlighting it. Designed for cases where you need to render code blocks or demos based on client-side state or dynamic content loading.

The heavy functions are bundled eagerly here, so they resolve instantly with no fetch — best when a layout will definitely render code. To keep them out of the initial bundle (loaded on demand, deduped across the page), use CodeProviderLazy instead.

PropTypeDescription
loadCodeMeta
LoadCodeMeta | undefined

Function to load code metadata from a URL

loadSource
LoadSource | undefined

Function to load raw source code and dependencies

loadVariantMeta
LoadVariantMeta | undefined

Function to load specific variant metadata

sourceEnhancers
SourceEnhancer[] | undefined
children
React.ReactNode

Child components that will have access to the code handling context

Worker-Backed Live Highlighting

When the runtime is a browser that supports Web Workers, CodeProvider automatically spawns a single dedicated worker per provider and uses it to syntax-highlight source code while the user is typing in an editable code block. This keeps the main thread responsive during fast keystrokes — the synchronous highlighter that runs after each commit can still take low single-digit milliseconds for large files, which is enough to drop a frame on slower devices.

Lifecycle

  1. The provider creates the worker lazily, on the first editable block (when CodeHighlighter calls the context's ensureParseSourceWorker with that block's grammar scopes) — not on mount. A read-only page never spins one up.
  2. The worker module and the per-language grammar chunks are fetched via dynamic import(...), so neither ships in SSR bundles and neither blocks first paint.
  3. Only the editable block's grammar scopes are sent across the boundary via postMessage (not all of them). The worker calls createStarryNight(grammars) and acknowledges with init-ack. If grammar creation throws, the worker posts init-error and every queued parse request rejects.
  4. While initialization is in flight, parse requests are queued and drained in arrival order on init-ack. If a later editable block needs a language the worker wasn't initialized with, the provider sends a register message (acknowledged with register-ack) to add it without recreating the worker.
  5. On unmount the worker is terminated; any still-pending requests reject with Worker terminated.

Cancellation

Each parse request accepts an optional AbortSignal. When a newer keystroke supersedes an older one, the consumer (typically useEditable's preParse hook) aborts the older signal so the worker's response is dropped on arrival rather than replacing fresher highlighted output. Pre-aborted signals reject synchronously without any cross-thread traffic.

Wiring

The worker integration is fully automatic — no opt-in is required. A consumer that wraps an editable code block in CodeProvider will see worker-backed highlighting as long as Worker is defined and the editable's host (typically useCode) provides a setSource callback. When either condition is missing the editable falls back to the synchronous main-thread highlighter and the UX is unchanged.

Note

The worker is initialized with only the editable block's grammar scopes, the same per-language chunks the main-thread parseSource loads (see Per-language grammar loading). The browser dedupes the chunk fetch across the worker and main thread, so enabling the worker does not double the grammar download cost.

Per-language grammar loading

Syntax-highlighting grammars (TextMate JSON for each language) are the heaviest part of client-side highlighting — all ten together are roughly 146 KB gzipped. Under CodeProviderLazy they are loaded per language and on demand, so a block only fetches the grammars it actually needs:

  • A read-only block that ships precomputed (already-highlighted) HAST never highlights client-side, so it loads no grammar at all.
  • A block that highlights client-side loads only its languages' chunks — a tsx+css block fetches ~45 KB instead of ~146 KB.

CodeHighlighter detects the grammar scopes a block needs from its variants' file extensions and language props, and speculatively preloads them on first render — in parallel with the content — so they are usually ready before the block parses. A block keeps showing its fallback until its grammars land (rather than flashing un-highlighted text), then highlights. Languages discovered later (e.g. a variant fetched from a URL) are loaded on demand the same way.

Note

The eager CodeProvider bundles all grammars (no per-request fetch), so this per-language behavior applies to CodeProviderLazy.

Preloading grammars at the provider level

By default each block loads its own grammars on demand. If a layout knows the languages it will render, CodeProviderLazy accepts a preloadGrammars prop to warm them at the provider level instead — a list of language names ('tsx', 'css') or scope names ('source.tsx'), or 'all' to fetch the single grammar barrel up front:

import { CodeProviderLazy } from '@mui/internal-docs-infra/CodeProvider';

export default function Layout({ children }) {
  // Warm just the languages this docs section uses.
  return <CodeProviderLazy preloadGrammars={['tsx', 'css']}>{children}</CodeProviderLazy>;
}

Use a list when a section renders a fixed, small set of languages; use 'all' when it renders many languages and a single upfront fetch beats many per-language chunks. Omit it (the default) to keep grammars fully lazy.

Best Practices

  1. Wrap at the appropriate level - Place CodeProvider high enough to cover all components that need highlighting
  2. Handle loading states - Remember that highlighting is async, so provide loading feedback
  3. Use for dynamic content - CodeProvider is perfect for code that can't be precomputed
  4. Consider performance - For static content, precomputed demos are faster
  5. Handle errors gracefully - Custom loading functions should include proper error handling

Comparison with Other Components

FeatureCodeProviderCode ControllerCodeHighlighter
PurposeClient-side highlighting & fetchingInteractive code editingDisplaying code with previews
State management× No shared state✓ Shared editing state× Component-level only
Dynamic loading✓ Custom loading functions× Static content focused✓ Various loading options
User editing× Display only✓ Real-time editing× Display only
Build optimization× Client-side only✓ Can use precomputation✓ Build-time optimization
Use caseDynamic code displayCode playgroundsDocumentation sites

Bundle weight: CodeProvider vs CodeProviderLazy

The heaviest functions are exposed through the same accessor interface by two providers, so you choose the bundle/latency tradeoff without changing any consumer. They are: the variant loader, the fallback loader, the transform-delta computer and the client-side transform applier (both pull jsondiffpatch), the live-editing engine (contentEditable setup + keystroke/caret/paste handling), the source-editing engine (the edit-time line/comment shifting), and the @highlight/@focus emphasis enhancer (~13 KB). Loaded only when a block actually needs them, this keeps a simple read-only block's useCode chunk far smaller (roughly half).

  • CodeProvider (eager) statically bundles these functions, so they resolve instantly with no fetch. Use it for a layout you know will render code - zero load delay, at the cost of a heavier initial bundle.
  • CodeProviderLazy loads them via dynamic import() on demand, keeping them out of the initial bundle. Use it as the general default. CodeHighlighter speculatively preloads the right chunk on first render (when it detects multiple variants, configured transforms, or an editable controller), in parallel with the content, so the fetch is usually in flight - or already resolved - before the content needs it.

Either way the first render is unaffected: CodeHighlighter paints its fallback synchronously and these functions only run in effects after the first paint - post-swap transforms for the loaders/delta computer, and a layout effect once a block becomes editable for the editing/source-editing engines. The synchronous parser function and the HAST decompression path stay eager in both providers — but the syntax-highlighting grammars the parser needs (~146 KB gzip for all ten) are loaded per-language and on demand under CodeProviderLazy, so a block only fetches the grammars for its languages and a precomputed read-only block fetches none (see Per-language grammar loading). The emphasis enhancer stays eager only under the eager CodeProvider (so its synchronous client-side editing re-enhancement never waits); under CodeProviderLazy it is a lazy wrapper that a precomputed block skips without loading, and that an editing/highlighting block warms before it is needed.

Deduping across a page

CodeProviderLazy renders its own PreloadProvider, so every code block in its subtree shares a single fetch of each lazy chunk, and CodeHighlighter's first-render speculative preload (kicked off when it detects it will fetch data or compute transforms) shares the same promise the eventual consumer resolves - all with no extra wiring:

import { CodeProviderLazy } from '@mui/internal-docs-infra/CodeProvider';

export default function Layout({ children }) {
  return <CodeProviderLazy>{children}</CodeProviderLazy>;
}

Cross-page network fetches of an identical chunk are deduped by the browser module cache regardless, so multiple providers never double-fetch a chunk - the built-in PreloadProvider just adds promise sharing (speculative preload ↔ consumer) within each provider's subtree.

Troubleshooting

Common Issues

Highlighting not working:

  • Ensure components are wrapped in CodeProvider
  • Check browser console for loading errors
  • Verify that code is being processed client-side

Performance issues:

  • Consider using precomputed demos for static content
  • Implement loading states for better UX
  • Cache loaded code when possible

SSR errors:

  • CodeProvider automatically handles SSR safety
  • Use forceClient on CodeHighlighter when needed
  • Ensure custom loading functions handle server/client differences