UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

149 lines (140 loc) 4.52 kB
// webpack doesn't like node: imports // eslint-disable-next-line n/prefer-node-protocol import fs from 'fs/promises'; // eslint-disable-next-line n/prefer-node-protocol import path from 'path'; import ts from 'typescript'; // Cache for loaded TypeScript configurations // Uses process object to persist across Turbopack module contexts const TSCONFIG_CACHE_KEY = Symbol.for('@mui/docs-infra/tsconfig-cache'); const TSCONFIG_WATCHERS_KEY = Symbol.for('@mui/docs-infra/tsconfig-watchers'); function getTsConfigCache() { const processObj = process; if (!processObj[TSCONFIG_CACHE_KEY]) { processObj[TSCONFIG_CACHE_KEY] = new Map(); } return processObj[TSCONFIG_CACHE_KEY]; } function getTsConfigWatchers() { const processObj = process; if (!processObj[TSCONFIG_WATCHERS_KEY]) { processObj[TSCONFIG_WATCHERS_KEY] = new Set(); } return processObj[TSCONFIG_WATCHERS_KEY]; } function setupFileWatcher(configPath) { const cache = getTsConfigCache(); const watchers = getTsConfigWatchers(); // Skip if already watching this file if (watchers.has(configPath)) { return; } // Mark as watching watchers.add(configPath); // Set up file watcher const watcher = fs.watch(configPath); // Handle file changes (async () => { try { for await (const event of watcher) { if (event.eventType === 'change') { // Purge this config and any configs that might extend it const cachedPaths = Array.from(cache.keys()); const toPurge = cachedPaths.filter(cachedPath => cachedPath === configPath || cachedPath.includes(configPath)); toPurge.forEach(pathToPurge => { cache.delete(pathToPurge); }); } } } catch (error) { // Watcher was closed or file was deleted watchers.delete(configPath); } })(); } function mergeConfig(target, source) { for (const key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { if (typeof target[key] === 'object' && target[key] !== null && typeof source[key] === 'object' && source[key] !== null && !Array.isArray(target[key]) && !Array.isArray(source[key])) { mergeConfig(target[key], source[key]); } else { target[key] = source[key]; } } } return target; } async function loadTypescriptConfigUncached(configPath) { const dependencies = [configPath]; const projectPath = path.dirname(configPath); const data = await fs.readFile(configPath, 'utf-8'); const { config, error } = ts.readConfigFile(configPath, filePath => { if (filePath !== configPath) { throw new Error(`Unexpected file path: ${filePath}`); } return data.toString(); }); if (error) { throw error; } const { options, errors, raw } = ts.parseJsonConfigFileContent(config, ts.sys, projectPath); options.configFilePath = configPath; let mergedConfig = options; if (raw.extends) { const extendedConfigPath = path.resolve(projectPath, raw.extends); const { options: parentOptions, projectPath: parentPath, dependencies: parentDependencies } = await loadTypescriptConfig(extendedConfigPath); dependencies.push(...parentDependencies); // scope compilerOptions.paths relative to the tsconfig location if (parentOptions.paths) { Object.keys(parentOptions.paths).forEach(key => { if (!parentOptions.paths) { return; } const paths = []; parentOptions.paths[key].forEach(relativePath => { const absolutePath = path.resolve(parentPath, relativePath); let scopedPath = path.relative(projectPath, absolutePath); if (!scopedPath.startsWith('.')) { scopedPath = `./${scopedPath}`; } paths.push(scopedPath); }); parentOptions.paths[key] = paths; }); } mergedConfig = mergeConfig(parentOptions, mergedConfig); } if (errors.length > 0) { throw errors[0]; } return { projectPath, options: mergedConfig, dependencies }; } export async function loadTypescriptConfig(configPath) { const cache = getTsConfigCache(); // Return cached promise if it exists const cached = cache.get(configPath); if (cached) { return cached; } // Set up file watcher to purge cache on changes setupFileWatcher(configPath); // Create and cache the promise const promise = loadTypescriptConfigUncached(configPath); cache.set(configPath, promise); return promise; }