0
# @pnpm/hoist
1
2
@pnpm/hoist provides sophisticated dependency hoisting algorithms for pnpm-managed node_modules directories. It implements logic to move dependencies up the directory tree hierarchy to reduce duplication and improve module resolution performance, supporting both public and private hoisting patterns with configurable matching rules.
3
4
## Package Information
5
6
- **Package Name**: @pnpm/hoist
7
- **Package Type**: npm
8
- **Language**: TypeScript
9
- **Installation**: `pnpm add @pnpm/hoist`
10
11
## Core Imports
12
13
```typescript
14
import { hoist, getHoistedDependencies, graphWalker } from "@pnpm/hoist";
15
import type {
16
HoistOpts,
17
DependenciesGraph,
18
DirectDependenciesByImporterId,
19
HoistedWorkspaceProject
20
} from "@pnpm/hoist";
21
```
22
23
For CommonJS:
24
25
```javascript
26
const { hoist, getHoistedDependencies, graphWalker } = require("@pnpm/hoist");
27
```
28
29
## Basic Usage
30
31
```typescript
32
import { hoist } from "@pnpm/hoist";
33
import type { HoistOpts, DependenciesGraph } from "@pnpm/hoist";
34
import type { ProjectId } from "@pnpm/types";
35
36
// Basic hoisting operation
37
const graph: DependenciesGraph<string> = {
38
// ... dependency graph data
39
};
40
41
const hoistedDeps = await hoist({
42
graph,
43
directDepsByImporterId: {
44
'.': new Map([['lodash', 'lodash@4.17.21']]),
45
},
46
importerIds: ['.' as ProjectId],
47
privateHoistPattern: ['*'],
48
privateHoistedModulesDir: '/path/to/node_modules/.pnpm',
49
publicHoistPattern: ['lodash'],
50
publicHoistedModulesDir: '/path/to/node_modules',
51
virtualStoreDir: '/path/to/.pnpm',
52
virtualStoreDirMaxLength: 120,
53
skipped: new Set(),
54
});
55
```
56
57
## Architecture
58
59
@pnpm/hoist is built around several key components:
60
61
- **Hoisting Engine**: Core algorithms that determine which dependencies should be hoisted and where
62
- **Pattern Matching**: Configurable glob patterns to control public vs private hoisting behavior
63
- **Graph Analysis**: Dependency graph traversal and analysis utilities
64
- **Symlink Management**: Safe creation and management of symbolic links in the filesystem
65
- **Binary Linking**: Automatic linking of executable binaries from hoisted packages
66
67
## Capabilities
68
69
### Main Hoisting Function
70
71
Performs complete dependency hoisting with symlink creation and binary linking.
72
73
```typescript { .api }
74
/**
75
* Performs dependency hoisting with symlink creation and binary linking
76
* @param opts - Configuration options for hoisting operation
77
* @returns Promise resolving to hoisted dependencies data or null if no hoisting needed
78
*/
79
async function hoist<T extends string>(opts: HoistOpts<T>): Promise<HoistedDependencies | null>;
80
81
interface HoistOpts<T extends string> extends GetHoistedDependenciesOpts<T> {
82
/** Additional Node.js paths for binary resolution */
83
extraNodePath?: string[];
84
/** Whether to prefer symlinked executables over copied ones */
85
preferSymlinkedExecutables?: boolean;
86
/** Path to the virtual store directory */
87
virtualStoreDir: string;
88
/** Maximum length allowed for virtual store directory paths */
89
virtualStoreDirMaxLength: number;
90
}
91
```
92
93
### Hoisting Analysis
94
95
Computes which dependencies should be hoisted without creating symlinks.
96
97
```typescript { .api }
98
/**
99
* Computes which dependencies should be hoisted without actually creating symlinks
100
* @param opts - Configuration options for hoisting analysis
101
* @returns Hoisting analysis result or null if no dependencies to analyze
102
*/
103
function getHoistedDependencies<T extends string>(opts: GetHoistedDependenciesOpts<T>): {
104
hoistedDependencies: HoistedDependencies;
105
hoistedDependenciesByNodeId: Map<T | ProjectId, Record<string, 'public' | 'private'>>;
106
hoistedAliasesWithBins: string[];
107
} | null;
108
109
interface GetHoistedDependenciesOpts<T extends string> {
110
/** The dependency graph to analyze */
111
graph: DependenciesGraph<T>;
112
/** Set of dependency paths to skip */
113
skipped: Set<DepPath>;
114
/** Maps importer IDs to their direct dependencies */
115
directDepsByImporterId: DirectDependenciesByImporterId<T>;
116
/** Optional list of importer IDs to process */
117
importerIds?: ProjectId[];
118
/** Glob patterns for private hoisting (hoisted to .pnpm directory) */
119
privateHoistPattern: string[];
120
/** Directory for privately hoisted modules */
121
privateHoistedModulesDir: string;
122
/** Glob patterns for public hoisting (hoisted to root node_modules) */
123
publicHoistPattern: string[];
124
/** Directory for publicly hoisted modules */
125
publicHoistedModulesDir: string;
126
/** Optional workspace packages that should be hoisted */
127
hoistedWorkspacePackages?: Record<ProjectId, HoistedWorkspaceProject>;
128
}
129
```
130
131
### Graph Traversal
132
133
Creates a walker for efficient dependency graph traversal.
134
135
```typescript { .api }
136
/**
137
* Creates a walker for traversing the dependency graph
138
* @param graph - The dependency graph to traverse
139
* @param directDepsByImporterId - Maps importer IDs to their direct dependencies
140
* @param opts - Optional traversal configuration
141
* @returns Graph walker instance for step-by-step traversal
142
*/
143
function graphWalker<T extends string>(
144
graph: DependenciesGraph<T>,
145
directDepsByImporterId: DirectDependenciesByImporterId<T>,
146
opts?: {
147
include?: { [dependenciesField in DependenciesField]: boolean };
148
skipped?: Set<DepPath>;
149
}
150
): GraphWalker<T>;
151
152
interface GraphWalker<T extends string> {
153
/** Direct dependencies found during graph analysis */
154
directDeps: Array<{
155
alias: string;
156
nodeId: T;
157
}>;
158
/** Initial step for graph traversal */
159
step: GraphWalkerStep<T>;
160
}
161
162
interface GraphWalkerStep<T extends string> {
163
/** Dependencies found at this step */
164
dependencies: Array<GraphDependency<T>>;
165
/** Linked dependencies found at this step */
166
links: string[];
167
/** Missing dependencies found at this step */
168
missing: string[];
169
}
170
171
interface GraphDependency<T extends string> {
172
/** Node ID for this dependency */
173
nodeId: T;
174
/** The dependency graph node */
175
node: DependenciesGraphNode<T>;
176
/** Function to get the next step in traversal */
177
next: () => GraphWalkerStep<T>;
178
}
179
```
180
181
## Core Types
182
183
### Dependency Graph Structure
184
185
```typescript { .api }
186
interface DependenciesGraphNode<T extends string> {
187
/** Directory path where the dependency is located */
188
dir: string;
189
/** Child dependencies mapped by alias to node ID */
190
children: Record<string, T>;
191
/** Set of optional dependency aliases */
192
optionalDependencies: Set<string>;
193
/** Whether this dependency has executable binaries */
194
hasBin: boolean;
195
/** Package name */
196
name: string;
197
/** Dependency path identifier */
198
depPath: DepPath;
199
}
200
201
type DependenciesGraph<T extends string> = Record<T, DependenciesGraphNode<T>>;
202
203
interface DirectDependenciesByImporterId<T extends string> {
204
[importerId: string]: Map<string, T>;
205
}
206
```
207
208
### Workspace Support
209
210
```typescript { .api }
211
interface HoistedWorkspaceProject {
212
/** Package name */
213
name: string;
214
/** Directory path of the workspace project */
215
dir: string;
216
}
217
```
218
219
### Dependency Representation
220
221
```typescript { .api }
222
interface Dependency<T extends string> {
223
/** Child dependencies mapped by alias to node ID or project ID */
224
children: Record<string, T | ProjectId>;
225
/** Node ID for this dependency */
226
nodeId: T;
227
/** Depth level in the dependency tree */
228
depth: number;
229
}
230
```
231
232
233
## Imported Types
234
235
These types are imported from `@pnpm/types`:
236
237
```typescript { .api }
238
type DepPath = string & { __brand: 'DepPath' };
239
type ProjectId = string & { __brand: 'ProjectId' };
240
type DependenciesField = 'optionalDependencies' | 'dependencies' | 'devDependencies';
241
type HoistedDependencies = Record<DepPath | ProjectId, Record<string, 'public' | 'private'>>;
242
```
243
244
## Error Handling
245
246
The hoisting functions handle various error conditions:
247
248
- **Missing Dependencies**: Logged as debug messages, do not cause failures
249
- **Symlink Conflicts**: Existing symlinks are safely replaced if they point to the virtual store
250
- **External Symlinks**: External symlinks are preserved and not overwritten
251
- **Directory Conflicts**: Regular directories at target locations are preserved
252
- **Binary Linking Errors**: Binary linking failures are caught and logged but don't prevent hoisting
253
254
## Usage Examples
255
256
### Basic Public Hoisting
257
258
```typescript
259
import { hoist } from "@pnpm/hoist";
260
261
const result = await hoist({
262
graph: dependencyGraph,
263
directDepsByImporterId: { '.': directDeps },
264
importerIds: ['.'],
265
privateHoistPattern: [],
266
privateHoistedModulesDir: '/project/node_modules/.pnpm',
267
publicHoistPattern: ['lodash', 'react*'],
268
publicHoistedModulesDir: '/project/node_modules',
269
virtualStoreDir: '/project/node_modules/.pnpm',
270
virtualStoreDirMaxLength: 120,
271
skipped: new Set(),
272
});
273
```
274
275
### Analysis Only (No Symlinks)
276
277
```typescript
278
import { getHoistedDependencies } from "@pnpm/hoist";
279
280
const analysis = getHoistedDependencies({
281
graph: dependencyGraph,
282
directDepsByImporterId: { '.': directDeps },
283
privateHoistPattern: ['*'],
284
privateHoistedModulesDir: '/project/node_modules/.pnpm',
285
publicHoistPattern: ['popular-*'],
286
publicHoistedModulesDir: '/project/node_modules',
287
skipped: new Set(),
288
});
289
```
290
291
### Graph Traversal
292
293
```typescript
294
import { graphWalker } from "@pnpm/hoist";
295
296
const walker = graphWalker(graph, directDepsByImporterId, {
297
include: {
298
dependencies: true,
299
devDependencies: false,
300
optionalDependencies: true,
301
peerDependencies: true
302
}
303
});
304
305
// Process direct dependencies
306
for (const { alias, nodeId } of walker.directDeps) {
307
console.log(`Direct dependency: ${alias} -> ${nodeId}`);
308
}
309
310
// Walk through dependency steps
311
let currentStep = walker.step;
312
while (currentStep.dependencies.length > 0) {
313
for (const dep of currentStep.dependencies) {
314
console.log(`Processing: ${dep.nodeId}`);
315
currentStep = dep.next();
316
}
317
}
318
```