@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
469 lines (459 loc) • 20 kB
JavaScript
import _createForOfIteratorHelper from "@babel/runtime/helpers/esm/createForOfIteratorHelper";
import _regeneratorRuntime from "@babel/runtime/helpers/esm/regeneratorRuntime";
import _typeof from "@babel/runtime/helpers/esm/typeof";
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
import * as React from 'react';
import { loadVariant } from "./loadVariant.js";
import { loadFallbackCode } from "./loadFallbackCode.js";
import { stringOrHastToJsx } from "../pipeline/hastUtils/index.js";
import { CodeHighlighterClient } from "./CodeHighlighterClient.js";
import { maybeInitialData } from "./maybeInitialData.js";
import { hasAllVariants } from "./hasAllVariants.js";
import { getFileNameFromUrl } from "../pipeline/loaderUtils/getFileNameFromUrl.js";
import { CodeErrorHandler } from "./CodeErrorHandler.js";
// Common props shared across helper functions
import { jsx as _jsx } from "react/jsx-runtime";
var DEFAULT_HIGHLIGHT_AT = 'stream';
var DEBUG = false; // Set to true for debugging purposes
function createClientProps(props) {
var highlightAt = props.highlightAt === 'stream' ? 'init' : props.highlightAt;
var contentProps = _objectSpread({
code: props.code || props.precompute,
components: props.components,
name: props.name,
slug: props.slug,
url: props.url,
variantType: props.variantType
}, props.contentProps || {});
var ErrorHandler = props.ErrorHandler || CodeErrorHandler;
return {
url: props.url,
code: props.code,
precompute: props.precompute,
components: props.components,
variants: props.variants,
variant: props.variant,
fileName: props.fileName,
initialVariant: props.initialVariant,
defaultVariant: props.defaultVariant,
highlightAt: highlightAt || 'init',
skipFallback: props.skipFallback,
controlled: props.controlled,
name: props.name,
slug: props.slug,
// Use processedGlobalsCode if available, otherwise fall back to raw globalsCode
globalsCode: props.processedGlobalsCode || props.globalsCode,
// Note: it is important that we render components before passing them to the client
// otherwise we will get an error because functions can't be serialized
// On the client, in order to send data to these components, we have to set context
fallback: props.fallback,
errorHandler: /*#__PURE__*/_jsx(ErrorHandler, {}),
children: /*#__PURE__*/_jsx(props.Content, _objectSpread({}, contentProps))
};
}
function CodeSourceLoader(_x) {
return _CodeSourceLoader.apply(this, arguments);
}
function _CodeSourceLoader() {
_CodeSourceLoader = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(props) {
var ErrorHandler, loadedCode, processedGlobalsCode, hasStringUrls, globalsPromises, variantNames, variantCodes, processedCode, errors, _iterator2, _step2, item, clientProps;
return _regeneratorRuntime().wrap(function _callee2$(_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
ErrorHandler = props.ErrorHandler || CodeErrorHandler; // Start with the loaded code from precompute, or load it if needed
loadedCode = props.code || props.precompute;
if (loadedCode) {
_context2.next = 16;
break;
}
if (props.loadCodeMeta) {
_context2.next = 5;
break;
}
return _context2.abrupt("return", /*#__PURE__*/_jsx(ErrorHandler, {
errors: [new Error('No code provided and "loadCodeMeta" function is not defined')]
}));
case 5:
if (props.url) {
_context2.next = 7;
break;
}
return _context2.abrupt("return", /*#__PURE__*/_jsx(ErrorHandler, {
errors: [new Error('URL is required when loading code with "loadCodeMeta"')]
}));
case 7:
_context2.prev = 7;
_context2.next = 10;
return props.loadCodeMeta(props.url);
case 10:
loadedCode = _context2.sent;
_context2.next = 16;
break;
case 13:
_context2.prev = 13;
_context2.t0 = _context2["catch"](7);
return _context2.abrupt("return", /*#__PURE__*/_jsx(ErrorHandler, {
errors: [new Error("Failed to load code from URL: ".concat(props.url, ". Error: ").concat(JSON.stringify(_context2.t0)))]
}));
case 16:
// TODO: if props.variant is provided, we should only load that variant
// Process globalsCode: use already processed version if available, otherwise convert string URLs to Code objects
processedGlobalsCode = props.processedGlobalsCode;
if (!(!processedGlobalsCode && props.globalsCode && props.globalsCode.length > 0)) {
_context2.next = 31;
break;
}
hasStringUrls = props.globalsCode.some(function (item) {
return typeof item === 'string';
});
if (!(hasStringUrls && !props.loadCodeMeta)) {
_context2.next = 21;
break;
}
return _context2.abrupt("return", /*#__PURE__*/_jsx(ErrorHandler, {
errors: [new Error('loadCodeMeta function is required when globalsCode contains string URLs')]
}));
case 21:
// Load all string URLs in parallel, keep Code objects as-is
globalsPromises = props.globalsCode.map(/*#__PURE__*/function () {
var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee(globalItem) {
return _regeneratorRuntime().wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
if (!(typeof globalItem === 'string')) {
_context.next = 12;
break;
}
_context.prev = 1;
_context.next = 4;
return props.loadCodeMeta(globalItem);
case 4:
return _context.abrupt("return", _context.sent);
case 7:
_context.prev = 7;
_context.t0 = _context["catch"](1);
throw new Error("Failed to load globalsCode from URL: ".concat(globalItem, ". Error: ").concat(JSON.stringify(_context.t0)));
case 10:
_context.next = 13;
break;
case 12:
return _context.abrupt("return", globalItem);
case 13:
case "end":
return _context.stop();
}
}, _callee, null, [[1, 7]]);
}));
return function (_x4) {
return _ref.apply(this, arguments);
};
}());
_context2.prev = 22;
_context2.next = 25;
return Promise.all(globalsPromises);
case 25:
processedGlobalsCode = _context2.sent;
_context2.next = 31;
break;
case 28:
_context2.prev = 28;
_context2.t1 = _context2["catch"](22);
return _context2.abrupt("return", /*#__PURE__*/_jsx(ErrorHandler, {
errors: [_context2.t1]
}));
case 31:
variantNames = Object.keys(props.components || loadedCode || {});
_context2.next = 34;
return Promise.all(variantNames.map(function (variantName) {
var variantCode = loadedCode[variantName];
var variantUrl = _typeof(variantCode) === 'object' && variantCode != null && variantCode.url ? variantCode.url : props.url;
// Convert processedGlobalsCode to VariantCode | string for this specific variant
var resolvedGlobalsCode;
if (processedGlobalsCode && processedGlobalsCode.length > 0) {
resolvedGlobalsCode = [];
var _iterator = _createForOfIteratorHelper(processedGlobalsCode),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var codeObj = _step.value;
// Only include if this variant exists in the globalsCode
var targetVariant = codeObj[variantName];
if (targetVariant) {
resolvedGlobalsCode.push(targetVariant);
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
return loadVariant(variantUrl, variantName, variantCode, props.sourceParser, props.loadSource, props.loadVariantMeta, props.sourceTransformers, {
globalsCode: resolvedGlobalsCode
}).then(function (variant) {
return {
name: variantName,
variant: variant
};
})["catch"](function (error) {
return {
error: error
};
});
}));
case 34:
variantCodes = _context2.sent;
processedCode = {};
errors = [];
_iterator2 = _createForOfIteratorHelper(variantCodes);
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
item = _step2.value;
if ('error' in item) {
errors.push(item.error);
} else {
processedCode[item.name] = item.variant.code;
}
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
if (!(errors.length > 0)) {
_context2.next = 41;
break;
}
return _context2.abrupt("return", /*#__PURE__*/_jsx(ErrorHandler, {
errors: errors
}));
case 41:
clientProps = createClientProps(_objectSpread(_objectSpread({}, props), {}, {
code: processedCode,
processedGlobalsCode: processedGlobalsCode
}));
return _context2.abrupt("return", /*#__PURE__*/_jsx(CodeHighlighterClient, _objectSpread({}, clientProps)));
case 43:
case "end":
return _context2.stop();
}
}, _callee2, null, [[7, 13], [22, 28]]);
}));
return _CodeSourceLoader.apply(this, arguments);
}
function renderCodeHighlighter(props) {
var ErrorHandler = props.ErrorHandler || CodeErrorHandler;
var code = props.code || props.precompute;
var variants = props.variants || Object.keys(props.components || code || {});
var allCodeVariantsLoaded = code && hasAllVariants(variants, code, true);
if (!allCodeVariantsLoaded) {
if (props.forceClient) {
return /*#__PURE__*/_jsx(ErrorHandler, {
errors: [new Error('Client only mode requires precomputed source code')]
});
}
return /*#__PURE__*/_jsx(CodeSourceLoader, _objectSpread({}, props));
}
var clientProps = createClientProps(_objectSpread({}, props));
return /*#__PURE__*/_jsx(CodeHighlighterClient, _objectSpread({}, clientProps));
// TODO: we might not need the client if hydrateAt is 'init' or 'stream' and there is no props.controlled
}
/**
* Ensures that the suspense boundary is always rendered, even if none of the children have async operations.
*/
function CodeHighlighterSuspense(_x2) {
return _CodeHighlighterSuspense.apply(this, arguments);
}
function _CodeHighlighterSuspense() {
_CodeHighlighterSuspense = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee3(props) {
return _regeneratorRuntime().wrap(function _callee3$(_context3) {
while (1) switch (_context3.prev = _context3.next) {
case 0:
return _context3.abrupt("return", props.children);
case 1:
case "end":
return _context3.stop();
}
}, _callee3);
}));
return _CodeHighlighterSuspense.apply(this, arguments);
}
function renderWithInitialSource(props) {
var fileNames = [].concat(_toConsumableArray(props.initialFilename ? [props.initialFilename] : []), _toConsumableArray(Object.keys(props.initialExtraFiles || {})));
var source = stringOrHastToJsx(props.initialSource, props.highlightAt === 'init');
var contentProps = _objectSpread({
name: props.name,
slug: props.slug,
url: props.url,
fileNames: fileNames,
source: source
}, props.contentProps || {});
var ContentLoading = props.ContentLoading;
var fallback = /*#__PURE__*/_jsx(ContentLoading, _objectSpread({}, contentProps));
if (props.forceClient) {
return renderCodeHighlighter(_objectSpread(_objectSpread({}, props), {}, {
fallback: fallback,
processedGlobalsCode: props.processedGlobalsCode
}));
}
return /*#__PURE__*/_jsx(React.Suspense, {
fallback: fallback,
children: /*#__PURE__*/_jsx(CodeHighlighterSuspense, {
children: renderCodeHighlighter(_objectSpread(_objectSpread({}, props), {}, {
fallback: fallback,
skipFallback: true,
processedGlobalsCode: props.processedGlobalsCode
}))
})
});
}
function CodeInitialSourceLoader(_x3) {
return _CodeInitialSourceLoader.apply(this, arguments);
}
function _CodeInitialSourceLoader() {
_CodeInitialSourceLoader = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee4(props) {
var ErrorHandler, loaded, code, initialFilename, initialSource, initialExtraFiles, processedGlobalsCode;
return _regeneratorRuntime().wrap(function _callee4$(_context4) {
while (1) switch (_context4.prev = _context4.next) {
case 0:
ErrorHandler = props.ErrorHandler || CodeErrorHandler;
if (props.url) {
_context4.next = 3;
break;
}
return _context4.abrupt("return", /*#__PURE__*/_jsx(ErrorHandler, {
errors: [new Error('URL is required for loading initial source')]
}));
case 3:
_context4.next = 5;
return loadFallbackCode(props.url, props.initialVariant, props.code, props.highlightAt === 'init', props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants, props.sourceParser, props.loadSource, props.loadVariantMeta, props.loadCodeMeta, props.fileName, props.variants, props.globalsCode)["catch"](function (error) {
return {
error: error
};
});
case 5:
loaded = _context4.sent;
if (!('error' in loaded)) {
_context4.next = 8;
break;
}
return _context4.abrupt("return", /*#__PURE__*/_jsx(ErrorHandler, {
errors: [loaded.error]
}));
case 8:
code = loaded.code, initialFilename = loaded.initialFilename, initialSource = loaded.initialSource, initialExtraFiles = loaded.initialExtraFiles, processedGlobalsCode = loaded.processedGlobalsCode;
return _context4.abrupt("return", renderWithInitialSource(_objectSpread(_objectSpread({}, props), {}, {
code: code,
initialFilename: initialFilename,
initialSource: initialSource,
initialExtraFiles: initialExtraFiles,
ContentLoading: props.ContentLoading,
processedGlobalsCode: processedGlobalsCode
})));
case 10:
case "end":
return _context4.stop();
}
}, _callee4);
}));
return _CodeInitialSourceLoader.apply(this, arguments);
}
export function CodeHighlighter(props) {
var _code, _props$components;
var ErrorHandler = props.ErrorHandler || CodeErrorHandler;
// Validate mutually exclusive props
if (props.children && (props.code || props.precompute)) {
return /*#__PURE__*/_jsx(ErrorHandler, {
errors: [new Error('Cannot provide both "children" and "code" or "precompute" props')]
});
}
// Handle children as string -> Default variant
var code = props.code;
if (props.children && typeof props.children === 'string') {
var fileName = props.fileName || (props.url ? getFileNameFromUrl(props.url).fileName : undefined);
code = {
Default: {
fileName: fileName,
source: props.children,
url: props.url
}
};
}
var variants = props.variants || Object.keys(props.components || code || props.precompute || {});
if (variants.length === 0) {
return /*#__PURE__*/_jsx(ErrorHandler, {
errors: [new Error('No code or components provided')]
});
}
// Validate fileName is provided when extraFiles are present
if (code) {
for (var _i = 0, _Object$entries = Object.entries(code); _i < _Object$entries.length; _i++) {
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
variantName = _Object$entries$_i[0],
variantCode = _Object$entries$_i[1];
if (_typeof(variantCode) === 'object' && variantCode != null && variantCode.extraFiles && Object.keys(variantCode.extraFiles).length > 0 && !variantCode.fileName && !variantCode.url) {
return /*#__PURE__*/_jsx(ErrorHandler, {
errors: [new Error("fileName or url is required for variant \"".concat(variantName, "\" when extraFiles are provided"))]
});
}
}
}
var ContentLoading = props.ContentLoading;
if (!ContentLoading) {
if (props.highlightAt === 'stream') {
// if the user explicitly sets highlightAt to 'stream', we need a ContentLoading component
return /*#__PURE__*/_jsx(ErrorHandler, {
errors: [new Error('ContentLoading component is required for stream highlighting')]
});
}
return renderCodeHighlighter(_objectSpread(_objectSpread({}, props), {}, {
code: code
}));
}
var initialKey = props.initialVariant || props.variant || props.defaultVariant || variants[0];
var initial = (_code = code) == null ? void 0 : _code[initialKey];
if (!initial && !((_props$components = props.components) != null && _props$components[initialKey])) {
return /*#__PURE__*/_jsx(ErrorHandler, {
errors: [new Error("No code or component for variant \"".concat(initialKey, "\""))]
});
}
// TODO: use initial.filesOrder to determing which source to use
var highlightAt = props.highlightAt || DEFAULT_HIGHLIGHT_AT;
var _maybeInitialData = maybeInitialData(variants, initialKey, code || props.precompute, undefined,
// TODO: use initial.filesOrder if provided?
highlightAt === 'init', props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants),
initialData = _maybeInitialData.initialData,
reason = _maybeInitialData.reason;
if (!initialData) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.log('Initial data not found:', reason);
}
if (props.forceClient) {
if (highlightAt === 'init') {
return /*#__PURE__*/_jsx(ErrorHandler, {
errors: [new Error('Client only mode with highlightAt: init requires precomputed and parsed source code')]
});
}
// TODO: send directly to client component?
return /*#__PURE__*/_jsx(ErrorHandler, {
errors: [new Error('Client only mode requires precomputed source code')]
});
}
return /*#__PURE__*/_jsx(CodeInitialSourceLoader, _objectSpread(_objectSpread({}, props), {}, {
ContentLoading: ContentLoading,
initialVariant: initialKey
}));
}
return renderWithInitialSource(_objectSpread(_objectSpread({}, props), {}, {
code: initialData.code,
ContentLoading: ContentLoading,
initialVariant: initialKey,
initialFilename: initialData.initialFilename,
initialSource: initialData.initialSource,
initialExtraFiles: initialData.initialExtraFiles
}));
}