Warning
This is an internal project, and is not intended for public use. No support or stability guarantees are provided.
The loadIsomorphicCodeVariant module provides utilities for loading, processing, and transforming code variants with support for syntax highlighting, TypeScript-to-JavaScript transformation, extra files, and metadata management.
Note
This module is primarily used internally by
CodeHighlighterfor runtime code loading and processing. Most users won't need to call these functions directly.
import { loadIsomorphicCodeVariant } from '@mui/internal-docs-infra/pipeline/loadIsomorphicCodeVariant';
// Load and process a single variant
const result = await loadIsomorphicCodeVariant(
'file:///app/components/button/Button.tsx',
'default',
{
fileName: 'Button.tsx',
url: 'file:///app/components/button/Button.tsx',
},
{
sourceParser: createParseSource(),
loadSource: myLoadSourceFunction,
sourceTransformers: [typescriptToJavaScript],
},
);
// Returns:
// {
// code: { fileName: 'Button.tsx', source: /* HAST nodes */, transforms: { ... } },
// dependencies: ['file:///app/components/button/Button.tsx'],
// externals: {}
// }The function:
Extra files can be specified directly in the variant, or returned automatically by your loadSource function:
// Option 1: Specify extraFiles in the variant
const result = await loadIsomorphicCodeVariant(
'file:///app/components/button/Button.tsx',
'default',
{
fileName: 'Button.tsx',
source: buttonSource,
extraFiles: {
'Button.module.css': 'file:///app/components/button/Button.module.css',
'../utils/helpers.ts': 'file:///app/components/utils/helpers.ts',
},
},
options,
);
// Option 2: Return extraFiles from loadSource (automatically loaded)
const loadSource = async (url) => {
const source = await readFile(url);
const extraFiles = await findDependencies(url); // e.g., CSS imports, relative imports
return {
source,
extraFiles: {
'Button.module.css': 'file:///app/components/button/Button.module.css',
'../utils/helpers.ts': 'file:///app/components/utils/helpers.ts',
},
};
};
// Both approaches result in all files loaded and highlighted recursively
// result.code.extraFiles contains processed CSS and helpersNote
Each
extraFilesvalue is a URL/source string or an object{ source?, transforms?, relativeUrl?, metadata?, ... }. When an entry is rewritten relative to the entry source (e.g. in'flat'mode),relativeUrlis preserved so the original file URL can be reconstructed vianew URL(relativeUrl, variant.url); otherwise the key itself resolves againstvariant.url.
Add shared dependencies (like package.json or global configs) to all variants:
const result = await loadIsomorphicCodeVariant(
'file:///app/components/button/Button.tsx',
'default',
variant,
{
...options,
globalsCode: [
{ fileName: 'package.json', source: packageJsonContent },
'file:///app/tsconfig.json',
],
},
);
// Global files are merged with metadata flag for filteringConvert code objects to HAST for rendering:
import { parseCode } from '@mui/internal-docs-infra/pipeline/loadIsomorphicCodeVariant';
const code = {
default: {
fileName: 'Example.tsx',
source: 'const x = 1;', // String source
},
};
const parsed = parseCode(code, parseSource);
// Converts string sources to HAST nodes for renderingUse sourceEnhancers to modify HAST nodes after parsing. Enhancers receive the parsed HAST root, any comments extracted from the source (via loadServerCodeSource), and the file name:
import type { SourceEnhancer, SourceComments } from '@mui/internal-docs-infra/CodeHighlighter';
import type { HastRoot } from 'hast';
// SourceComments is Record<number, string[]> - line numbers to comment arrays
// Example: Add line numbers based on comment sections
const addSectionMarkers: SourceEnhancer = (root, comments, fileName) => {
// comments is Record<number, string[]> mapping line numbers to comment arrays
// Use comments to enhance the HAST tree, e.g., add section markers
if (comments) {
for (const [lineNumber, lineComments] of Object.entries(comments)) {
// Process comments to add markers, highlights, etc.
}
}
return root;
};
// Example: Async enhancer that fetches additional data
const addTypeInfo: SourceEnhancer = async (root, comments, fileName) => {
const typeData = await fetchTypeInfo(fileName);
// Modify root with type information
return root;
};
const result = await loadIsomorphicCodeVariant(
'file:///app/components/button/Button.tsx',
'default',
variant,
{
sourceParser: createParseSource(),
loadSource: myLoadSourceFunction,
sourceEnhancers: [addSectionMarkers, addTypeInfo], // Run in order
},
);Enhancers run sequentially after parsing, each receiving the output of the previous enhancer. They are applied to both main files and extra files.
Convert nested variant structure to flat file paths:
import { flattenCodeVariant } from '@mui/internal-docs-infra/pipeline/loadIsomorphicCodeVariant';
const variant = {
url: 'file:///src/components/button/Button.tsx',
fileName: 'Button.tsx',
source: buttonSource,
extraFiles: {
'../shared/utils.ts': { source: utilsSource },
'../../package.json': { source: pkgSource, metadata: true },
},
};
const flattened = flattenCodeVariant(variant);
// Returns:
// {
// 'src/components/button/Button.tsx': { source: buttonSource },
// 'src/components/shared/utils.ts': { source: utilsSource },
// 'src/package.json': { source: pkgSource, metadata: true }
// }Determine if you have enough data to render without loading:
import { maybeCodeInitialData } from '@mui/internal-docs-infra/pipeline/loadIsomorphicCodeVariant';
const { initialData, reason } = maybeCodeInitialData(
['typescript', 'javascript'],
'typescript',
code,
undefined,
true, // needsHighlight
false, // needsAllFiles
false, // needsAllVariants
);
if (initialData) {
// Ready to render immediately
return <CodeDisplay {...initialData} />;
} else {
// Need to load more data
console.log('Missing data:', reason);
}Use loadCodeFallback to load the minimal data needed for initial render:
import { loadCodeFallback } from '@mui/internal-docs-infra/pipeline/loadIsomorphicCodeVariant';
const fallback = await loadCodeFallback(
'file:///app/demos/example/index.ts',
'default', // initialVariant
existingCode, // may be undefined
{
loadCodeMeta,
loadVariantMeta,
loadSource,
sourceParser,
variants: ['default', 'typescript'],
fallbackUsesExtraFiles: true,
fallbackUsesAllVariants: false,
},
);
// Returns minimal data for initial render:
// {
// code: { default: { ... } },
// initialFilename: 'Example.tsx',
// initialSource: /* HAST nodes */,
// allFileNames: ['Example.tsx', 'Example.module.css'],
// }Apply TypeScript-to-JavaScript transforms to rendered code:
import { applyCodeTransform } from '@mui/internal-docs-infra/pipeline/loadIsomorphicCodeVariant';
const jsSource = applyCodeTransform(
tsSource, // Original TypeScript HAST
transforms, // From variant.transforms
'javascript', // Transform key
);
// Returns JavaScript version of the source as HASTPosition metadata files (package.json, configs) relative to source files:
import { mergeCodeMetadata } from '@mui/internal-docs-infra/pipeline/loadIsomorphicCodeVariant';
const merged = mergeCodeMetadata(
variant,
{
'package.json': { source: pkgJson },
'tsconfig.json': { source: tsConfig },
},
{ metadataPrefix: 'src/' },
);
// Positions metadata files at correct depth based on source structureAnalyze variant structure for path resolution:
import { examineCodeVariant } from '@mui/internal-docs-infra/pipeline/loadIsomorphicCodeVariant';
const context = examineCodeVariant(variant);
// Returns:
// {
// hasUrl: true,
// hasMetadata: true,
// maxSourceBackNavigation: 2,
// urlDirectory: ['src', 'components', 'button'],
// rootLevel: 'src',
// pathInwardFromRoot: 'components/button',
// actualUrl: 'file:///src/components/button/Button.tsx'
// }loadVariantMetaloadSource if not providedsourceParsersourceEnhancers sequentially with comments from sourceWhen sourceEnhancers are provided, they process the HAST after parsing:
// Enhancement flow
const enhancedRoot = await sourceEnhancers.reduce(async (rootPromise, enhancer) => {
const root = await rootPromise;
return enhancer(root, comments, fileName);
}, Promise.resolve(parsedRoot));Each enhancer receives:
root - The current HAST root (output of previous enhancer)comments - Comments extracted by loadSource (e.g., from parseImportsAndComments)fileName - The file name for contextEnhancers can be synchronous or asynchronous and are applied to both main files and extra files.
Extra files support relative paths that are resolved recursively:
Button.tsx
├── Button.module.css (same directory)
├── ../utils/helpers.ts (parent directory)
└── ../../package.json (grandparent, metadata)The loader:
When sourceTransformers are provided:
.ts, .tsx, etc.)transforms objectTransforms are applied on-demand during rendering via applyCodeTransform.
Source comments (e.g. // @focus, // @padding-top) are keyed by line
number. When a transform is applied, those keys need to track the lines
they were attached to. The runtime handles this automatically for the
common case:
comments map for this case.comments map alongside each transformed source.
The returned map uses the same 0-indexed line scheme as the
transformed source string and replaces (rather than augments) the
source's original comment map for that transform.Wiped-line runs are rendered as <span class="collapse" data-lines={n}>
inside their parent frame. Demos in this repo style the placeholder via
syntax.css:
the element animates in at n * var(--docs-infra-syntax-line-height) and
transitions to height: 0 on first paint, so the collapse reads as
motion rather than an instant jump. Per-count rules cover data-lines
1–25; beyond that the stylesheet falls back to typed attr(), which
currently only Chromium implements — other engines skip the entry
animation for those runs but still render the final collapsed state
correctly.
Because the placeholder animates from a non-zero height to zero, any
content below the collapse will move up by that many lines on first
paint. To keep that motion off-screen — and avoid a perceived layout
shift — wrap the focused region of the demo in @focus /
@focus-start / @focus-end markers (see
lintJavascriptDemoFocus)
that exclude the lines that get wiped (e.g. type User = {} and
other type-only lines stripped by
transformTypescriptToJavascript).
When the wiped lines fall outside the visible-when-collapsed frame, the
placeholder lives in a hidden frame and its entry animation never
displaces visible code.
Switching to a different transform normally commits the new tree
synchronously, which means .collapse placeholders in the outgoing
tree are replaced by real .line spans in a single paint — the lines
just snap back into place. To play the reverse of the entry animation
(expand the placeholder up to its original height before the real lines
arrive), set
transformDelay
on useCode():
const { selectedFile, selectTransform } = useCode(contentProps, {
// wait 350ms after the user clicks before swapping in the new tree
transformDelay: 350,
});While a change is in flight, <Pre> annotates the rendered <pre>
with data-transforming to drive consumer CSS animations. The
attribute moves through a two-step handshake per window so the new
tree (or bridge) has a paint to settle into its start position before
the keyframe runs:
data-transforming="collapsed" (pre-swap) or
data-transforming="expanded" (post-swap). Consumer CSS should
place the bridge/added lines at the animation's starting height
without running a keyframe.data-transforming="expanding" (pre-swap)
or data-transforming="collapsing" (post-swap). The keyframe runs
from the start position established by the paused step to the end
position.The four rules in
syntax.css
(two paused holds + two active keyframes) match the placeholder height
to the lines being added or removed, so the swap reads as motion
instead of an instant jump. A subsequent click during the pending
window cancels the previous timer and re-arms with the new target.
The attribute value reflects the direction of the in-flight animation:
data-transforming="collapsed" → "expanding" — pre-swap exit
window. The OUTGOING tree's .collapse placeholders hold at 0 (the
collapsed start), then expand back to their original height (the
lines being kept by the new transform are about to reappear).data-transforming="expanded" → "collapsing" — post-swap
entry window. The INCOMING tree's .collapse placeholders hold at
their original height (the expanded start), then collapse down to 0
(the lines just removed by the new transform).The exact sequence depends on the swap direction:
null → transform — the new tree commits immediately so the
user sees the requested code right away, and the post-swap
"expanded" → "collapsing" window animates its placeholders
closed.transform → null — the pre-swap "collapsed" → "expanding"
window plays first (the outgoing placeholders open to reveal the
lines about to come back), then the swap commits to the
untransformed source.transform → transform — both halves run back-to-back:
"collapsed" → "expanding" on the outgoing tree for
transformDelay ms, the swap commits, then "expanded" →
"collapsing" on the incoming tree for another transformDelay ms
(total 2 × transformDelay).Gating the animation on data-transforming (rather than the bare
.collapse selector) keeps initial renders and unrelated re-renders
silent — only an actual transform swap triggers motion.
The metadataPrefix option positions source files within a directory while keeping metadata files at the project root:
Without metadataPrefix:
├── Button.tsx (source)
├── utils.ts (source)
├── package.json (metadata)
└── tsconfig.json (metadata)
With metadataPrefix: 'src/':
├── src/
│ ├── Button.tsx (source)
│ └── utils.ts (source)
├── package.json (metadata - stays at root)
└── tsconfig.json (metadata - stays at root)This ensures metadata files remain accessible at the project root while source files are scoped within their directory.
When NOT to use:
CodeHighlighterparseSource directly for basic needsThe loader uses Promise.all to load extra files in parallel:
// Loads all files simultaneously
extraFiles: {
'styles.css': 'url1',
'utils.ts': 'url2',
'types.ts': 'url3',
}
// All three load in parallel, not sequentiallyloadSource calls are cached within a single loadIsomorphicCodeVariant execution to prevent duplicate requests for the same file.
The loader tracks loaded files and throws errors on circular dependencies:
// This would throw an error:
// A.tsx imports ../B.tsx
// B.tsx imports ../A.tsxoutput: 'hastJson' - JSON stringified HAST (development)output: 'hastCompressed' - Compressed HAST (production, smaller bundles)Circular dependency detected:
Error: Circular dependency detected: file:///path/to/file.tsFix: Restructure imports to remove circular references.
Invalid extraFiles:
Error: Invalid extraFiles from loadSource: key "file:///..." appears to be an absolute path.Fix: Use relative paths as keys in extraFiles.
Missing loadSource:
Error: "loadSource" function is required when source is not providedFix: Provide loadSource in options or include source inline.
Transform not found:
Error: Transform "javascript" not found in transformsFix: Ensure sourceTransformers generated the requested transform.
loadIsomorphicCodeVariantLoads a variant with support for recursive extra file loading. The loadSource function can now return extraFiles that will be loaded recursively. Supports both relative and absolute paths for extra files. Uses Promise.all for efficient parallel loading of extra files.
| Parameter | Type | Description |
|---|---|---|
| url | | File URL for the variant |
| variantName | | Name of the variant (used for error messages) |
| variant | | Variant data object or URL string |
| options | | Loading and processing options (source parser, transformers, enhancers, etc.) |
Promise<{ code: VariantCode; dependencies: string[]; externals: Externals }>loadCodeFallbackLoads minimal data needed for fallback rendering. Returns code, initial filename, initial source, extra files, all file names, and processed globals code.
| Parameter | Type | Description |
|---|---|---|
| url | | File URL for the variant |
| initialVariant | | Name of the initial variant to load |
| loaded | | Previously loaded Code object, if any |
| options | | Optional loading configuration |
Promise<FallbackVariants>parseCodePure function to parse code variants and their extraFiles. Converts string sources to HAST nodes and handles hastJson parsing.
| Parameter | Type | Description |
|---|---|---|
| code | | |
| parseSource | |
CodeflattenCodeVariantFlatten a VariantCode into a flat files structure Resolves relative paths and handles metadata file scoping Uses addPathsToVariant for path resolution logic
| Parameter | Type | Description |
|---|---|---|
| variant | |
mergeCodeMetadata| Parameter | Type | Description |
|---|---|---|
| variant | | |
| metadataFiles | | |
| options | |
VariantCodeexamineCodeVariantCreate path context for processing files with extended information
| Parameter | Type | Description |
|---|---|---|
| variant | |
maybeCodeInitialDataType guard function that determines if we have sufficient data to render a code highlighter component immediately, or if we need to start loading data first.
This function acts as a validation layer to ensure we have the minimal required data to render either a fallback state or the actual code content, helping to prevent rendering errors and provide better user experience.
Usage Contexts
This function is used in two main scenarios:
Server-side rendering (CodeHighlighter): Determines if we can render with initial
source content immediately, or if we need to load fallback data via CodeInitialSourceLoader
Client-side hydration (CodeHighlighterClient): Within useInitialData hook to determine
if we should trigger loading effects or if we can render with available data
Decision Flow
The function checks data availability in this order:
needsAllVariants is true)needsAllFiles is true)needsHighlight is true)Synchronous vs Asynchronous Behavior
This function operates synchronously and only validates existing data — it never triggers any loading operations. This design is crucial for performance and rendering strategies:
initialData, avoiding async components entirelyWhen initialData: false is returned, the calling component is responsible for initiating
asynchronous loading operations (e.g., loadCodeFallback, CodeInitialSourceLoader).
| Parameter | Type | Description |
|---|---|---|
| variants | | Array of all available variant names for this code block (e.g., [‘javascript’, ‘typescript’]) |
| variant | | The specific variant we want to display (must exist in variants array) |
| code | | The code object containing all variant data (may be undefined if not loaded) |
| fileName | | Optional specific file to display. Resolution logic:
|
| needsHighlight | | Whether the code needs to be syntax highlighted (source must be highlighted object, not string) |
| needsAllFiles | | Whether all extra files must be loaded before rendering (checks that all extraFiles have source content) |
| needsAllVariants | | Whether all variants must be available before rendering (validates using hasAllVariants) |
| Key | Type | Description |
|---|---|---|
| initialData | | |
| reason | |
hasAllVariantsDetermines if all code variants are fully loaded and ready to render the complete content component.
This function validates that we have all necessary data to transition from fallback/loading state to the full interactive code highlighter. It checks both main files and extra files for all variants.
Used primarily to determine when to show the full Content component instead of ContentLoading fallback, ensuring a smooth user experience without rendering errors.
| Parameter | Type | Description |
|---|---|---|
| variants | | Array of variant names that must all be ready (e.g., [‘javascript’, ‘typescript’]) |
| code | | The code object containing variant data |
| needsHighlight | | Whether all sources need to be syntax highlighted (hast nodes, not strings) |
booleanTrue if all variants and their files are loaded and ready for full rendering
enhanceCodeAsync function to enhance parsed code variants and their extraFiles. Applies sourceEnhancers to HAST nodes, using comments stored in the variant.
| Parameter | Type | Description |
|---|---|---|
| code | | |
| sourceEnhancers | |
Promise<Code>applyCodeTransformConvenience wrapper around applyCodeTransformWithComments for
callers that don’t need the shifted comments map. Returns the transformed
VariantSource directly.
| Parameter | Type | Description |
|---|---|---|
| source | | |
| transforms | | |
| transformKey | | |
| fallback | |
VariantSourceapplyCodeTransformWithCommentsApplies a specific transform to a variant source and returns the transformed source
along with a remapped copy of the supplied comments map (when any) shifted to
line up with the renumbered dataLn values in the transformed tree.
Return shape, by input shape:
string input → string output.HastRoot, { hastJson }, or { hastCompressed })
that actually applies a delta → live HastRoot output, regardless of the
input wire shape. The serialized wire shapes are not re-emitted: every
downstream reader in this package funnels through decodeHastSource,
which accepts a live root directly, so re-stringifying / re-compressing
here would just be undone by the next consumer (and would defeat the
shared decode cache, which is keyed on payload identity). Callers
outside this package that need a serialized payload must re-encode
the returned root themselves.hasDelta: false) and unknown-transform passthrough
return the original source object untouched (same shape and identity).| Parameter | Type | Description |
|---|---|---|
| source | | The original variant source (string, |
| transforms | | Object containing all available transforms |
| transformKey | | The key of the specific transform to apply |
| comments | | Optional 1-indexed comment map keyed by the source’s original line numbers. Returned shifted so each entry now sits on the line its original source line occupies in the transformed tree; entries whose source line was wiped by the transform are dropped. |
| fallback | |
| Key | Type | Description |
|---|---|---|
| source | | |
| comments | |
applyCodeTransformsConvenience wrapper around applyCodeTransformsWithComments for
callers that don’t need the shifted comments map. Returns the transformed
VariantSource directly.
| Parameter | Type | Description |
|---|---|---|
| source | | |
| transforms | | |
| transformKeys | | |
| fallback | |
VariantSourceapplyCodeTransformsWithCommentsApplies multiple transforms to a variant source in sequence. Comments are shifted by each transform in turn so the returned map lines up with the fully-transformed source.
| Parameter | Type | Description |
|---|---|---|
| source | | The original variant source |
| transforms | | Object containing all available transforms |
| transformKeys | | Array of transform keys to apply in order |
| comments | | Optional 1-indexed comment map for the original source |
| fallback | |
| Key | Type | Description |
|---|---|---|
| source | | |
| comments | |
addPathsToVariantAdd flat paths to all files in a variant
| Parameter | Type | Description |
|---|---|---|
| variant | |
VariantCodetype FallbackVariants = {
code: Code;
initialFilename: string | undefined;
initialSource: VariantSource;
initialExtraFiles?: VariantExtraFiles;
allFileNames: string[];
processedGlobalsCode?: Code[];
}type FileWithPath = { source?: VariantSource; metadata?: boolean; path: string }type FlatFile = { source: string; metadata?: boolean }type FlattenedFiles = { [filePath: string]: FlatFile }type PathContext = PathContextWithUrl | PathContextWithoutUrlloadPrecomputedCodeHighlighter - Build-time alternativeparseSource - Syntax highlighting functionloadServerCodeSource - Server-side source loadingtransformTypescriptToJavascript - TypeScript transformer