Skip to content

Commit 2acedd6

Browse files
author
Andy Hanson
committed
Reduce file lookups in node_modules module resolution
1 parent 537695c commit 2acedd6

File tree

69 files changed

+140
-379
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+140
-379
lines changed

src/compiler/moduleNameResolver.ts

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,27 @@ namespace ts {
3232
* Kinds of file that we are currently looking for.
3333
* Typically there is one pass with Extensions.TypeScript, then a second pass with Extensions.JavaScript.
3434
*/
35-
enum Extensions {
36-
TypeScript, /** '.ts', '.tsx', or '.d.ts' */
37-
JavaScript, /** '.js' or '.jsx' */
38-
DtsOnly /** Only '.d.ts' */
35+
const enum Extensions {
36+
/**
37+
* '.ts', '.tsx', or '.d.ts'
38+
* This is for regular imports of TypeScript code.
39+
*/
40+
TypeScript = "TypeScript",
41+
/**
42+
* Same as TypeScript, but look for a '.d.ts' file first.
43+
* This is used inside node_modules (in the first phase, before we look for '.js')
44+
*/
45+
TypeScriptDtsFirst = "TypeScript ('.d.ts' preferred)",
46+
/**
47+
* '.js' or '.jsx'
48+
* This is done only after either TypeScript or TypeScriptDtsFirst has been tried.
49+
*/
50+
JavaScript = "JavaScript",
51+
/**
52+
* Only '.d.ts'
53+
* This is used for lookups in '@types'
54+
*/
55+
DtsOnly = "'.d.ts' only"
3956
}
4057

4158
/** Used with `Extensions.DtsOnly` to extract the path from TypeScript results. */
@@ -717,7 +734,7 @@ namespace ts {
717734

718735
if (moduleHasNonRelativeName(moduleName)) {
719736
if (traceEnabled) {
720-
trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]);
737+
trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, extensions);
721738
}
722739
const resolved = loadModuleFromNodeModules(extensions, moduleName, containingDirectory, failedLookupLocations, state, cache);
723740
// For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files.
@@ -745,7 +762,7 @@ namespace ts {
745762

746763
function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined {
747764
if (state.traceEnabled) {
748-
trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, Extensions[extensions]);
765+
trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, extensions);
749766
}
750767
if (!pathEndsWithDirectorySeparator(candidate)) {
751768
if (!onlyRecordFailures) {
@@ -818,6 +835,8 @@ namespace ts {
818835
return tryExtension(Extension.Dts);
819836
case Extensions.TypeScript:
820837
return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts);
838+
case Extensions.TypeScriptDtsFirst:
839+
return tryExtension(Extension.Dts) || tryExtension(Extension.Ts) || tryExtension(Extension.Tsx);
821840
case Extensions.JavaScript:
822841
return tryExtension(Extension.Js) || tryExtension(Extension.Jsx);
823842
}
@@ -920,22 +939,26 @@ namespace ts {
920939
return combinePaths(directory, "package.json");
921940
}
922941

923-
function loadModuleFromNodeModulesFolder(extensions: Extensions, moduleName: string, nodeModulesFolder: string, nodeModulesFolderExists: boolean, failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
924-
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
942+
// Inside of node_modules we should be looking for '.d.ts' files before '.ts' files.
943+
type NodeModulesExtensions = Extensions.TypeScriptDtsFirst | Extensions.JavaScript | Extensions.DtsOnly;
925944

926-
return loadModuleFromFile(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state) ||
927-
loadNodeModuleFromDirectory(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state);
945+
function loadModuleFromNodeModulesFolder(extensions: NodeModulesExtensions, moduleName: string, nodeModulesFolder: string, nodeModulesFolderExists: boolean, failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
946+
const packageDir = normalizePath(combinePaths(nodeModulesFolder, moduleName));
947+
return loadNodeModuleFromDirectory(extensions, packageDir, failedLookupLocations, !nodeModulesFolderExists, state);
928948
}
929949

930950
function loadModuleFromNodeModules(extensions: Extensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, cache: NonRelativeModuleNameResolutionCache): SearchResult<Resolved> {
931-
return loadModuleFromNodeModulesWorker(extensions, moduleName, directory, failedLookupLocations, state, /*typesOnly*/ false, cache);
951+
// In node_modules, we will look for a '.d.ts' file first.
952+
const ext = extensions === Extensions.TypeScript ? Extensions.TypeScriptDtsFirst : extensions;
953+
return loadModuleFromNodeModulesWorker(ext, moduleName, directory, failedLookupLocations, state, /*typesOnly*/ false, cache);
932954
}
955+
933956
function loadModuleFromNodeModulesAtTypes(moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState): SearchResult<Resolved> {
934957
// Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly.
935958
return loadModuleFromNodeModulesWorker(Extensions.DtsOnly, moduleName, directory, failedLookupLocations, state, /*typesOnly*/ true, /*cache*/ undefined);
936959
}
937960

938-
function loadModuleFromNodeModulesWorker(extensions: Extensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, typesOnly: boolean, cache: NonRelativeModuleNameResolutionCache): SearchResult<Resolved> {
961+
function loadModuleFromNodeModulesWorker(extensions: NodeModulesExtensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, typesOnly: boolean, cache: NonRelativeModuleNameResolutionCache): SearchResult<Resolved> {
939962
const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName);
940963
return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => {
941964
if (getBaseFileName(ancestorDirectory) !== "node_modules") {
@@ -949,7 +972,7 @@ namespace ts {
949972
}
950973

951974
/** Load a module from a single node_modules directory, but not from any ancestors' node_modules directories. */
952-
function loadModuleFromNodeModulesOneLevel(extensions: Extensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, typesOnly = false): Resolved | undefined {
975+
function loadModuleFromNodeModulesOneLevel(extensions: NodeModulesExtensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, typesOnly = false): Resolved | undefined {
953976
const nodeModulesFolder = combinePaths(directory, "node_modules");
954977
const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host);
955978
if (!nodeModulesFolderExists && state.traceEnabled) {

src/harness/unittests/moduleResolution.ts

Lines changed: 14 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -182,36 +182,26 @@ namespace ts {
182182

183183
function test(hasDirectoryExists: boolean) {
184184
const containingFile = { name: "/a/b/c/d/e.ts" };
185-
const moduleFile = { name: "/a/b/node_modules/foo.ts" };
185+
const moduleFile = { name: "/a/b/node_modules/foo/index.d.ts" };
186186
const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile));
187187
checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true), [
188-
"/a/b/c/d/node_modules/foo.ts",
189-
"/a/b/c/d/node_modules/foo.tsx",
190-
"/a/b/c/d/node_modules/foo.d.ts",
191188
"/a/b/c/d/node_modules/foo/package.json",
192-
189+
"/a/b/c/d/node_modules/foo/index.d.ts",
193190
"/a/b/c/d/node_modules/foo/index.ts",
194191
"/a/b/c/d/node_modules/foo/index.tsx",
195-
"/a/b/c/d/node_modules/foo/index.d.ts",
196192

197-
"/a/b/c/d/node_modules/@types/foo.d.ts",
198193
"/a/b/c/d/node_modules/@types/foo/package.json",
199-
200194
"/a/b/c/d/node_modules/@types/foo/index.d.ts",
201195

202-
"/a/b/c/node_modules/foo.ts",
203-
"/a/b/c/node_modules/foo.tsx",
204-
"/a/b/c/node_modules/foo.d.ts",
205196
"/a/b/c/node_modules/foo/package.json",
206-
197+
"/a/b/c/node_modules/foo/index.d.ts",
207198
"/a/b/c/node_modules/foo/index.ts",
208199
"/a/b/c/node_modules/foo/index.tsx",
209-
"/a/b/c/node_modules/foo/index.d.ts",
210200

211-
"/a/b/c/node_modules/@types/foo.d.ts",
212201
"/a/b/c/node_modules/@types/foo/package.json",
213-
214202
"/a/b/c/node_modules/@types/foo/index.d.ts",
203+
204+
"/a/b/node_modules/foo/package.json",
215205
]);
216206
}
217207
});
@@ -237,55 +227,31 @@ namespace ts {
237227
const moduleFile = { name: "/a/node_modules/foo/index.d.ts" };
238228
const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile));
239229
checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true), [
240-
"/a/node_modules/b/c/node_modules/d/node_modules/foo.ts",
241-
"/a/node_modules/b/c/node_modules/d/node_modules/foo.tsx",
242-
"/a/node_modules/b/c/node_modules/d/node_modules/foo.d.ts",
243230
"/a/node_modules/b/c/node_modules/d/node_modules/foo/package.json",
244-
231+
"/a/node_modules/b/c/node_modules/d/node_modules/foo/index.d.ts",
245232
"/a/node_modules/b/c/node_modules/d/node_modules/foo/index.ts",
246233
"/a/node_modules/b/c/node_modules/d/node_modules/foo/index.tsx",
247-
"/a/node_modules/b/c/node_modules/d/node_modules/foo/index.d.ts",
248234

249-
"/a/node_modules/b/c/node_modules/d/node_modules/@types/foo.d.ts",
250235
"/a/node_modules/b/c/node_modules/d/node_modules/@types/foo/package.json",
251-
252236
"/a/node_modules/b/c/node_modules/d/node_modules/@types/foo/index.d.ts",
253237

254-
"/a/node_modules/b/c/node_modules/foo.ts",
255-
"/a/node_modules/b/c/node_modules/foo.tsx",
256-
"/a/node_modules/b/c/node_modules/foo.d.ts",
257238
"/a/node_modules/b/c/node_modules/foo/package.json",
258-
239+
"/a/node_modules/b/c/node_modules/foo/index.d.ts",
259240
"/a/node_modules/b/c/node_modules/foo/index.ts",
260241
"/a/node_modules/b/c/node_modules/foo/index.tsx",
261-
"/a/node_modules/b/c/node_modules/foo/index.d.ts",
262242

263-
"/a/node_modules/b/c/node_modules/@types/foo.d.ts",
264243
"/a/node_modules/b/c/node_modules/@types/foo/package.json",
265-
266244
"/a/node_modules/b/c/node_modules/@types/foo/index.d.ts",
267245

268-
"/a/node_modules/b/node_modules/foo.ts",
269-
"/a/node_modules/b/node_modules/foo.tsx",
270-
"/a/node_modules/b/node_modules/foo.d.ts",
271246
"/a/node_modules/b/node_modules/foo/package.json",
272-
247+
"/a/node_modules/b/node_modules/foo/index.d.ts",
273248
"/a/node_modules/b/node_modules/foo/index.ts",
274249
"/a/node_modules/b/node_modules/foo/index.tsx",
275-
"/a/node_modules/b/node_modules/foo/index.d.ts",
276250

277-
"/a/node_modules/b/node_modules/@types/foo.d.ts",
278251
"/a/node_modules/b/node_modules/@types/foo/package.json",
279-
280252
"/a/node_modules/b/node_modules/@types/foo/index.d.ts",
281253

282-
"/a/node_modules/foo.ts",
283-
"/a/node_modules/foo.tsx",
284-
"/a/node_modules/foo.d.ts",
285254
"/a/node_modules/foo/package.json",
286-
287-
"/a/node_modules/foo/index.ts",
288-
"/a/node_modules/foo/index.tsx"
289255
]);
290256
}
291257
});
@@ -574,7 +540,7 @@ import b = require("./moduleB");
574540
const file4Typings: File = { name: "/root/generated/folder2/file4/package.json", content: JSON.stringify({ typings: "dist/types.d.ts" }) };
575541
const file4: File = { name: "/root/generated/folder2/file4/dist/types.d.ts" }; // load file pointed by typings
576542
const file5: File = { name: "/root/someanotherfolder/file5/index.d.ts" }; // load remapped module from folder
577-
const file6: File = { name: "/root/node_modules/file6.ts" }; // fallback to node
543+
const file6: File = { name: "/root/node_modules/file6/index.ts" }; // fallback to node
578544
const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3, file4, file4Typings, file5, file6);
579545

