With a global virtual store, package directories live outside the project, so Node's upward node_modules walk from their real paths never reaches the project's hoisted node_modules — undeclared (phantom) dependencies stop resolving, and Node ignores NODE_PATH entirely for ESM imports (Related to pnpm/pnpm#9618). When enableGlobalVirtualStore is on (and extendNodePath is not disabled), every child process pnpm spawns — run, exec, lifecycle scripts, dlx — now receives NODE_PATH pointing at the private hoist dir and the root node_modules, plus a NODE_OPTIONS --import flag that registers a resolve hook restoring NODE_PATH lookups for ESM, replacing the plugin-esm-node-path config dependency. The flag is a self-contained constant: the registration module and the hook are nested data: URLs, so no loader file has to exist on disk and the flag stays valid regardless of which project or pnpm version spawned the child. It prefers module.registerHooks() (in-thread, no DEP0205 deprecation warning) and falls back to module.register() on Node >=18.19 <22.15. Everything outside the RFC 3986 unreserved set is percent-encoded: encodeURIComponent alone leaves single quotes bare, and the NODE_OPTIONS tokenizer treats those as quote delimiters. Both stacks embed identical hook sources and each asserts its derived flag against the shared golden file pnpm11/exec/esm-node-path-loader/test/import-flag.txt, so they cannot drift apart silently. Where the nodeOptions setting overwrites NODE_OPTIONS (run, exec, recursive run, install lifecycle), the flag is re-applied. pacquet's dlx and global installs force GVS off for their self-contained trees and now strip the injected env from the caller's cloned config, so a tool never resolves phantoms from the invoking project's tree; the TypeScript dlx (GVS on by default) adds the flag and lets the bin shim supply NODE_PATH. This deliberately does not address the TypeScript-compiler side of pnpm/pnpm#13210: tsc, tsserver, and bundlers implement their own resolution and honor neither NODE_PATH nor Node loader hooks. That class remains packageExtensions / compat-DB territory.
910 B
910 B
@pnpm/exec.esm-node-path-loader, @pnpm/config.reader, @pnpm/exec.commands, pnpm, pacquet
| @pnpm/exec.esm-node-path-loader | @pnpm/config.reader | @pnpm/exec.commands | pnpm | pacquet |
|---|---|---|---|---|
| minor | minor | minor | minor | minor |
When enableGlobalVirtualStore is on, every process pnpm spawns for the project (pnpm run, pnpm exec, lifecycle scripts) now receives a NODE_PATH pointing at the project's hoisted node_modules, plus a NODE_OPTIONS --import flag that registers a resolve hook restoring NODE_PATH lookups for ESM imports. Dependencies that import undeclared ("phantom") packages keep resolving under the global virtual store — for both CommonJS and ESM — without installing the @pnpm/plugin-esm-node-path config dependency pnpm/pnpm#9618. Tools run by pnpm dlx resolve such dependencies too: the JS CLI passes them the same environment, while the Rust CLI's dlx cache is self-contained, so its layout already exposes them.