@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
144 lines (138 loc) • 4 kB
JavaScript
/**
* 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('');
}