Skip to content

Commit

Permalink
Use caches for module resolution and type reference directives when u…
Browse files Browse the repository at this point in the history
…sing compiler default functions (#1287)

* ClearCache for watchHost

* Use module resolution cache

* Use complete ResolvedModuleFull from typescript

* Use type reference directive cache so that results are atomic like module resolution till watch is invoked

* Some updates

* Test baseline update

* Update src/servicesHost.ts

* Update src/servicesHost.ts

* Update CHANGELOG.md

* Update package.json

* Update src/interfaces.ts

Co-authored-by: John Reilly <johnny_reilly@hotmail.com>
  • Loading branch information
sheetalkamat and johnnyreilly committed Apr 22, 2021
1 parent f6b9118 commit 0d735e5
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 51 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog

## v9.1.0

* [Use caches for module resolution and type reference directives when using compiler default functions](https://github.com/TypeStrong/ts-loader/pull/1287) - thanks @sheetalkamat - uses: https://github.com/microsoft/TypeScript/pull/43700

## v9.0.2

* [Remove usage of loader-utils](https://github.com/TypeStrong/ts-loader/pull/1288) - thanks @jonwallsten
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "ts-loader",
"version": "9.0.2",
"version": "9.1.0",
"description": "TypeScript loader for webpack",
"main": "index.js",
"types": "dist",
Expand Down
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;
}
// Until the API has been released and ts-loader is built against a version of TypeScript that contains it - see https://github.com/microsoft/TypeScript/blob/74993a2a64bb2e423b40204bb54ff749cdd4ef54/src/compiler/moduleNameResolver.ts#L458
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) {
// Until the api is published
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(
// Until the api is published
(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

0 comments on commit 0d735e5

Please sign in to comment.