From 50c9bd9b038fccb7b5ac0713fbea8ace53cc25e9 Mon Sep 17 00:00:00 2001 From: sebastien Date: Mon, 2 Nov 2026 10:45:42 +0100 Subject: [PATCH] refactor: replace lodash helpers that have a native equivalent (AI-assisted) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `_.last()` → `Array.prototype.at(-1)` - `_.uniq()` → `new Set()` - `_.groupBy()` → `Map.groupBy()` where we iterate the groups - `_.difference()` / `_.intersection()` → `Array.prototype.filter()` Co-Authored-By: Claude Opus 5.5 --- .../src/collectRedirects.ts | 4 +-- .../src/sidebars/generator.ts | 2 +- .../src/sidebars/utils.ts | 7 ++-- .../src/translations.ts | 6 ++-- .../src/versions/validation.ts | 11 +++--- packages/docusaurus-utils/src/tags.ts | 2 +- .../src/commands/swizzle/actions.ts | 2 +- .../src/commands/swizzle/components.ts | 6 ++-- .../docusaurus/src/commands/swizzle/themes.ts | 16 +++++---- .../src/server/codegen/codegenRoutes.ts | 6 ++-- .../src/server/plugins/pluginIds.ts | 35 +++++++++---------- .../src/server/translations/translations.ts | 10 +++--- 12 files changed, 52 insertions(+), 55 deletions(-) diff --git a/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts b/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts index 225ab35c118f..65d76173de99 100644 --- a/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts +++ b/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts @@ -169,8 +169,8 @@ function filterUnwantedRedirects( ): RedirectItem[] { // We don't want to create the same redirect twice, since that would lead to // writing the same html redirection file twice. - Object.entries(_.groupBy(redirects, (redirect) => redirect.from)).forEach( - ([from, groupedFromRedirects]) => { + Map.groupBy(redirects, (redirect) => redirect.from).forEach( + (groupedFromRedirects, from) => { if (groupedFromRedirects.length > 1) { logger.report( pluginContext.siteConfig.onDuplicateRoutes, diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts index 3ab888317df4..d478cdc5999e 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts @@ -23,7 +23,7 @@ const BreadcrumbSeparator = '/'; // Just an alias to the make code more explicit function getLocalDocId(docId: string): string { - return _.last(docId.split('/'))!; + return docId.split('/').at(-1)!; } export const CategoryMetadataFilenamePattern = '_category_.{json,yml,yaml}'; diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts index 3ed0e06099f8..d79be363ff1a 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts @@ -308,7 +308,10 @@ export function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils { sidebarFilePath: string; }) { const allSidebarDocIds = Object.values(sidebarNameToDocIds).flat(); - const invalidDocIds = _.difference(allSidebarDocIds, allDocIds); + const existingDocIds = new Set(allDocIds); + const invalidDocIds = allSidebarDocIds.filter( + (docId) => !existingDocIds.has(docId), + ); if (invalidDocIds.length > 0) { throw new Error( @@ -319,7 +322,7 @@ These sidebar document ids do not exist: - ${invalidDocIds.sort().join('\n- ')} Available document ids are: -- ${_.uniq(allDocIds).sort().join('\n- ')} +- ${[...existingDocIds].sort().join('\n- ')} `, ); } diff --git a/packages/docusaurus-plugin-content-docs/src/translations.ts b/packages/docusaurus-plugin-content-docs/src/translations.ts index 4fa0801c52b9..5250f719fe8f 100644 --- a/packages/docusaurus-plugin-content-docs/src/translations.ts +++ b/packages/docusaurus-plugin-content-docs/src/translations.ts @@ -46,10 +46,8 @@ type TranslationMessageEntry = [string, TranslationMessage]; function ensureNoSidebarDuplicateEntries( translationEntries: TranslationMessageEntry[], ): void { - const grouped = _.groupBy(translationEntries, (entry) => entry[0]); - const duplicates = Object.entries(grouped).filter( - (entry) => entry[1].length > 1, - ); + const grouped = Map.groupBy(translationEntries, (entry) => entry[0]); + const duplicates = [...grouped].filter((entry) => entry[1].length > 1); if (duplicates.length > 0) { throw new Error(`Multiple docs sidebar items produce the same translation key. diff --git a/packages/docusaurus-plugin-content-docs/src/versions/validation.ts b/packages/docusaurus-plugin-content-docs/src/versions/validation.ts index c2f1505275a0..e668d13e8d8a 100644 --- a/packages/docusaurus-plugin-content-docs/src/versions/validation.ts +++ b/packages/docusaurus-plugin-content-docs/src/versions/validation.ts @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import _ from 'lodash'; import type {VersionsOptions} from '@docusaurus/plugin-content-docs'; export function validateVersionName(name: unknown): asserts name is string { @@ -74,9 +73,8 @@ export function validateVersionsOptions( `Docs option lastVersion: ${options.lastVersion} is invalid. ${availableVersionNamesMsg}`, ); } - const unknownVersionConfigNames = _.difference( - Object.keys(options.versions), - availableVersionNames, + const unknownVersionConfigNames = Object.keys(options.versions).filter( + (versionName) => !availableVersionNames.includes(versionName), ); if (unknownVersionConfigNames.length > 0) { throw new Error( @@ -92,9 +90,8 @@ export function validateVersionsOptions( `Invalid docs option "onlyIncludeVersions": an empty array is not allowed, at least one version is needed.`, ); } - const unknownOnlyIncludeVersionNames = _.difference( - options.onlyIncludeVersions, - availableVersionNames, + const unknownOnlyIncludeVersionNames = options.onlyIncludeVersions.filter( + (versionName) => !availableVersionNames.includes(versionName), ); if (unknownOnlyIncludeVersionNames.length > 0) { throw new Error( diff --git a/packages/docusaurus-utils/src/tags.ts b/packages/docusaurus-utils/src/tags.ts index c221f8a26b87..24a88e025cc7 100644 --- a/packages/docusaurus-utils/src/tags.ts +++ b/packages/docusaurus-utils/src/tags.ts @@ -191,7 +191,7 @@ export function groupTaggedItems( // If user add twice the same tag to a md doc (weird but possible), // we don't want the item to appear twice in the list... Object.values(result).forEach((group) => { - group.items = _.uniq(group.items); + group.items = [...new Set(group.items)]; }); return result; diff --git a/packages/docusaurus/src/commands/swizzle/actions.ts b/packages/docusaurus/src/commands/swizzle/actions.ts index 28cce032afc6..8f420ca11156 100644 --- a/packages/docusaurus/src/commands/swizzle/actions.ts +++ b/packages/docusaurus/src/commands/swizzle/actions.ts @@ -121,7 +121,7 @@ export async function wrap({ const isDirectory = await isDir(path.join(themePath, themeComponentName)); // Top/Parent/ComponentName => ComponentName - const componentName = _.last(themeComponentName.split('/'))!; + const componentName = themeComponentName.split('/').at(-1)!; const wrapperComponentName = `${componentName}Wrapper`; const wrapperFileName = `${themeComponentName}${isDirectory ? '/index' : ''}${ diff --git a/packages/docusaurus/src/commands/swizzle/components.ts b/packages/docusaurus/src/commands/swizzle/components.ts index 30ed943f1035..4fffafeb4686 100644 --- a/packages/docusaurus/src/commands/swizzle/components.ts +++ b/packages/docusaurus/src/commands/swizzle/components.ts @@ -58,13 +58,15 @@ export function getMissingIntermediateComponentFolderNames( return _.range(1, paths.length + 1).map((i) => paths.slice(0, i).join('/')); } - const expandedComponentNames = _.uniq( + const expandedComponentNames = new Set( componentNames.flatMap((componentName) => getAllIntermediatePaths(componentName), ), ); - return _.difference(expandedComponentNames, componentNames); + return [...expandedComponentNames].filter( + (componentName) => !componentNames.includes(componentName), + ); } const skipReadDirNames = ['__test__', '__tests__', '__mocks__', '__fixtures__']; diff --git a/packages/docusaurus/src/commands/swizzle/themes.ts b/packages/docusaurus/src/commands/swizzle/themes.ts index 369f2211dcb7..c8cef045599e 100644 --- a/packages/docusaurus/src/commands/swizzle/themes.ts +++ b/packages/docusaurus/src/commands/swizzle/themes.ts @@ -33,13 +33,15 @@ export function getPluginByThemeName( } export function getThemeNames(plugins: SwizzlePlugin[]): string[] { - const themeNames = _.uniq( - // The fact that getThemePath is attached to the plugin instance makes - // this code impossible to optimize. If this is a static method, we don't - // need to initialize all plugins just to filter which are themes - // Benchmark: loadContext-58ms; initPlugins-323ms - plugins.map((plugin) => pluginToThemeName(plugin)).filter(Boolean), - ) as string[]; + const themeNames = [ + ...new Set( + // The fact that getThemePath is attached to the plugin instance makes + // this code impossible to optimize. If this is a static method, we don't + // need to initialize all plugins just to filter which are themes + // Benchmark: loadContext-58ms; initPlugins-323ms + plugins.map((plugin) => pluginToThemeName(plugin)).filter(Boolean), + ), + ] as string[]; // Opinionated ordering: user is most likely to swizzle: // - the classic theme diff --git a/packages/docusaurus/src/server/codegen/codegenRoutes.ts b/packages/docusaurus/src/server/codegen/codegenRoutes.ts index 9f6a9dc1c0d2..8ab3ba9374ca 100644 --- a/packages/docusaurus/src/server/codegen/codegenRoutes.ts +++ b/packages/docusaurus/src/server/codegen/codegenRoutes.ts @@ -405,9 +405,9 @@ function ensureNoPropsConflict(route: RouteConfig) { if (!route.props && !route.modules) { return; } - const conflictingPropNames = _.intersection( - Object.keys(route.props ?? {}), - Object.keys(route.modules ?? {}), + const moduleNames = Object.keys(route.modules ?? {}); + const conflictingPropNames = Object.keys(route.props ?? {}).filter( + (propName) => moduleNames.includes(propName), ); if (conflictingPropNames.length > 0) { throw new Error( diff --git a/packages/docusaurus/src/server/plugins/pluginIds.ts b/packages/docusaurus/src/server/plugins/pluginIds.ts index 86e3eb5d9f97..82f97b38fc29 100644 --- a/packages/docusaurus/src/server/plugins/pluginIds.ts +++ b/packages/docusaurus/src/server/plugins/pluginIds.ts @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import _ from 'lodash'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; import type {InitializedPlugin} from '@docusaurus/types'; @@ -16,26 +15,24 @@ import type {InitializedPlugin} from '@docusaurus/types'; export function ensureUniquePluginInstanceIds( plugins: InitializedPlugin[], ): void { - const pluginsByName = _.groupBy(plugins, (p) => p.name); - Object.entries(pluginsByName).forEach(([pluginName, pluginInstances]) => { - const pluginInstancesById = _.groupBy( + const pluginsByName = Map.groupBy(plugins, (p) => p.name); + pluginsByName.forEach((pluginInstances, pluginName) => { + const pluginInstancesById = Map.groupBy( pluginInstances, (p) => p.options.id ?? DEFAULT_PLUGIN_ID, ); - Object.entries(pluginInstancesById).forEach( - ([pluginId, pluginInstancesWithId]) => { - if (pluginInstancesWithId.length !== 1) { - throw new Error( - `Plugin "${pluginName}" is used ${ - pluginInstancesWithId.length - } times with ID "${pluginId}".\nTo use the same plugin multiple times on a Docusaurus site, you need to assign a unique ID to each plugin instance.${ - pluginId === DEFAULT_PLUGIN_ID - ? `\n\nThe plugin ID is "${DEFAULT_PLUGIN_ID}" by default. It's possible that the preset you are using already includes a plugin instance, in which case you either want to disable the plugin in the preset (to use a single instance), or assign another ID to your extra plugin instance (to use multiple instances).` - : '' - }`, - ); - } - }, - ); + pluginInstancesById.forEach((pluginInstancesWithId, pluginId) => { + if (pluginInstancesWithId.length !== 1) { + throw new Error( + `Plugin "${pluginName}" is used ${ + pluginInstancesWithId.length + } times with ID "${pluginId}".\nTo use the same plugin multiple times on a Docusaurus site, you need to assign a unique ID to each plugin instance.${ + pluginId === DEFAULT_PLUGIN_ID + ? `\n\nThe plugin ID is "${DEFAULT_PLUGIN_ID}" by default. It's possible that the preset you are using already includes a plugin instance, in which case you either want to disable the plugin in the preset (to use a single instance), or assign another ID to your extra plugin instance (to use multiple instances).` + : '' + }`, + ); + } + }); }); } diff --git a/packages/docusaurus/src/server/translations/translations.ts b/packages/docusaurus/src/server/translations/translations.ts index cfee61b004b0..91b350b701ef 100644 --- a/packages/docusaurus/src/server/translations/translations.ts +++ b/packages/docusaurus/src/server/translations/translations.ts @@ -113,9 +113,8 @@ async function writeTranslationFileContent({ const existingContent = await readTranslationFileContent(filePath); // Warn about potential legacy keys - const unknownKeys = _.difference( - Object.keys(existingContent ?? {}), - Object.keys(newContent), + const unknownKeys = Object.keys(existingContent ?? {}).filter( + (key) => !Object.hasOwn(newContent, key), ); if (unknownKeys.length > 0) { logger.warn`Some translation keys looks unknown to us in file path=${filePath}. @@ -276,9 +275,8 @@ export function applyDefaultCodeTranslations({ extractedCodeTranslations: TranslationFileContent; defaultCodeMessages: CodeTranslations; }): TranslationFileContent { - const unusedDefaultCodeMessages = _.difference( - Object.keys(defaultCodeMessages), - Object.keys(extractedCodeTranslations), + const unusedDefaultCodeMessages = Object.keys(defaultCodeMessages).filter( + (messageId) => !Object.hasOwn(extractedCodeTranslations, messageId), ); if (unusedDefaultCodeMessages.length > 0) { logger.warn`Unused default message codes found.