UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

208 lines (193 loc) 8.82 kB
// The transform-application runtime, split out of `useCodeUtils` so the heavy // `applyCodeTransform` path (which statically pulls `jsondiffpatch`) loads only // when a block actually applies a transform. `useTransformManagement` reaches it // through the `transformEngineLoader` accessor on `CodeContext` (the eager // `CodeProvider` bundles it; `CodeProviderLazy` dynamic-imports it), with a // built-in fallback so transforms still work without a provider. The pure, // manifest-only transform helpers stay in `useCodeUtils` so read-only / no-transform // blocks never pull this chunk. // Import the transform core (which takes its hast helpers injected) rather than // the `applyCodeTransform` wrapper that binds them, so this dynamic engine chunk // never statically pulls `decodeHastSource` / `frameFallbackFromSpans` (and // their `hastDecompress` dependency). The helpers are threaded in from the // always-loaded `useCode` shell, which already has them, so they stay counted // there instead of being hoisted into their own chunks. import { applyCodeTransformWithComments } from "../pipeline/loadIsomorphicCodeVariant/applyCodeTransformWithComments.mjs"; /** * Function signature of {@link createTransformedFiles}. Used by the * `transformEngineLoader` accessor and `useTransformManagement` so they can * reference the engine without statically importing this (heavy) module. * * `deps` (the decoder + frame-fallback helper) is injected by the caller (the * `useCode` shell already loads them) so this engine chunk doesn't statically * depend on them. */ /** * Pure helper function to apply transform to a source file. * * @param source - The source code to transform * @param fileName - The filename for the source * @param transforms - Available transforms for this source * @param selectedTransform - The transform to apply * @param comments - Optional 1-indexed comment map for the source. Returned * shifted onto the transformed source's line numbering. * @returns Object with transformed source, name, and shifted comments */ export function applyTransformToSource(source, fileName, transforms, selectedTransform, deps, comments, fallback) { if (!transforms?.[selectedTransform]) { return { transformedSource: source, transformedName: fileName, transformedComments: comments }; } try { const transformData = transforms[selectedTransform]; // Apply transform — `applyCodeTransform` will look up the delta inside // `source.data.transforms` if `transformData.delta` is absent (manifest // mode after embedding). const result = applyCodeTransformWithComments(source, transforms, selectedTransform, deps, comments, fallback); const transformedName = transformData.fileName || fileName; return { transformedSource: result.source, transformedName, transformedComments: result.comments }; } catch (error) { console.error(`Transform failed for ${fileName}:`, error); return { transformedSource: source, transformedName: fileName, transformedComments: comments }; } } /** * Pure function to create transformed files from a variant and selected transform. * * @param selectedVariant - The currently selected variant * @param selectedTransform - The transform to apply * @returns Object with transformed files and filename mapping, or undefined if no transform */ export function createTransformedFiles(selectedVariant, selectedTransform, // Hast helpers injected by the caller (the `useCode` shell), so this engine // chunk never statically imports them. See the module comment. deps, // Per-file DEFLATE dictionaries hoisted from a `ContentLoading` component. // A file's fallback may live here (hoisted) instead of on the variant // (stripped) — applying a transform must decode `hastCompressed`, so resolve // from both, preferring the hoisted copy. fallbacks) { // Only create transformed files when there's actually a transform selected if (!selectedVariant || !selectedTransform) { return undefined; } const files = []; const filenameMap = {}; // First, check if any file has a transform manifest entry for the selected // transform. A manifest entry may carry a real embedded delta (`hasDelta: true`) // or be rename-only (`hasDelta: false`) — both cases are "meaningful" here // because either the source changes or the filename does. const variantTransforms = 'transforms' in selectedVariant ? selectedVariant.transforms : undefined; let hasAnyMeaningfulTransform = false; // Check main file for the transform key if (selectedVariant.fileName && variantTransforms?.[selectedTransform]) { hasAnyMeaningfulTransform = true; } // Check extraFiles for the transform key if (!hasAnyMeaningfulTransform && selectedVariant.extraFiles) { Object.values(selectedVariant.extraFiles).forEach(fileData => { if (fileData && typeof fileData === 'object' && 'transforms' in fileData && fileData.transforms?.[selectedTransform]) { hasAnyMeaningfulTransform = true; } }); } // If no file has a meaningful transform, return empty result if (!hasAnyMeaningfulTransform) { return { files: [], filenameMap: {} }; } // Process main file if we have a fileName and source if (selectedVariant.fileName && selectedVariant.source) { const { transformedSource: mainSource, transformedName: mainName, transformedComments: mainComments } = applyTransformToSource(selectedVariant.source, selectedVariant.fileName, variantTransforms, selectedTransform, deps, selectedVariant.comments, (selectedVariant.fileName ? fallbacks?.[selectedVariant.fileName] : undefined) ?? selectedVariant.fallback); const fileName = selectedVariant.fileName; filenameMap[fileName] = mainName; files.push({ name: mainName, originalName: fileName, source: mainSource, ...(mainComments && { comments: mainComments }) }); } // Process extra files if (selectedVariant.extraFiles) { Object.entries(selectedVariant.extraFiles).forEach(([extraFileName, fileData]) => { let source; let transforms; let fileComments; // Handle different extraFile structures if (typeof fileData === 'string') { source = fileData; transforms = undefined; // Don't inherit variant transforms for simple string files } else if (fileData && typeof fileData === 'object' && 'source' in fileData) { source = fileData.source; transforms = fileData.transforms; // Only use explicit transforms for this file fileComments = fileData.comments; } else { return; // Skip invalid entries } // Skip if source is undefined if (!source) { return; } // Apply transforms if available, otherwise use original source let transformedSource = source; let transformedName = extraFileName; let transformedComments = fileComments; if (transforms?.[selectedTransform]) { try { const transformData = transforms[selectedTransform]; // The presence of an entry in the (manifest or legacy) transforms // record is enough — `applyCodeTransform` will look up the delta // inside `source.data.transforms` if it isn't on the entry. const result = applyCodeTransformWithComments(source, transforms, selectedTransform, deps, fileComments, fallbacks?.[extraFileName] ?? (typeof fileData === 'object' ? fileData.fallback : undefined)); transformedSource = result.source; transformedComments = result.comments; transformedName = transformData.fileName || extraFileName; } catch (error) { console.error(`Transform failed for ${extraFileName}:`, error); // Continue with original source if transform fails } } // Only update filenameMap and add to files if this doesn't conflict with existing files // If a file already exists with the target name, skip this transformation to preserve original files const existingFile = files.find(f => f.name === transformedName); if (!existingFile) { filenameMap[extraFileName] = transformedName; files.push({ name: transformedName, originalName: extraFileName, source: transformedSource, ...(transformedComments && { comments: transformedComments }) }); } else { // If there's a conflict, skip this file with a warning console.warn(`Transform conflict: ${extraFileName} would transform to ${transformedName} but that name is already taken. Skipping this file.`); } }); } return { files, filenameMap }; }