580546
const options: CompilerOptions = {
@@ -681,22 +647,19 @@ import b = require("./moduleB");
681647
"/root/generated/file6/index.d.ts",
682648

683649
// fallback to standard node behavior
684-
// load from file
685-
"/root/folder1/node_modules/file6.ts",
686-
"/root/folder1/node_modules/file6.tsx",
687-
"/root/folder1/node_modules/file6.d.ts",
688650

689651
// load from folder
690652
"/root/folder1/node_modules/file6/package.json",
653+
"/root/folder1/node_modules/file6/index.d.ts",
691654
"/root/folder1/node_modules/file6/index.ts",
692655
"/root/folder1/node_modules/file6/index.tsx",
693-
"/root/folder1/node_modules/file6/index.d.ts",
694-
695-
"/root/folder1/node_modules/@types/file6.d.ts",
696656

697657
"/root/folder1/node_modules/@types/file6/package.json",
698658
"/root/folder1/node_modules/@types/file6/index.d.ts",
699-
// success on /root/node_modules/file6.ts
659+
660+
"/root/node_modules/file6/package.json",
661+
"/root/node_modules/file6/index.d.ts",
662+
// success on /root/node_modules/file6/index.ts
700663
], /*isExternalLibraryImport*/ true);
701664

702665
function check(name: string, expected: File, expectedFailedLookups: string[], isExternalLibraryImport = false) {
@@ -977,11 +940,6 @@ import b = require("./moduleB");
977940
}
978941
});
979942
it("Can be resolved from secondary location", () => {
980-
{
981-
const f1 = { name: "/root/src/app.ts" };
982-
const f2 = { name: "/root/node_modules/lib.d.ts" };
983-
test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2);
984-
}
985943
{
986944
const f1 = { name: "/root/src/app.ts" };
987945
const f2 = { name: "/root/node_modules/lib/index.d.ts" };

0 commit comments

Comments
 (0)