Skip to content

refactor: prune lockfile importers when running pnpm deploy to prepare for allProjectsGraph refactor#9258

Merged
zkochan merged 2 commits intomainfrom
gluxon/node-linker-hoisted-deploy-allProjectsGraph
Mar 11, 2025
Merged

refactor: prune lockfile importers when running pnpm deploy to prepare for allProjectsGraph refactor#9258
zkochan merged 2 commits intomainfrom
gluxon/node-linker-hoisted-deploy-allProjectsGraph

Conversation

@gluxon
Copy link
Member

@gluxon gluxon commented Mar 10, 2025

Context

Upon updating allProjectsGraph to always contain all projects (#9259), only 1 test began failing:

Screenshot 2025-03-10 at 12 04 59 AM

Explanation

This test caught a real problem. When project-1 depends on project-2, the workspace dependency was no longer getting injected into the deploy directory.

This is because when pnpm deploy is ran with node-linker=hoisted, we add all workspace packages as an argument to the hoist function.

for (const [importerId, importer] of Object.entries(lockfile.importers)) {
if (importerId === '.') continue
const importerNode: HoisterTree = {
name: encodeURIComponent(importerId),
identName: encodeURIComponent(importerId),
reference: `workspace:${importerId}`,
peerNames: new Set<string>([]),
dependencyKind: HoisterDependencyKind.WORKSPACE,
dependencies: _toTree({
...importer.dependencies,
...importer.devDependencies,
...importer.optionalDependencies,
}),
}
node.dependencies.add(importerNode)
}

This hoists all workspace packages into the original root node_modules instead of the injected deploy directory. As a result, pnpm installs them in their normal location and never in the deploy directory.

Why did the allProjectsGraph refactor cause this?

Before this change, the allProjectsGraph only contained the root project and the selected project for deploy. This meant lockfile.importers only contained those 2 projects. When allProjectsGraph was updated to be all projects, the lockfile.importers object contained all projects.

Here's this loop before the allProjectsGraph refactor:

Screenshot 2025-03-10 at 12 16 58 AM

Here's this same code path after my allProjectsGraph change. Note how project-2 and project-3 are now included.

Screenshot 2025-03-10 at 12 16 19 AM

Changes

I've brainstormed a few fixes, but I think the best one is to pass down pruneLockfileImporters from the deploy function. This results in lockfile.importers only containing the deployed project.

Workspace packages that the deployed project depends on are still recursively included through the file: protocol, but they're no longer seen as importers from the perspective of a deployed install. I think this makes sense, especially since the lockfile entry for the these importers are always empty objects in this scenario.

Alternatives

We could also pass an importersToHoist array to @pnpm/real-hoist and iterate over that instead of lockfile.importers, but this felt hackier. It also doesn't fix the wanted lockfile for deploy. There would still be empty unused importers in deploy/node_modules/.pnpm/lock.yaml.

I also explored updating the modulesDir used to install hoisted workspace packages here:

const modulesDir = path.join(projectDir, 'node_modules')

To respect opts.modulesDir like we do for external packages slightly above:

const modulesDir = path.join(opts.lockfileDir, opts.modulesDir ?? 'node_modules')

However, this would be a very large refactor and potentially change the behavior of node-linker=hoisted.

@gluxon gluxon marked this pull request as ready for review March 10, 2025 05:00
@gluxon gluxon requested a review from zkochan as a code owner March 10, 2025 05:00
peer: opts.savePeer,
pruneLockfileImporters: ((opts.ignoredPackages == null) || opts.ignoredPackages.size === 0) &&
pkgs.length === allProjects.length,
pruneLockfileImporters: opts.pruneLockfileImporters ??
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One other option: We could name the publicly exposed option produceFilteredWantedLockfile and have that value imply the internal pruneLockfileImporters value here.

The idea of produceFilteredWantedLockfile would be to intentionally create a wanted lockfile that omits certain importers, which is the goal of pnpm deploy. There could probably be a better name for produceFilteredWantedLockfile if we like that idea.

@zkochan zkochan merged commit cda1c43 into main Mar 11, 2025
16 checks passed
@zkochan zkochan deleted the gluxon/node-linker-hoisted-deploy-allProjectsGraph branch March 11, 2025 01:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants