From 04db603896f9764ec9c9b30c80ccf1ba3f603216 Mon Sep 17 00:00:00 2001 From: sebastien Date: Fri, 23 Oct 2026 18:15:37 +0200 Subject: [PATCH 1/2] refactor(theme-common): replace the `groupBy()` polyfill by the native `Object.groupBy()` (AI-assisted) The blog sidebar now calls `Object.groupBy()` directly. The public `groupBy()` export of `@docusaurus/theme-common` keeps its signature and delegates to `Object.groupBy()`, so it returns a null-prototype object. Co-Authored-By: Claude Opus 5.5 --- .../src/client/sidebarUtils.tsx | 3 +-- .../src/utils/__tests__/jsUtils.test.ts | 11 +++++++++++ .../src/utils/jsUtils.ts | 17 +++++------------ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/docusaurus-plugin-content-blog/src/client/sidebarUtils.tsx b/packages/docusaurus-plugin-content-blog/src/client/sidebarUtils.tsx index 88d3a41f3f2c..b9431d100d3d 100644 --- a/packages/docusaurus-plugin-content-blog/src/client/sidebarUtils.tsx +++ b/packages/docusaurus-plugin-content-blog/src/client/sidebarUtils.tsx @@ -8,7 +8,6 @@ import {type ReactNode, useMemo} from 'react'; import {useLocation} from '@docusaurus/router'; import Link from '@docusaurus/Link'; -import {groupBy} from '@docusaurus/theme-common'; import {isSamePath} from '@docusaurus/theme-common/internal'; import type {BlogSidebarItem} from '@docusaurus/plugin-content-blog'; @@ -36,7 +35,7 @@ export function useVisibleBlogSidebarItems( export function groupBlogSidebarItemsByYear( items: BlogSidebarItem[], ): [string, BlogSidebarItem[]][] { - const groupedByYear = groupBy(items, (item) => { + const groupedByYear = Object.groupBy(items, (item) => { return `${new Date(item.date).getFullYear()}`; }); // "as" is safe here diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/jsUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/jsUtils.test.ts index 7f14acc1a878..73f0f9184c58 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/jsUtils.test.ts +++ b/packages/docusaurus-theme-common/src/utils/__tests__/jsUtils.test.ts @@ -103,4 +103,15 @@ describe('groupBy', () => { Robert: [user2], }); }); + + it('group by keys named like Object.prototype properties', () => { + const keys = ['constructor', 'toString', 'constructor']; + const groups = groupBy(keys, (key) => key); + + expect(Object.getPrototypeOf(groups)).toBeNull(); + expect(groups).toEqual({ + constructor: ['constructor', 'constructor'], + toString: ['toString'], + }); + }); }); diff --git a/packages/docusaurus-theme-common/src/utils/jsUtils.ts b/packages/docusaurus-theme-common/src/utils/jsUtils.ts index f1f0ac92010d..e1fe1038133f 100644 --- a/packages/docusaurus-theme-common/src/utils/jsUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/jsUtils.ts @@ -35,20 +35,13 @@ export function uniq(arr: T[]): T[] { return Array.from(new Set(arr)); } -// TODO 2025: replace by std Object.groupBy ? -// This is a local polyfill with exact same TS signature -// see https://github.com/microsoft/TypeScript/blob/main/src/lib/esnext.object.d.ts +/** + * Groups items by key, using the native `Object.groupBy()`. + * @returns A null-prototype object mapping each key to its items. + */ export function groupBy( items: Iterable, keySelector: (item: T, index: number) => K, ): Partial> { - const result: Partial> = {}; - let index = 0; - for (const item of items) { - const key = keySelector(item, index); - result[key] ??= []; - result[key]!.push(item); - index += 1; - } - return result; + return Object.groupBy(items, keySelector); } From 2306101b6d4943486ba83d20c791af2f0dc15b3e Mon Sep 17 00:00:00 2001 From: sebastien Date: Fri, 23 Oct 2026 18:33:30 +0200 Subject: [PATCH 2/2] break(theme-common): remove the `groupBy()` export, use the native `Object.groupBy()` instead (AI-assisted) Nothing uses the polyfill anymore: the blog sidebar calls `Object.groupBy()`, which has the same signature. Co-Authored-By: Claude Opus 5.5 --- packages/docusaurus-theme-common/src/index.ts | 2 +- .../src/utils/__tests__/jsUtils.test.ts | 65 +------------------ .../src/utils/jsUtils.ts | 11 ---- 3 files changed, 2 insertions(+), 76 deletions(-) diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts index 5a49c06daaad..23229a1b645b 100644 --- a/packages/docusaurus-theme-common/src/index.ts +++ b/packages/docusaurus-theme-common/src/index.ts @@ -77,7 +77,7 @@ export {isMultiColumnFooterLinks} from './utils/footerUtils'; export {isRegexpStringMatch} from './utils/regexpUtils'; -export {duplicates, uniq, groupBy} from './utils/jsUtils'; +export {duplicates, uniq} from './utils/jsUtils'; export {usePrismTheme} from './hooks/usePrismTheme'; diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/jsUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/jsUtils.test.ts index 73f0f9184c58..f494e33d0ece 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/jsUtils.test.ts +++ b/packages/docusaurus-theme-common/src/utils/__tests__/jsUtils.test.ts @@ -6,7 +6,7 @@ */ import {describe, expect, it} from 'vitest'; -import {uniq, duplicates, groupBy} from '../jsUtils'; +import {uniq, duplicates} from '../jsUtils'; describe('duplicates', () => { it('gets duplicate values', () => { @@ -52,66 +52,3 @@ describe('uniq', () => { ).toEqual([obj1, obj2, array1, array3, array2, obj3]); }); }); - -describe('groupBy', () => { - type User = {name: string; age: number; type: 'a' | 'b' | 'c'}; - - const user1: User = {name: 'Seb', age: 42, type: 'c'}; - const user2: User = {name: 'Robert', age: 42, type: 'b'}; - const user3: User = {name: 'Seb', age: 32, type: 'c'}; - - const users = [user1, user2, user3]; - - it('group by name', () => { - const groups = groupBy(users, (u) => u.name); - - expect(Object.keys(groups)).toEqual(['Seb', 'Robert']); - expect(groups).toEqual({ - Seb: [user1, user3], - Robert: [user2], - }); - }); - - it('group by age', () => { - const groups = groupBy(users, (u) => u.age); - - // Surprising keys order due to JS behavior - // see https://x.com/sebastienlorber/status/1806371668614369486 - expect(Object.keys(groups)).toEqual(['32', '42']); - expect(groups).toEqual({ - '32': [user3], - '42': [user1, user2], - }); - }); - - it('group by type', () => { - const groups = groupBy(users, (u) => u.type); - - expect(Object.keys(groups)).toEqual(['c', 'b']); - expect(groups).toEqual({ - c: [user1, user3], - b: [user2], - }); - }); - - it('group by name even duplicates', () => { - const groups = groupBy([user1, user2, user3, user1, user3], (u) => u.name); - - expect(Object.keys(groups)).toEqual(['Seb', 'Robert']); - expect(groups).toEqual({ - Seb: [user1, user3, user1, user3], - Robert: [user2], - }); - }); - - it('group by keys named like Object.prototype properties', () => { - const keys = ['constructor', 'toString', 'constructor']; - const groups = groupBy(keys, (key) => key); - - expect(Object.getPrototypeOf(groups)).toBeNull(); - expect(groups).toEqual({ - constructor: ['constructor', 'constructor'], - toString: ['toString'], - }); - }); -}); diff --git a/packages/docusaurus-theme-common/src/utils/jsUtils.ts b/packages/docusaurus-theme-common/src/utils/jsUtils.ts index e1fe1038133f..740a5a8b61df 100644 --- a/packages/docusaurus-theme-common/src/utils/jsUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/jsUtils.ts @@ -34,14 +34,3 @@ export function uniq(arr: T[]): T[] { // Note: had problems with [...new Set()]: https://github.com/facebook/docusaurus/issues/4972#issuecomment-863895061 return Array.from(new Set(arr)); } - -/** - * Groups items by key, using the native `Object.groupBy()`. - * @returns A null-prototype object mapping each key to its items. - */ -export function groupBy( - items: Iterable, - keySelector: (item: T, index: number) => K, -): Partial> { - return Object.groupBy(items, keySelector); -}