Skip to content

fix: emit libvips shared library for sharp@0.35 - #595

Open
styfle wants to merge 1 commit into
mainfrom
styfle/fix-sharp-libvips-emit
Open

fix: emit libvips shared library for sharp@0.35#595
styfle wants to merge 1 commit into
mainfrom
styfle/fix-sharp-libvips-emit

Conversation

@styfle

@styfle styfle commented Jun 16, 2026

Copy link
Copy Markdown
Member

Problem

sharp@0.35 reorganized its prebuilt binaries. The platform package (e.g. @img/sharp-darwin-arm64) loads its native .node addon, which in turn dlopens the libvips shared library (libvips-cpp.*.dylib/.so/.dll) from a sibling package, @img/sharp-libvips-<platform>. Because that load happens inside the compiled binary rather than via a JS require, static analysis never saw it, so the shared library was missing from the trace and the bundled app failed at runtime with Cannot find module / dlopen errors.

The previous sharp special case keyed on sharp/lib/index.js, which sharp@0.35 moved to dist/, so it silently stopped firing.

Fix

Two complementary mechanisms, covering old and new sharp:

  • sharp ≥ 0.35sharedLibEmit already runs whenever a .node file is emitted and globs the binary's own package for shared libraries. It now also reads that package's package.json and emits the shared libraries from each optionalDependencies package (resolved relative to the node_modules root, with realpath so pnpm symlink layouts work). This is version-independent: it follows the declared dependency (@img/sharp-libvips-<platform>) rather than hardcoding paths.
  • sharp < 0.35 — the older loader is too dynamic to trace statically (require(path) over a list of candidate specifiers), so the existing sharp special case in special-cases.ts is kept for that case, with a clarifying comment.

Validation

  • node out/cli.js print test/integration/sharp.js now lists @img/sharp-libvips-<platform>/lib/libvips-cpp.*.
  • pnpm test test/integration.test.js passes all 89 tests, including:
    • sharp.js (sharp 0.35.1 → manifest path)
    • sharp-pnpm.js (sharp 0.33.2 → special-case path)

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@styfle
styfle requested review from a team, icyJoseph and ijjk as code owners June 16, 2026 03:22
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​sharp@​0.35.19710010095100

View full report

@siddharthborderwala

Copy link
Copy Markdown

Just a note that this is biting in production, in case it helps with prioritising.

We hit it on Next.js 16.2.12 (Turbopack, Vercel, pnpm, linux-x64) upgrading sharp 0.34.5 → 0.35.3 to clear GHSA-f88m-g3jw-g9cj:

ERR_DLOPEN_FAILED: libvips-cpp.so.8.18.3: cannot open shared object file

Every route whose import graph reached sharp returned a 500 at module init. The same failure is being reported in lovell/sharp#4567 and vercel/next.js#96064.

Two things that might be worth folding in:

  • Next vendors its own copy of nft at next/dist/compiled/@vercel/nft. On 16.2.12 that copy still contains the sharp/lib/index.js gate, so merging here won't reach Next users until Next re-vendors it. Worth knowing so it doesn't get reported as still broken afterwards.
  • The 0.35.2 breadcrumb isn't sufficient on the Next path. Calling nodeFileTrace directly on that vendored copy does emit the libvips .so — the require.resolve('@img/sharp-libvips-<platform>/binary') breadcrumb gets picked up fine. But the same project built through Turbopack produces a function without it. So there may be a second gap on the Next side beyond nft, which seems relevant to whether this PR alone closes things out.

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.

3 participants