Resolve bare and relative import specifiers to files on disk.
Implements the Node.js module resolution algorithm for use by bundlers, TypeScript tooling, and anything that needs to turn an import specifier into an absolute file path.
This is the npm domain half of resolution — specifier parsing,
node_modules traversal, package.json entry points, and file
extension probing. AST rewriting stays in the consumer (e.g. OXC).
Examples
iex> NPM.Resolution.PackageResolver.split_specifier("@babel/core/lib/parse")
{"@babel/core", "./lib/parse"}
iex> NPM.Resolution.PackageResolver.relative?("./utils")
true
iex> NPM.Resolution.PackageResolver.node_builtin?("node:fs")
true
iex> NPM.Resolution.PackageResolver.find_node_modules("/app/src")
"/app/node_modules"
Summary
Functions
Returns true for bare (package) specifiers — anything that is
neither relative nor a Node.js built-in.
Walk up from dir to find the nearest node_modules directory.
Find the nearest package root from dir by walking up to package.json.
Returns true for Node.js built-in modules (fs, node:path, etc.).
Find an installed package root visible from from_dir.
Returns true for relative specifiers (./, ../, or /).
Compute a relative import path from importer to target within project_root.
Resolve a full import specifier to an absolute file path.
Resolve the entry point of a package directory.
Split a bare specifier into {package_name, subpath | nil}.
Resolve a file path by probing extensions and index.* files.
Functions
Returns true for bare (package) specifiers — anything that is
neither relative nor a Node.js built-in.
Walk up from dir to find the nearest node_modules directory.
Returns the absolute path or nil if none is found before the
filesystem root.
Find the nearest package root from dir by walking up to package.json.
Returns true for Node.js built-in modules (fs, node:path, etc.).
Find an installed package root visible from from_dir.
Returns true for relative specifiers (./, ../, or /).
Compute a relative import path from importer to target within project_root.
Both paths must be absolute. Returns a POSIX-style relative path with
a guaranteed ./ or ../ prefix, suitable for use as an import specifier.
Examples
iex> NPM.Resolution.PackageResolver.relative_import_path(
...> "/app/src/pages/home.js",
...> "/app/src/utils/format.js",
...> "/app"
...> )
"../utils/format.js"
iex> NPM.Resolution.PackageResolver.relative_import_path(
...> "/app/src/index.js",
...> "/app/src/app.js",
...> "/app"
...> )
"./app.js"
@spec resolve(String.t(), String.t(), keyword()) :: {:ok, String.t()} | {:builtin, String.t()} | :error
Resolve a full import specifier to an absolute file path.
Handles bare, relative, and built-in specifiers:
- Relative (
./foo) — resolved againstfrom_dirwith extension probing - Package imports (
#internal) — resolved from the nearest package.jsonimportsmap - Built-in (
node:fs) — returns{:builtin, name} - Bare (
lodash/fp) — locatesnode_modules, then resolves the entry point
Options
:conditions— condition names for theexportsfield:extensions— extensions for file probing
Resolve the entry point of a package directory.
Reads package.json and checks (in order):
exportsfield (viaNPM.Resolution.Exports) with the given subpath and conditionsbrowserfield (when"browser"is in conditions and value is a string)modulefieldmainfield./index.jsfallback
Options
:subpath— export subpath to resolve (default:"."):conditions— condition names for theexportsfield (default:["import", "default"]):extensions— extensions for file probing (default:[".js", ".mjs", ".cjs", ".json"])
Split a bare specifier into {package_name, subpath | nil}.
Handles scoped packages correctly:
"lodash" → {"lodash", nil}
"lodash/fp" → {"lodash", "./fp"}
"@babel/core" → {"@babel/core", nil}
"@babel/core/lib/parse" → {"@babel/core", "./lib/parse"}
Resolve a file path by probing extensions and index.* files.
Given a base path (without extension), tries each extension in order,
then tries base/index.* for directory imports.
Options
:extensions— list of extensions to probe (default:[".js", ".mjs", ".cjs", ".json"])