Skip to content

fix[groupBy, maxBy, minBy]: call the iteratee with only the element like lodash - #2122

Merged
raon0211 merged 1 commit into
toss:mainfrom
Patrick-SCH03:fix/compat-groupby-maxby-minby-iteratee-args
Sep 30, 2026
Merged

raon0211 merged 1 commit into
toss:mainfrom
Patrick-SCH03:fix/compat-groupby-maxby-minby-iteratee-args

Conversation

@Patrick-SCH03

Copy link
Copy Markdown
Contributor

Summary

Fixes es-toolkit/compat's groupBy, maxBy, and minBy to call iteratees with only the current element, matching Lodash. Passing (element, index, array) instead can produce different results for callbacks such as parseInt, even though the equivalent Lodash calls compile in strict mode with @types/lodash. For groupBy, this mismatch was introduced when #1561 added index and array arguments to the main implementation used by compat.

import { groupBy, maxBy, minBy } from 'es-toolkit/compat';

groupBy(['10', '11', '12'], parseInt);
// lodash:            { '10': ['10'], '11': ['11'], '12': ['12'] }
// es-toolkit/compat:  { '1': ['12'], '10': ['10'], 'NaN': ['11'] }

maxBy(['10', '9', '11'], parseInt);
// lodash: '11'    es-toolkit/compat: '10'

minBy(['10', '9', '11'], parseInt);
// lodash: '9'     es-toolkit/compat: '11'

Changes

  • Call getValue(element) instead of getValue(element, i, array) in compat maxBy and minBy.
  • Use a local loop in compat groupBy to pass only the element to the iteratee, rather than delegating to the main implementation or adding a wrapper callback.
  • Leave the main es-toolkit implementations unchanged, including their index and array arguments.
  • Add regression tests for single-argument iteratee calls and parseInt behavior in all three functions. These tests fail on main and pass on this branch.

Validation

All 3,301 compat and array tests pass. Type checking, Prettier, and ESLint checks pass; the two ESLint warnings also exist on main.

Benchmarks

Measured locally on Windows with Node.js 22, comparing main (d6f79606) against this branch (0666b800). The benchmarks workspace failed with ERR_UNSUPPORTED_ESM_URL_SCHEME on Windows, so equivalent benchmarks were run with vitest bench, importing directly from src/compat.

Results are in ops/sec; higher is better. Each groupBy cell contains results from two separate runs.

Function Case main This branch
groupBy 5 items, using the data from benchmarks/performance/groupBy.bench.ts 4,973,898 / 4,749,554 4,725,273 / 5,008,803
groupBy 10,000 items 5,528 / 4,787 5,749 / 6,027
maxBy 10,000 items 127,462 127,408
minBy 10,000 items 128,306 126,796

The final implementation showed no consistent slowdown across these local runs. An initial wrapper-based groupBy implementation reached 3,882,366–4,494,805 ops/sec in the 5-item case, so it was replaced with the local loop.

…ike lodash

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
es-toolkit Ready Ready Preview Sep 27, 2026 6:09am UTC

Request Review

@raon0211
raon0211 merged commit d830350 into toss:main Sep 30, 2026
12 checks passed
raon0211 pushed a commit that referenced this pull request Oct 9, 2026
compat's groupBy has its own implementation since #2122, so the main-library
groupBy is no longer in compat's dependency graph. Revert it to main and drop
Object.hasOwn from compat's groupBy instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
raon0211 added a commit that referenced this pull request Oct 23, 2026
* feat(compat): support Node.js 6+ in es-toolkit/compat

es-toolkit/compat exists to migrate lodash code, which often still runs in old
environments. Make it work on Node.js 6 as published, with no transpilation and
no polyfills, and keep it that way.

- Restrict src/compat/** to ES2015 syntax and built-ins: rewrite optional
  chaining, nullish coalescing, object spread, Object.hasOwn, Object.values,
  Array#flat/flatMap/at/findLast(Index), String#matchAll/trimStart/trimEnd.
- words: fall back to lodash's ES5 word patterns when the engine cannot compile
  the Unicode-property regex, so camelCase and friends work on Node.js 6.
- truncate: drop the named capture group (Node.js 10+ only).
- isNative: use the internal globalThis shim instead of the bare global.
- cloneDeepWith: give compat its own implementation instead of delegating to
  the main library's, which relies on structuredClone.
- Fix the few main-library files compat imports (groupBy, pull, remove,
  isEqualWith, clone, deburr, trimStart, trimEnd, debounce) and stop invokeMap
  from importing the whole predicate barrel.
- ESLint: es-x + eslint-plugin-compat with a Node.js 6 target on src/compat.
- CI: new compat-node6 job runs the built package on a real Node.js 6
  (entry points + every compat @example case) on every change.
- Docs: Node.js support section in browser-support (en/ko/ja/zh_hans).

* test: update bundle size snapshots

* fix(trimStart, trimEnd): drop the whitespace regex flagged by CodeQL

`str.replace(/\s+$/, '')` is quadratic on inputs with a long run of
whitespace followed by a non-whitespace character. Scan from the edges
with a per-character `\s` test instead, which is linear and still
ES2015-compatible. The compat variants delegate to the main
implementations instead of carrying their own regex.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(compat/groupBy): avoid Object.hasOwn for Node.js 6

compat's groupBy has its own implementation since #2122, so the main-library
groupBy is no longer in compat's dependency graph. Revert it to main and drop
Object.hasOwn from compat's groupBy instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: update bundle size snapshots

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* build: upgrade tsdown and lower published code to ES2019 syntax

Lowering `?.` and `??` at build time lets the source keep using them
while `es-toolkit/compat` still loads on ES2015 engines such as Node.js 6.
Oxc cannot lower individual syntax features yet
(oxc-project/oxc#15373), so the whole output
targets ES2019.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(compat): keep the main library untouched for Node.js 6 support

- Revert the main-library changes; compat now has ES2015 copies of the
  pieces it needs (`basePull`, `baseIsEqualWith`, `baseClone`, and local
  `remove`/`trimStart`/`trimEnd` implementations), plus its own `isEqual`.
- Allow `?.` and `??` in compat again, since the build lowers them.
- Add `hasOwn` and `objectValues` helpers instead of repeating
  `hasOwnProperty.call` and `Object.keys(...).map(...)`.
- Merge `legacyWords` into `compat/_internal/words.ts`.
- Use array spread in `cloneDeepWith`'s `copyProperties`; `concat` made
  deep clones of objects 8-21% slower than before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: move compat environment support to its own page

Add a "Browser & Node.js Support" page under /compat in all four
languages, and link to it from the main Browser Support page instead of
merging the content there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(browser-compat): add a manual case for Queue

`Queue` (#2150) has no runnable `@example`, so the Node check in the
browser-compat workflow failed with "1 exports have no test case", which
then left the WebKit lane waiting on a fixture that was never built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(compat/words): always split words with lodash's patterns

The Unicode-property pattern only approximated lodash and still disagreed
on inputs such as `ПриветМир`, `foo©bar`, `foo™bar`, `DžDž`, and
mathematical letters, while lodash's own patterns match lodash exactly
and already run on every engine, including Node.js 6. Using them
everywhere removes the second implementation, makes non-ASCII input about
1.5x faster, and shrinks compat `camelCase` from 4,410 to 3,448 bytes.

* docs: name es-toolkit/compat in the environment support link

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: T3 Code Service User <t3code@raonserver-t3code.Maxio>

This branch was successfully deployed

1 active deployment
Preview — 0666b800 Deployed Sep 27, 2026 by vercel[bot]
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.

2 participants