Jest Module Resolver Simulation
Jest relies on a sophisticated module resolution mechanism to locate and load the correct files for your tests. This challenge asks you to implement a simplified version of Jest's module resolver in TypeScript, allowing you to understand the core logic behind how Jest finds your modules. This is crucial for debugging import issues and for building custom Jest configurations.
Problem Description
Your task is to create a function, resolveModule, that takes a module path (the string used in import or require statements), a reference file path, and a list of potential module directories. This function should simulate how Jest resolves a module path by searching through various locations, considering different file extensions and the possibility of module directories.
Key Requirements:
- Relative Path Resolution: If the module path starts with
./or../, it should be resolved relative to thereferenceFilePath. - Absolute Path Resolution: If the module path is an absolute path, it should be resolved directly.
- Node Modules Resolution: If the module path does not start with
./or../and is not an absolute path, it should be treated as an npm package and resolved by searching within the providedmoduleDirectories.- This involves looking for:
package.jsonmainfield.- Index files (
index.js,index.json,index.node). - File with the exact name.
- This involves looking for:
- File Extensions: When resolving, consider the following file extensions in order:
.js,.json,.node, and then.ts(if TypeScript is implicitly supported). - Directory Index: If a directory is found, look for an
indexfile within it with the supported extensions. - Error Handling: If the module cannot be resolved after all checks, the function should return
null.
Expected Behavior:
The function should return the absolute path to the resolved module file if found, or null if not.
Important Edge Cases:
- Module paths pointing to directories.
- Module paths with no file extension (requiring extension probing).
- Module paths that resolve to
package.json'smainfield. - The order of checking directories for npm packages.
Examples
Example 1: Relative Path Resolution
Input:
modulePath = "./utils/helpers"
referenceFilePath = "/path/to/project/src/tests/myTest.ts"
moduleDirectories = ["node_modules"] // Not relevant for this example
// Assume the following file structure:
// /path/to/project/src/utils/helpers.ts
// /path/to/project/src/utils/helpers/index.js
Output: "/path/to/project/src/utils/helpers.ts"
Explanation: The path is relative to `referenceFilePath`. It first checks for `helpers.ts` and finds it.
Example 2: Package Resolution with main field
Input:
modulePath = "lodash"
referenceFilePath = "/path/to/project/src/index.ts"
moduleDirectories = ["/path/to/project/node_modules", "/another/global/node_modules"]
// Assume the following structure in /path/to/project/node_modules/lodash/:
// /path/to/project/node_modules/lodash/package.json (with "main": "dist/lodash.js")
// /path/to/project/node_modules/lodash/dist/lodash.js
Output: "/path/to/project/node_modules/lodash/dist/lodash.js"
Explanation: "lodash" is treated as an npm package. The resolver finds `node_modules/lodash/package.json` and uses its `main` field to locate the entry point.
Example 3: Package Resolution with Index File
Input:
modulePath = "some-package"
referenceFilePath = "/path/to/project/src/index.ts"
moduleDirectories = ["/path/to/project/node_modules"]
// Assume the following structure in /path/to/project/node_modules/some-package/:
// /path/to/project/node_modules/some-package/lib/index.js
Output: "/path/to/project/node_modules/some-package/lib/index.js"
Explanation: "some-package" is treated as an npm package. Since there's no `main` field in `package.json`, the resolver looks for an index file within the package directory. It finds `lib/index.js`.
Example 4: Absolute Path and Extension Resolution
Input:
modulePath = "/usr/local/lib/my-module.json"
referenceFilePath = "/tmp/test.js"
moduleDirectories = []
// Assume the file exists at the specified absolute path.
Output: "/usr/local/lib/my-module.json"
Explanation: The module path is absolute and directly points to a `.json` file, which is resolved.
Example 5: Non-existent Module
Input:
modulePath = "non-existent-module"
referenceFilePath = "/path/to/project/src/index.ts"
moduleDirectories = ["/path/to/project/node_modules"]
// Assume no module named "non-existent-module" is found.
Output: null
Explanation: The module could not be found in any of the specified directories or via other resolution strategies.
Constraints
- The file system is assumed to be accessible for checking file existence.
- You can assume standard Node.js module resolution behavior for npm packages, but you only need to implement the core logic described above.
- The maximum depth of nested
node_modulesfolders is not a primary concern for this simulation; focus on the providedmoduleDirectories. - The supported file extensions are
.js,.json,.node, and.ts. - You should not rely on external libraries that perform module resolution themselves (e.g.,
require.resolve).
Notes
- You'll need to use Node.js's built-in
pathmodule extensively for manipulating file paths. - Consider how to handle directory paths (e.g.,
/some/directoryvs./some/directory.js). - The order of checking extensions and file types is critical.
- For the
moduleDirectoriespart, remember to iterate through each directory in the provided array and search for the package within it. - Simulating
package.json'smainfield resolution is key for npm package imports. - When checking for an index file within a directory, ensure you probe with all supported extensions.