Node-Modules & 호이스팅 설정
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
노드 패키지 설치에 사용해야 하는 링커를 정의합니다. 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가 생성됩니다. 이는 npm 또는 Yarn Classic이 생성하는node_modules와 같습니다. pnpm hoists with Yarn's hoisting algorithm. 이 설정을 사용해야 하는 정당한 이유:- 여러분의 도구가 심볼릭 링크와 잘 작동하지 않습니다.
- 여러분의 프로젝트가 서버리스 호스팅 제공업체에 배포됩니다. 일부 서버리스 공급자 (예: AWS Lambda)는 심볼릭 링크를 지원하지 않습니다. 이 문제에 대한 대안 솔루션은 배포 전에 애플리케이션을 번들로 묶는 것입니다.
"bundledDependencies"로 패키지를 게시하려는 경우.- --preserve-symlinks 플래그로 Node.js를 실행하는 경우.
-
pnp -
node_modules가 없습니다. Plug'n'Play는 Yarn Berry에서 사용하는 Node용 혁신적인 전략입니다.pnp를 링커로 사용할 때는symlink설정도false로 지정하는 것이 좋습니다. -
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.
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-mapoption toNODE_OPTIONSin pnpm-managed Node.js script environments.
The environment variable is PNPM_CONFIG_NODE_PACKAGE_MAP_MODE.
nodePackageMap:
mode: write-only
nodePackageMap.type#
Added in: v12.13.0
- 기본값: standard
- 유형: standard, loose
node_modules/.package-map.json이 생성되는 방식을 제어합니다.
- standard - 선언된 종속성만 패키지 맵을 통해 사용할 수 있습니다.
- loose - 설치된
node_modules레이아웃을 통해 도달할 수 있는 패키지도 매핑하므로, 선언되지 않은 호이스팅된 종속성이 해석될 수 있습니다.
The environment variable is PNPM_CONFIG_NODE_PACKAGE_MAP_TYPE.
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 및 환경 구성에서는 케밥 케이스 이름 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) 의 파일 시스템으로 마운트될 때 유용합니다. FUSE로 모듈 디렉터리를 마운트할 수 있는 실험적인 CLI가 있습니다: @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은 임포터 심볼릭 링크 생성, 호이스팅, bin 링크 생성 또는 라이프사이클 스크립트 실행 없이 가상 스토어를 채웁니다. 이는 불필요한 프로젝트 수준 아티팩트를 만들지 않고 스토어를 미리 채울 때(예를 들어, Nix 빌드에서) 유용합니다. pnpm fetch는 내부적으로 이 모드를 사용합니다.
packageImportMethod#
- 기본값: auto
- 유형: auto, hardlink, copy, clone, clone-or-copy
스토어에서 패키지를 가져오는 방식을 제어합니다(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 또는 reference link라고도 함)합니다
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.
When a link succeeds but costs more than a copy#
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.
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에 있습니다(<store-path>를 찾으려면 pnpm 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[]
node_modules/.pnpm/node_modules로 호이스팅할 패키지를 pnpm에 알려줍니다. 기본적으로 모든 패키지는 호이스팅됩니다, 하지만 유령 의존성을 갖는 잘못된 패키지를 알고 있다면 이 옵션을 이용해 유령 의존성을 호이스팅할 수 있습니다. (권장)
예를 들어:
hoistPattern:
- "*eslint*"
- "*babel*"
!를 사용하여 패턴을 호이스팅에서 제외할 수도 있습니다.
예를 들어:
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은 패턴과 일치하는 종속성을 루트 모듈 디렉터리로 호이스팅합니다. 루트 모듈 디렉토리에 대한 호이스팅은 애플리케이션 코드가 해결 전략을 부적절하게 수정하더라도 유령 의존성에 액세스할 수 있음을 의미합니다.
이 설정은 의존성을 제대로 해결하지 못하는 일부 결함이 있는 플러그형 도구를 처리할 때 유용합니다.
예를 들어:
publicHoistPattern:
- "*plugin*"
참고: shamefullyHoist를 true로 설정하는 것은 publicHoistPattern을 *로 설정하는 것과 같습니다.
!를 사용하여 패턴을 호이스팅에서 제외할 수도 있습니다.
예를 들어:
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
publicHoistPatternis symlinked into<workspace_root>/node_modules. - A project whose name matches only
hoistPatternis 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:
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
namein itspackage.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
hoistPatternandpublicHoistPatternare both empty, nothing is hoisted at all, workspace projects included. SettinghoisttofalseemptieshoistPattern.
shamefullyHoist#
- 기본값: false
- 유형: Boolean
기본적으로 pnpm은 반엄격 node_modules를 생성합니다. 즉, 종속성은 선언되지 않은 종속성에 접근할 수 있지만 node_modules 외부의 모듈은 접근할 수 없습니다.
이 레이아웃을 사용하면 생태계의 대부분의 패키지가 문제 없이 작동합니다.
하지만 일부 도구가 호이스팅된 종속성이 node_modules 루트에 있을 때만 동작한다면, 이 값을 true로 설정하여 종속성을 호이스팅할 수 있습니다.
hoistingLimits#
- 기본값: none
- 유형: none, workspaces, dependencies
nodeLinker: hoisted를 사용할 때 종속성을 얼마나 멀리 호이스팅할지 제어합니다. 이 설정은 Yarn의 nmHoistingLimits와 동일합니다.
- none - 가능한 한 멀리 호이스팅합니다(기본값).
- workspaces - 각 워크스페이스 패키지까지만 호이스팅하여, 종속성이 해당 종속성을 사용하는 워크스페이스 패키지보다 위로 호이스팅되지 않도록 합니다.
- dependencies - 각 워크스페이스 패키지의 직접 종속성까지만 호이스팅하여, 전이 종속성이 워크스페이스 패키지의
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.
externalDependencies:
- bit-bin