Skip to content

Refactored to ESM ("EcmaScript Modules")#356

Open
catamphetamine wants to merge 8 commits into
ZJONSSON:masterfrom
catamphetamine:master
Open

Refactored to ESM ("EcmaScript Modules")#356
catamphetamine wants to merge 8 commits into
ZJONSSON:masterfrom
catamphetamine:master

Conversation

@catamphetamine

@catamphetamine catamphetamine commented Jun 10, 2026

Copy link
Copy Markdown

Changes:

  • Added TypeScript definitions by copy-pasting them from DefinitelyTyped.
    • This allows using this package in TypeScript applications. But it also moves the responsibility to keep the file updated to the maintainers.
  • Replaced dynamic import("@aws-sdk/client-s3") with optional predefined global variables global.GetObjectCommand and global.HeadObjectCommand.
    • This fixes a bug when bundlers can't bundle an app that uses unzipper package because of that dynamic import() statement when @aws-sdk/client-s3 package is not already installed by someone else in the application directory.
  • Replaced package duplexer2 with a copy of duplexer3.
    • Reasons? Not really, just had a bit of fun. The readme of duplexer3 just says: "Modern version of duplexer2".
    • duplexer3 had to be copy-pasted (with the tests) because it didn't provide CommonJS exports. Called it DuplexStream.js.
  • Replaced package bluebird with a combination of native Promises and a copy of p-map.
    • Reasons? Not really, just had a bit of fun. Bluebird source code seems to be no longer available, and it's unlikely that it supports "tree shaking". Hence, this change theoretically reduces the size of unzipper itself.
    • p-map had to be copy-pasted (with the tests) because it didn't provide CommonJS exports. Called it mapPromises.js.
  • Removed package fs-extra.
    • Reasons? Not really, just had a bit of fun. This reduced the number of dependencies by 1. Called it ensureDir.js.

While this pull request is in review, the changes have been published as a separate package: https://www.npmjs.com/package/unzipper-esm


P.S.

There's a strange issue with the tests: the results seem visually same, yet the testing framework complains about some kind of a mismatch.

The same issue can be observed when cloning the original unmodified repo and running the tests. Therefore, I assume that tests are actually passing and it's just an operating system compatibility issue (I'm on Windows). Perhaps it's related to line endings being different between Windows and Linux.

image

@catamphetamine catamphetamine changed the title Refactored to ESM Refactored to ESM ("EcmaScript Modules") Jun 10, 2026

@ZJONSSON ZJONSSON left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome — really appreciate the work here.

Could you split this into a few smaller PRs? Right now it blends several different changes together, which makes it difficult to review each one properly.

Ideally, each PR should cover one logical change. For example, ESM changes only could be one PR, Bluebird removal another, and any unrelated cleanup or behavior changes should be separate as well.

@catamphetamine

Copy link
Copy Markdown
Author

Hi,

Indeed, I agree. If someone submitted a PR in my repo, I'd probably have answered the same.

The thing is, I got a bit carried away while doing the refactoring and decided to also update all dependencies due to how massive the ESM change alone would be.

Frankly, all of the dependency replacements do make sense from my point of view. The general rule is: the smaller the count of dependencies, the better (in public opinion). And also: the fresher the dependency packages, the better. Both principles were applied when doing the refactoring.

Could I split it in several atomic PRs? I could but if all those are gonna eventually be merged anyway then there would be no point really and it would feel pointless doing that. So I think that I'll pass. I'd just prefer to be done with it and not open it again, haha.

If you're interested, I already shipped this PR in 'read-excel-file' package. For no reason, really. Just had some fun refactoring on a chill day.

@catamphetamine

Copy link
Copy Markdown
Author

I can see you've added CI tests 👌

I'll see why it didn't pass when I get home later.

@ZJONSSON

Copy link
Copy Markdown
Owner

Well I really appreciate your efforts, so I will take a look and see if I can split them up.

Frankly, all of the dependency replacements do make sense from my point of view. The general rule is: the smaller the count of dependencies, the better (in public opinion). And also: the fresher the dependency packages, the better. Both principles were applied when doing the refactoring.

Could not agree more

I can see you've added CI tests

The CI tests have been there for years - but I had to permission them for each build. You should be able to npm run test locally and see if everything works.

@catamphetamine

catamphetamine commented Jun 21, 2026 via email

Copy link
Copy Markdown
Author

@catamphetamine

Copy link
Copy Markdown
Author

Here's a shortened version that's just about ESM: #360

@catamphetamine catamphetamine requested a review from ZJONSSON July 5, 2026 22:15
@catamphetamine

Copy link
Copy Markdown
Author

@ZJONSSON Hi. I can see you've merged the ESM refactor.

This PR has been updated to reflect the other changes.

These "other" changes are completely optional and see for yourself if you like some of them or not.

Comment thread lib/DuplexStream.js
@@ -0,0 +1,89 @@
// Copy-pasted from `duplexer3` source code on Jun 19th, 2026:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just use Duplex.from

https://e18e.dev/docs/replacements/duplexer

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacing duplexer with Duplex.from results in errors in tests:

Error: test end() method called more than once

The reason is that there's code like this:

  const extractor = src.pipe(Extract({ path: dest }));
  extractor.on('close', function() {
    t.ok(fs.existsSync(path.join(dest, 'subdir', 'file.txt')), 'normal entry must be extracted');
    t.end();
  });

The close event can be emitted multiple times when using Duplex.from():

  • When the readable stream closes
  • When the writable stream closes
  • When the duplex stream closes

Workarounds:

  • Use import { finished } from 'stream' instead of stream.on('close', ...)
    • That would have to be replaced throughout the code (+ tests) and there's a hypothetical probability of stuff malfunctioning after the change, so I won't go this way.
  • Use .once() instead of .on()
    • This one seems even hackier, so I'll pass too.

In summary, there seems to be no simple find-and-replace solution so DuplexStream.js is not removed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"node.js duplex.from readable writable close event emitted multiple times"

image

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there is a way to solve that

Comment thread lib/ensureDir.js Outdated
@catamphetamine catamphetamine requested a review from gameroman July 6, 2026 19:25
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