UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

144 lines (138 loc) 4 kB
/** * Compact serialization format for fallback HAST trees. * * A `FallbackNode` is either: * - a plain string (text node), or * - a variable-length tuple whose meaning is determined by length: * - `[tagName, children]` – no class, no props * - `[tagName, className, children]` – has class, no props * - `[tagName, className, properties, children]` – has class and props * - `[tagName, className, properties, children, extra]` – full * * Where: * - `tagName` – HTML element name (e.g. `'span'`, `'a'`) * - `className` – space-joined class string * - `properties` – remaining HTML properties (without `className`) * - `children` – a single text string **or** an array of child `FallbackNode`s * - `extra` – optional bag for anything else (data attributes, etc.) * * This eliminates the repeated `type`, `tagName`, `properties`, `children`, * and `value` keys that make raw HAST costly in RSC payloads. */ /** * Convert a HAST root into the compact `FallbackNode[]` format. */ export function hastToFallback(root) { return convertChildren(root.children); } function convertChildren(children) { const result = []; for (const child of children) { const node = convertNode(child); if (node !== null) { result.push(node); } } return result; } function convertNode(node) { if (node.type === 'text') { return node.value; } if (node.type === 'element') { const el = node; const { className, ...restProps } = el.properties || {}; let classStr = ''; if (className != null) { classStr = Array.isArray(className) ? className.join(' ') : String(className); } const props = Object.keys(restProps).length > 0 ? restProps : null; const kids = el.children; // Optimize single-text-child to inline string let childValue; if (kids.length === 1 && kids[0].type === 'text') { childValue = kids[0].value; } else { childValue = convertChildren(kids); } // Variable-length tuple: omit trailing empty fields if (props !== null) { return [el.tagName, classStr, props, childValue]; } if (classStr) { return [el.tagName, classStr, childValue]; } return [el.tagName, childValue]; } // Skip non-text/non-element nodes (comment, doctype, etc.) return null; } /** * Convert the compact `FallbackNode[]` format back into a HAST root. */ export function fallbackToHast(nodes) { return { type: 'root', children: nodes.map(nodeToHast) }; } function nodeToHast(node) { if (typeof node === 'string') { return { type: 'text', value: node }; } let tagName; let classStr; let props; let children; let extra; if (node.length === 2) { [tagName, children] = node; } else if (node.length === 3) { [tagName, classStr, children] = node; } else if (node.length === 5) { [tagName, classStr, props, children, extra] = node; } else { [tagName, classStr, props, children] = node; } const properties = { ...props, ...extra }; if (classStr) { properties.className = classStr.split(' '); } const childNodes = typeof children === 'string' ? [{ type: 'text', value: children }] : children.map(nodeToHast); return { type: 'element', tagName, properties, children: childNodes }; } /** * Extract the text content from compact `FallbackNode[]` without * converting back to HAST. Used to build DEFLATE dictionaries. */ export function fallbackToText(nodes) { return nodes.map(nodeText).join(''); } function nodeText(node) { if (typeof node === 'string') { return node; } // children is always the last element (or second-to-last when extra is present) const children = node.length === 5 ? node[3] : node[node.length - 1]; if (typeof children === 'string') { return children; } return children.map(nodeText).join(''); }