pnpm 11 workspace: collector drops dependencies due to cross-project deduped `pnpm list` output (Cannot find module 'mime-types' / 'mime-db')
Summary
In a pnpm 11 workspace, the pnpm node-module collector silently drops packages from app.asar, and the packaged app crashes on launch with Cannot find module 'mime-types'. The cause is pnpm 11's pnpm list --json output, which dedupes entries across workspace projects: for a given name@version, only ONE occurrence anywhere in the workspace carries the fully-expanded subtree; every other occurrence is a stub with dedupedDependenciesCount > 0 and no children.
Two distinct collector behaviors interact badly with this format:
Released 26.15.3 / 26.15.6: the
collectedDepsvisited-set (introduced in #9732, same family as #9903) prunes byname@version. If the first visited occurrence of an intermediate package (e.g.@trpc/server) is one whose own children are deduped stubs, the later occurrence that carries the real expanded subtree is skipped entirely — everything below it is never collected. In our tree this drops 7 packages:mime-types,mime-db,get-intrinsic,es-object-atoms,call-bind-apply-helpers,dunder-proto,get-proto. They are logged under "unresolved duplicate dependency references" / "cannot find path for dependency" and omitted from the asar.Current
master(7a0abca): much better — 6 of the 7 come back — but it still dropsmime-db, the runtime dependency ofmime-types. The app's ownmime-typesentry is a deduped stub, so inextractProductionDependencyGraphthe on-diskpackage.jsonis used for theallcheck, but the children iterated are still the stub's (empty)tree.dependencies— somime-dbis never linked into the production graph. It also isn't reachable any other way:mime-dbsits as a top-level entry of the app project in the pnpm list output (pnpm 11 hoisted-dedupe placement), where the graph extraction filters it out because it isn't a declared dependency of the app'spackage.json. Packaged app then fails withCannot find module 'mime-db'(required frommime-types/index.js).
Note: adding the missing package as a direct dependency of the app does not help — our mime-types was a direct dependency (pinned 2.1.35) and was still dropped, because its entry in the app's tree is a deduped stub either way.
Environment
- electron-builder / app-builder-lib: 26.15.3 and 26.15.6 (also reproduced against
master@ 7a0abca via the collector'sgetNodeModules()directly) - pnpm: 11.9.0 (worked fine before upgrading from pnpm 10.34.4 — same repo, same electron-builder)
- pnpm workspace monorepo (Next.js apps + packages + one Electron app project), default virtual-store layout (no
node-linker=hoisted) - Affects both macOS and Windows packaging: verified on both platforms (macOS 15 universal dmg/zip, and Windows NSIS) — the packaged artifacts are missing the identical set of modules.
Shape of the pnpm 11 list output that triggers it
The Electron app (electron/) depends on axios → form-data → [email protected], and directly on [email protected]. Another workspace project (apps/web) also reaches [email protected] via @trpc/server > express > accepts. pnpm 11 emits the full expansion only once, in the web project's tree:
web > @trpc/client > @trpc/server > express > accepts > [email protected] ← only full entry (has children)
web > api > @trpc/server ← visited FIRST; its express child is a deduped stub
app > [email protected] { dedupedDependenciesCount: 1 } ← stub, no children
app > [email protected] ← full, but top-level of the project ⇒ filtered (not a declared dep)On 26.15.x, @trpc/[email protected] gets marked visited at web > api > @trpc/server (children deduped), so web > @trpc/client > @trpc/server — the copy that actually contains express > accepts > mime-types — is skipped by the visited-set, and [email protected] ends up with no full entry collected at all.
Results (running getNodeModules({packageName}) against our workspace)
| collector | result |
|---|---|
| 26.15.3 / 26.15.6 release | ❌ drops mime-types, mime-db, get-intrinsic, es-object-atoms, call-bind-apply-helpers, dunder-proto, get-proto → Cannot find module 'mime-types' on launch |
master @ 7a0abca |
❌ still drops mime-db → Cannot find module 'mime-db' on launch |
| 26.15.3 + patch below | ✅ all packages collected, no "unresolved duplicate" warnings |
Workaround we're shipping (26.15.3, via pnpm patch)
In pnpmNodeModulesCollector.js collectDepsRecursively, re-walk children of already-visited nodes instead of pruning the whole subtree (the visited-set still prevents re-registering/re-locating; since each JSON node is structurally visited at most once, this stays O(total nodes)):
if (this.collectedDeps.has(id)) {
// pnpm v11 dedupes the list output: the fully-expanded subtree for a given
// name@version can appear at a later occurrence than the first one visited.
// Re-walk this occurrence's children (without re-registering the package)
// so those subtrees are not lost.
if (value.dependencies || value.optionalDependencies) {
await this.collectDepsRecursively(value);
}
return;
}For master, the remaining gap is different: when a deduped stub's dependencies are discovered from the on-disk package.json (the all map), the children that get linked into the production graph should come from that map too (or from the deduped target's real entry), not from the stub's empty tree.dependencies.
Suggested regression test
A workspace fixture with two projects where (a) project B contains the only full expansion of a shared name@version behind an intermediate package whose first-visited occurrence has deduped children, and (b) the app's own direct dep is a deduped stub whose transitive dep (mime-types → mime-db) only exists elsewhere as a project-top-level dedupe placement. Must be generated with pnpm ≥ 11 (pnpm list --prod --json --depth Infinity output differs fundamentally from pnpm 10).
Happy to test a fix branch against our workspace and report back.
Source: electron-userland/electron-builder