@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
274 lines (257 loc) • 10.1 kB
JavaScript
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { toKebabCase } from "../loaderUtils/toKebabCase.mjs";
import { nameMark, performanceMeasure } from "../loadPrecomputedCodeHighlighter/performanceLogger.mjs";
import { highlightTypes } from "./highlightTypes.mjs";
import { highlightTypesMeta } from "./highlightTypesMeta.mjs";
import { syncTypes } from "../syncTypes/index.mjs";
import { loadServerTypesText } from "../loadServerTypesText/index.mjs";
const functionName = 'Load Server Types';
/**
* Server-side function for loading and processing TypeScript types.
*
* This function:
* 1. Either syncs types from source (sync: true) or loads from existing types.md (sync: false)
* 2. Applies syntax highlighting to markdown content via highlightTypes
* 3. Highlights type fields with HAST via highlightTypesMeta
*
* The pipeline is:
* - sync: true: syncTypes extracts types, formats to plain text, generates markdown
* - sync: false: loadServerTypesText reads and parses an existing types.md file
* - highlightTypes: highlights markdown code blocks, builds highlightedExports map
* - highlightTypesMeta: converts type text to HAST, derives shortType/detailedType
*/
export async function loadServerTypes(options) {
const {
typesMarkdownPath,
rootContext,
formattingOptions,
sync = false,
output = 'hast',
codeBlockEmphasisOptions
} = options;
// Derive relative path for logging
const relativePath = path.relative(rootContext, typesMarkdownPath);
let currentMark = nameMark(functionName, 'Start Loading', [relativePath]);
performance.mark(currentMark);
// Either sync types from source or load from existing markdown
const syncResult = sync ? await syncTypes(options) : await loadServerTypesText(pathToFileURL(typesMarkdownPath).href, options.ordering);
currentMark = performanceMeasure(currentMark, {
mark: 'types loaded',
measure: sync ? 'type syncing' : 'types.md loading'
}, [functionName, relativePath]);
// Compute slugs for all types
// Determine the common component prefix from the first dotted name (e.g., "Accordion")
let componentPrefix = '';
for (const exportData of Object.values(syncResult.exports)) {
if (exportData.type.name.includes('.')) {
componentPrefix = exportData.type.name.split('.')[0];
break;
}
}
const computeSlug = name => {
if (name.includes('.')) {
const parts = name.split('.');
if (parts[0] === componentPrefix && parts.length > 1) {
// Strip the component prefix, keep the rest
return parts.slice(1).join('.').toLowerCase();
}
// No prefix match, use the full name
return name.toLowerCase();
}
// Non-dotted name: use as-is
return name.toLowerCase();
};
// Assign slugs to all types in exports
for (const exportData of Object.values(syncResult.exports)) {
exportData.type.slug = computeSlug(exportData.type.name);
for (const addType of exportData.additionalTypes) {
addType.slug = computeSlug(addType.name);
}
}
// Assign slugs to top-level additional types
for (const addType of syncResult.additionalTypes) {
addType.slug = computeSlug(addType.name);
}
// Assign slugs to variant-only additional types
for (const variantTypes of Object.values(syncResult.variantOnlyAdditionalTypes)) {
for (const addType of variantTypes) {
addType.slug = computeSlug(addType.name);
}
}
// Apply syntax highlighting and enhancement to each export's types, maintaining structure
const highlightStart = performance.now();
// Process each export in parallel to maintain the organized structure
const exportEntries = Object.entries(syncResult.exports);
// Collect ALL types to build a shared rawTypeProperties map.
// This allows the enhancement stage to convert named return type references into property tables.
const allTypes = [];
for (const [, exportData] of exportEntries) {
allTypes.push(exportData.type, ...exportData.additionalTypes);
}
allTypes.push(...syncResult.additionalTypes);
for (const variantTypes of Object.values(syncResult.variantOnlyAdditionalTypes)) {
allTypes.push(...variantTypes);
}
const sharedRawTypeProperties = {};
for (const typeMeta of allTypes) {
if (typeMeta.type === 'raw' && typeMeta.data.properties) {
sharedRawTypeProperties[typeMeta.data.name] = typeMeta.data.properties;
}
}
const processedExports = await Promise.all(exportEntries.map(async ([exportName, exportData]) => {
const exportTypes = [exportData.type, ...exportData.additionalTypes];
const highlightResult = await highlightTypes(exportTypes, syncResult.externalTypes, output, codeBlockEmphasisOptions);
const highlightedTypes = await highlightTypesMeta(highlightResult.types, {
highlightedExports: highlightResult.highlightedExports,
rawTypeProperties: sharedRawTypeProperties,
formatting: formattingOptions,
output,
codeBlockEmphasisOptions
});
// First highlighted type is the main export type, rest are additional
const [mainType, ...additionalHighlighted] = highlightedTypes;
return {
exportName,
data: {
type: mainType,
additionalTypes: additionalHighlighted
}
};
}));
const exports = {};
for (const {
exportName,
data
} of processedExports) {
exports[exportName] = data;
}
// Process top-level additional types
let additionalTypes = [];
if (syncResult.additionalTypes.length > 0) {
const highlightResult = await highlightTypes(syncResult.additionalTypes, syncResult.externalTypes, output, codeBlockEmphasisOptions);
additionalTypes = await highlightTypesMeta(highlightResult.types, {
highlightedExports: highlightResult.highlightedExports,
rawTypeProperties: sharedRawTypeProperties,
formatting: formattingOptions,
output,
codeBlockEmphasisOptions
});
}
// Process variant-only additional types
const variantOnlyAdditionalTypes = {};
const variantOnlyEntries = Object.entries(syncResult.variantOnlyAdditionalTypes);
if (variantOnlyEntries.length > 0) {
const processedVariants = await Promise.all(variantOnlyEntries.map(async ([variantName, types]) => {
if (types.length === 0) {
return {
variantName,
enhanced: []
};
}
const highlightResult = await highlightTypes(types, syncResult.externalTypes, output, codeBlockEmphasisOptions);
const enhanced = await highlightTypesMeta(highlightResult.types, {
highlightedExports: highlightResult.highlightedExports,
rawTypeProperties: sharedRawTypeProperties,
formatting: formattingOptions,
output,
codeBlockEmphasisOptions
});
return {
variantName,
enhanced
};
}));
for (const {
variantName,
enhanced
} of processedVariants) {
variantOnlyAdditionalTypes[variantName] = enhanced;
}
}
const highlightEnd = performance.now();
const highlightCompleteMark = nameMark(functionName, 'types highlighted and enhanced', [relativePath]);
performance.mark(highlightCompleteMark);
performance.measure(nameMark(functionName, 'highlighting and enhancement', [relativePath]), {
start: highlightStart,
end: highlightEnd
});
currentMark = nameMark(functionName, 'types highlighted and enhanced', [relativePath]);
// Use variantTypeNames directly from syncResult
const {
variantTypeNames
} = syncResult;
// Build anchorMap from all types (using their computed slugs)
const anchorMap = {};
// Add all types from exports
for (const exportData of Object.values(exports)) {
if (exportData.type.slug) {
const anchor = `#${exportData.type.slug}`;
anchorMap[exportData.type.name] = anchor;
// Add parameter anchors for property linking
const {
data,
type: metaType
} = exportData.type;
if (metaType === 'component') {
// Component props are always param 0, named "props"
anchorMap[`${exportData.type.name}[0]`] = `${anchor}:props`;
} else if ((metaType === 'hook' || metaType === 'function') && 'parameters' in data && data.parameters) {
// Named parameters: each gets its own index entry (kebab-cased)
const paramNames = Object.keys(data.parameters);
for (let i = 0; i < paramNames.length; i += 1) {
anchorMap[`${exportData.type.name}[${i}]`] = `${anchor}:${toKebabCase(paramNames[i])}`;
}
}
}
for (const addType of exportData.additionalTypes) {
if (addType.slug) {
anchorMap[addType.name] = `#${addType.slug}`;
}
}
}
// Add top-level additional types
for (const addType of additionalTypes) {
if (addType.slug) {
anchorMap[addType.name] = `#${addType.slug}`;
}
}
// Add variant-only additional types
for (const variantTypes of Object.values(variantOnlyAdditionalTypes)) {
for (const addType of variantTypes) {
if (addType.slug) {
anchorMap[addType.name] = `#${addType.slug}`;
}
}
}
// Add flat name mappings from typeNameMap
if (syncResult.typeNameMap) {
for (const [flatName, dottedName] of Object.entries(syncResult.typeNameMap)) {
const dottedAnchor = anchorMap[dottedName];
if (dottedAnchor) {
anchorMap[flatName] = dottedAnchor;
}
// Also map parameter keys for flat names (e.g., "AccordionRoot[0]" from "Accordion.Root[0]")
let i = 0;
while (anchorMap[`${dottedName}[${i}]`]) {
anchorMap[`${flatName}[${i}]`] = anchorMap[`${dottedName}[${i}]`];
i += 1;
}
}
}
performanceMeasure(currentMark, {
mark: 'complete',
measure: 'total processing'
}, [functionName, relativePath], true);
return {
exports,
additionalTypes,
variantOnlyAdditionalTypes,
variantTypeNames,
allDependencies: syncResult.allDependencies,
typeNameMap: syncResult.typeNameMap,
anchorMap: {
js: anchorMap
}
};
}