Back to Tsx

Package subpath resolution

notes/node/module-resolution.md

4.23.92.8 KB
Original Source

Package subpath resolution

Node separates a dependency's root package map from filesystem resolution of an unexported subpath. The dependency root is node_modules/dependency/package.json; a manifest in node_modules/dependency/subpath/package.json is a directory manifest, not another package exports map.

ESM

Node ESM reads exports only from the dependency root. When that map exists, it resolves dependency/subpath through the root map and does not inspect a nested manifest. Without root exports, packageResolve() uses legacy main only for dependency; it resolves dependency/subpath as a literal URL instead (v24.15.0 packageResolve(), v18.20.8 equivalent).

finalizeResolution() rejects that URL with ERR_UNSUPPORTED_DIR_IMPORT when it names a directory (v24.15.0, v18.20.8). ESM has no implicit extensions or folder mains; directory indexes must be imported with an explicit filename (v24.15.0 ESM documentation, resolver properties).

CommonJS

CommonJS first applies exports from the dependency root. resolveExports() parses the request into a package name and subpath, then reads only node_modules/<name>/package.json (v24.15.0, v18.20.8).

When no root exports map resolves the request and the filesystem target is a directory, tryPackage() reads that directory's package.json#main, tries the main target, then falls back to the directory index (v24.15.0, v18.20.8). A nested exports field is not consulted.

ESM error decoration

For ERR_MODULE_NOT_FOUND and ERR_UNSUPPORTED_DIR_IMPORT, Node ESM decorates errors with a CommonJS resolution hint before rethrowing (v24.15.0). The hint can expose a legacy directory main without changing the ESM resolution result. Loaders must preserve the original ESM error unless they intentionally select a supported compatibility fallback.