match-path-sync.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import * as path from "path";
  2. import * as Filesystem from "./filesystem";
  3. import * as MappingEntry from "./mapping-entry";
  4. import * as TryPath from "./try-path";
  5. /**
  6. * Function that can match a path
  7. */
  8. export interface MatchPath {
  9. (
  10. requestedModule: string,
  11. readJson?: Filesystem.ReadJsonSync,
  12. fileExists?: (name: string) => boolean,
  13. extensions?: ReadonlyArray<string>
  14. ): string | undefined;
  15. }
  16. /**
  17. * Creates a function that can resolve paths according to tsconfig paths property.
  18. * @param absoluteBaseUrl Absolute version of baseUrl as specified in tsconfig.
  19. * @param paths The paths as specified in tsconfig.
  20. * @param mainFields A list of package.json field names to try when resolving module files.
  21. * @param addMatchAll Add a match-all "*" rule if none is present
  22. * @returns a function that can resolve paths.
  23. */
  24. export function createMatchPath(
  25. absoluteBaseUrl: string,
  26. paths: { [key: string]: Array<string> },
  27. mainFields: string[] = ["main"],
  28. addMatchAll: boolean = true
  29. ): MatchPath {
  30. const absolutePaths = MappingEntry.getAbsoluteMappingEntries(
  31. absoluteBaseUrl,
  32. paths,
  33. addMatchAll
  34. );
  35. return (
  36. requestedModule: string,
  37. readJson?: Filesystem.ReadJsonSync,
  38. fileExists?: Filesystem.FileExistsSync,
  39. extensions?: Array<string>
  40. ) =>
  41. matchFromAbsolutePaths(
  42. absolutePaths,
  43. requestedModule,
  44. readJson,
  45. fileExists,
  46. extensions,
  47. mainFields
  48. );
  49. }
  50. /**
  51. * Finds a path from tsconfig that matches a module load request.
  52. * @param absolutePathMappings The paths to try as specified in tsconfig but resolved to absolute form.
  53. * @param requestedModule The required module name.
  54. * @param readJson Function that can read json from a path (useful for testing).
  55. * @param fileExists Function that checks for existence of a file at a path (useful for testing).
  56. * @param extensions File extensions to probe for (useful for testing).
  57. * @param mainFields A list of package.json field names to try when resolving module files.
  58. * @returns the found path, or undefined if no path was found.
  59. */
  60. export function matchFromAbsolutePaths(
  61. absolutePathMappings: ReadonlyArray<MappingEntry.MappingEntry>,
  62. requestedModule: string,
  63. readJson: Filesystem.ReadJsonSync = Filesystem.readJsonFromDiskSync,
  64. fileExists: Filesystem.FileExistsSync = Filesystem.fileExistsSync,
  65. extensions: Array<string> = Object.keys(require.extensions),
  66. mainFields: string[] = ["main"]
  67. ): string | undefined {
  68. const tryPaths = TryPath.getPathsToTry(
  69. extensions,
  70. absolutePathMappings,
  71. requestedModule
  72. );
  73. if (!tryPaths) {
  74. return undefined;
  75. }
  76. return findFirstExistingPath(tryPaths, readJson, fileExists, mainFields);
  77. }
  78. function findFirstExistingMainFieldMappedFile(
  79. packageJson: Filesystem.PackageJson,
  80. mainFields: string[],
  81. packageJsonPath: string,
  82. fileExists: Filesystem.FileExistsSync
  83. ): string | undefined {
  84. for (let index = 0; index < mainFields.length; index++) {
  85. const mainFieldName = mainFields[index];
  86. const candidateMapping = packageJson[mainFieldName];
  87. if (candidateMapping && typeof candidateMapping === "string") {
  88. const candidateFilePath = path.join(
  89. path.dirname(packageJsonPath),
  90. candidateMapping
  91. );
  92. if (fileExists(candidateFilePath)) {
  93. return candidateFilePath;
  94. }
  95. }
  96. }
  97. return undefined;
  98. }
  99. function findFirstExistingPath(
  100. tryPaths: ReadonlyArray<TryPath.TryPath>,
  101. readJson: Filesystem.ReadJsonSync = Filesystem.readJsonFromDiskSync,
  102. fileExists: Filesystem.FileExistsSync,
  103. mainFields: string[] = ["main"]
  104. ): string | undefined {
  105. for (const tryPath of tryPaths) {
  106. if (
  107. tryPath.type === "file" ||
  108. tryPath.type === "extension" ||
  109. tryPath.type === "index"
  110. ) {
  111. if (fileExists(tryPath.path)) {
  112. return TryPath.getStrippedPath(tryPath);
  113. }
  114. } else if (tryPath.type === "package") {
  115. const packageJson: Filesystem.PackageJson = readJson(tryPath.path);
  116. if (packageJson) {
  117. const mainFieldMappedFile = findFirstExistingMainFieldMappedFile(
  118. packageJson,
  119. mainFields,
  120. tryPath.path,
  121. fileExists
  122. );
  123. if (mainFieldMappedFile) {
  124. return mainFieldMappedFile;
  125. }
  126. }
  127. } else {
  128. TryPath.exhaustiveTypeException(tryPath.type);
  129. }
  130. }
  131. return undefined;
  132. }