@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
82 lines (81 loc) • 3.02 kB
JavaScript
import { hasClassName, isFrameSpan } from "../parseSource/isFrameSpan.mjs";
/**
* Strip all non-structural `<span>` elements from a HAST tree while preserving
* semantic structure and text content. Produces a "links-only" version of the
* tree suitable as a lightweight server-rendered fallback for deferred highlighting.
*
* - All `<span>` elements except frame and collapse spans: removed, children promoted
* - Frame `<span>` elements (`frame`): preserved with their data attributes
* (except `data-lined`, which is redundant once line spans are gone)
* - Collapse `<span>` elements (`collapse`): preserved with their `data-lines`
* attribute so CSS can size the placeholder, keeping the fallback render's
* height in sync with the fully-highlighted render
* - `<a>` elements: preserved, children recursively processed
* - text nodes: preserved, adjacent text nodes merged
* - other elements (pre, code, etc.): preserved, children recursively processed
*
* Does not mutate the input tree.
*/
export function stripHighlightingSpans(root) {
return {
...root,
children: processChildren(root.children)
};
}
function isCollapseSpan(element) {
return hasClassName(element, 'collapse');
}
function processChildren(children) {
const flat = children.flatMap(node => {
if (node.type !== 'element') {
return [node];
}
const element = node;
if (element.tagName === 'span' && !isFrameSpan(element) && !isCollapseSpan(element)) {
// Unwrap highlighting spans: replace with recursively-processed children
return processChildren(element.children);
}
if (isCollapseSpan(element)) {
// Collapse placeholders carry one empty `<span/>` per collapsed
// line as a structural payload (consumer CSS sizes them by
// intrinsic layout). The children are inert: no recursion needed,
// and reusing the same element reference keeps downstream JSX
// caches (WeakMap-keyed) stable across re-renders.
return [element];
}
// Keep semantic spans, links, and other elements — process their children
const processed = {
...element,
children: processChildren(element.children)
};
// Strip data-lined from frame spans since line spans are removed in the
// fallback HAST.
if (isFrameSpan(element) && processed.properties) {
const {
dataLined,
...rest
} = processed.properties;
if (dataLined !== undefined) {
processed.properties = rest;
}
}
return [processed];
});
return mergeAdjacentText(flat);
}
function mergeAdjacentText(nodes) {
const result = [];
for (const node of nodes) {
const prev = result[result.length - 1];
if (node.type === 'text' && prev?.type === 'text') {
// Replace the previous text node with a merged one (no mutation)
result[result.length - 1] = {
type: 'text',
value: prev.value + node.value
};
} else {
result.push(node);
}
}
return result;
}