UNPKG

@mintlify/common

Version:

Commonly shared code within Mintlify

136 lines (135 loc) 5.69 kB
import { visit } from 'unist-util-visit'; import { isExportNode, isMdxJsEsm } from '../../utils.js'; // Underscore-prefixed so it cannot collide with a user-authored MDX component name. The client // (MdxContent) maps this to an error boundary; see apps/client/src/ui/MdxContent.tsx. export const MDX_COMPONENT_BOUNDARY_NAME = '_MdxComponentBoundary'; /** * Collects the names of function-valued exports declared in the MDX itself — `export function X`, * `export const X = () => …` / `function(){}`, and re-exported local functions (`export { X }`). * These are the user-authored components we treat as renderable (and, in remarkMdxRemoveUnknownJsx, * as "known"), as opposed to exported plain values which are not components. */ export function getExportedFunctionNames(tree) { const names = new Set(); visit(tree, 'mdxjsEsm', (node) => { var _a, _b, _c, _d, _e, _f; if (!isMdxJsEsm(node) || !((_b = (_a = node.data) === null || _a === void 0 ? void 0 : _a.estree) === null || _b === void 0 ? void 0 : _b.body)) return; const localFunctionNames = new Set(); for (const bodyChild of node.data.estree.body) { if (bodyChild.type === 'FunctionDeclaration' && ((_c = bodyChild.id) === null || _c === void 0 ? void 0 : _c.name)) { localFunctionNames.add(bodyChild.id.name); } if (bodyChild.type === 'VariableDeclaration') { addExportedVariableFunctionNames(bodyChild, localFunctionNames); } } for (const bodyChild of node.data.estree.body) { if (!isExportNode(bodyChild) || bodyChild.type === 'ExportAllDeclaration') continue; if (((_d = bodyChild.declaration) === null || _d === void 0 ? void 0 : _d.type) === 'FunctionDeclaration') { if ((_e = bodyChild.declaration.id) === null || _e === void 0 ? void 0 : _e.name) names.add(bodyChild.declaration.id.name); continue; } if (((_f = bodyChild.declaration) === null || _f === void 0 ? void 0 : _f.type) === 'VariableDeclaration') { addExportedVariableFunctionNames(bodyChild.declaration, names); continue; } if (bodyChild.type === 'ExportNamedDeclaration') { addExportedSpecifierNames(bodyChild, names, localFunctionNames); } } }); return names; } function addExportedVariableFunctionNames(declaration, names) { var _a, _b; for (const declarator of declaration.declarations) { if (declarator.id.type !== 'Identifier') continue; if (((_a = declarator.init) === null || _a === void 0 ? void 0 : _a.type) === 'ArrowFunctionExpression' || ((_b = declarator.init) === null || _b === void 0 ? void 0 : _b.type) === 'FunctionExpression') { names.add(declarator.id.name); } } } function addExportedSpecifierNames(node, names, localFunctionNames) { for (const specifier of node.specifiers) { if (localFunctionNames.has(specifier.local.name)) { names.add(specifier.exported.name); } } } function shouldWrapComponent(name, componentNames) { if (!name) return false; if (name === MDX_COMPONENT_BOUNDARY_NAME) return false; return componentNames.has(name); } function wrapFlowNode(node) { var _a; return { type: 'mdxJsxFlowElement', name: MDX_COMPONENT_BOUNDARY_NAME, attributes: [ { type: 'mdxJsxAttribute', name: 'name', value: (_a = node.name) !== null && _a !== void 0 ? _a : 'MDX component', }, ], children: [node], }; } function wrapTextNode(node) { var _a; return { type: 'mdxJsxTextElement', name: MDX_COMPONENT_BOUNDARY_NAME, attributes: [ { type: 'mdxJsxAttribute', name: 'name', value: (_a = node.name) !== null && _a !== void 0 ? _a : 'MDX component', }, ], children: [node], }; } /** * Wraps each user-authored component usage in a `_MdxComponentBoundary` element so the client can * isolate it behind an error boundary — a single component that throws at render time then renders * nothing instead of crashing the whole page. */ export const remarkMdxClientComponentBoundaries = ({ allowlist = [] } = {}) => (tree) => { const componentNames = getExportedFunctionNames(tree); for (const name of allowlist) { componentNames.add(name); } if (componentNames.size === 0) return; const flowReplacements = []; const textReplacements = []; visit(tree, 'mdxJsxFlowElement', (node, index, parent) => { if (index === undefined || !(parent === null || parent === void 0 ? void 0 : parent.children)) return; if (!shouldWrapComponent(node.name, componentNames)) return; flowReplacements.push({ parent, index, node }); }); visit(tree, 'mdxJsxTextElement', (node, index, parent) => { if (index === undefined || !(parent === null || parent === void 0 ? void 0 : parent.children)) return; if (!shouldWrapComponent(node.name, componentNames)) return; textReplacements.push({ parent, index, node }); }); for (const { parent, index, node } of flowReplacements) { parent.children[index] = wrapFlowNode(node); } for (const { parent, index, node } of textReplacements) { parent.children[index] = wrapTextNode(node); } };