Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nodejs esm module output bundling lacks module prototype for externals #7446

Open
tom2strobl opened this issue Oct 30, 2024 · 5 comments · May be fixed by #7460
Open

nodejs esm module output bundling lacks module prototype for externals #7446

tom2strobl opened this issue Oct 30, 2024 · 5 comments · May be fixed by #7460
Assignees

Comments

@tom2strobl
Copy link

Bug report

What is the current behavior?

For my electron app I want to bundle the nodejs code of my main thread because of a) typescript and b) performance optimizations. That means I have "type": "module", regular node 20+ esm style code and want to bundle my dependencies into a final bundle (as I ship this bundle with my electron executable and not as part of a backend that has a node_modules dir).

To achieve a clean esm bundle (without createRequire and with support for dynamic imports) I use:

  • target: "es2022"
  • define standard node modules as externals
  • use experiments.outModule: true
  • output.chunkFormat: "module"
  • and module: true

I now want to bundle in the jimp library that has the pngjs dependency, which uses an util.inherits() call onto node-native stream in their chunkstream.js file:
https://github.com/pngjs/pngjs/blob/c565210c602527eb459f857eeb78183997482d5b/lib/chunkstream.js#L18

It looks like this in the bundle:

let Stream = __webpack_require__(/*! stream */ "stream");
util.inherits(ChunkStream, Stream);

Now the issue is that util.inherits expects stream to have a prototype and errors like so when run:

node:util:290
    throw new ERR_INVALID_ARG_TYPE('superCtor.prototype',
          ^

TypeError [ERR_INVALID_ARG_TYPE]: The "superCtor.prototype" property must be of type object. Received undefined
    at Module.inherits (node:util:290:11)
    at ./node_modules/pngjs/lib/chunkstream.js (file:///Users/myname/dev/jimp-nodenext/main.bundle.dev.js:15981:6)
    at __webpack_require__ (file:///Users/myname/dev/jimp-nodenext/main.bundle.dev.js:45536:41)
    at ./node_modules/pngjs/lib/parser-async.js (file:///Users/myname/dev/jimp-nodenext/main.bundle.dev.js:17191:19)
    at __webpack_require__ (file:///Users/myname/dev/jimp-nodenext/main.bundle.dev.js:45536:41)
    at ./node_modules/pngjs/lib/png.js (file:///Users/myname/dev/jimp-nodenext/main.bundle.dev.js:17814:14)
    at __webpack_require__ (file:///Users/myname/dev/jimp-nodenext/main.bundle.dev.js:45536:41)
    at ./node_modules/@jimp/js-png/dist/esm/index.js (file:///Users/myname/dev/jimp-nodenext/main.bundle.dev.js:31862:63)
    at __webpack_require__ (file:///Users/myname/dev/jimp-nodenext/main.bundle.dev.js:45536:41)
    at ./node_modules/jimp/dist/esm/index.js (file:///Users/myname/dev/jimp-nodenext/main.bundle.dev.js:39797:70) {
  code: 'ERR_INVALID_ARG_TYPE'
}

When I edit the bundle, manually logging Stream.prototype yields undefined, and logging Stream yields:

[Module: null prototype] {
  Duplex: [Function: Duplex] {
    fromWeb: [Function (anonymous)],
    toWeb: [Function (anonymous)],
    from: [Function (anonymous)]
  },
  PassThrough: [Function: PassThrough],
  Readable: [Function: Readable] {
    ReadableState: [Function: ReadableState],
    _fromList: [Function: fromList],
    from: [Function (anonymous)],
    fromWeb: [Function (anonymous)],
    toWeb: [Function (anonymous)],
    wrap: [Function (anonymous)]
  },
  ...

I'm assuming the prototype that util.inherit expects got lost during webpacks bundling.

If the current behavior is a bug, please provide the steps to reproduce.

Here's a minimal repo for reproduction: https://github.com/tom2strobl/webpack-jimp-nodenext

See for additional comments regarding the webpack configuration: https://github.com/tom2strobl/webpack-jimp-nodenext/blob/main/webpack.config.js

What is the expected behavior?

webpack to bundle in a way that standard node modules that are defined as external retain their original prototypes for util.inherits compatibility.

Other relevant information:
webpack version: 5.95
Node.js version: 20.17
Operating System: macOS 15.0.1
Additional tools: jimp 1.6.0 / pngjs 7.0.0

@alexander-akait
Copy link
Member

The original code is written using commonjs and without extra setup it is impossible to solve, because that's how esm work (we are unable to determine how the module is used in code and by default we use namespace import and so the prototype is lost), solutions:

  • externalsType: "node-commonjs"
  • more flexible:
 externals:  function ({ request }, callback) {
      if (/^node:/.test(request) || builtinModules.includes(request)) {
          return callback(null, 'node-commonjs ' + request);
      }
      callback();
  },

Feel free to feedback

@alexander-akait alexander-akait transferred this issue from webpack/webpack Oct 30, 2024
@alexander-akait
Copy link
Member

@snitin315 We should improve node-commonjs docs, using it we generate:

import { createRequire as __WEBPACK_EXTERNAL_createRequire } from "node:module";
// ...
/***/ 2613:
/***/ ((module) => {

module.exports = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("stream");

/***/ }),

This allows you to solve problems when you have:

// ...
function ChunkStream() {
  Stream.call(this);
}

util.inherits(ChunkStream, Stream);

@snitin315
Copy link
Member

I'll update the docs 👍

@snitin315 snitin315 self-assigned this Oct 30, 2024
@tom2strobl
Copy link
Author

Wow thank you so much for the swift and detailed response! I can confirm it working for my case. I think it's on me sleeping on externalsType, I should have played around with it a bit. Have a nice remaining week!

@alexander-akait
Copy link
Member

Let's keep open, we need update docs in such cases

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 a pull request may close this issue.

3 participants