Node-Modules & Hoisting Settings

node_modules settings#

modulesDir#

  • Стандартно: node_modules
  • Тип: path

Тека, до якої буде встановлено залежності (замість node_modules).

In a workspace, a relative path is used inside every project. An absolute path, or one that starts with .., is the root project's directory only. Every other project uses a directory named after its last part, such as vendor for ../vendor.

nodeLinker#

  • Стандартно: isolated
  • Type: isolated, hoisted, pnp, loaded, or a loaded-linker object

Визначає, який компонувальник слід використовувати для встановлення пакунків Node. The existing scalar values remain supported. The experimental loaded linker also accepts an object with type: loaded and an optional excluded list. Other linker object forms are not supported yet.

Configuration sources replace the whole nodeLinker value. A higher-priority scalar or an object without excluded clears exclusions from lower-priority sources. The environment variable PNPM_CONFIG_NODE_LINKER and --config.node-linker accept the object as JSON.

  • isolated — залежності компонуються з віртуального сховища за адресою node_modules/.pnpm.

  • hoisted — створюється плаский node_modules без символічних посилань. Так само як і node_modules, створені за допомогою npm або Yarn Classic. pnpm hoists with Yarn's hoisting algorithm. Законні причини для використання цього налаштування:

    1. Ваші інструменти погано працюють із символічними посиланнями.
    2. Ваш проєкт буде розгорнуто на безсерверний хостинг. Деякі безсерверні провайдери (наприклад, AWS Lambda) не підтримують symlinks. Альтернативним розвʼязання цієї проблеми є пакування програми перед розгортанням.
    3. Якщо ви хочете опублікувати свій пакунок за допомогою "bundledDependencies".
    4. Якщо ви використовуєте Node.js із прапорцем --preserve-symlinks.
  • pnp — без node_modules. Plug'n'Play — це інноваційна стратегія для Node, яка використовується Yarn Berry. Рекомендується також встановити параметр symlink у значення false, якщо ви використовуєте pnp як ваш компонувальник.

  • loaded - experimental loading directly from the content-addressable store. pnpm preloads its Node.js loader for scripts and commands. Selected compatibility packages and their dependency trees use the global virtual store. See Experimental store loader.

nodeLinker.excluded#

  • Стандартно: []
  • Тип: string[]

With nodeLinker.type: loaded, materialize these package names and their complete dependency trees in the global virtual store. Names match all installed versions and peer contexts. Packages outside these trees load directly from CAS. Build approval settings still apply.

pnpm-workspace.yaml
nodeLinker:
  type: loaded
  excluded:
    - vitest

nodePackageMap.mode#

Added in: v12.13.0

  • Стандартно: off
  • Type: off, write-only, load

Controls node_modules/.package-map.json, which pnpm writes during isolated and hoisted installs.

  • off - pnpm writes no package map. An install also removes a map that an earlier install left.
  • write-only - pnpm writes the package map for other tools to read. Node.js does not load it.
  • load - pnpm writes the package map and adds Node's --experimental-package-map option to NODE_OPTIONS in pnpm-managed Node.js script environments.

The environment variable is PNPM_CONFIG_NODE_PACKAGE_MAP_MODE.

pnpm-workspace.yaml
nodePackageMap:
  mode: write-only

nodePackageMap.type#

Added in: v12.13.0

  • Default: standard
  • Type: standard, loose

Controls how node_modules/.package-map.json is generated.

  • standard - only declared dependencies are available through the package map.
  • loose - also maps packages that are reachable through the installed node_modules layout, which can allow undeclared hoisted dependencies to resolve.

The environment variable is PNPM_CONFIG_NODE_PACKAGE_MAP_TYPE.

pnpm-workspace.yaml
nodePackageMap:
  mode: load
  type: loose

nodeExperimentalPackageMap#

Deprecated since v12.13.0: use nodePackageMap.mode instead. nodeExperimentalPackageMap: true is the same as mode: load, and false is the same as mode: off. When both are set in the same place, nodePackageMap takes precedence.

CLI and environment configuration use the kebab-case name node-experimental-package-map.

nodePackageMapType#

Deprecated since v12.13.0: use nodePackageMap.type instead. When both are set in the same place, nodePackageMap takes precedence.

  • Стандартно: true
  • Тип: Boolean

