Skip to content

fix(isBuffer): add browser export condition to avoid 44KB Buffer polyfill#1671

Merged
raon0211 merged 6 commits intotoss:mainfrom
jantimon:fix/browser-buffer-polyfill
Apr 22, 2026
Merged

fix(isBuffer): add browser export condition to avoid 44KB Buffer polyfill#1671
raon0211 merged 6 commits intotoss:mainfrom
jantimon:fix/browser-buffer-polyfill

Conversation

@jantimon
Copy link
Copy Markdown
Contributor

  • adds a browser-safe isBuffer stub (() => false) and a rollup alias plugin that swaps it in during the browser build
  • adds browser export condition to every subpath in package.json so bundlers like webpack/vite pick up the polyfill-free build automatically
  • refactors all inline typeof Buffer / Buffer.isBuffer() checks across mergeWith, isEmpty, cloneDeepWith, and isEqualWith to use the centralized isBuffer function

closes #1670

Build size comparison (Next.js test app)

Webpack

Raw Gzipped
Before 458 KB 143 KB
After 438 KB 136 KB
Diff -19.3 KB -6.5 KB (4.5%)

Turbopack

Raw Gzipped
Before 460 KB 134 KB
After 416 KB 116 KB
Diff -44.3 KB -18.2 KB (13.6%)

Test plan

  • yarn vitest run — all existing tests pass (isBuffer tests run against the real Node.js implementation)
  • yarn lint / tsc --noEmit — clean
  • Verified with a Next.js app: .next/static drops from 492K to 464K after the change
  • grep -r "Buffer" dist/browser/ confirms no Buffer references in the browser build

Copilot AI review requested due to automatic review settings March 29, 2026 15:06
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
es-toolkit Ready Ready Preview, Comment Apr 18, 2026 10:14am

Request Review

@jantimon jantimon changed the title fix[isBuffer]: add browser export condition to avoid 28KB Buffer polyfill fix[isBuffer]: add browser export condition to avoid 44KB Buffer polyfill Mar 29, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a browser-safe build path for isBuffer to prevent bundlers (e.g. webpack/Next.js, Vite) from injecting large Buffer polyfills, and refactors internal call sites to use the centralized predicate.

Changes:

  • Introduces isBuffer.browser.ts stub and a Rollup alias to swap it into the browser ESM/UMD builds.
  • Adds browser export conditions for top-level subpath exports in publishConfig.exports.
  • Replaces inline Buffer checks in several utilities (isEqualWith, cloneDeepWith, isEmpty, mergeWith) with isBuffer().

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/predicate/isEqualWith.ts Refactors Buffer detection in typed-array comparison to use isBuffer().
src/predicate/isBuffer.browser.ts Adds browser-safe isBuffer stub used by the browser build.
src/object/cloneDeepWith.ts Switches Buffer cloning branch to use centralized isBuffer().
src/compat/predicate/isEmpty.ts Replaces inline Buffer check with isBuffer() in compat isEmpty.
src/compat/object/mergeWith.ts Uses isBuffer() when deciding whether to deep-clone Buffer source values.
rollup.config.mjs Adds browser ESM build output + alias plugin wiring for isBuffer stub.
package.json Adds browser conditional export mappings under publishConfig.exports.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/predicate/isBuffer.browser.ts Outdated
Comment thread package.json
Comment thread rollup.config.mjs
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@kms0219kms kms0219kms left a comment

Choose a reason for hiding this comment

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

LGTM

@raon0211 raon0211 changed the title fix[isBuffer]: add browser export condition to avoid 44KB Buffer polyfill fix(isBuffer): add browser export condition to avoid 44KB Buffer polyfill Apr 18, 2026
@raon0211
Copy link
Copy Markdown
Collaborator

Hello, thanks for your pull request!

Your change definitely fixes the bundle size bloat. That said, I'm a bit concerned that having multiple builds could lead to larger install sizes over time, and it would make adding additional subpath exports more complex down the line.

As an alternative, I took a different approach and used globalThis.Buffer instead of Buffer so that the Buffer polyfill isn't pulled in automatically. Could you take a look and test it on your end?

@jantimon
Copy link
Copy Markdown
Contributor Author

jantimon commented Apr 18, 2026

when fixing this bug claude also suggested the globalThis hack to trick bundlers in not seeing that Buffer is used
so it is a bet that bundlers will never improve global object detection

to be more future proof and to ensure it works with every bundler I removed the Buffer part entirely so a bundle update won't silently add this bug again

also targeted builds are always smaller than isomorphic builds

but I can totally understand that you don't want to maintain the more complex build setup

I will try the globalThis hack in webpack turbopack and tanstack start and let you know - rspack2 is hard to test right now as it is still alpha

jantimon added a commit to jantimon/es-toolkit that referenced this pull request Apr 20, 2026
…k/turbopack polyfill

Webpack and Turbopack auto-inject the ~20KB `buffer` polyfill into browser
bundles whenever they see the free identifier `Buffer`, even when every call
site is guarded by `typeof Buffer !== 'undefined'` — the guard runs too late,
the bundler has already decided.

Rewriting the 5 call sites (`isBuffer`, `isEqualWith`, `cloneDeepWith`,
compat `mergeWith`, compat `isEmpty`) to go through `globalThis.Buffer` turns
the bare identifier into a property access that neither plugin triggers on.
Node behaviour is unchanged.

Alternative to toss#1671 (no rollup alias plugin, no browser export condition,
no second build path).

Measured in a minimal Next.js 16 app importing cloneDeep client-side:

- webpack:   -21.97 KB raw / -6.68 KB gz
- turbopack: -22.12 KB raw / -6.69 KB gz
- vite:      ±0 KB (already rewrites bare Buffer to globalThis.Buffer itself)

closes toss#1670
@jantimon
Copy link
Copy Markdown
Contributor Author

jantimon commented Apr 20, 2026

hey @raon0211 tested the globalThis.Buffer route, tested it across 3 bundlers

just realised you already changed it on this branch

numbers on a minimal next 16 + tanstack start (vite 8) app that imports cloneDeep client-side:

bundler delta raw delta gz
webpack -21.97 -6.68
turbopack -22.12 -6.69
vite ±0 ±0

turbopack number is smaller than what I got here originally (-44 KB)

it already rewrites bare Buffer → globalThis.Buffer itself, so no polyfill ever got injected there.

sanity check on the next/webpack client chunk: pages/index-*.js drops from 26 KB → 3.7 KB, and _isBuffer /
INSPECT_MAX_BYTES symbols disappear entirely.

#1691 is just the globalThis change

@jantimon
Copy link
Copy Markdown
Contributor Author

jantimon commented Apr 20, 2026

I also measured your approach is ~250 bytes larger in the browser after minification - I guess that's not much just wanted to state it because es-toolkit is build for optimal size and speed

Copy link
Copy Markdown
Collaborator

@raon0211 raon0211 left a comment

Choose a reason for hiding this comment

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

Thanks for your help!

@raon0211
Copy link
Copy Markdown
Collaborator

I also measured your approach is ~250 bytes larger in the browser after minification - I guess that's not much just wanted to state it because es-toolkit is build for optimal size and speed

Yup, thanks for pointing that out. I think that's an acceptable tradeoff to keep the overall library complexity down.

@raon0211 raon0211 merged commit 7cc004b into toss:main Apr 22, 2026
7 of 10 checks passed
@jantimon
Copy link
Copy Markdown
Contributor Author

@raon0211 thanks for releasing!

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.

es-toolkit blows up next.js bundle size by 28kb to 40kb

4 participants