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
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