Коли symlink встановлено у false, pnpm створює віртуальну теку сховища без жодних символьних посилань. Це корисний параметр разом з nodeLinker=pnp.

enableModulesDir#

  • Стандартно: true
  • Тип: Boolean

Якщо false, pnpm не записуватиме жодних файлів до теки модулів (node_modules). Це корисно, коли теку модулів змонтовано з файловою системою у просторі користувача (FUSE). Існує експериментальна версія CLI, яка дозволяє змонтувати теку модулів за допомогою FUSE: @pnpm/mount-modules.

virtualStoreDir#

  • Стандартно: node_modules/.pnpm
  • Тип: path

Тека з посиланнями на сховище. Всі прямі та непрямі залежності проєкту повʼязані з цією текою.

Dependencies installed from a URL or local path get a hash suffix on their directory name when the part after the scheme contains +, #, :, ?, or another character that is not allowed in file names. This keeps distinct URLs in separate directories.

Це корисний параметр, який може усунути проблеми з довгими шляхами у Windows. Якщо у вас є залежності з дуже довгими шляхами, ви можете вибрати віртуальне сховище у корені диска (наприклад, C:\my-project-store).

Або ви можете встановити віртуальне сховище у .pnpm і додати його до .gitignore. Це зробить трасування стеку чистішим, оскільки шляхи до залежностей будуть на одну теку вище.

NOTE: without a global virtual store, the virtual store cannot be shared between several projects. Every project should have its own virtual store (except for in workspaces where the root is shared).

When enableGlobalVirtualStore is on, virtualStoreDir sets the location of the global virtual store, which several projects can share. Each project still keeps its current lockfile and hidden hoisted dependencies in its own node_modules/.pnpm.

virtualStoreDirMaxLength#

  • Стандартно:
    • У Linux/macOS: 120
    • У Windows: 60
  • Тип: number

Встановлює максимально допустиму довжину назв тек всередині теки віртуального сховища (node_modules/.pnpm). Ви можете встановити менше значення, якщо у вас виникають проблеми з довгими шляхами у Windows.

virtualStoreOnly#

  • Стандартно: false
  • Тип: Boolean

Якщо встановлено значення true, pnpm заповнює віртуальний репозиторій без створення символьних посилань імпортера, підйому, посилань на бінарні файли або запуску скриптів життєвого циклу. Це корисно для попереднього заповнення сховища (наприклад, у збірках Nix) без створення зайвих артефактів на рівні проєкту. Команда pnpm fetch використовує цей режим внутрішньо.

packageImportMethod#

  • Стандартно: auto
  • Тип: auto, hardlink, copy, clone, clone-or-copy

Керує способом імпорту пакунків зі сховища (якщо ви хочете вимкнути symlinks всередині node_modules, то вам потрібно змінити параметр nodeLinker, а не цей).

  • auto - try the platform's cheap link tiers in order, falling back to copying when none of them is possible. On Linux and Windows it tries hardlinking before cloning; on macOS it tries cloning before hardlinking.
  • hardlink — жорстке посилання на пакунки зі сховища
  • clone-or-copy — спробувати клонувати пакунки зі сховища. Якщо клонування не підтримується, поверніться до копіювання
  • copy — скопіювати пакунки зі сховища
  • clone —клонувати (те ж що й copy-on-write або посилатись за посиланням) пакунки зі сховища

A package that a lifecycle script or a patch will still write must not share files with the store, so pnpm imports it with clone-or-copy when this setting is auto, hardlink, or clone. With copy, such a package is copied like every other package.

Cloning, like copying, keeps node_modules separate from the store. You may edit files in your node_modules and they will not be modified in the central content-addressable store. Unlike a copy, a clone shares its data with the store file until one of them is written, so its contents take no extra disk space.

Not all file systems support cloning. To clone on Linux, use a copy-on-write (CoW) file system, for instance btrfs instead of ext4, and set packageImportMethod to clone. On Linux and Windows, auto tries hardlinking first.

What auto tries first#

auto is a per-platform order:

Platform Order
Linux hardlink → clone → copy
macOS clone → hardlink → copy
Windows hardlink → clone → copy

