Skip to content

Bundling with Webpack #4380

@Bessonov

Description

@Bessonov

Possible install-time or require-time problem

You must confirm both of these before continuing.

Are you using the latest version of sharp?

I first tried with the latest version (0.34.1), but saw here some current issues, so now I'm trying 0.33.5 instead.

Are you using a supported runtime?

  • I am using Node.js with a version that satisfies ^18.17.0 || ^20.3.0 || >=21.0.0: v22.14.0

Are you using a supported package manager and installing optional dependencies?

  • I am using pnpm >= 7.1.0 with --no-optional=false: 10.6.5

What is the complete error message, including the full stack trace?

What is the complete output of running npm install --verbose --foreground-scripts sharp in an empty directory?

What is the output of running npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp?

First of all, thank you for the amazing library!

I’m packaging my app using Webpack and generate a single server.mjs file along with the necessary *.node binaries. This helps significantly reduce the size of my Docker image (few MBs vs. ~700MB). This approach previously worked well with sharp, even in Lambda environments.

However, I've noticed some changes that seem to interfere with this setup.

I’ve read through the documentation on pnpm and webpack. However, when using:

externals: {
  'sharp': 'commonjs sharp'
}

...this assumes the library is discoverable at runtime, typically via node_modules. But in my setup, after bundling, that’s not the case.

From what I understand, this limitation stems from the way prebuilt binaries are resolved:

const paths = [
  `../src/build/Release/sharp-${runtimePlatform}.node`,
  '../src/build/Release/sharp-wasm32.node',
  `@img/sharp-${runtimePlatform}/sharp.node`,
  '@img/sharp-wasm32/sharp.node'
];

let sharp;
const errors = [];
for (const path of paths) {
  try {
    sharp = require(path);

This dynamic resolution makes it difficult for bundlers to include the relevant .node binaries, as these paths aren’t statically analyzable.

A possible workaround would be to hardcode the potential paths to make them discoverable during bundling, for example:

let sharp = undefined;
try { sharp = require(`../src/build/Release/sharp-linux-x64.node`,); } catch {...}
if (!sharp) try { sharp = require(`../src/build/Release/sharp-linux-arm.node`,); } catch {...}
if (!sharp) try { sharp = require(`../src/build/Release/sharp-linux-arm64.node`,); } catch {...}
if (!sharp) try { sharp = require(`@img/sharp-linux-x64/sharp.node`,); } catch {...}
[...]

Yes, it's tedious and not very elegant, but I haven’t found a better solution so far. I think it's acceptable to start with an initial list and expand it as needed.

Additionally, we could fall back to the current dynamic require logic at the end, preserving backward compatibility.

Does this approach make sense? Am I missing a better alternative?

Thanks again for the great work on sharp!

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions