-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Refactor module resolution logic to have configurable goal extensions #9430
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 2 commits
ed46d89
5a3c695
5e40c8e
7894136
fa9bfc8
6a257a4
ef102da
903ad65
b1b31d2
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 |
---|---|---|
|
@@ -129,57 +129,49 @@ namespace ts { | |
skipTsx: boolean; | ||
} | ||
|
||
function tryReadTypesSection(packageJsonPath: string, baseDirectory: string, state: ModuleResolutionState): string { | ||
let jsonContent: { typings?: string, types?: string, main?: string }; | ||
try { | ||
const jsonText = state.host.readFile(packageJsonPath); | ||
jsonContent = jsonText ? <{ typings?: string, types?: string, main?: string }>JSON.parse(jsonText) : {}; | ||
function getPackageEntry(packageJson: any, key: string, tag: string, state: ModuleResolutionState) { | ||
const value = packageJson[key]; | ||
if (typeof value === tag) { | ||
return value; | ||
} | ||
catch (e) { | ||
// gracefully handle if readFile fails or returns not JSON | ||
jsonContent = {}; | ||
if (state.traceEnabled) { | ||
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, key, tag, typeof value); | ||
} | ||
return undefined; | ||
} | ||
|
||
let typesFile: string; | ||
let fieldName: string; | ||
// first try to read content of 'typings' section (backward compatibility) | ||
if (jsonContent.typings) { | ||
if (typeof jsonContent.typings === "string") { | ||
fieldName = "typings"; | ||
typesFile = jsonContent.typings; | ||
} | ||
else { | ||
if (state.traceEnabled) { | ||
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, "typings", typeof jsonContent.typings); | ||
} | ||
} | ||
function getPackageEntryAsPath(packageJson: any, packageJsonPath: string, key: string, state: ModuleResolutionState) { | ||
const value = getPackageEntry(packageJson, key, "string", state); | ||
const path = value ? normalizePath(combinePaths(getDirectoryPath(packageJsonPath), value)) : undefined; | ||
if (path && state.traceEnabled) { | ||
trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, key, value, path); | ||
} | ||
// then read 'types' | ||
if (!typesFile && jsonContent.types) { | ||
if (typeof jsonContent.types === "string") { | ||
fieldName = "types"; | ||
typesFile = jsonContent.types; | ||
} | ||
else { | ||
if (state.traceEnabled) { | ||
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, "types", typeof jsonContent.types); | ||
} | ||
} | ||
return path; | ||
} | ||
|
||
function getPackageTypes(packageJsonPath: string, state: ModuleResolutionState) { | ||
const { config } = readConfigFile(packageJsonPath, state.host.readFile); | ||
if (config) { | ||
return getPackageEntryAsPath(config, packageJsonPath, "typings", state) | ||
|| getPackageEntryAsPath(config, packageJsonPath, "types", state); | ||
} | ||
if (typesFile) { | ||
const typesFilePath = normalizePath(combinePaths(baseDirectory, typesFile)); | ||
else { | ||
if (state.traceEnabled) { | ||
trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, typesFile, typesFilePath); | ||
trace(state.host, Diagnostics.package_json_does_not_have_0_field, "types"); | ||
} | ||
return typesFilePath; | ||
} | ||
// Use the main module for inferring types if no types package specified and the allowJs is set | ||
if (state.compilerOptions.allowJs && jsonContent.main && typeof jsonContent.main === "string") { | ||
return undefined; | ||
} | ||
|
||
function getPackageMain(packageJsonPath: string, state: ModuleResolutionState) { | ||
const { config } = readConfigFile(packageJsonPath, state.host.readFile); | ||
if (config) { | ||
return getPackageEntryAsPath(config, packageJsonPath, "main", state); | ||
} | ||
else { | ||
if (state.traceEnabled) { | ||
trace(state.host, Diagnostics.No_types_specified_in_package_json_but_allowJs_is_set_so_returning_main_value_of_0, jsonContent.main); | ||
trace(state.host, Diagnostics.package_json_does_not_have_0_field, "main"); | ||
} | ||
const mainFilePath = normalizePath(combinePaths(baseDirectory, jsonContent.main)); | ||
return mainFilePath; | ||
} | ||
return undefined; | ||
} | ||
|
@@ -727,25 +719,20 @@ namespace ts { | |
} | ||
} | ||
|
||
function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string { | ||
function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState, loadJS?: boolean): string { | ||
const packageJsonPath = combinePaths(candidate, "package.json"); | ||
const directoryExists = !onlyRecordFailures && directoryProbablyExists(candidate, state.host); | ||
if (directoryExists && state.host.fileExists(packageJsonPath)) { | ||
if (state.traceEnabled) { | ||
trace(state.host, Diagnostics.Found_package_json_at_0, packageJsonPath); | ||
} | ||
const typesFile = tryReadTypesSection(packageJsonPath, candidate, state); | ||
const typesFile = loadJS ? getPackageMain(packageJsonPath, state) : getPackageTypes(packageJsonPath, state); | ||
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 won't work if the package.json doesn't have a |
||
if (typesFile) { | ||
const result = loadModuleFromFile(typesFile, extensions, failedLookupLocation, !directoryProbablyExists(getDirectoryPath(typesFile), state.host), state); | ||
if (result) { | ||
return result; | ||
} | ||
} | ||
else { | ||
if (state.traceEnabled) { | ||
trace(state.host, Diagnostics.package_json_does_not_have_types_field); | ||
} | ||
} | ||
} | ||
else { | ||
if (state.traceEnabled) { | ||
|
@@ -758,7 +745,7 @@ namespace ts { | |
return loadModuleFromFile(combinePaths(candidate, "index"), extensions, failedLookupLocation, !directoryExists, state); | ||
} | ||
|
||
function loadModuleFromNodeModulesFolder(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string { | ||
function loadModuleFromNodeModulesFolder(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState, loadJS?: boolean): string { | ||
const nodeModulesFolder = combinePaths(directory, "node_modules"); | ||
const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host); | ||
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName)); | ||
|
@@ -768,7 +755,7 @@ namespace ts { | |
if (result) { | ||
return result; | ||
} | ||
result = loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state); | ||
result = loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state, loadJS); | ||
if (result) { | ||
return result; | ||
} | ||
|
@@ -783,7 +770,9 @@ namespace ts { | |
// first: try to load module as-is | ||
loadModuleFromNodeModulesFolder(moduleName, directory, failedLookupLocations, state) || | ||
// second: try to load module from the scope '@types' | ||
loadModuleFromNodeModulesFolder(combinePaths("@types", moduleName), directory, failedLookupLocations, state); | ||
loadModuleFromNodeModulesFolder(combinePaths("@types", moduleName), directory, failedLookupLocations, state) || | ||
// third: if allowJS try to load the JS module | ||
(state.compilerOptions.allowJs && loadModuleFromNodeModulesFolder(moduleName, directory, failedLookupLocations, state, true)) | ||
if (result) { | ||
return result; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
define(["require", "exports", "m1"], function (require, exports, m1) { | ||
"use strict"; | ||
exports.m1 = m1; | ||
var val = m1("works", 42); | ||
void (m1.name + ": " + val); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"scenario": "Realistic npm package types", | ||
"projectRoot": "tests/cases/projects/realisticNpmTypes", | ||
"inputFiles": [ | ||
"index.ts" | ||
], | ||
"declaration": true, | ||
"baselineCheck": true, | ||
"moduleResolution": "node", | ||
"resolvedInputFiles": [ | ||
"lib.d.ts", | ||
"node_modules/@types/m1/index.d.ts", | ||
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. So this file doesn't exist, but it's referenced? 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. Hah. It didn't get committed because it's in a node_modules folder. That's funny. And annoying. Let me fix that. 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. Yeah, see the last line I added to the gitignore file on my prior merge to master. That nearly got me too. |
||
"index.ts" | ||
], | ||
"emittedFiles": [ | ||
"index.js", | ||
"index.d.ts" | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
"use strict"; | ||
var m1 = require("m1"); | ||
exports.m1 = m1; | ||
var val = m1("works", 42); | ||
void (m1.name + ": " + val); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"scenario": "Realistic npm package types", | ||
"projectRoot": "tests/cases/projects/realisticNpmTypes", | ||
"inputFiles": [ | ||
"index.ts" | ||
], | ||
"declaration": true, | ||
"baselineCheck": true, | ||
"moduleResolution": "node", | ||
"resolvedInputFiles": [ | ||
"lib.d.ts", | ||
"node_modules/@types/m1/index.d.ts", | ||
"index.ts" | ||
], | ||
"emittedFiles": [ | ||
"index.js", | ||
"index.d.ts" | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"scenario": "Realistic npm package types", | ||
"projectRoot": "tests/cases/projects/realisticNpmTypes", | ||
"inputFiles": [ | ||
"index.ts" | ||
], | ||
"declaration": true, | ||
"baselineCheck": true, | ||
"moduleResolution": "node" | ||
} | ||
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. You are not specifying 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. 'doh. It's just added by default in so many places that I thought this may be one of them. Added and fixed. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import m1 = require("m1"); | ||
|
||
const val = m1("works", 42); | ||
void `${m1.name}: ${val}`; | ||
|
||
export {m1}; |
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.
when do we need something that is not a string?
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.
At this point, never. This was mostly @rbuckton's suggestion. I can specialize the function, if you'd prefer it.
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.
We discussed the possibility of augmenting the
package.json
for extensions, which could leverage this if we decided to lean in that direction.