A reflink materializes a new inode and copies extent bookkeeping inside the filesystem's metadata trees, where a hardlink is one directory entry. On btrfs that difference roughly halves the time an install spends materializing node_modules from a warm store, which is why pnpm reaches for the hardlink first there. ext4 is unaffected. It never supported cloning, so auto already hardlinked. On a Windows Dev Drive (ReFS), a block clone takes several calls per file where a hardlink takes one, so Windows hardlinks first as well. macOS keeps clone-first, since APFS clonefile is that platform's cheap primitive.

If you edit files inside node_modules, ask for clone explicitly. Under a hardlink, the file in node_modules is the file in the store, so editing it changes every project that links the same package.

auto steps down a tier only when a link fails, never when one succeeds slowly. Two setups produce links that succeed and are expensive, and on both packageImportMethod: copy can be considerably faster.

Neither is a reason to reach for copy generally. On an ordinary install (store and node_modules on the same everyday filesystem, no image layer in between), copying is the slowest option and the one that uses the most space. Measure both on the machine in question.

A store baked into a container image layer#

Container runtimes assemble an image from stacked layers, usually with overlayfs. A hardlink to a file that lives in a lower layer succeeds there, but the file is copied into the writable upper layer first. An image that ships a pre-populated pnpm store leaves that store in a lower layer, so the first link to any store file copies that file up. pnpm's "Packages are hard linked from the content-addressable store" line stays accurate while the install copies most of the store out anyway.

The cost is per file, not per link: a package linked into several projects is copied up once and linked cheaply after that. It still lands on most of the store, because an install links nearly every file it needs at least once. The run below made 69k links and copied up 36.7k distinct files.

Measured with pnpm 12 (through @pnpm/napi 12.1.0) on a 13-project workspace, 2074 packages, store fully pre-populated so nothing was downloaded, timing only the phase that writes node_modules. Rootless podman on the kernel overlay storage driver with metacopy off, backed by btrfs, --cpus=8:

packageImportMethod Link phase System time Written
auto (hardlinking) 2.1s 8.0s 36.7k files copied up, 366 MB
copy 1.5s 5.6s 874 MB, nothing copied up

Backing that overlay with btrfs is what keeps the gap small: a copy-up there can share extents rather than duplicate them. On ext4 every copy-up is a full data copy, and the gap widens. One project's CI, running this shape on an ext4 boot disk, went from 38s to 16s by switching to copy.

The EXDEV fallback below does not help here. A cross-layer hardlink does not fail. It copies up and reports success, so auto has no failure to step down on.

Network and virtualized filesystems#

The same "succeeds, but slowly" shape appears when the store and node_modules sit on the same filesystem and that filesystem services every metadata operation over a round trip: a network share (NFS, SMB, Amazon EFS) or a directory shared from a host into a VM or container (virtiofs, gRPC-FUSE, 9p). Cloning, where the platform tries it first, fails there and steps down; hardlinking then succeeds, so auto stays on it and the install pays a round trip per file. Copying reads and writes whole files instead of performing per-file metadata operations the filesystem has to service remotely.

This only arises when both live on that filesystem. A store on a genuinely different filesystem from node_modules cannot be hardlinked from at all. The attempt fails with EXDEV, so auto already falls back to copying on its own.

How many files macOS writes at once#

On macOS, pnpm writes package files into node_modules with 4 threads, whatever the number of CPU cores. On the Macs measured, more threads made installs slower.

Security software that monitors file operations through Endpoint Security, as on many company-managed Macs, can reverse this. On one such Mac, installs took about 40% less time when pnpm wrote files with the threads it uses for the rest of the install. Set the PNPM_MACOS_LINK_THREADS environment variable to 0 for that, or to a positive number to use that many threads. If your Mac runs such software, compare install times with 0 and the default:

PNPM_MACOS_LINK_THREADS=0 pnpm install

modulesCacheMaxAge#

  • Стандартно: 10080 (7 днів в хвилинах)
  • Тип: number

Час у хвилинах, через який слід видалити порожні пакунки з теки модулів. pnpm зберігає кеш пакунків у теці модулів. Це підвищує швидкість встановлення при перемиканні гілок або пониженню версій залежностей.

dlxCacheMaxAge#

  • Стандартно: 1440 (1 день в хвилинах)
  • Тип: number

Час у хвилинах, через який закінчується кеш dlx. Після виконання команди dlx pnpm зберігає кеш, який пропускає крок встановлення для наступних викликів тієї самої команди dlx.

