Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use caches for module resolution and type reference directives when using compiler default functions #1287

Merged
merged 12 commits into from Apr 22, 2021
46 changes: 45 additions & 1 deletion src/interfaces.ts
Expand Up @@ -68,7 +68,8 @@ export interface ServiceHostWhichMayBeCacheable
HostMayBeCacheable {}

export interface WatchHost
extends typescript.WatchCompilerHostOfFilesAndCompilerOptions<typescript.EmitAndSemanticDiagnosticsBuilderProgram> {
extends typescript.WatchCompilerHostOfFilesAndCompilerOptions<typescript.EmitAndSemanticDiagnosticsBuilderProgram>,
HostMayBeCacheable {
invokeFileWatcher: WatchFactory['invokeFileWatcher'];
updateRootFileNames(): void;
outputFiles: Map<FilePathKey, typescript.OutputFile[]>;
Expand Down Expand Up @@ -139,13 +140,56 @@ export interface ConfigFileInfo {
dtsFiles?: string[];
}

interface CacheWithRedirects<T> {
ownMap: Map<string, T>;
redirectsMap: Map<typescript.Path, Map<string, T>>;
getOrCreateMapOfCacheRedirects(
redirectedReference: typescript.ResolvedProjectReference | undefined
): Map<string, T>;
clear(): void;
setOwnOptions(newOptions: typescript.CompilerOptions): void;
setOwnMap(newOwnMap: Map<string, T>): void;
}
interface PerModuleNameCache {
get(
directory: string
): typescript.ResolvedModuleWithFailedLookupLocations | undefined;
set(
directory: string,
result: typescript.ResolvedModuleWithFailedLookupLocations
): void;
}
export interface ModuleResolutionCache
extends typescript.ModuleResolutionCache {
directoryToModuleNameMap: CacheWithRedirects<
Map<string, typescript.ResolvedModuleWithFailedLookupLocations>
>;
moduleNameToDirectoryMap: CacheWithRedirects<PerModuleNameCache>;
clear(): void;
update(compilerOptions: typescript.CompilerOptions): void;
getPackageJsonInfoCache?(): any;
}
// Till api is updated
johnnyreilly marked this conversation as resolved.
Show resolved Hide resolved
export interface TypeReferenceDirectiveResolutionCache {
getOrCreateCacheForDirectory(
directoryName: string,
redirectedReference?: typescript.ResolvedProjectReference
): Map<
string,
typescript.ResolvedTypeReferenceDirectiveWithFailedLookupLocations
>;
clear(): void;
update(compilerOptions: typescript.CompilerOptions): void;
}
export interface TSInstance {
compiler: typeof typescript;
compilerOptions: typescript.CompilerOptions;
/** Used for Vue for the most part */
appendTsTsxSuffixesIfRequired: (filePath: string) => string;
loaderOptions: LoaderOptions;
rootFileNames: Set<string>;
moduleResolutionCache?: ModuleResolutionCache;
typeReferenceResolutionCache?: TypeReferenceDirectiveResolutionCache;
/**
* a cache of all the files
*/
Expand Down
190 changes: 147 additions & 43 deletions src/servicesHost.ts
Expand Up @@ -10,6 +10,7 @@ import {
CustomResolveModuleName,
CustomResolveTypeReferenceDirective,
FilePathKey,
ModuleResolutionCache,
ModuleResolutionHostMayBeCacheable,
ResolvedModule,
ServiceHostWhichMayBeCacheable,
Expand Down Expand Up @@ -247,39 +248,19 @@ function makeResolvers<T extends typescript.ModuleResolutionHost>(
scriptRegex: RegExp,
instance: TSInstance
) {
const resolveTypeReferenceDirective = makeResolveTypeReferenceDirective(
compiler,
compilerOptions,
moduleResolutionHost,
customResolveTypeReferenceDirective
);

const resolveTypeReferenceDirectives = (
typeDirectiveNames: string[],
containingFile: string,
_redirectedReference?: typescript.ResolvedProjectReference
): (typescript.ResolvedTypeReferenceDirective | undefined)[] =>
typeDirectiveNames.map(
directive =>
resolveTypeReferenceDirective(
directive,
containingFile,
_redirectedReference
).resolvedTypeReferenceDirective
);

const resolveModuleName = makeResolveModuleName(
compiler,
compilerOptions,
moduleResolutionHost,
customResolveModuleName
customResolveModuleName,
instance
);

const resolveModuleNames = (
moduleNames: string[],
containingFile: string,
_reusedNames?: string[] | undefined,
_redirectedReference?: typescript.ResolvedProjectReference | undefined
redirectedReference?: typescript.ResolvedProjectReference | undefined
): (typescript.ResolvedModule | undefined)[] => {
const resolvedModules = moduleNames.map(moduleName =>
resolveModule(
Expand All @@ -288,7 +269,8 @@ function makeResolvers<T extends typescript.ModuleResolutionHost>(
appendTsTsxSuffixesIfRequired,
scriptRegex,
moduleName,
containingFile
containingFile,
redirectedReference
)
);

Expand All @@ -297,6 +279,28 @@ function makeResolvers<T extends typescript.ModuleResolutionHost>(
return resolvedModules;
};

const resolveTypeReferenceDirective = makeResolveTypeReferenceDirective(
compiler,
compilerOptions,
moduleResolutionHost,
customResolveTypeReferenceDirective,
instance
);

const resolveTypeReferenceDirectives = (
typeDirectiveNames: string[],
containingFile: string,
redirectedReference?: typescript.ResolvedProjectReference
): (typescript.ResolvedTypeReferenceDirective | undefined)[] =>
typeDirectiveNames.map(
directive =>
resolveTypeReferenceDirective(
directive,
containingFile,
redirectedReference
).resolvedTypeReferenceDirective
);

return {
resolveTypeReferenceDirectives,
resolveModuleNames,
Expand Down Expand Up @@ -492,7 +496,7 @@ export function makeWatchHost(
fileName =>
files.has(filePathKeyMapper(fileName)) ||
compiler.sys.fileExists(fileName),
/*enabledCaching*/ false
instance.loaderOptions.experimentalFileCaching
);

const watchHost: WatchHost = {
Expand Down Expand Up @@ -600,6 +604,60 @@ export function makeWatchHost(

const missingFileModifiedTime = new Date(0);

function identity<T>(x: T) {
return x;
}
function toLowerCase(x: string) {
return x.toLowerCase();
}
const fileNameLowerCaseRegExp = /[^\u0130\u0131\u00DFa-z0-9\\/:\-_\. ]+/g;
function toFileNameLowerCase(x: string) {
return fileNameLowerCaseRegExp.test(x)
? x.replace(fileNameLowerCaseRegExp, toLowerCase)
: x;
}
function createGetCanonicalFileName(instance: TSInstance) {
return useCaseSensitiveFileNames(instance.compiler, instance.loaderOptions)
? identity
: toFileNameLowerCase;
}

function createModuleResolutionCache(
instance: TSInstance,
moduleResolutionHost: typescript.ModuleResolutionHost
): ModuleResolutionCache {
const cache = instance.compiler.createModuleResolutionCache(
moduleResolutionHost.getCurrentDirectory!(),
createGetCanonicalFileName(instance),
instance.compilerOptions
) as ModuleResolutionCache;
// Add new API optional methods
if (!cache.clear) {
cache.clear = () => {
cache.directoryToModuleNameMap.clear();
cache.moduleNameToDirectoryMap.clear();
};
}
if (!cache.update) {
cache.update = options => {
if (!options.configFile) return;
const ref: typescript.ResolvedProjectReference = {
sourceFile: options.configFile! as typescript.TsConfigSourceFile,
commandLine: { options } as typescript.ParsedCommandLine,
};
cache.directoryToModuleNameMap.setOwnMap(
cache.directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref)
);
cache.moduleNameToDirectoryMap.setOwnMap(
cache.moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref)
);
cache.directoryToModuleNameMap.setOwnOptions(options);
cache.moduleNameToDirectoryMap.setOwnOptions(options);
};
}
return cache;
}

/**
* Create the TypeScript Watch host
*/
Expand All @@ -617,12 +675,7 @@ export function makeSolutionBuilderHost(
// loader.context seems to work fine on Linux / Mac regardless causes problems for @types resolution on Windows for TypeScript < 2.3
const formatDiagnosticHost: typescript.FormatDiagnosticsHost = {
getCurrentDirectory: compiler.sys.getCurrentDirectory,
getCanonicalFileName: useCaseSensitiveFileNames(
compiler,
instance.loaderOptions
)
? s => s
: s => s.toLowerCase(),
getCanonicalFileName: createGetCanonicalFileName(instance),
getNewLine: () => compiler.sys.newLine,
};

Expand Down Expand Up @@ -705,6 +758,28 @@ export function makeSolutionBuilderHost(
const solutionBuilderHost: SolutionBuilderWithWatchHost = {
...sysHost,
...moduleResolutionHost,
createProgram: (
rootNames,
options,
host,
oldProgram,
configFileParsingDiagnostics,
projectReferences
) => {
instance.moduleResolutionCache?.update(options || {});
instance.typeReferenceResolutionCache?.update(options || {});
const result = sysHost.createProgram(
rootNames,
options,
host,
oldProgram,
configFileParsingDiagnostics,
projectReferences
);
instance.typeReferenceResolutionCache?.update(instance.compilerOptions);
instance.moduleResolutionCache?.update(instance.compilerOptions);
return result;
},
resolveModuleNames,
resolveTypeReferenceDirectives,
diagnostics,
Expand Down Expand Up @@ -1091,16 +1166,31 @@ function makeResolveTypeReferenceDirective(
moduleResolutionHost: typescript.ModuleResolutionHost,
customResolveTypeReferenceDirective:
| CustomResolveTypeReferenceDirective
| undefined
| undefined,
instance: TSInstance
): ResolveTypeReferenceDirective {
if (customResolveTypeReferenceDirective === undefined) {
// Till we the api is published
johnnyreilly marked this conversation as resolved.
Show resolved Hide resolved
if (
(compiler as any).createTypeReferenceDirectiveResolutionCache &&
!instance.typeReferenceResolutionCache
) {
instance.typeReferenceResolutionCache = (compiler as any).createTypeReferenceDirectiveResolutionCache(
moduleResolutionHost.getCurrentDirectory!(),
createGetCanonicalFileName(instance),
instance.compilerOptions,
instance.moduleResolutionCache?.getPackageJsonInfoCache?.()
);
}
return (directive, containingFile, redirectedReference) =>
compiler.resolveTypeReferenceDirective(
// Till we the api is published
johnnyreilly marked this conversation as resolved.
Show resolved Hide resolved
(compiler.resolveTypeReferenceDirective as any)(
directive,
containingFile,
compilerOptions,
moduleResolutionHost,
redirectedReference
redirectedReference,
instance.typeReferenceResolutionCache
);
}

Expand Down Expand Up @@ -1130,7 +1220,8 @@ function resolveModule(
appendTsTsxSuffixesIfRequired: (filePath: string) => string,
scriptRegex: RegExp,
moduleName: string,
containingFile: string
containingFile: string,
redirectedReference: typescript.ResolvedProjectReference | undefined
) {
let resolutionResult: ResolvedModule;

Expand All @@ -1150,16 +1241,19 @@ function resolveModule(
}
} catch (e) {}

const tsResolution = resolveModuleName(moduleName, containingFile);
const tsResolution = resolveModuleName(
moduleName,
containingFile,
redirectedReference
);
if (tsResolution.resolvedModule !== undefined) {
const resolvedFileName = path.normalize(
tsResolution.resolvedModule.resolvedFileName
);
const tsResolutionResult: ResolvedModule = {
...tsResolution.resolvedModule,
originalFileName: resolvedFileName,
resolvedFileName,
isExternalLibraryImport:
tsResolution.resolvedModule.isExternalLibraryImport,
};

return resolutionResult! === undefined ||
Expand All @@ -1174,26 +1268,36 @@ function resolveModule(

type ResolveModuleName = (
moduleName: string,
containingFile: string
containingFile: string,
redirectedReference: typescript.ResolvedProjectReference | undefined
) => typescript.ResolvedModuleWithFailedLookupLocations;

function makeResolveModuleName(
compiler: typeof typescript,
compilerOptions: typescript.CompilerOptions,
moduleResolutionHost: typescript.ModuleResolutionHost,
customResolveModuleName: CustomResolveModuleName | undefined
customResolveModuleName: CustomResolveModuleName | undefined,
instance: TSInstance
): ResolveModuleName {
if (customResolveModuleName === undefined) {
return (moduleName: string, containingFile: string) =>
if (!instance.moduleResolutionCache) {
instance.moduleResolutionCache = createModuleResolutionCache(
instance,
moduleResolutionHost
);
}
return (moduleName, containingFile, redirectedReference) =>
compiler.resolveModuleName(
moduleName,
containingFile,
compilerOptions,
moduleResolutionHost
moduleResolutionHost,
instance.moduleResolutionCache,
redirectedReference
);
}

return (moduleName: string, containingFile: string) =>
return (moduleName, containingFile) =>
customResolveModuleName(
moduleName,
containingFile,
Expand Down
3 changes: 3 additions & 0 deletions src/watch-run.ts
Expand Up @@ -22,6 +22,9 @@ export function makeWatchRun(

return (compiler: webpack.Compiler, callback: (err?: Error) => void) => {
instance.servicesHost?.clearCache?.();
instance.watchHost?.clearCache?.();
instance.moduleResolutionCache?.clear();
instance.typeReferenceResolutionCache?.clear();
const promises = [];
if (instance.loaderOptions.transpileOnly) {
instance.reportTranspileErrors = true;
Expand Down