Skip to content

Commit

Permalink
Add support for nested main field selectors (#218)
Browse files Browse the repository at this point in the history
* Add support for nested main field selectors

* Update description of `mainFields`
  • Loading branch information
aaronadamsCA committed Aug 6, 2022
1 parent f420039 commit 9124aa6
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 22 deletions.
10 changes: 5 additions & 5 deletions README.md
Expand Up @@ -146,7 +146,7 @@ The public API consists of these functions:
export interface ExplicitParams {
baseUrl: string;
paths: { [key: string]: Array<string> };
mainFields?: Array<string>;
mainFields?: (string | string[])[];
addMatchAll?: boolean;
cwd?: string;
}
Expand Down Expand Up @@ -202,14 +202,14 @@ export interface MatchPath {
* Creates a function that can resolve paths according to tsconfig paths property.
* @param absoluteBaseUrl Absolute version of baseUrl as specified in tsconfig.
* @param paths The paths as specified in tsconfig.
* @param mainFields A list of package.json field names to try when resolving module files.
* @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.
* @param addMatchAll Add a match-all "*" rule if none is present
* @returns a function that can resolve paths.
*/
export function createMatchPath(
absoluteBaseUrl: string,
paths: { [key: string]: Array<string> },
mainFields: string[] = ["main"],
mainFields: (string | string[])[] = ["main"],
addMatchAll: boolean = true
): MatchPath {
```
Expand All @@ -226,7 +226,7 @@ The `createMatchPath` function will create a function that can match paths. It a
* @param readJson Function that can read json from a path (useful for testing).
* @param fileExists Function that checks for existence of a file at a path (useful for testing).
* @param extensions File extensions to probe for (useful for testing).
* @param mainFields A list of package.json field names to try when resolving module files.
* @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.
* @returns the found path, or undefined if no path was found.
*/
export function matchFromAbsolutePaths(
Expand All @@ -235,7 +235,7 @@ export function matchFromAbsolutePaths(
readJson: Filesystem.ReadJsonSync = Filesystem.readJsonFromDiskSync,
fileExists: Filesystem.FileExistsSync = Filesystem.fileExistsSync,
extensions: Array<string> = Object.keys(require.extensions),
mainFields: string[] = ["main"]
mainFields: (string | string[])[] = ["main"]
): string | undefined {
```
Expand Down
13 changes: 12 additions & 1 deletion src/__tests__/data/match-path-data.ts
Expand Up @@ -7,7 +7,7 @@ export interface OneTest {
readonly skip?: boolean;
readonly absoluteBaseUrl: string;
readonly paths: { [key: string]: Array<string> };
readonly mainFields?: string[];
readonly mainFields?: (string | string[])[];
readonly addMatchAll?: boolean;
readonly existingFiles: ReadonlyArray<string>;
readonly requestedModule: string;
Expand Down Expand Up @@ -149,6 +149,17 @@ export const tests: ReadonlyArray<OneTest> = [
extensions: [".ts", ".js"],
expectedPath: join("/root", "location", "mylibjs", "kalle.js"),
},
{
name: "should resolve nested main fields",
absoluteBaseUrl: "/root/",
paths: { "lib/*": ["location/*"] },
mainFields: [["esnext", "main"]],
packageJson: { esnext: { main: "./main.js" } },
existingFiles: [join("/root", "location", "mylibjs", "main.js")],
extensions: [".ts", ".js"],
requestedModule: "lib/mylibjs",
expectedPath: join("/root", "location", "mylibjs", "main.js"),
},
{
name: "should ignore advanced field mappings in package.json",
absoluteBaseUrl: "/root/",
Expand Down
4 changes: 2 additions & 2 deletions src/config-loader.ts
Expand Up @@ -4,7 +4,7 @@ import * as path from "path";
export interface ExplicitParams {
baseUrl: string;
paths: { [key: string]: Array<string> };
mainFields?: Array<string>;
mainFields?: (string | string[])[];
addMatchAll?: boolean;
}

Expand All @@ -24,7 +24,7 @@ export interface ConfigLoaderSuccessResult {
baseUrl?: string;
absoluteBaseUrl: string;
paths: { [key: string]: Array<string> };
mainFields?: Array<string>;
mainFields?: (string | string[])[];
addMatchAll?: boolean;
}

Expand Down
2 changes: 1 addition & 1 deletion src/filesystem.ts
Expand Up @@ -4,7 +4,7 @@ import * as fs from "fs";
* Typing for the fields of package.json we care about
*/
export interface PackageJson {
[key: string]: string;
[key: string]: string | PackageJson;
}

/**
Expand Down
14 changes: 9 additions & 5 deletions src/match-path-async.ts
Expand Up @@ -27,7 +27,7 @@ export interface MatchPathAsyncCallback {
export function createMatchPathAsync(
absoluteBaseUrl: string,
paths: { [key: string]: Array<string> },
mainFields: string[] = ["main"],
mainFields: (string | string[])[] = ["main"],
addMatchAll: boolean = true
): MatchPathAsync {
const absolutePaths = MappingEntry.getAbsoluteMappingEntries(
Expand Down Expand Up @@ -64,7 +64,7 @@ export function matchFromAbsolutePathsAsync(
fileExists: Filesystem.FileExistsAsync = Filesystem.fileExistsAsync,
extensions: ReadonlyArray<string> = Object.keys(require.extensions),
callback: MatchPathAsyncCallback,
mainFields: string[] = ["main"]
mainFields: (string | string[])[] = ["main"]
): void {
const tryPaths = TryPath.getPathsToTry(
extensions,
Expand All @@ -88,7 +88,7 @@ export function matchFromAbsolutePathsAsync(

function findFirstExistingMainFieldMappedFile(
packageJson: Filesystem.PackageJson,
mainFields: string[],
mainFields: (string | string[])[],
packageJsonPath: string,
fileExistsAsync: Filesystem.FileExistsAsync,
doneCallback: (err?: Error, filepath?: string) => void,
Expand All @@ -108,7 +108,11 @@ function findFirstExistingMainFieldMappedFile(
index + 1
);

const mainFieldMapping = packageJson[mainFields[index]];
const mainFieldSelector = mainFields[index];
const mainFieldMapping =
typeof mainFieldSelector === "string"
? packageJson[mainFieldSelector]
: mainFieldSelector.reduce((obj, key) => obj[key], packageJson);
if (typeof mainFieldMapping !== "string") {
// Skip mappings that are not pointers to replacement files
return tryNext();
Expand Down Expand Up @@ -136,7 +140,7 @@ function findFirstExistingPath(
fileExists: Filesystem.FileExistsAsync,
doneCallback: MatchPathAsyncCallback,
index: number = 0,
mainFields: string[] = ["main"]
mainFields: (string | string[])[] = ["main"]
): void {
const tryPath = tryPaths[index];
if (
Expand Down
19 changes: 11 additions & 8 deletions src/match-path-sync.ts
Expand Up @@ -20,14 +20,14 @@ export interface MatchPath {
*
* @param absoluteBaseUrl Absolute version of baseUrl as specified in tsconfig.
* @param paths The paths as specified in tsconfig.
* @param mainFields A list of package.json field names to try when resolving module files.
* @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.
* @param addMatchAll Add a match-all "*" rule if none is present
* @returns a function that can resolve paths.
*/
export function createMatchPath(
absoluteBaseUrl: string,
paths: { [key: string]: Array<string> },
mainFields: string[] = ["main"],
mainFields: (string | string[])[] = ["main"],
addMatchAll: boolean = true
): MatchPath {
const absolutePaths = MappingEntry.getAbsoluteMappingEntries(
Expand Down Expand Up @@ -60,7 +60,7 @@ export function createMatchPath(
* @param readJson Function that can read json from a path (useful for testing).
* @param fileExists Function that checks for existence of a file at a path (useful for testing).
* @param extensions File extensions to probe for (useful for testing).
* @param mainFields A list of package.json field names to try when resolving module files.
* @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.
* @returns the found path, or undefined if no path was found.
*/
export function matchFromAbsolutePaths(
Expand All @@ -69,7 +69,7 @@ export function matchFromAbsolutePaths(
readJson: Filesystem.ReadJsonSync = Filesystem.readJsonFromDiskSync,
fileExists: Filesystem.FileExistsSync = Filesystem.fileExistsSync,
extensions: Array<string> = Object.keys(require.extensions),
mainFields: string[] = ["main"]
mainFields: (string | string[])[] = ["main"]
): string | undefined {
const tryPaths = TryPath.getPathsToTry(
extensions,
Expand All @@ -86,13 +86,16 @@ export function matchFromAbsolutePaths(

function findFirstExistingMainFieldMappedFile(
packageJson: Filesystem.PackageJson,
mainFields: string[],
mainFields: (string | string[])[],
packageJsonPath: string,
fileExists: Filesystem.FileExistsSync
): string | undefined {
for (let index = 0; index < mainFields.length; index++) {
const mainFieldName = mainFields[index];
const candidateMapping = packageJson[mainFieldName];
const mainFieldSelector = mainFields[index];
const candidateMapping =
typeof mainFieldSelector === "string"
? packageJson[mainFieldSelector]
: mainFieldSelector.reduce((obj, key) => obj[key], packageJson);
if (candidateMapping && typeof candidateMapping === "string") {
const candidateFilePath = path.join(
path.dirname(packageJsonPath),
Expand All @@ -111,7 +114,7 @@ function findFirstExistingPath(
tryPaths: ReadonlyArray<TryPath.TryPath>,
readJson: Filesystem.ReadJsonSync = Filesystem.readJsonFromDiskSync,
fileExists: Filesystem.FileExistsSync,
mainFields: string[] = ["main"]
mainFields: (string | string[])[] = ["main"]
): string | undefined {
for (const tryPath of tryPaths) {
if (
Expand Down

0 comments on commit 9124aa6

Please sign in to comment.