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
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
46 changes: 45 additions & 1 deletion src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -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[]>;
@@ -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
*/
190 changes: 147 additions & 43 deletions src/servicesHost.ts
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import {
CustomResolveModuleName,
CustomResolveTypeReferenceDirective,
FilePathKey,
ModuleResolutionCache,
ModuleResolutionHostMayBeCacheable,
ResolvedModule,
ServiceHostWhichMayBeCacheable,
@@ -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(
@@ -288,7 +269,8 @@ function makeResolvers<T extends typescript.ModuleResolutionHost>(
appendTsTsxSuffixesIfRequired,
scriptRegex,
moduleName,
containingFile
containingFile,
redirectedReference
)
);

@@ -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,
@@ -492,7 +496,7 @@ export function makeWatchHost(
fileName =>
files.has(filePathKeyMapper(fileName)) ||
compiler.sys.fileExists(fileName),
/*enabledCaching*/ false
instance.loaderOptions.experimentalFileCaching
);

const watchHost: WatchHost = {
@@ -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
*/
@@ -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,
};

@@ -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,
@@ -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
);
}

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

@@ -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 ||
@@ -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,
Loading