@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
591 lines (557 loc) • 22.2 kB
JavaScript
import { loadIsomorphicCodeVariant } from "./loadIsomorphicCodeVariant.mjs";
import { getFileNameFromUrl, getLanguageFromExtension } from "../loaderUtils/index.mjs";
import { performanceMeasure } from "../loadPrecomputedCodeHighlighter/performanceLogger.mjs";
// Helper function to get the source for a specific filename from a variant
async function getFileSource(variant, requestedFilename, loadSource) {
const filename = requestedFilename || variant.fileName;
if (!filename) {
// If no filename is available, return the main variant source
if (variant.source !== undefined) {
return {
source: variant.source,
filename: undefined
};
}
throw new Error('No filename available and no source in variant');
}
// If requesting the main file and we have its source
if (filename === variant.fileName && variant.source !== undefined) {
return {
source: variant.source,
filename
};
}
// If requesting the main file but only have its URL, load it
if (filename === variant.fileName && variant.url && loadSource) {
const loadResult = await loadSource(variant.url);
return {
source: loadResult.source,
filename,
comments: loadResult.comments,
externals: loadResult.externals
};
}
// If requesting an extra file and we have its source
if (filename !== variant.fileName && variant.extraFiles) {
const extraFile = variant.extraFiles[filename];
if (extraFile && typeof extraFile !== 'string' && extraFile.source !== undefined) {
return {
source: extraFile.source,
filename
};
}
// If we have the URL but not the source, we need to load it
if (typeof extraFile === 'string' && loadSource) {
const loadResult = await loadSource(extraFile);
return {
source: loadResult.source,
filename,
comments: loadResult.comments,
externals: loadResult.externals
};
}
if (extraFile && typeof extraFile !== 'string' && !extraFile.source && loadSource) {
// This case shouldn't normally happen, but handle it anyway
throw new Error(`Extra file ${filename} has no source or URL to load from`);
}
}
throw new Error(`File ${filename} not found in variant or cannot be loaded`);
}
/**
* Persist `comments`/`externals` produced by `loadSource` onto the cached
* variant so downstream consumers (enhancers via `useFileNavigation`, export
* via `useDemo`) can read them without re-fetching the file. Mirrors how
* `loadIsomorphicCodeVariant` shapes these fields on the returned variant.
*/
function enrichVariantWithLoadedSource(base, source, comments, externals) {
const enriched = {
...base,
source
};
// `loadSource` returns 1-indexed comments (the stored `Code` convention), so apply them
// as-is — no conversion at the source-loader boundary.
if (comments) {
enriched.comments = comments;
}
if (externals && Object.keys(externals).length > 0) {
enriched.externals = Object.keys(externals);
}
return enriched;
}
/**
* Loads minimal data needed for fallback rendering.
* Returns code, initial filename, initial source, extra files, all file names,
* and processed globals code.
*
* @param url - File URL for the variant
* @param initialVariant - Name of the initial variant to load
* @param loaded - Previously loaded Code object, if any
* @param options - Optional loading configuration
*/
export async function loadCodeFallback(url, initialVariant, loaded, options = {}) {
const {
shouldHighlight,
fallbackUsesExtraFiles,
fallbackUsesAllVariants,
sourceParser,
loadSource,
loadVariantMeta,
loadCodeMeta,
sourceEnhancers,
initialFilename,
variants,
globalsCode,
output,
urlPrefix
} = options;
loaded = {
...loaded
};
// When not highlighting (deferred), pass `disableParsing: true`: the source must
// stay a plain string so the client knows it still needs syntax highlighting (a
// HAST source reads as "already loaded"). `loadIsomorphicCodeVariant` still frames
// the loading fallback itself in that mode — line gutters + enhancers → root
// fallback — only the syntax colors are deferred. When highlighting, the real
// `sourceParser` runs and the source becomes a highlighted HAST as before.
const functionName = 'Load Fallback Code';
let currentMark = performanceMeasure(undefined, {
mark: 'Start',
measure: 'Start'
}, [functionName, url], true);
// Step 1: Ensure we have the initial variant loaded
let initial = loaded[initialVariant];
if (!initial) {
if (!loadCodeMeta) {
throw new Error('"loadCodeMeta" function is required when initial variant is not provided');
}
try {
loaded = await loadCodeMeta(url);
} catch (error) {
throw new Error(`Failed to load code from URL: ${url}. Error: ${JSON.stringify(error)}`);
}
initial = loaded[initialVariant];
if (!initial) {
throw new Error(`Initial variant "${initialVariant}" not found in loaded code.`);
}
currentMark = performanceMeasure(currentMark, {
mark: 'Loaded Code Meta',
measure: 'Code Meta Loading'
}, [functionName, url]);
}
// Check if we can return early after loadCodeMeta
if (typeof initial !== 'string' && initial.allFilesListed && !fallbackUsesExtraFiles && !fallbackUsesAllVariants) {
// Collect all file names from the loaded code
const allFileNames = new Set();
if (initial.fileName) {
allFileNames.add(initial.fileName);
}
if (initial.extraFiles) {
Object.keys(initial.extraFiles).forEach(fileName => allFileNames.add(fileName));
}
// Get the source for the requested filename (or main file if not specified)
let fileSource;
let actualFilename;
let loadedComments;
let loadedExternals;
try {
const result = await getFileSource(initial, initialFilename, loadSource);
fileSource = result.source;
actualFilename = result.filename;
loadedComments = result.comments;
loadedExternals = result.externals;
} catch (error) {
throw new Error(`Failed to get source for file ${initialFilename || initial.fileName} in variant ${initialVariant}: ${error}`);
}
currentMark = performanceMeasure(currentMark, {
mark: 'Loaded Main File',
measure: 'Main File Loading'
}, [functionName, url]);
// If we need highlighting and have a string source, parse it
if (shouldHighlight && typeof fileSource === 'string' && sourceParser && actualFilename) {
try {
const parseSource = await sourceParser;
fileSource = parseSource(fileSource, actualFilename);
} catch (error) {
throw new Error(`Failed to parse source for highlighting (variant: ${initialVariant}, file: ${actualFilename}): ${JSON.stringify(error)}`);
}
currentMark = performanceMeasure(currentMark, {
mark: 'Parsed Main File',
measure: 'Main File Parsing'
}, [functionName, url]);
} else if (shouldHighlight && typeof fileSource === 'string' && !actualFilename) {
// Create basic HAST node when we can't parse due to missing filename
// This marks that the source has passed through the parsing pipeline
fileSource = {
type: 'root',
children: [{
type: 'text',
value: fileSource
}]
};
}
// Update the loaded code with any changes we made.
if (actualFilename && actualFilename === initial.fileName || !actualFilename && !initial.fileName) {
initial = enrichVariantWithLoadedSource(initial, fileSource, loadedComments, loadedExternals);
loaded = {
...loaded,
[initialVariant]: initial
};
}
// Early return - we have all the info we need
return {
code: loaded,
initialFilename: actualFilename,
initialSource: fileSource,
initialExtraFiles: initial.extraFiles || {},
allFileNames: Array.from(allFileNames)
};
}
// Step 2: Try to get variant metadata quickly first
if (typeof initial === 'string') {
try {
let quickVariant;
if (loadVariantMeta) {
// Use provided loadVariantMeta function
quickVariant = await loadVariantMeta(initialVariant, initial);
currentMark = performanceMeasure(currentMark, {
mark: 'Loaded Initial Variant Meta',
measure: 'Initial Variant Meta Loading'
}, [functionName, url]);
} else {
// Create a basic variant using fallback logic
const derivedFileName = getFileNameFromUrl(initial).fileName;
const extension = derivedFileName.slice(derivedFileName.lastIndexOf('.'));
quickVariant = {
url: initial,
fileName: derivedFileName,
language: getLanguageFromExtension(extension)
};
}
const beforeInitialVariantMark = currentMark;
loaded = {
...loaded,
[initialVariant]: quickVariant
};
initial = quickVariant;
// If we have all files listed and don't need extra file processing, we can optimize
if (quickVariant.allFilesListed && !fallbackUsesExtraFiles && !fallbackUsesAllVariants) {
// Collect all file names from the quick load
const allFileNames = new Set();
if (quickVariant.fileName) {
allFileNames.add(quickVariant.fileName);
}
if (quickVariant.extraFiles) {
Object.keys(quickVariant.extraFiles).forEach(fileName => allFileNames.add(fileName));
}
// Get the source for the requested filename (or main file if not specified)
let fileSource;
let actualFilename;
let loadedComments;
let loadedExternals;
try {
const result = await getFileSource(quickVariant, initialFilename, loadSource);
fileSource = result.source;
actualFilename = result.filename;
loadedComments = result.comments;
loadedExternals = result.externals;
currentMark = performanceMeasure(currentMark, {
mark: 'Loaded Initial File',
measure: 'Initial File Loading'
}, [functionName, initialFilename || 'unknown', url]);
} catch (error) {
throw new Error(`Failed to get source for file ${initialFilename || quickVariant.fileName} in variant ${initialVariant}: ${error}`);
}
// If we need highlighting and have a string source, parse it
if (shouldHighlight && typeof fileSource === 'string' && sourceParser && actualFilename) {
try {
const parseSource = await sourceParser;
fileSource = parseSource(fileSource, actualFilename);
currentMark = performanceMeasure(currentMark, {
mark: 'Parsed Initial File',
measure: 'Initial File Parsing'
}, [functionName, initialFilename || 'unknown', url]);
} catch (error) {
throw new Error(`Failed to parse source for highlighting (variant: ${initialVariant}, file: ${actualFilename}): ${JSON.stringify(error)}`);
}
} else if (shouldHighlight && typeof fileSource === 'string' && !actualFilename) {
// Create basic HAST node when we can't parse due to missing filename
// This marks that the source has passed through the parsing pipeline
fileSource = {
type: 'root',
children: [{
type: 'text',
value: fileSource
}]
};
}
// Update the loaded code with any changes we made.
if (actualFilename && actualFilename === quickVariant.fileName || !actualFilename && !quickVariant.fileName) {
initial = enrichVariantWithLoadedSource(quickVariant, fileSource, loadedComments, loadedExternals);
loaded = {
...loaded,
[initialVariant]: initial
};
}
currentMark = performanceMeasure(beforeInitialVariantMark, {
mark: 'Loaded Initial Files',
measure: 'Initial Files Loading'
}, [functionName, url], true);
// Early return - we have all the info we need
return {
code: loaded,
initialFilename: actualFilename,
initialSource: fileSource,
initialExtraFiles: quickVariant.extraFiles || {},
allFileNames: Array.from(allFileNames)
};
}
} catch (error) {
throw new Error(`Failed to load initial variant code (variant: ${initialVariant}, url: ${initial}): ${JSON.stringify(error)}`);
}
}
const beforeGlobalsMark = currentMark;
// Step 2b: Fall back to full loadIsomorphicCodeVariant processing
// Load globalsCode - convert string URLs to Code objects, keep Code objects as-is
let globalsCodeObjects;
if (globalsCode && globalsCode.length > 0) {
const hasStringUrls = globalsCode.some(item => typeof item === 'string');
if (hasStringUrls && !loadCodeMeta) {
throw new Error('loadCodeMeta function is required when globalsCode contains string URLs');
}
// Load all string URLs in parallel, keep Code objects as-is
const globalsPromises = globalsCode.map(async globalItem => {
if (typeof globalItem === 'string') {
// String URL - load Code object via loadCodeMeta
try {
const codeMeta = await loadCodeMeta(globalItem);
currentMark = performanceMeasure(currentMark, {
mark: 'Loaded Global Code Meta',
measure: 'Global Code Meta Loading'
}, [functionName, globalItem, url]);
return codeMeta;
} catch (error) {
throw new Error(`Failed to load globalsCode from URL: ${globalItem}. Error: ${JSON.stringify(error)}`);
}
} else {
// Code object - return as-is
return globalItem;
}
});
globalsCodeObjects = await Promise.all(globalsPromises);
currentMark = performanceMeasure(beforeGlobalsMark, {
mark: 'Loaded Globals Meta',
measure: 'Globals Meta Loading'
}, [functionName, url], true);
}
// Convert globalsCodeObjects to VariantCode | string for this specific variant
let resolvedGlobalsCode;
if (globalsCodeObjects && globalsCodeObjects.length > 0) {
resolvedGlobalsCode = [];
for (const codeObj of globalsCodeObjects) {
// Only use the variant that matches the current initialVariant
const targetVariant = codeObj[initialVariant];
if (targetVariant) {
resolvedGlobalsCode.push(targetVariant);
}
}
}
try {
const {
code: loadedVariant
} = await loadIsomorphicCodeVariant(url, initialVariant, initial, {
sourceParser,
loadSource,
loadVariantMeta,
sourceTransformers: undefined,
// sourceTransformers - skip transforms for fallback
sourceEnhancers,
disableTransforms: true,
// Don't apply transforms for fallback
// Deferred highlight: keep the source a plain string, but still frame the
// loading fallback (plain-text gutters + enhancers → root fallback).
disableParsing: !shouldHighlight,
framePlainFallback: true,
globalsCode: resolvedGlobalsCode,
// Pass resolved globalsCode
output,
urlPrefix
});
currentMark = performanceMeasure(currentMark, {
mark: 'Loaded Initial Variant',
measure: 'Initial Variant Loading'
}, [functionName, url], true);
// Update the loaded code with the processed variant
loaded = {
...loaded,
[initialVariant]: loadedVariant
};
initial = loadedVariant;
} catch (error) {
throw new Error(`Failed to load initial variant using loadIsomorphicCodeVariant (variant: ${initialVariant}, url: ${url}): ${JSON.stringify(error)}`);
}
// Step 3: Collect all file names
const allFileNames = new Set();
if (initial.fileName) {
allFileNames.add(initial.fileName);
}
// Add extra files from the initial variant
if (initial.extraFiles) {
Object.keys(initial.extraFiles).forEach(fileName => allFileNames.add(fileName));
}
// Step 4: Handle fallbackUsesAllVariants - load all variants to get all possible files
if (fallbackUsesAllVariants) {
const beforeAllVariantMark = currentMark;
// Determine all variants to process - use provided variants or infer from loaded code
const allVariants = variants || Object.keys(loaded || {});
if (allVariants.length === 0) {
console.warn('No variants found for fallbackUsesAllVariants processing');
} else {
// Process all required variants, not just the ones already loaded
const variantPromises = allVariants.map(async variantName => {
if (variantName === initialVariant) {
// Skip initial variant as it's already processed
return {
variantName,
loadedVariant: null,
fileNames: []
};
}
let variant = loaded?.[variantName];
// If variant is not loaded yet, load it first using loadCodeMeta
if (!variant && loadCodeMeta) {
try {
const allCode = await loadCodeMeta(url);
variant = allCode[variantName];
// Update loaded with all variants from loadCodeMeta
loaded = {
...loaded,
...allCode
};
currentMark = performanceMeasure(currentMark, {
mark: 'Loaded Initial Code Meta',
measure: 'Initial Code Meta Loading'
}, [functionName, url]);
} catch (error) {
console.warn(`Failed to load code meta for variant ${variantName}: ${error}`);
return {
variantName,
loadedVariant: null,
fileNames: []
};
}
}
if (!variant) {
console.warn(`Variant ${variantName} not found after loading code meta`);
return {
variantName,
loadedVariant: null,
fileNames: []
};
}
try {
const {
code: loadedVariant
} = await loadIsomorphicCodeVariant(url, variantName, variant, {
sourceParser,
loadSource,
loadVariantMeta,
sourceTransformers: undefined,
// sourceTransformers
sourceEnhancers,
disableTransforms: true,
disableParsing: !shouldHighlight,
framePlainFallback: true,
output,
urlPrefix,
globalsCode: globalsCodeObjects && globalsCodeObjects.length > 0 ? (() => {
// Convert globalsCodeObjects to VariantCode | string for this specific variant
const variantGlobalsCode = [];
for (const codeObj of globalsCodeObjects) {
// Only use the variant that matches the current variantName
const targetVariant = codeObj[variantName];
if (targetVariant) {
variantGlobalsCode.push(targetVariant);
}
}
return variantGlobalsCode;
})() : undefined
});
// Collect file names from this variant
const fileNames = loadedVariant.fileName ? [loadedVariant.fileName] : [];
if (loadedVariant.extraFiles) {
fileNames.push(...Object.keys(loadedVariant.extraFiles));
}
currentMark = performanceMeasure(currentMark, {
mark: 'Loaded Initial Variant',
measure: 'Initial Variant Loading'
}, [functionName, variantName, url], true);
return {
variantName,
loadedVariant,
fileNames
};
} catch (error) {
// Log but don't fail - we want to get as many file names as possible
console.warn(`Failed to load variant ${variantName} for file listing: ${error}`);
return {
variantName,
loadedVariant: null,
fileNames: []
};
}
});
const variantResults = await Promise.all(variantPromises);
// Update loaded code and collect file names
variantResults.forEach(({
variantName,
loadedVariant,
fileNames
}) => {
if (loadedVariant) {
loaded = {
...loaded,
[variantName]: loadedVariant
};
}
fileNames.forEach(fileName => allFileNames.add(fileName));
});
}
currentMark = performanceMeasure(beforeAllVariantMark, {
mark: 'Loaded Initial Variants',
measure: 'Initial Variants Loading'
}, [functionName, url], true);
}
// Ensure we have the latest initial variant data
const finalInitial = loaded[initialVariant];
if (!finalInitial || typeof finalInitial === 'string') {
throw new Error(`Failed to process initial variant: ${initialVariant}`);
}
// Get the source for the requested filename (or main file if not specified) for the final return
let finalFileSource;
let finalFilename;
try {
const result = await getFileSource(finalInitial, initialFilename, loadSource);
finalFileSource = result.source;
finalFilename = result.filename;
} catch (error) {
// If we can't get the specific file, fall back to main file
if (!finalInitial.fileName && !finalInitial.source) {
throw new Error(`Cannot determine filename for initial variant "${initialVariant}". ` + `No fileName available in variant definition, no initialFilename provided, and no source available.`);
}
// Fall back to the main file with proper validation
finalFileSource = finalInitial.source || '';
finalFilename = finalInitial.fileName;
}
currentMark = performanceMeasure(currentMark, {
mark: 'Loaded Initial File',
measure: 'Initial File Loading'
}, [functionName, url]);
return {
code: loaded,
initialFilename: finalFilename,
initialSource: finalFileSource,
initialExtraFiles: finalInitial.extraFiles || {},
allFileNames: Array.from(allFileNames),
processedGlobalsCode: globalsCodeObjects
};
}