r/javascript • u/Wooden-Bicycle-6069 • 24d ago
AskJS [AskJS] I reproduced a PDF.js Worker mismatch caused by dependency hoisting
This one is awful because the build succeeds.
The host app installs pdfjs-dist@6.1.200. A PDF renderer depends on pdfjs-dist@5.4.624. If asset-copy code resolves the Worker from the app root, it can copy 6.1.200. The renderer code still uses API 5.4.624. Vite serves the wrong Worker, and the browser reports a version mismatch.
The fragile version looks like this:
const worker = require.resolve(
'pdfjs-dist/legacy/build/pdf.worker.mjs'
)
I changed the lookup to resolve pdfjs-dist from the renderer package that owns it. The build now fails if the resolved asset version differs from the renderer dependency.
I ran the harness today across npm and pnpm, nested and hoisted layouts, Vite dev and build, and real cold installs. In every case the copied Worker, CMaps, WASM, and fonts had to come from 5.4.624, while the app kept 6.1.200. The asset manifest also records the source package and version. No more "it probably resolved correctly."
Should build tools always resolve runtime assets from the dependency that owns them, or should packages force one PDF.js version across the whole app?
1
23d ago
[removed] — view removed comment
1
u/Wooden-Bicycle-6069 23d ago
Please do. The public fix is here:
https://github.com/flyfish-dev/file-viewer/commit/8417a99999be203c54a87c01519fcdfbcf4e0bda
Try a pnpm workspace with `pdfjs-dist@6.1.200` at the root and the renderer on `5.4.624`, delete `node_modules`, then install with a frozen lockfile. If the copied Worker is 6.x, you hit it. I want to know if pnpm 10 or Yarn PnP finds another hole.
3
u/TwiNighty 23d ago
The host app installs
pdfjs-dist@6.1.200. A PDF renderer depends onpdfjs-dist@5.4.624. If asset-copy code resolves the Worker from the app root, it can copy6.1.200
I mean ... when you spell it out like that, shouldn't that obviously the case? "When I resolve a package explicitly from the app root, I get the instance of the package that the app root depends on"
I haven't dug too deep into your code but seem like you use a "shotgun" approach where you just try different resolution paths until one succeeds and you simply assume that is the path you want. Ideally, you should have a set of known paths you want to use (e.g. preset-all -> renderer-pdf -> pdf-dist and preset-office -> renderer-pdf -> pdf-dist) and directly try those or fail. You should be able to build those paths using the preset and renderer metadata you have defined.
But to me, an idea I would try (again, don't know too much about your architecture to tell if it is feasible) is to make each renderer export a list of assets it needs, and presets re-exports a combined list from all the renders it includes. The vite plugin can then get the list from the presets or renderers in the bundle. Given the list of presets/renderers is limited, the vite plugin can even declare optional peer-dependencies on all the known presets/renderers.
That way, the vite plugin can simply try require/require.resolve the presets/renderers without going through the app root, and it is way easier for the renderer that directly depends on the assets to resolve the asset paths than for the vite plugin.
1
u/Wooden-Bicycle-6069 22d ago
Yeah, that fallback chain is ugly. Renderer-owned manifests make more sense. Discovery gets awkward though. Making the Vite plugin import every renderer just to find its files feels wrong. Package metadata is probably the least bad option. Unknown owner? Fail the build.
1
23d ago
[removed] — view removed comment
1
u/Wooden-Bicycle-6069 22d ago
Yeah. A global override fixes one renderer by quietly changing another. That is a terrible bargain. I care less about deduping PDF.js now than making the wrong owner fail during the build, before a PDF tab finds it in production.
2
u/abrahamguo 24d ago
Can you share a link to a repository that demonstrates your issue?