virtualStoreType#

  • Default: project
  • Type: project, global

Names where the virtual store lives: one store per project, or one store per machine.

pnpm-workspace.yaml
virtualStoreType: global

project is the default layout: every project gets its own virtual store inside node_modules/.pnpm. global is the global virtual store: a single store shared by every project on the machine, with each project's node_modules holding only symlinks into it.

This is the canonical spelling of enableGlobalVirtualStore, which keeps working; when a project sets both, virtualStoreType wins. It can also be set through the PNPM_CONFIG_VIRTUAL_STORE_TYPE env variable and read back with pnpm config get virtualStoreType.

The setting is independent of nodeLinker: isolated and pnp both work with either store type, and hoisted writes no virtual store at all, so it is unaffected.

enableGlobalVirtualStore#

  • Стандартно: false
  • Тип: Boolean

нотатка

virtualStoreType is the canonical spelling of this setting: enableGlobalVirtualStore: true is virtualStoreType: global. Both work, and virtualStoreType wins when a project sets both.

нотатка

Global installs (pnpm add -g) use the global virtual store by default. pnpm dlx does not: each entry of the dlx cache keeps its own virtual store.

When enabled with nodeLinker: isolated, node_modules contains only symlinks to a central virtual store, rather than to node_modules/.pnpm. Стандартно, це центральне сховище розташовано за адресою <store-path>/links (використовуйте pnpm store path, щоб знайти <store-path>). Set globalVirtualStoreDir or virtualStoreDir to place it elsewhere.

У центральному віртуальному сховищі кожен пакунок жорстко повʼязаний з текою, назва якої є хешем його графа залежностей. В результаті, всі проєкти в системі можуть звʼязати свої залежності з цим спільним місцем на диску. Цей підхід концептуально подібний до того, як NixOS керує пакунками, використовуючи хеші графів залежностей для створення ізольованих та спільних тек пакунків у сховищі Nix.

Це не слід плутати з глобальним сховищем адресованого вмісту. The actual package files are still hard linked from the content-addressable store, but instead of being linked directly into node_modules/.pnpm, they are linked into the global virtual store.

Using a global virtual store can speed up installations when a warm cache is available. Однак у середовищі CI (де кеш зазвичай відсутній) це може сповільнити встановлення.

important

Для підтримки піднятих залежностей при використанні глобального віртуального сховища pnpm покладається на змінну оточення NODE_PATH. Це дозволяє Node.js розпізнавати пакунки з піднятої теки node_modules. Node.js does not respect NODE_PATH for ESM imports, so every process pnpm spawns for the project (pnpm run, pnpm exec, lifecycle scripts, and the tools pnpm dlx runs) receives both NODE_PATH and a NODE_OPTIONS --import flag that registers a resolve hook restoring NODE_PATH lookups for ESM. A dependency that imports a package not declared in its own package.json (which is considered bad practice) resolves for CommonJS and ESM alike.

Two things fall outside that: a node process you start yourself rather than through pnpm, and a project that sets extendNodePath to false, which turns the whole NODE_PATH mechanism off and takes the resolve hook with it. Use packageExtensions to declare the missing dependencies if you need them to resolve in either case.

globalVirtualStoreDir#

  • Default: <store-path>/links
  • Тип: path

The location of the global virtual store. It takes precedence over virtualStoreDir. When it is unset and the global virtual store is on, an explicitly set virtualStoreDir places the global virtual store.

Dependency hoisting settings#

hoist#

  • Стандартно: true
  • Тип: Boolean

Коли true, всі залежності підіймаються до node_modules/.pnpm/node_modules. Це робить не перелічені залежності доступними для всіх пакунків всередині node_modules.

hoistPattern#

  • Стандартно: ['*']
  • Тип: string[]

Вказує pnpm, які пакунки слід підняти до node_modules/.pnpm/node_modules. Стандартно, всі пакунки буде піднято — однак, якщо ви знаєте, що лише деякі пакунки мають фантомні залежності, ви можете скористатися цим параметром, щоб підняти лише фантомні залежності (рекомендується).

Наприклад:

pnpm-workspace.yaml
hoistPattern:
- "*eslint*"
- "*babel*"

Ви також можете заборонити підйом шаблонів за допомогою !.

Наприклад:

pnpm-workspace.yaml
hoistPattern:
- "*types*"
- "!@types/react"

