-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Adding different module resolution strategies #4352
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
Changes from 5 commits
3b95ea4
1cb5280
049a5fb
f415097
dde7545
dc2c968
4f25efb
ab6fe49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,11 +36,150 @@ namespace ts { | |
} | ||
|
||
export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModule { | ||
// TODO: use different resolution strategy based on compiler options | ||
return legacyNameResolver(moduleName, containingFile, compilerOptions, host); | ||
let moduleResolution = compilerOptions.moduleResolution !== undefined | ||
? compilerOptions.moduleResolution | ||
: compilerOptions.module === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic; | ||
|
||
switch (moduleResolution) { | ||
case ModuleResolutionKind.NodeJs: return nodeModuleNameResolver(moduleName, containingFile, host); | ||
case ModuleResolutionKind.Classic: return classicNameResolver(moduleName, containingFile, compilerOptions, host); | ||
} | ||
} | ||
|
||
export function nodeModuleNameResolver(moduleName: string, containingFile: string, host: ModuleResolutionHost): ResolvedModule { | ||
let containingDirectory = getDirectoryPath(containingFile); | ||
|
||
if (getRootLength(moduleName) !== 0 || nameStartsWithDotSlashOrDotDotSlash(moduleName)) { | ||
let failedLookupLocations: string[] = []; | ||
let candidate = normalizePath(combinePaths(containingDirectory, moduleName)); | ||
let resolvedFileName = loadNodeModuleFromFile(candidate, /* loadOnlyDts */ false, failedLookupLocations, host); | ||
|
||
if (resolvedFileName) { | ||
return { resolvedFileName, failedLookupLocations }; | ||
} | ||
|
||
resolvedFileName = loadNodeModuleFromDirectory(candidate, /* loadOnlyDts */ false, failedLookupLocations, host); | ||
return { resolvedFileName, failedLookupLocations }; | ||
} | ||
else { | ||
return loadModuleFromNodeModules(moduleName, containingDirectory, host); | ||
} | ||
} | ||
|
||
function loadNodeModuleFromFile(candidate: string, loadOnlyDts: boolean, failedLookupLocation: string[], host: ModuleResolutionHost): string { | ||
if (loadOnlyDts) { | ||
return tryLoad(".d.ts"); | ||
} | ||
else { | ||
return forEach(supportedExtensions, tryLoad); | ||
} | ||
|
||
function tryLoad(ext: string): string { | ||
let fileName = fileExtensionIs(candidate, ext) ? candidate : candidate + ext; | ||
if (host.fileExists(fileName)) { | ||
return fileName; | ||
} | ||
else { | ||
failedLookupLocation.push(fileName); | ||
return undefined; | ||
} | ||
} | ||
} | ||
|
||
function loadNodeModuleFromDirectory(candidate: string, loadOnlyDts: boolean, failedLookupLocation: string[], host: ModuleResolutionHost): string { | ||
let packageJsonPath = combinePaths(candidate, "package.json"); | ||
if (host.fileExists(packageJsonPath)) { | ||
|
||
let jsonContent: { typings?: string }; | ||
|
||
try { | ||
let jsonText = host.readFile(packageJsonPath); | ||
jsonContent = jsonText ? <{ typings?: string }>JSON.parse(jsonText) : { typings: undefined }; | ||
} | ||
catch (e) { | ||
// gracefully handle if readFile fails or returns not JSON | ||
jsonContent = { typings: undefined }; | ||
} | ||
|
||
if (jsonContent.typings) { | ||
let result = loadNodeModuleFromFile(normalizePath(combinePaths(candidate, jsonContent.typings)), loadOnlyDts, failedLookupLocation, host); | ||
if (result) { | ||
return result; | ||
} | ||
} | ||
} | ||
else { | ||
// record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results | ||
failedLookupLocation.push(packageJsonPath); | ||
} | ||
|
||
return loadNodeModuleFromFile(combinePaths(candidate, "index"), loadOnlyDts, failedLookupLocation, host); | ||
} | ||
|
||
function loadModuleFromNodeModules(moduleName: string, directory: string, host: ModuleResolutionHost): ResolvedModule { | ||
let failedLookupLocations: string[] = []; | ||
directory = normalizeSlashes(directory); | ||
while (true) { | ||
let baseName = getBaseFileName(directory); | ||
if (baseName !== "node_modules") { | ||
let nodeModulesFolder = combinePaths(directory, "node_modules"); | ||
let candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName)); | ||
let result = loadNodeModuleFromFile(candidate, /* loadOnlyDts */ true, failedLookupLocations, host); | ||
if (result) { | ||
return { resolvedFileName: result, failedLookupLocations }; | ||
} | ||
|
||
result = loadNodeModuleFromDirectory(candidate, /* loadOnlyDts */ true, failedLookupLocations, host); | ||
if (result) { | ||
return { resolvedFileName: result, failedLookupLocations }; | ||
} | ||
} | ||
|
||
let parentPath = getDirectoryPath(directory); | ||
if (parentPath === directory) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment to explain what's going on here. |
||
break; | ||
} | ||
|
||
directory = parentPath; | ||
} | ||
|
||
return { resolvedFileName: undefined, failedLookupLocations }; | ||
} | ||
|
||
export function baseUrlModuleNameResolver(moduleName: string, containingFile: string, baseUrl: string, host: ModuleResolutionHost): ResolvedModule { | ||
Debug.assert(baseUrl !== undefined); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should there be a check and give out compilerOptions error if the baseUrl is not defined? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this check will be in |
||
|
||
let normalizedModuleName = normalizeSlashes(moduleName); | ||
let basePart = useBaseUrl(moduleName) ? baseUrl : getDirectoryPath(containingFile); | ||
let candidate = normalizePath(combinePaths(basePart, moduleName)); | ||
|
||
let failedLookupLocations: string[] = []; | ||
|
||
return forEach(supportedExtensions, ext => tryLoadFile(candidate + ext)) || { resolvedFileName: undefined, failedLookupLocations }; | ||
|
||
function tryLoadFile(location: string): ResolvedModule { | ||
if (host.fileExists(location)) { | ||
return { resolvedFileName: location, failedLookupLocations }; | ||
} | ||
else { | ||
failedLookupLocations.push(location); | ||
return undefined; | ||
} | ||
} | ||
} | ||
|
||
function nameStartsWithDotSlashOrDotDotSlash(name: string) { | ||
let i = name.lastIndexOf("./", 1); | ||
return i === 0 || (i === 1 && name.charCodeAt(0) === CharacterCodes.dot); | ||
} | ||
|
||
function useBaseUrl(moduleName: string): boolean { | ||
// path is not rooted | ||
// module name does not start with './' or '../' | ||
return getRootLength(moduleName) === 0 && !nameStartsWithDotSlashOrDotDotSlash(moduleName); | ||
} | ||
|
||
function legacyNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModule { | ||
export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModule { | ||
|
||
// module names that contain '!' are used to reference resources and are not resolved to actual files on disk | ||
if (moduleName.indexOf('!') != -1) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1025,6 +1025,9 @@ module Harness { | |
options.module = ts.ModuleKind.UMD; | ||
} else if (setting.value.toLowerCase() === "commonjs") { | ||
options.module = ts.ModuleKind.CommonJS; | ||
if (options.moduleResolution === undefined) { | ||
options.moduleResolution = ts.ModuleResolutionKind.Classic; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add a comment |
||
} | ||
} else if (setting.value.toLowerCase() === "system") { | ||
options.module = ts.ModuleKind.System; | ||
} else if (setting.value.toLowerCase() === "unspecified") { | ||
|
@@ -1036,7 +1039,16 @@ module Harness { | |
options.module = <any>setting.value; | ||
} | ||
break; | ||
|
||
case "moduleresolution": | ||
switch((setting.value || "").toLowerCase()) { | ||
case "classic": | ||
options.moduleResolution = ts.ModuleResolutionKind.Classic; | ||
break; | ||
case "node": | ||
options.moduleResolution = ts.ModuleResolutionKind.NodeJs; | ||
break; | ||
} | ||
break; | ||
case "target": | ||
case "codegentarget": | ||
if (typeof setting.value === "string") { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
//// [tests/cases/compiler/nodeResolution1.ts] //// | ||
|
||
//// [a.ts] | ||
|
||
export var x = 1; | ||
|
||
//// [b.ts] | ||
import y = require("./a"); | ||
|
||
//// [a.js] | ||
exports.x = 1; | ||
//// [b.js] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
=== tests/cases/compiler/b.ts === | ||
import y = require("./a"); | ||
>y : Symbol(y, Decl(b.ts, 0, 0)) | ||
|
||
=== tests/cases/compiler/a.ts === | ||
|
||
export var x = 1; | ||
>x : Symbol(x, Decl(a.ts, 1, 10)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
=== tests/cases/compiler/b.ts === | ||
import y = require("./a"); | ||
>y : typeof y | ||
|
||
=== tests/cases/compiler/a.ts === | ||
|
||
export var x = 1; | ||
>x : number | ||
>1 : number | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
//// [tests/cases/compiler/nodeResolution2.ts] //// | ||
|
||
//// [a.d.ts] | ||
|
||
export var x: number; | ||
|
||
//// [b.ts] | ||
import y = require("a"); | ||
|
||
//// [b.js] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
=== tests/cases/compiler/b.ts === | ||
import y = require("a"); | ||
>y : Symbol(y, Decl(b.ts, 0, 0)) | ||
|
||
=== tests/cases/compiler/node_modules/a.d.ts === | ||
|
||
export var x: number; | ||
>x : Symbol(x, Decl(a.d.ts, 1, 10)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
=== tests/cases/compiler/b.ts === | ||
import y = require("a"); | ||
>y : typeof y | ||
|
||
=== tests/cases/compiler/node_modules/a.d.ts === | ||
|
||
export var x: number; | ||
>x : number | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
//// [tests/cases/compiler/nodeResolution3.ts] //// | ||
|
||
//// [index.d.ts] | ||
|
||
export var x: number; | ||
|
||
//// [a.ts] | ||
import y = require("b"); | ||
|
||
//// [a.js] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
=== tests/cases/compiler/a.ts === | ||
import y = require("b"); | ||
>y : Symbol(y, Decl(a.ts, 0, 0)) | ||
|
||
=== tests/cases/compiler/node_modules/b/index.d.ts === | ||
|
||
export var x: number; | ||
>x : Symbol(x, Decl(index.d.ts, 1, 10)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
=== tests/cases/compiler/a.ts === | ||
import y = require("b"); | ||
>y : typeof y | ||
|
||
=== tests/cases/compiler/node_modules/b/index.d.ts === | ||
|
||
export var x: number; | ||
>x : number | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// @module: commonjs | ||
// @moduleResolution: node | ||
|
||
// @filename: a.ts | ||
export var x = 1; | ||
|
||
// @filename: b.ts | ||
import y = require("./a"); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// @module: commonjs | ||
// @moduleResolution: node | ||
|
||
// @filename: node_modules/a.d.ts | ||
export var x: number; | ||
|
||
// @filename: b.ts | ||
import y = require("a"); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// @module: commonjs | ||
// @moduleResolution: node | ||
|
||
// @filename: node_modules/b/index.d.ts | ||
export var x: number; | ||
|
||
// @filename: a.ts | ||
import y = require("b"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this used to give out any error or it is mainly use for testing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is used as the part of output