From 16cef9901d328fc698bacfede7c58dae7c99807b Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 5 Sep 2024 21:36:13 -0700 Subject: [PATCH] chrome: cache tsconfig for folder (#32477) Fixes https://github.com/microsoft/playwright/issues/32459 --- .../src/third_party/tsconfig-loader.ts | 47 ++-------------- .../playwright/src/transform/transform.ts | 55 ++++++++++++++++--- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/packages/playwright/src/third_party/tsconfig-loader.ts b/packages/playwright/src/third_party/tsconfig-loader.ts index 490704c330..61ad11a3bd 100644 --- a/packages/playwright/src/third_party/tsconfig-loader.ts +++ b/packages/playwright/src/third_party/tsconfig-loader.ts @@ -52,49 +52,12 @@ export interface LoadedTsConfig { allowJs?: boolean; } -export function tsConfigLoader(tsconfigPathOrDirecotry: string): LoadedTsConfig[] { - const configPath = resolveConfigPath(tsconfigPathOrDirecotry); - - if (!configPath) - return []; - +export function loadTsConfig(configPath: string): LoadedTsConfig[] { const references: LoadedTsConfig[] = []; - const config = loadTsConfig(configPath, references); + const config = innerLoadTsConfig(configPath, references); return [config, ...references]; } -function resolveConfigPath(tsconfigPathOrDirecotry: string): string | undefined { - if (fs.statSync(tsconfigPathOrDirecotry).isFile()) { - return path.resolve(tsconfigPathOrDirecotry); - } - - const configAbsolutePath = walkForTsConfig(tsconfigPathOrDirecotry); - return configAbsolutePath ? path.resolve(configAbsolutePath) : undefined; -} - -export function walkForTsConfig( - directory: string, - existsSync: (path: string) => boolean = fs.existsSync -): string | undefined { - const tsconfigPath = path.join(directory, "./tsconfig.json"); - if (existsSync(tsconfigPath)) { - return tsconfigPath; - } - const jsconfigPath = path.join(directory, "./jsconfig.json"); - if (existsSync(jsconfigPath)) { - return jsconfigPath; - } - - const parentDirectory = path.join(directory, "../"); - - // If we reached the top - if (directory === parentDirectory) { - return undefined; - } - - return walkForTsConfig(parentDirectory, existsSync); -} - function resolveConfigFile(baseConfigFile: string, referencedConfigFile: string) { if (!referencedConfigFile.endsWith('.json')) referencedConfigFile += '.json'; @@ -106,7 +69,7 @@ function resolveConfigFile(baseConfigFile: string, referencedConfigFile: string) return resolvedConfigFile; } -function loadTsConfig( +function innerLoadTsConfig( configFilePath: string, references: LoadedTsConfig[], visited = new Map(), @@ -130,7 +93,7 @@ function loadTsConfig( const extendsArray = Array.isArray(parsedConfig.extends) ? parsedConfig.extends : (parsedConfig.extends ? [parsedConfig.extends] : []); for (const extendedConfig of extendsArray) { const extendedConfigPath = resolveConfigFile(configFilePath, extendedConfig); - const base = loadTsConfig(extendedConfigPath, references, visited); + const base = innerLoadTsConfig(extendedConfigPath, references, visited); // Retain result instance, so that caching works. Object.assign(result, base, { tsConfigPath: configFilePath }); } @@ -154,7 +117,7 @@ function loadTsConfig( } for (const ref of parsedConfig.references || []) - references.push(loadTsConfig(resolveConfigFile(configFilePath, ref.path), references, visited)); + references.push(innerLoadTsConfig(resolveConfigFile(configFilePath, ref.path), references, visited)); if (path.basename(configFilePath) === 'jsconfig.json' && result.allowJs === undefined) result.allowJs = true; diff --git a/packages/playwright/src/transform/transform.ts b/packages/playwright/src/transform/transform.ts index 925cf7a83e..52d05dde90 100644 --- a/packages/playwright/src/transform/transform.ts +++ b/packages/playwright/src/transform/transform.ts @@ -15,12 +15,13 @@ */ import crypto from 'crypto'; +import fs from 'fs'; import path from 'path'; import url from 'url'; import { sourceMapSupport, pirates } from '../utilsBundle'; import type { Location } from '../../types/testReporter'; import type { LoadedTsConfig } from '../third_party/tsconfig-loader'; -import { tsConfigLoader } from '../third_party/tsconfig-loader'; +import { loadTsConfig } from '../third_party/tsconfig-loader'; import Module from 'module'; import type { BabelPlugin, BabelTransformFunction } from './babelBundle'; import { createFileMatcher, fileIsModule, resolveImportSpecifierExtension } from '../util'; @@ -57,14 +58,15 @@ export function transformConfig(): TransformConfig { return _transformConfig; } -let _singleTSConfig: string | undefined; +let _singleTSConfigPath: string | undefined; +let _singleTSConfig: ParsedTsConfigData[] | undefined; export function setSingleTSConfig(value: string | undefined) { - _singleTSConfig = value; + _singleTSConfigPath = value; } export function singleTSConfig(): string | undefined { - return _singleTSConfig; + return _singleTSConfigPath; } function validateTsConfig(tsconfig: LoadedTsConfig): ParsedTsConfigData { @@ -81,12 +83,47 @@ function validateTsConfig(tsconfig: LoadedTsConfig): ParsedTsConfigData { } function loadAndValidateTsconfigsForFile(file: string): ParsedTsConfigData[] { - const tsconfigPathOrDirecotry = _singleTSConfig || path.dirname(file); - if (!cachedTSConfigs.has(tsconfigPathOrDirecotry)) { - const loaded = tsConfigLoader(tsconfigPathOrDirecotry); - cachedTSConfigs.set(tsconfigPathOrDirecotry, loaded.map(validateTsConfig)); + if (_singleTSConfigPath && !_singleTSConfig) + _singleTSConfig = loadTsConfig(_singleTSConfigPath).map(validateTsConfig); + if (_singleTSConfig) + return _singleTSConfig; + return loadAndValidateTsconfigsForFolder(path.dirname(file)); +} + +function loadAndValidateTsconfigsForFolder(folder: string): ParsedTsConfigData[] { + const foldersWithConfig: string[] = []; + let currentFolder = path.resolve(folder); + let result: ParsedTsConfigData[] | undefined; + while (true) { + const cached = cachedTSConfigs.get(currentFolder); + if (cached) { + result = cached; + break; + } + + foldersWithConfig.push(currentFolder); + + for (const name of ['tsconfig.json', 'jsconfig.json']) { + const configPath = path.join(currentFolder, name); + if (fs.existsSync(configPath)) { + const loaded = loadTsConfig(configPath); + result = loaded.map(validateTsConfig); + break; + } + } + if (result) + break; + + const parentFolder = path.resolve(currentFolder, '../'); + if (currentFolder === parentFolder) + break; + currentFolder = parentFolder; } - return cachedTSConfigs.get(tsconfigPathOrDirecotry)!; + + result = result || []; + for (const folder of foldersWithConfig) + cachedTSConfigs.set(folder, result); + return result; } const pathSeparator = process.platform === 'win32' ? ';' : ':';