@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
179 lines (162 loc) • 6.47 kB
JavaScript
/**
* Generates a unique import name based on module path and original name
*/
function generateUniqueImportName(originalName, modulePath, type, usedNames) {
// If no conflict, use original name
if (!usedNames.has(originalName)) {
return originalName;
}
// For conflicts, strategy depends on type and context:
// - Namespace imports: always use numbered suffixes
// - Named imports from simple test cases (lib1, lib2, etc.): use numbered suffixes
// - Other cases: try module-based names first
const wantsNumberedSuffix = type === 'namespace' || modulePath.startsWith('lib') && /^lib\d+$/.test(modulePath);
if (wantsNumberedSuffix) {
// Use numbered suffixes
let attempt = 1;
let uniqueName = `${originalName}${attempt}`;
while (usedNames.has(uniqueName)) {
attempt += 1;
uniqueName = `${originalName}${attempt}`;
}
return uniqueName;
}
// For real modules, try module-based names first
const moduleKey = modulePath.replace(/[@/.-]/g, '') // Remove special characters
.toLowerCase().slice(0, 20); // Limit length
let uniqueName = `${originalName}${moduleKey}`;
// If that's still taken, try numbered suffixes
if (usedNames.has(uniqueName)) {
let attempt = 1;
do {
uniqueName = `${originalName}${attempt}`;
attempt += 1;
} while (usedNames.has(uniqueName));
}
return uniqueName;
}
/**
* Helper function to check if a string is a valid JavaScript identifier
*/
function isValidIdentifier(str) {
// JavaScript identifier rules: must start with letter, $, or _, followed by letters, digits, $, or _
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(str);
}
/**
* Generates both import statements and resolved externals object
* Returns the import statements and the externals as a JavaScript object
*
* `existingNames` are identifier names already in scope at the injection site
* (e.g. existing imports or top-level declarations in the destination file).
* They are seeded into the conflict-resolution set so injected imports get
* aliased to a unique name when they would collide.
*/
export function generateResolvedExternals(externals, existingNames) {
const moduleImports = {};
const usedNames = new Set(existingNames);
const seenImports = new Set();
// First pass: collect all imports and resolve naming conflicts
for (const [modulePath, importItems] of Object.entries(externals)) {
if (!moduleImports[modulePath]) {
moduleImports[modulePath] = {
named: [],
namespace: []
};
}
for (const {
name: originalName,
type,
isType
} of importItems) {
// Skip type-only imports and empty names
if (isType || !originalName.trim()) {
continue;
}
const importKey = `${modulePath}:${originalName}:${type}`;
// Skip duplicates
if (seenImports.has(importKey)) {
continue;
}
seenImports.add(importKey);
const uniqueName = generateUniqueImportName(originalName, modulePath, type, usedNames);
usedNames.add(uniqueName);
if (type === 'default') {
moduleImports[modulePath].default = uniqueName;
} else if (type === 'named') {
moduleImports[modulePath].named.push({
original: originalName,
unique: uniqueName
});
} else if (type === 'namespace') {
moduleImports[modulePath].namespace.push(uniqueName);
}
}
}
// Second pass: generate consolidated import statements and resolved externals
const imports = [];
const resolvedExternalsObject = {};
for (const [modulePath, moduleImport] of Object.entries(moduleImports)) {
const hasDefault = moduleImport.default !== undefined;
const hasNamed = moduleImport.named.length > 0;
const hasNamespace = moduleImport.namespace.length > 0;
// Skip modules that have no valid imports
if (!hasDefault && !hasNamed && !hasNamespace) {
continue;
}
const importParts = [];
// Add default import
if (moduleImport.default) {
importParts.push(moduleImport.default);
}
// Add named imports (consolidated into one statement)
if (moduleImport.named.length > 0) {
const namedImports = moduleImport.named.map(({
original,
unique
}) => original === unique ? original : `${original} as ${unique}`).join(', ');
importParts.push(`{ ${namedImports} }`);
}
// Generate import statement
if (importParts.length > 0) {
imports.push(`import ${importParts.join(', ')} from '${modulePath}';`);
}
// Add namespace imports (separate statements as they can't be combined)
for (const namespaceName of moduleImport.namespace) {
imports.push(`import * as ${namespaceName} from '${modulePath}';`);
}
// Generate resolved externals entry for this module
// For invalid JavaScript identifiers, use the quoted version as the key
const objectKey = isValidIdentifier(modulePath) ? modulePath : `"${modulePath}"`;
let resolvedValue;
if (hasDefault && !hasNamed && !hasNamespace) {
// Single default export - use direct assignment (e.g., 'react': React)
resolvedValue = moduleImport.default;
} else if (!hasDefault && hasNamed && !hasNamespace) {
// Named exports only - use object syntax (e.g., '@mui/material': { Button, TextField }).
// When a name had to be aliased due to a collision, emit `{ original: unique }`
// so the resolved value still references the renamed local binding.
const namedExports = moduleImport.named.map(({
original,
unique
}) => original === unique ? original : `${original}: ${unique}`).join(', ');
resolvedValue = `{ ${namedExports} }`;
} else if (!hasDefault && !hasNamed && hasNamespace) {
// Single namespace export - use direct assignment (e.g., 'lodash': lodash)
resolvedValue = moduleImport.namespace[0];
} else if (hasDefault) {
// Mixed imports - prefer default for the resolved externals
resolvedValue = moduleImport.default;
} else if (hasNamespace) {
// Mixed imports - use namespace if no default
resolvedValue = moduleImport.namespace[0];
} else {
continue; // Should not happen, but safety check
}
// Add to the resolved externals object using the object key
resolvedExternalsObject[objectKey] = resolvedValue;
}
return {
imports,
resolvedExternals: resolvedExternalsObject
};
}