Skip to content
Merged
46 changes: 45 additions & 1 deletion src/interfaces.ts
Original file line number Diff line number Diff line change
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
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
Original file line number Diff line number Diff line change
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
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
(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
Original file line number Diff line number Diff line change
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
Loading