@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
422 lines (386 loc) • 18.6 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
/**
* Export variant functionality to add extra files like package.json, tsconfig, etc.
* Users can pass configuration options that vary the output here.
*/
import { externalsToPackages } from "../pipeline/loaderUtils/index.js";
import { getFileNameFromUrl } from "../pipeline/loaderUtils/getFileNameFromUrl.js";
import { createPathContext } from "../CodeHighlighter/examineVariant.js";
import { mergeMetadata, extractMetadata } from "../CodeHighlighter/mergeMetadata.js";
/**
* Merges multiple file objects into a single object.
* Similar to mergeExternals but for file structures.
* Automatically adds metadata: false to files that don't have a metadata property.
*/
function mergeFiles() {
var merged = {};
for (var _len = arguments.length, fileSets = new Array(_len), _key = 0; _key < _len; _key++) {
fileSets[_key] = arguments[_key];
}
for (var _i = 0, _fileSets = fileSets; _i < _fileSets.length; _i++) {
var fileSet = _fileSets[_i];
for (var _i2 = 0, _Object$entries = Object.entries(fileSet); _i2 < _Object$entries.length; _i2++) {
var _Object$entries$_i = _slicedToArray(_Object$entries[_i2], 2),
fileName = _Object$entries$_i[0],
fileData = _Object$entries$_i[1];
// Later files override earlier ones (similar to Object.assign behavior)
var normalizedData = typeof fileData === 'string' ? {
source: fileData
} : _extends({}, fileData);
// Add metadata: false if not already set (source files default to false)
if (!('metadata' in normalizedData)) {
normalizedData.metadata = false;
}
merged[fileName] = normalizedData;
}
}
return merged;
}
/**
* Extract filename from URL or return undefined if not available
*/
export function getFilenameFromVariant(variantCode) {
if (variantCode.fileName) {
return variantCode.fileName;
}
if (variantCode.url) {
var _getFileNameFromUrl = getFileNameFromUrl(variantCode.url),
fileName = _getFileNameFromUrl.fileName;
return fileName || undefined;
}
return undefined;
}
/**
* Generate a unique entrypoint filename that doesn't conflict with existing files
*/
export function generateEntrypointFilename(existingFiles, sourceFilename, useTypescript) {
var pathPrefix = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '';
var ext = useTypescript ? 'tsx' : 'jsx';
var candidates = ["".concat(pathPrefix, "App.").concat(ext), "".concat(pathPrefix, "entrypoint.").concat(ext), "".concat(pathPrefix, "main.").concat(ext)];
// If we have a source filename, also try variations
if (sourceFilename) {
var baseName = sourceFilename.replace(/\.[^.]*$/, '');
candidates.push("".concat(pathPrefix).concat(baseName, "-entry.").concat(ext));
}
for (var _i3 = 0, _candidates = candidates; _i3 < _candidates.length; _i3++) {
var candidate = _candidates[_i3];
if (candidate !== "".concat(pathPrefix).concat(sourceFilename) && !existingFiles[candidate]) {
return candidate;
}
}
// Generate with hash if all candidates are taken
var hash = Math.random().toString(36).substring(2, 8);
return "".concat(pathPrefix, "entrypoint-").concat(hash, ".").concat(ext);
}
/**
* Generate the relative import path from entrypoint to source file
*/
export function getRelativeImportPath(sourceFilename) {
if (!sourceFilename) {
return './App'; // Default fallback
}
// Remove extension for import
var baseName = sourceFilename.replace(/\.[^.]*$/, '');
return "./".concat(baseName);
}
/**
* Default HTML template function for Vite-based demos
*/
export function defaultHtmlTemplate(_ref) {
var language = _ref.language,
title = _ref.title,
description = _ref.description,
head = _ref.head,
entrypoint = _ref.entrypoint;
return "<!DOCTYPE html>\n<html lang=\"".concat(language, "\">\n <head>\n <meta charset=\"utf-8\" />\n <title>").concat(title, "</title>\n ").concat(description ? "<meta name=\"description\" content=\"".concat(description, "\" />") : '', "\n <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\" />").concat(head ? "\n ".concat(head.split('\n').join('\n ')) : '', "\n </head>\n <body>\n <div id=\"root\"></div>").concat(entrypoint ? "\n <script type=\"module\" src=\"".concat(entrypoint, "\"></script>") : '', "\n </body>\n</html>\n");
}
/**
* Export a variant as a standalone project with metadata files properly scoped
*/
export function exportVariant(variantCode) {
var _frameworkFiles$varia;
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var _config$title = config.title,
title = _config$title === void 0 ? 'Demo' : _config$title,
titlePrefix = config.titlePrefix,
titleSuffix = config.titleSuffix,
_config$description = config.description,
description = _config$description === void 0 ? 'Demo created with Vite' : _config$description,
descriptionPrefix = config.descriptionPrefix,
descriptionSuffix = config.descriptionSuffix,
variantName = config.variantName,
_config$language = config.language,
language = _config$language === void 0 ? 'en' : _config$language,
_config$htmlPrefix = config.htmlPrefix,
htmlPrefix = _config$htmlPrefix === void 0 ? '' : _config$htmlPrefix,
_config$sourcePrefix = config.sourcePrefix,
sourcePrefix = _config$sourcePrefix === void 0 ? 'src/' : _config$sourcePrefix,
_config$assetPrefix = config.assetPrefix,
assetPrefix = _config$assetPrefix === void 0 ? '' : _config$assetPrefix,
_config$frameworkHand = config.frameworkHandlesEntrypoint,
frameworkHandlesEntrypoint = _config$frameworkHand === void 0 ? false : _config$frameworkHand,
_config$htmlSkipJsLin = config.htmlSkipJsLink,
htmlSkipJsLink = _config$htmlSkipJsLin === void 0 ? false : _config$htmlSkipJsLin,
htmlTemplate = config.htmlTemplate,
headTemplate = config.headTemplate,
rootIndexTemplate = config.rootIndexTemplate,
_config$dependencies = config.dependencies,
dependencies = _config$dependencies === void 0 ? {} : _config$dependencies,
_config$devDependenci = config.devDependencies,
devDependencies = _config$devDependenci === void 0 ? {} : _config$devDependenci,
_config$scripts = config.scripts,
scripts = _config$scripts === void 0 ? {} : _config$scripts,
packageType = config.packageType,
_config$packageJsonFi = config.packageJsonFields,
packageJsonFields = _config$packageJsonFi === void 0 ? {} : _config$packageJsonFi,
_config$tsconfigOptio = config.tsconfigOptions,
tsconfigOptions = _config$tsconfigOptio === void 0 ? {} : _config$tsconfigOptio,
_config$viteConfig = config.viteConfig,
viteConfig = _config$viteConfig === void 0 ? {} : _config$viteConfig,
_config$useTypescript = config.useTypescript,
useTypescript = _config$useTypescript === void 0 ? false : _config$useTypescript,
_config$extraMetadata = config.extraMetadataFiles,
extraMetadataFiles = _config$extraMetadata === void 0 ? {} : _config$extraMetadata,
_config$frameworkFile = config.frameworkFiles,
frameworkFiles = _config$frameworkFile === void 0 ? {} : _config$frameworkFile,
transformVariant = config.transformVariant,
_config$versions = config.versions,
versions = _config$versions === void 0 ? {} : _config$versions,
resolveDependencies = config.resolveDependencies;
// Build final title and description with prefixes and suffixes
var finalTitle = [titlePrefix, title, titleSuffix].filter(Boolean).join('');
var finalDescription = [descriptionPrefix, description, descriptionSuffix].filter(Boolean).join('');
// Use extractMetadata to properly separate metadata and non-metadata files
var _extractMetadata = extractMetadata(variantCode),
processedVariantCode = _extractMetadata.variant,
processedGlobals = _extractMetadata.metadata;
if (transformVariant) {
var transformed = transformVariant(processedVariantCode, variantName, processedGlobals);
if (transformed) {
// Re-extract metadata after transformation
var result = transformed.variant && extractMetadata(transformed.variant);
processedVariantCode = (result == null ? void 0 : result.variant) || processedVariantCode;
// Start fresh with only the new metadata and explicitly transformed globals
// Do NOT merge with the original processedGlobals to avoid duplication
processedGlobals = _extends(_extends({}, result == null ? void 0 : result.metadata), transformed.globals);
}
}
// If packageType is explicitly provided (even as undefined), use that value
var finalPackageType;
if ('packageType' in config) {
finalPackageType = packageType;
} else {
finalPackageType = !Object.keys(frameworkFiles).length ? 'module' : undefined;
}
// Get existing extraFiles and source filename
var sourceFilename = getFilenameFromVariant(processedVariantCode);
// Get path context to understand navigation
var pathContext = createPathContext(variantCode);
// Determine if we need to rename the source file
var ext = useTypescript ? 'tsx' : 'jsx';
var isSourceFileIndex = sourceFilename === "index.".concat(ext);
var hasBackNavigation = pathContext.maxSourceBackNavigation > 0;
var actualSourceFilename = sourceFilename;
// Use urlDirectory to construct the full path from src root
var directoryPath = pathContext.urlDirectory.slice(1).join('/'); // Remove 'src' and join the rest
var actualRootFile = directoryPath ? "".concat(sourcePrefix).concat(directoryPath, "/").concat(sourceFilename) : "".concat(sourcePrefix).concat(sourceFilename);
// If the source file is index.tsx and it's in the src root, we need to rename it
if (isSourceFileIndex && !hasBackNavigation) {
actualSourceFilename = generateEntrypointFilename(processedVariantCode.extraFiles || {}, sourceFilename, useTypescript);
// When renaming due to conflicts, place the file in src root regardless of original location
actualRootFile = "".concat(sourcePrefix).concat(actualSourceFilename);
}
// The main entrypoint is always src/index.tsx (or .jsx)
var mainEntrypointFilename = "index.".concat(ext);
var entrypoint = !htmlSkipJsLink ? "".concat(sourcePrefix).concat(mainEntrypointFilename) : undefined;
// Get relative import path for the main component
var importPath;
if (!hasBackNavigation) {
// Component is in src root - import directly
importPath = getRelativeImportPath(actualSourceFilename);
} else {
// Component is in a subdirectory - import with full path from src root
var componentPath = directoryPath ? "".concat(directoryPath, "/").concat(actualSourceFilename) : actualSourceFilename;
importPath = "./".concat((componentPath || '').replace(/\.[^.]*$/, '')); // Remove extension
}
// Strip /index from the end of import paths since module resolution handles it automatically
if (importPath.endsWith('/index')) {
importPath = importPath.slice(0, -6); // Remove '/index'
}
var importString = processedVariantCode.namedExport ? "import { ".concat(processedVariantCode.namedExport, " as App } from '").concat(importPath, "';") : "import App from '".concat(importPath, "';");
// Collect all files that will be generated
var generatedFiles = {};
// Update the variant's fileName if we renamed it
if (isSourceFileIndex && !hasBackNavigation && actualSourceFilename && actualSourceFilename !== sourceFilename) {
processedVariantCode.fileName = actualSourceFilename;
}
// Check if they're providing their own framework
var isFramework = 'frameworkFiles' in config;
var externalPackages = externalsToPackages(processedVariantCode.externals || []);
var variantDeps = Object.keys(externalPackages).reduce(function (acc, pkg) {
// Check if we have a specific version for this package first
if (versions[pkg]) {
acc[pkg] = versions[pkg];
} else if (resolveDependencies) {
var resolvedDeps = resolveDependencies(pkg);
Object.assign(acc, resolvedDeps);
} else {
// Simple fallback: just use 'latest' for each package
acc[pkg] = 'latest';
}
return acc;
}, {});
// Collect metadata files to be generated
var metadataFiles = {};
// Generate package.json
var packageJson = _extends(_extends({
"private": true,
name: finalTitle.toLowerCase().replace(/[^a-z0-9]/g, '-'),
version: '0.0.0',
description: finalDescription
}, finalPackageType && {
type: finalPackageType
}), {}, {
// Add type if specified
scripts: _extends(_extends({}, !isFramework && {
dev: 'vite',
build: 'vite build',
preview: 'vite preview'
}), scripts),
dependencies: _extends(_extends({
react: versions.react || 'latest',
'react-dom': versions['react-dom'] || 'latest'
}, variantDeps), dependencies),
devDependencies: _extends(_extends(_extends({}, !isFramework && {
'@vitejs/plugin-react': 'latest',
vite: 'latest'
}), useTypescript && {
typescript: 'latest',
'@types/react': versions['@types/react'] || 'latest',
'@types/react-dom': versions['@types/react-dom'] || 'latest'
}), devDependencies)
}, packageJsonFields);
metadataFiles['package.json'] = {
source: "".concat(JSON.stringify(packageJson, null, 2), "\n")
};
// Generate entrypoint and HTML files unless framework handles them
if (!frameworkHandlesEntrypoint) {
// Create entrypoint file that imports the main component
var defaultEntrypointContent = "import * as React from 'react';\nimport * as ReactDOM from 'react-dom/client';\n".concat(importString, "\n\nReactDOM.createRoot(document.getElementById('root')").concat(useTypescript ? '!' : '', ").render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n");
var entrypointContent = rootIndexTemplate ? rootIndexTemplate({
importString: importString,
useTypescript: useTypescript
}) : defaultEntrypointContent;
generatedFiles[mainEntrypointFilename] = {
source: entrypointContent
};
}
// Add Vite config file only if no framework files (Vite-specific)
if (!isFramework) {
var viteConfigContent = "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n plugins: [react()],\n define: { 'process.env': {} },\n ...".concat(JSON.stringify(viteConfig, null, 2).split('\n').join('\n '), "\n});\n");
metadataFiles["vite.config.".concat(useTypescript ? 'ts' : 'js')] = {
source: viteConfigContent
};
}
// Add TypeScript configuration if requested
if (useTypescript) {
// Check if frameworkFiles already includes a tsconfig
var hasFrameworkTsConfig = (frameworkFiles == null ? void 0 : frameworkFiles.globals) && Object.keys(frameworkFiles.globals).some(function (fileName) {
return fileName.includes('tsconfig.json') && !fileName.includes('tsconfig.node.json');
});
if (!hasFrameworkTsConfig) {
// Main tsconfig.json (default Vite config)
var defaultTsConfig = _extends({
compilerOptions: _extends({
target: 'ES2020',
useDefineForClassFields: true,
lib: ['ES2020', 'DOM', 'DOM.Iterable'],
module: 'ESNext',
skipLibCheck: true,
moduleResolution: 'bundler',
allowImportingTsExtensions: true,
resolveJsonModule: true,
isolatedModules: true,
noEmit: true,
jsx: 'react-jsx',
strict: true,
noUnusedLocals: true,
noUnusedParameters: true,
noFallthroughCasesInSwitch: true
}, tsconfigOptions),
include: ['src']
}, !isFramework && {
references: [{
path: './tsconfig.node.json'
}]
});
metadataFiles['tsconfig.json'] = {
source: "".concat(JSON.stringify(defaultTsConfig, null, 2), "\n")
};
}
// Only add tsconfig.node.json for Vite (not for framework files)
if (!isFramework) {
// Node tsconfig for Vite config
var nodeTsConfig = {
compilerOptions: {
composite: true,
skipLibCheck: true,
module: 'ESNext',
moduleResolution: 'bundler',
allowSyntheticDefaultImports: true
},
include: ['vite.config.ts']
};
metadataFiles['tsconfig.node.json'] = {
source: "".concat(JSON.stringify(nodeTsConfig, null, 2), "\n")
};
}
}
// Generate HTML file after all files are ready
if (!frameworkHandlesEntrypoint) {
// Add index.html
var headContent = headTemplate ? headTemplate({
sourcePrefix: sourcePrefix,
assetPrefix: assetPrefix,
variant: processedVariantCode,
variantName: variantName
}) : undefined;
var htmlContent = htmlTemplate ? htmlTemplate({
language: language,
title: finalTitle,
description: finalDescription,
head: headContent,
entrypoint: entrypoint,
variant: processedVariantCode,
variantName: variantName
}) : defaultHtmlTemplate({
language: language,
title: finalTitle,
description: finalDescription,
head: headContent,
entrypoint: entrypoint
});
var htmlFileName = htmlPrefix ? "".concat(htmlPrefix, "index.html") : 'index.html';
metadataFiles[htmlFileName] = {
source: htmlContent
};
}
// Merge all metadata files including framework metadata and globals
var allMetadataFiles = mergeFiles(processedGlobals || {}, metadataFiles, extraMetadataFiles, frameworkFiles.globals || {});
// Merge all files using mergeMetadata to properly position everything with 'src/' (sourcePrefix opt) prefix
var allSourceFilesWithFramework = mergeFiles(processedVariantCode.extraFiles || {}, generatedFiles, ((_frameworkFiles$varia = frameworkFiles.variant) == null ? void 0 : _frameworkFiles$varia.extraFiles) || {});
// Update the variant with all source files including framework source files
var finalVariantWithSources = _extends(_extends({}, processedVariantCode), {}, {
extraFiles: allSourceFilesWithFramework
});
// Use mergeMetadata to position everything correctly
var finalVariant = mergeMetadata(finalVariantWithSources, allMetadataFiles, {
metadataPrefix: sourcePrefix
});
// Return new VariantCode with properly positioned files
return {
exported: finalVariant,
rootFile: actualRootFile
};
}