In a workspace, the pattern is matched against the names of the workspace projects too, unless hoistWorkspacePackages is turned off.

publicHoistPattern#

  • Стандартно: []
  • Тип: string[]

На відміну від hoistPattern , який підіймає залежності до прихованої теки модулів у віртуальному сховищі, publicHoistPattern підіймає залежності, що відповідають шаблону, до кореневої теки модулів. Підняття до кореневої теки модулів означає, що код застосунку матиме доступ до фантомних залежностей, навіть якщо вони неправильно змінять стратегію розвʼязання.

Цей параметр корисний при роботі з деякими недосконалими інструментами, що підключаються, які не обробляють залежності належним чином.

Наприклад:

pnpm-workspace.yaml
publicHoistPattern:
- "*plugin*"

Зауваження: Встановлення shamefullyHoist у true аналогічно встановленню publicHoistPattern у *.

Ви також можете заборонити підйом шаблонів за допомогою !.

Наприклад:

pnpm-workspace.yaml
publicHoistPattern:
- "*types*"
- "!@types/react"

In a workspace, the pattern is matched against the names of the workspace projects too, unless hoistWorkspacePackages is turned off.

hoistWorkspacePackages#

  • Стандартно: true
  • Тип: Boolean

When true, the projects of the workspace are hoisting candidates too, not only the packages that come from a registry.

hoistPattern and publicHoistPattern then decide where each project lands, exactly as they do for a registry dependency. publicHoistPattern is checked first, so a project matching both patterns goes to the root only:

  • A project whose name matches publicHoistPattern is symlinked into <workspace_root>/node_modules.
  • A project whose name matches only hoistPattern is symlinked into <workspace_root>/node_modules/.pnpm/node_modules.
  • A project whose name matches neither is not hoisted.

Because hoistPattern defaults to ['*'] and publicHoistPattern to [], the default is that every workspace project is symlinked into <workspace_root>/node_modules/.pnpm/node_modules.

This is how a tool that resolves packages by name from the workspace root finds a project of the workspace. The usual case is ESLint loading a plugin that is developed in the same monorepo:

pnpm-workspace.yaml
publicHoistPattern:
  - "*eslint*"

With that setting, a workspace project named eslint-plugin-acme appears at <workspace_root>/node_modules/eslint-plugin-acme, even if no package.json lists it as a dependency.

Details worth knowing:

  • A project is matched by the name in its package.json, not by its directory name.
  • Every project of the workspace is a candidate, including a project that nothing else depends on. The workspace root project is never hoisted.
  • The symlink points at the project's own directory in the workspace, so there is nothing to keep in sync.
  • A direct dependency wins a name conflict. If a package with the same name is a direct dependency of any workspace project, that package is hoisted and the workspace project is not.
  • The setting only adds candidates. When hoistPattern and publicHoistPattern are both empty, nothing is hoisted at all, workspace projects included. Setting hoist to false empties hoistPattern.

shamefullyHoist#

  • Стандартно: false
  • Тип: Boolean

Стандартно pnpm створює напівсувору теку node_modules, тобто залежності мають доступ до неоголошених залежностей, а модулі поза межами node_modules — ні. За такої схеми більшість пакунків в екосистемі працюють без проблем. Однак, якщо деякі інструменти працюють лише тоді, коли підняті залежності знаходяться у корені node_modules, ви можете встановити цей параметр у true, щоб підняти їх для вас.

hoistingLimits#

  • Default: none
  • Type: none, workspaces, dependencies

Controls how far dependencies are hoisted when using nodeLinker: hoisted. This setting mirrors Yarn's nmHoistingLimits.

  • none - hoist as far as possible (the default).
  • workspaces - hoist only as far as each workspace package, preventing dependencies from being hoisted above the workspace package that depends on them.
  • dependencies - hoist only up to each workspace package's direct dependencies, preventing transitive dependencies from being hoisted into the workspace package's node_modules.

externalDependencies#

  • Стандартно: []
  • Тип: string[]

Names of dependencies that another tool links into the root node_modules. With nodeLinker: hoisted, pnpm does not place a dependency whose alias is listed here at the top level, and leaves that slot to the other tool. This setting has no effect with other linkers.

pnpm-workspace.yaml
externalDependencies:
  - bit-bin