@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
177 lines (168 loc) • 7.55 kB
JavaScript
import * as React from 'react';
import { maybeCodeInitialData } from "../pipeline/loadIsomorphicCodeVariant/maybeCodeInitialData.mjs";
import { getFileNameFromUrl, getLanguageFromExtension } from "../pipeline/loaderUtils/index.mjs";
import { buildCodeHighlighterChunkProps } from "./buildCodeHighlighterChunkProps.mjs";
import { prepareInitialSource } from "./prepareInitialSource.mjs";
import { CodeHighlighterChunk } from "./CodeHighlighterChunk.mjs";
import * as Errors from "./errors.mjs";
import { jsx as _jsx } from "react/jsx-runtime";
const DEBUG = false; // Set to true for debugging purposes
/**
* Isomorphic entry for a code block. Validates and normalizes props, then maps them
* onto the generic {@link CodeHighlighterChunk} (a `createCoordinatedLazy` chunk):
* the decision inputs (`controlled`/`isInitial`/`forceClient`) computed by
* {@link buildCodeHighlighterChunkProps} route between rendering the client directly
* (precomputed content), dynamically importing the server `Loader` (load all
* variants), or the server `InitialLoader` (load a quick initial first). When a
* `ContentLoading` and an initial paint are available, the loading fallback +
* compressed residual are prepared up front via {@link prepareInitialSource}.
*
* The heavy load/parse pipeline lives behind the dynamically-imported loaders, so it
* never reaches the path that renders precomputed content.
*/
export function CodeHighlighter(props) {
// Validate mutually exclusive props
if (props.children && (props.code || props.precompute)) {
throw new Errors.ErrorCodeHighlighterServerInvalidProps();
}
// Handle children as string -> Default variant
let code = props.code;
if (props.children && typeof props.children === 'string') {
const fileName = props.fileName || (props.url ? getFileNameFromUrl(props.url).fileName : undefined);
// Derive language: use explicit prop, or derive from fileName extension
let language = props.language;
if (!language && fileName) {
const extension = fileName.slice(fileName.lastIndexOf('.'));
language = getLanguageFromExtension(extension);
}
code = {
Default: {
fileName,
language,
source: props.children,
url: props.url
}
};
}
const variants = props.variants || Object.keys(props.components || code || props.precompute || {});
if (variants.length === 0) {
throw new Errors.ErrorCodeHighlighterServerMissingData();
}
// Validate fileName is provided when extraFiles are present
if (code) {
for (const [variantName, variantCode] of Object.entries(code)) {
if (typeof variantCode === 'object' && variantCode?.extraFiles && Object.keys(variantCode.extraFiles).length > 0 && !variantCode.fileName && !variantCode.url) {
throw new Errors.ErrorCodeHighlighterServerMissingFileName(variantName);
}
}
}
const ContentLoading = props.ContentLoading;
const initialKey = props.initialVariant || props.variant || props.defaultVariant || variants[0];
// Map the props onto the chunk decision inputs (replaces the bespoke
// renderCodeHighlighter/renderWithInitialSource branching).
const {
controlled,
isInitial,
forceClient
} = buildCodeHighlighterChunkProps({
...props,
code
});
// Render the chunk. `controlled`/`isInitial`/`forceClient` drive the decision; a
// prepared `fallback`/`residualFallbacks` (and the wire `Code` as `preloaded`) are
// threaded through when an initial paint was available up front.
const renderChunk = options => {
const userProps = {
...props,
code: options.preloaded,
ContentLoading,
initialVariant: initialKey,
fallback: options.fallback,
residualFallbacks: options.residualFallbacks
// The user's content generic is erased at the chunk boundary; the real props
// ride through `Content`/`contentProps` and are rebuilt by `createClientProps`.
};
return /*#__PURE__*/_jsx(CodeHighlighterChunk, {
preloaded: options.preloaded,
controlled: controlled,
isInitial: isInitial,
forceClient: forceClient
// No ContentLoading -> no fallback to paint an initial into, so skip the
// initial-loader stage and load the full content directly.
,
skipInitialLoad: !ContentLoading
// Stream the fallback (Suspense) only when streaming the full load behind an
// already-in-hand initial; otherwise block on the loader so its content is in
// the initial HTML (the server-initial loader streams its own 2nd stage).
,
awaitServerLoad: !(isInitial && props.highlightAfter === 'stream'),
userProps: userProps
});
};
// No ContentLoading: render the content/full-load directly, with no loading
// fallback (the client shows nothing until content is ready). For
// `highlightAt: 'init'`, `createClientProps` folds each variant's highlighted-visible
// `fallbackCritical` over its plain `fallback`, so `<Pre>` paints the visible frames
// highlighted on the first render (no decompression) and decodes the full tree after
// paint (the `decodeAllowed` latch).
if (process.env.NODE_ENV !== "production") renderChunk.displayName = "renderChunk";
if (!ContentLoading) {
if (props.highlightAfter === 'stream') {
// `highlightAfter: 'stream'` needs a ContentLoading component to stream into.
throw new Errors.ErrorCodeHighlighterServerMissingContentLoading();
}
return renderChunk({
preloaded: code
});
}
const initial = code?.[initialKey] || props.precompute?.[initialKey];
if (!initial && !props.components?.[initialKey]) {
throw new Errors.ErrorCodeHighlighterServerMissingVariant(initialKey);
}
// TODO: use initial.filesOrder to determine which source to use
const {
initialData,
reason
} = maybeCodeInitialData(variants, initialKey, code || props.precompute, undefined,
// TODO: use initial.filesOrder if provided?
props.highlightAfter === 'init', props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants);
// No initial paint in hand: either the client takes over (no loader fns / forced
// client), or the server initial loader fetches a quick initial first.
if (!initialData) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.log('Initial data not found:', reason);
}
if (forceClient && props.highlightAfter === 'init') {
throw new Errors.ErrorCodeHighlighterServerInvalidClientMode();
}
return renderChunk({
preloaded: code
});
}
// Initial paint in hand: prepare the loading fallback + compressed residual, and
// send the wire `Code` as the preloaded value.
const {
fallback,
residualFallbacks,
codeForClient
} = prepareInitialSource({
...props,
code: initialData.code,
initialVariant: initialKey,
initialFilename: initialData.initialFilename,
initialSource: initialData.initialSource,
initialExtraFiles: initialData.initialExtraFiles,
ContentLoading,
// Compressing the residual fallbacks only shrinks the server→client payload. This
// entry is isomorphic, so when it runs on the client (e.g. a Pages-Router app that
// renders everything client-side) there is no wire — skip the compress and keep the
// fallbacks inline rather than compressing them only to decompress them right back.
compressResidual: typeof window === 'undefined'
});
return renderChunk({
preloaded: codeForClient,
fallback,
residualFallbacks
});
}