Skip to content

Commit 9124aa6

Browse files
authored
Add support for nested main field selectors (#218)
* Add support for nested main field selectors * Update description of `mainFields`
1 parent f420039 commit 9124aa6

File tree

6 files changed

+40
-22
lines changed

6 files changed

+40
-22
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ The public API consists of these functions:
146146
export interface ExplicitParams {
147147
baseUrl: string;
148148
paths: { [key: string]: Array<string> };
149-
mainFields?: Array<string>;
149+
mainFields?: (string | string[])[];
150150
addMatchAll?: boolean;
151151
cwd?: string;
152152
}
@@ -202,14 +202,14 @@ export interface MatchPath {
202202
* Creates a function that can resolve paths according to tsconfig paths property.
203203
* @param absoluteBaseUrl Absolute version of baseUrl as specified in tsconfig.
204204
* @param paths The paths as specified in tsconfig.
205-
* @param mainFields A list of package.json field names to try when resolving module files.
205+
* @param mainFields A list of package.json field names to try when resolving module files. Select a nested field using an array of field names.
206206
* @param addMatchAll Add a match-all "*" rule if none is present
207207
* @returns a function that can resolve paths.
208208
*/
209209
export function createMatchPath(
210210
absoluteBaseUrl: string,
211211
paths: { [key: string]: Array<string> },
212-
mainFields: string[] = ["main"],
212+
mainFields: (string | string[])[] = ["main"],
213213
addMatchAll: boolean = true
214214
): MatchPath {
215215
```
@@ -226,7 +226,7 @@ The `createMatchPath` function will create a function that can match paths. It a
226226
* @param readJson Function that can read json from a path (useful for testing).
227227
* @param fileExists Function that checks for existence of a file at a path (useful for testing).
228228
* @param extensions File extensions to probe for (useful for testing).
229-
* @param mainFields A list of package.json field names to try when resolving module files.
229+
* @param mainFields A list of package.json field names to try when resolving module files. Select a nested field using an array of field names.
230230
* @returns the found path, or undefined if no path was found.
231231
*/
232232
export function matchFromAbsolutePaths(
@@ -235,7 +235,7 @@ export function matchFromAbsolutePaths(
235235
readJson: Filesystem.ReadJsonSync = Filesystem.readJsonFromDiskSync,
236236
fileExists: Filesystem.FileExistsSync = Filesystem.fileExistsSync,
237237
extensions: Array<string> = Object.keys(require.extensions),
238-
mainFields: string[] = ["main"]
238+
mainFields: (string | string[])[] = ["main"]
239239
): string | undefined {
240240
```
241241

src/__tests__/data/match-path-data.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export interface OneTest {
77
readonly skip?: boolean;
88
readonly absoluteBaseUrl: string;
99
readonly paths: { [key: string]: Array<string> };
10-
readonly mainFields?: string[];
10+
readonly mainFields?: (string | string[])[];
1111
readonly addMatchAll?: boolean;
1212
readonly existingFiles: ReadonlyArray<string>;
1313
readonly requestedModule: string;
@@ -149,6 +149,17 @@ export const tests: ReadonlyArray<OneTest> = [
149149
extensions: [".ts", ".js"],
150150
expectedPath: join("/root", "location", "mylibjs", "kalle.js"),
151151
},
152+
{
153+
name: "should resolve nested main fields",
154+
absoluteBaseUrl: "/root/",
155+
paths: { "lib/*": ["location/*"] },
156+
mainFields: [["esnext", "main"]],
157+
packageJson: { esnext: { main: "./main.js" } },
158+
existingFiles: [join("/root", "location", "mylibjs", "main.js")],
159+
extensions: [".ts", ".js"],
160+
requestedModule: "lib/mylibjs",
161+
expectedPath: join("/root", "location", "mylibjs", "main.js"),
162+
},
152163
{
153164
name: "should ignore advanced field mappings in package.json",
154165
absoluteBaseUrl: "/root/",

src/config-loader.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as path from "path";
44
export interface ExplicitParams {
55
baseUrl: string;
66
paths: { [key: string]: Array<string> };
7-
mainFields?: Array<string>;
7+
mainFields?: (string | string[])[];
88
addMatchAll?: boolean;
99
}
1010

@@ -24,7 +24,7 @@ export interface ConfigLoaderSuccessResult {
2424
baseUrl?: string;
2525
absoluteBaseUrl: string;
2626
paths: { [key: string]: Array<string> };
27-
mainFields?: Array<string>;
27+
mainFields?: (string | string[])[];
2828
addMatchAll?: boolean;
2929
}
3030

src/filesystem.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as fs from "fs";
44
* Typing for the fields of package.json we care about
55
*/
66
export interface PackageJson {
7-
[key: string]: string;
7+
[key: string]: string | PackageJson;
88
}
99

1010
/**

src/match-path-async.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface MatchPathAsyncCallback {
2727
export function createMatchPathAsync(
2828
absoluteBaseUrl: string,
2929
paths: { [key: string]: Array<string> },
30-
mainFields: string[] = ["main"],
30+
mainFields: (string | string[])[] = ["main"],
3131
addMatchAll: boolean = true
3232
): MatchPathAsync {
3333
const absolutePaths = MappingEntry.getAbsoluteMappingEntries(
@@ -64,7 +64,7 @@ export function matchFromAbsolutePathsAsync(
6464
fileExists: Filesystem.FileExistsAsync = Filesystem.fileExistsAsync,
6565
extensions: ReadonlyArray<string> = Object.keys(require.extensions),
6666
callback: MatchPathAsyncCallback,
67-
mainFields: string[] = ["main"]
67+
mainFields: (string | string[])[] = ["main"]
6868
): void {
6969
const tryPaths = TryPath.getPathsToTry(
7070
extensions,
@@ -88,7 +88,7 @@ export function matchFromAbsolutePathsAsync(
8888

8989
function findFirstExistingMainFieldMappedFile(
9090
packageJson: Filesystem.PackageJson,
91-
mainFields: string[],
91+
mainFields: (string | string[])[],
9292
packageJsonPath: string,
9393
fileExistsAsync: Filesystem.FileExistsAsync,
9494
doneCallback: (err?: Error, filepath?: string) => void,
@@ -108,7 +108,11 @@ function findFirstExistingMainFieldMappedFile(
108108
index + 1
109109
);
110110

111-
const mainFieldMapping = packageJson[mainFields[index]];
111+
const mainFieldSelector = mainFields[index];
112+
const mainFieldMapping =
113+
typeof mainFieldSelector === "string"
114+
? packageJson[mainFieldSelector]
115+
: mainFieldSelector.reduce((obj, key) => obj[key], packageJson);
112116
if (typeof mainFieldMapping !== "string") {
113117
// Skip mappings that are not pointers to replacement files
114118
return tryNext();
@@ -136,7 +140,7 @@ function findFirstExistingPath(
136140
fileExists: Filesystem.FileExistsAsync,
137141
doneCallback: MatchPathAsyncCallback,
138142
index: number = 0,
139-
mainFields: string[] = ["main"]
143+
mainFields: (string | string[])[] = ["main"]
140144
): void {
141145
const tryPath = tryPaths[index];
142146
if (

src/match-path-sync.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ export interface MatchPath {
2020
*
2121
* @param absoluteBaseUrl Absolute version of baseUrl as specified in tsconfig.
2222
* @param paths The paths as specified in tsconfig.
23-
* @param mainFields A list of package.json field names to try when resolving module files.
23+
* @param mainFields A list of package.json field names to try when resolving module files. Select a nested field using an array of field names.
2424
* @param addMatchAll Add a match-all "*" rule if none is present
2525
* @returns a function that can resolve paths.
2626
*/
2727
export function createMatchPath(
2828
absoluteBaseUrl: string,
2929
paths: { [key: string]: Array<string> },
30-
mainFields: string[] = ["main"],
30+
mainFields: (string | string[])[] = ["main"],
3131
addMatchAll: boolean = true
3232
): MatchPath {
3333
const absolutePaths = MappingEntry.getAbsoluteMappingEntries(
@@ -60,7 +60,7 @@ export function createMatchPath(
6060
* @param readJson Function that can read json from a path (useful for testing).
6161
* @param fileExists Function that checks for existence of a file at a path (useful for testing).
6262
* @param extensions File extensions to probe for (useful for testing).
63-
* @param mainFields A list of package.json field names to try when resolving module files.
63+
* @param mainFields A list of package.json field names to try when resolving module files. Select a nested field using an array of field names.
6464
* @returns the found path, or undefined if no path was found.
6565
*/
6666
export function matchFromAbsolutePaths(
@@ -69,7 +69,7 @@ export function matchFromAbsolutePaths(
6969
readJson: Filesystem.ReadJsonSync = Filesystem.readJsonFromDiskSync,
7070
fileExists: Filesystem.FileExistsSync = Filesystem.fileExistsSync,
7171
extensions: Array<string> = Object.keys(require.extensions),
72-
mainFields: string[] = ["main"]
72+
mainFields: (string | string[])[] = ["main"]
7373
): string | undefined {
7474
const tryPaths = TryPath.getPathsToTry(
7575
extensions,
@@ -86,13 +86,16 @@ export function matchFromAbsolutePaths(
8686

8787
function findFirstExistingMainFieldMappedFile(
8888
packageJson: Filesystem.PackageJson,
89-
mainFields: string[],
89+
mainFields: (string | string[])[],
9090
packageJsonPath: string,
9191
fileExists: Filesystem.FileExistsSync
9292
): string | undefined {
9393
for (let index = 0; index < mainFields.length; index++) {
94-
const mainFieldName = mainFields[index];
95-
const candidateMapping = packageJson[mainFieldName];
94+
const mainFieldSelector = mainFields[index];
95+
const candidateMapping =
96+
typeof mainFieldSelector === "string"
97+
? packageJson[mainFieldSelector]
98+
: mainFieldSelector.reduce((obj, key) => obj[key], packageJson);
9699
if (candidateMapping && typeof candidateMapping === "string") {
97100
const candidateFilePath = path.join(
98101
path.dirname(packageJsonPath),
@@ -111,7 +114,7 @@ function findFirstExistingPath(
111114
tryPaths: ReadonlyArray<TryPath.TryPath>,
112115
readJson: Filesystem.ReadJsonSync = Filesystem.readJsonFromDiskSync,
113116
fileExists: Filesystem.FileExistsSync,
114-
mainFields: string[] = ["main"]
117+
mainFields: (string | string[])[] = ["main"]
115118
): string | undefined {
116119
for (const tryPath of tryPaths) {
117120
if (

0 commit comments

Comments
 (0)