UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

253 lines (242 loc) 9.12 kB
import { compressHast, decompressHast } from "../pipeline/hastUtils/index.mjs"; import { collapsedVisibleFallback, fallbackToText } from "./fallbackFormat.mjs"; /** * The residual fallbacks a `ContentLoading` component never renders, grouped * `variant → fileName → FallbackNode[]`. They exist only as the DEFLATE * dictionary for decompressing `hastCompressed`, so they ride across the * boundary as a single compressed blob rather than inline plain text. */ /** * Residual blobs whose JSON is below this many bytes are left uncompressed — * the DEFLATE + base64 framing would only grow such a small payload. */ export const FALLBACK_COMPRESSION_MIN_BYTES = 128; /** * Build the preset-dictionary text for the residual blob from the *rendered* * fallbacks (the subset `ContentLoading` paints, already on the client as plain * text). Priming the residual with this text lets DEFLATE backreference the * rendered file — most valuable for a near-duplicate sibling like a TypeScript * variant of a rendered JavaScript file. * * Deterministic across server and client: variants and files are visited in * sorted key order so both sides build byte-identical dictionaries (a mismatch * would otherwise fail the embedded checksum on decode). */ export function residualDictionaryText(rendered) { let text = ''; for (const variantName of Object.keys(rendered).sort()) { const files = rendered[variantName]; for (const fileName of Object.keys(files).sort()) { text += fallbackToText(files[fileName]); } } return text; } /** * Compress the residual fallbacks into a single self-describing blob. A single * DEFLATE stream deduplicates text shared across the residual files and * variants — and against the rendered subset too when `dictionaryText` is * supplied (see `residualDictionaryText`). * * Returns `undefined` when there is nothing worth compressing (no residual, or * a residual below the byte threshold), signalling the caller to keep the plain * fallbacks inline. */ export function compressResidualFallbacks(residual, dictionaryText) { if (Object.keys(residual).length === 0) { return undefined; } const json = JSON.stringify(residual); // Measure the encoded UTF-8 byte length (isomorphic via TextEncoder), not // `json.length` — which counts UTF-16 code units and undercounts multibyte // payloads, so a residual that genuinely exceeds the byte threshold would // otherwise be wrongly left inline. For ASCII the two are identical. if (new TextEncoder().encode(json).length < FALLBACK_COMPRESSION_MIN_BYTES) { return undefined; } return { fallbackCompressed: compressHast(json, dictionaryText) }; } /** * Reverse {@link compressResidualFallbacks}. `dictionaryText` must match what * was used to compress; the embedded checksum throws on a mismatch rather than * yielding a corrupt dictionary. */ export function decompressResidualFallbacks(blob, dictionaryText) { return JSON.parse(decompressHast(blob.fallbackCompressed, dictionaryText)); } /** * Pull every `fallback` off a (post-strip) `Code` into a `ResidualFallbacks` * map, returning a `Code` with those fallbacks removed. After * `stripFallbackHastsFromCode` has hoisted the rendered subset, every fallback * still on `Code` is residual — so this needs no scope flags. * * Pure: the input is left untouched; only the variants that lose a fallback are * shallow-cloned. */ export function extractResidualFallbacks(code) { const wireCode = {}; const residual = {}; for (const [variantName, variant] of Object.entries(code)) { if (!variant || typeof variant === 'string') { wireCode[variantName] = variant; continue; } const files = {}; let nextVariant = variant; // Main file — only extractable when its fileName can key the map. if (variant.fallback && variant.fileName) { files[variant.fileName] = variant.fallback; const { fallback: omitted, ...rest } = variant; nextVariant = rest; } // Extra files. if (variant.extraFiles) { let nextExtraFiles; for (const [fileName, fileData] of Object.entries(variant.extraFiles)) { if (typeof fileData === 'string' || !fileData.fallback) { continue; } files[fileName] = fileData.fallback; if (!nextExtraFiles) { nextExtraFiles = { ...variant.extraFiles }; } const { fallback: omitted, ...rest } = fileData; nextExtraFiles[fileName] = rest; } if (nextExtraFiles) { nextVariant = { ...nextVariant, extraFiles: nextExtraFiles }; } } wireCode[variantName] = nextVariant; if (Object.keys(files).length > 0) { residual[variantName] = files; } } return { wireCode, residual }; } /** * Scatter a decompressed `ResidualFallbacks` map back onto `Code`, restoring * each `fallback` to the variant or extra file it came from — the inverse of * {@link extractResidualFallbacks}. Reconstructs exactly the in-memory layout a * non-consolidated payload would have had, so downstream consumers are unaware * the residual ever travelled compressed. * * `preserveExisting` keeps any `fallback` already on the variant/extra file * instead of overwriting it. The hoisted-fallback scatter passes `true`: the * hoist is an *initial-paint* dictionary that, for an un-highlighted load, is a * raw-string fallback whose text keeps a trailing newline that `buildRootFallback` * drops — so it's the WRONG DEFLATE dictionary for a fully-loaded `hastCompressed` * source, which already carries its own source-paired (structured) `fallback`. * Overwriting that with the hoist makes `decodeHastSource` throw a dictionary * mismatch. The hoist is only the dictionary when the variant's own was stripped, * so apply it only where one isn't already present. The residual-blob scatter * keeps the default (`false`): it always writes onto server-stripped, fallback-less * variants, so there is nothing to preserve. * * Pure: only the variants that regain a fallback are shallow-cloned. */ export function scatterResidualFallbacks(code, residual, preserveExisting = false) { const restored = {}; for (const [variantName, variant] of Object.entries(code)) { const files = residual[variantName]; if (!variant || typeof variant === 'string' || !files) { restored[variantName] = variant; continue; } let nextVariant = variant; let nextExtraFiles; for (const [fileName, fallback] of Object.entries(files)) { if (fileName === variant.fileName) { if (preserveExisting && nextVariant.fallback) { continue; } nextVariant = { ...nextVariant, fallback }; } else { const fileData = variant.extraFiles?.[fileName]; if (fileData && typeof fileData === 'object') { if (preserveExisting && fileData.fallback) { continue; } if (!nextExtraFiles) { nextExtraFiles = { ...variant.extraFiles }; } nextExtraFiles[fileName] = { ...fileData, fallback }; } } } if (nextExtraFiles) { nextVariant = { ...nextVariant, extraFiles: nextExtraFiles }; } restored[variantName] = nextVariant; } return restored; } /** * Reduce every fallback in a rendered-subset map to its collapsed window (see * `collapsedVisibleFallback`). Used by `fallbackCollapsed` to hand * `ContentLoading` only the on-screen lines while the full fallbacks ride along * in the residual blob. * * `collapsesToEmpty(variantName, fileName)` reports the `oversizedFocus: 'hide'` * collapse-to-nothing case (the source's `focusedLines === 0`): such files get * an empty collapsed window so the loading UI matches the hydrated render * instead of briefly painting the first frame. */ export function collapseRenderedFallbacks(rendered, collapsesToEmpty) { const collapsed = {}; for (const [variantName, files] of Object.entries(rendered)) { const collapsedFiles = {}; for (const [fileName, fallback] of Object.entries(files)) { collapsedFiles[fileName] = collapsedVisibleFallback(fallback, collapsesToEmpty?.(variantName, fileName) ?? false); } collapsed[variantName] = collapsedFiles; } return collapsed; } /** * Deep-merge two residual maps (`variant → fileName → fallback`), with `b` * winning on conflicts. Used to fold the rendered files' full fallbacks into * the residual when `fallbackCollapsed` defers them. */ export function mergeResidualFallbacks(first, second) { const merged = {}; for (const [variantName, files] of Object.entries(first)) { merged[variantName] = { ...files }; } for (const [variantName, files] of Object.entries(second)) { merged[variantName] = { ...merged[variantName], ...files }; } return merged; }