From 73e038298ef3e316229c61ab4754f1487a2c748f Mon Sep 17 00:00:00 2001 From: Vedant Madane <6527493+VedantMadane@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:44:49 +0530 Subject: [PATCH 1/2] feat(pages): add support for Markdown file path links (#11666) Co-authored-by: sebastien --- .../src/contentHelpers.ts | 33 +++++++++++++++++++ .../src/index.ts | 12 +++++++ website/_dogfooding/_pages tests/index.mdx | 1 + .../_dogfooding/_pages tests/linking/index.md | 26 +++++++++++++++ .../_pages tests/linking/nested/page-b.md | 20 +++++++++++ .../_pages tests/linking/page-a.md | 19 +++++++++++ 6 files changed, 111 insertions(+) create mode 100644 packages/docusaurus-plugin-content-pages/src/contentHelpers.ts create mode 100644 website/_dogfooding/_pages tests/linking/index.md create mode 100644 website/_dogfooding/_pages tests/linking/nested/page-b.md create mode 100644 website/_dogfooding/_pages tests/linking/page-a.md diff --git a/packages/docusaurus-plugin-content-pages/src/contentHelpers.ts b/packages/docusaurus-plugin-content-pages/src/contentHelpers.ts new file mode 100644 index 000000000000..d3a96dfc3dc9 --- /dev/null +++ b/packages/docusaurus-plugin-content-pages/src/contentHelpers.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type {LoadedContent, Metadata} from '@docusaurus/plugin-content-pages'; + +function indexPagesBySource(content: LoadedContent): Map { + return new Map(content.map((page) => [page.source, page])); +} + +// TODO this is bad, we should have a better way to do this (new lifecycle?) +// The source to page/permalink is a mutable map passed to the mdx loader +// See https://github.com/facebook/docusaurus/pull/10457 +// See https://github.com/facebook/docusaurus/pull/10185 +export function createContentHelpers() { + const sourceToPage = new Map(); + const sourceToPermalink = new Map(); + + // Mutable map update :/ + function updateContent(content: LoadedContent): void { + sourceToPage.clear(); + sourceToPermalink.clear(); + indexPagesBySource(content).forEach((value, key) => { + sourceToPage.set(key, value); + sourceToPermalink.set(key, value.permalink); + }); + } + + return {updateContent, sourceToPage, sourceToPermalink}; +} diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index cb2a647b005b..3d250894d876 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -13,10 +13,12 @@ import { addTrailingPathSeparator, createAbsoluteFilePathMatcher, getContentPathList, + resolveMarkdownLinkPathname, } from '@docusaurus/utils'; import {createMDXLoaderRule} from '@docusaurus/mdx-loader'; import {createAllRoutes} from './routes'; import {createPagesContentPaths, loadPagesContent} from './content'; +import {createContentHelpers} from './contentHelpers'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type { PluginOptions, @@ -32,6 +34,7 @@ export default async function pluginContentPages( const {siteConfig, siteDir, generatedFilesDir} = context; const contentPaths = createPagesContentPaths({context, options}); + const contentHelpers = createContentHelpers(); const pluginDataDirRoot = path.join( generatedFilesDir, @@ -82,6 +85,14 @@ export default async function pluginContentPages( image: frontMatter.image, }), markdownConfig: siteConfig.markdown, + resolveMarkdownLink: ({linkPathname, sourceFilePath}) => { + return resolveMarkdownLinkPathname(linkPathname, { + sourceFilePath, + sourceToPermalink: contentHelpers.sourceToPermalink, + siteDir, + contentPaths, + }); + }, }, }); } @@ -109,6 +120,7 @@ export default async function pluginContentPages( if (!content) { return; } + contentHelpers.updateContent(content); await createAllRoutes({content, options, actions}); }, diff --git a/website/_dogfooding/_pages tests/index.mdx b/website/_dogfooding/_pages tests/index.mdx index 25da1e93a597..8cf447fec7d0 100644 --- a/website/_dogfooding/_pages tests/index.mdx +++ b/website/_dogfooding/_pages tests/index.mdx @@ -43,3 +43,4 @@ import Readme from "../README.mdx" - [Embeds](/tests/pages/embeds) - [Style Isolation tests](/tests/pages/style-isolation) - [IdealImage tests](/tests/pages/ideal-image) +- [Linking tests](./linking/index.md) diff --git a/website/_dogfooding/_pages tests/linking/index.md b/website/_dogfooding/_pages tests/linking/index.md new file mode 100644 index 000000000000..c6198eca2b64 --- /dev/null +++ b/website/_dogfooding/_pages tests/linking/index.md @@ -0,0 +1,26 @@ +--- +title: Markdown Linking Test +description: Test pages to verify markdown file path links work in the pages plugin +--- + +# Linking Test + +This folder contains test pages to verify markdown file path links work correctly in the `@docusaurus/plugin-content-pages`. + +## Relative file path links + +- [`./index.md`](./index.md) +- [`./page-a.md`](./page-a.md) +- [`./nested/page-b.md`](./nested/page-b.md) + +## Absolute file path links + +- [`/index.md`](/linking/index.md) +- [`/page-a.md`](/linking/page-a.md) +- [`/nested/page-b.md`](/linking/nested/page-b.md) + +## Site alias file path links + +- [`@site/_dogfooding/_pages tests/linking/index.md`](<@site/_dogfooding/_pages tests/linking/index.md>) +- [`@site/_dogfooding/_pages tests/linking/page-a.md`](<@site/_dogfooding/_pages tests/linking/page-a.md>) +- [`@site/_dogfooding/_pages tests/linking/nested/page-b.md`](<@site/_dogfooding/_pages tests/linking/nested/page-b.md>) diff --git a/website/_dogfooding/_pages tests/linking/nested/page-b.md b/website/_dogfooding/_pages tests/linking/nested/page-b.md new file mode 100644 index 000000000000..ac5c817edd6c --- /dev/null +++ b/website/_dogfooding/_pages tests/linking/nested/page-b.md @@ -0,0 +1,20 @@ +# Linking Test - Page B + +## Relative file path links + +- [`../index.md`](../index.md) +- [`../page-a.md`](../page-a.md) +- [`./page-b.md`](./page-b.md) +- [`../nested/page-b.md`](../nested/page-b.md) + +## Absolute file path links + +- [`/index.md`](/linking/index.md) +- [`/page-a.md`](/linking/page-a.md) +- [`/nested/page-b.md`](/linking/nested/page-b.md) + +## Site alias file path links + +- [`@site/_dogfooding/_pages tests/linking/index.md`](<@site/_dogfooding/_pages tests/linking/index.md>) +- [`@site/_dogfooding/_pages tests/linking/page-a.md`](<@site/_dogfooding/_pages tests/linking/page-a.md>) +- [`@site/_dogfooding/_pages tests/linking/nested/page-b.md`](<@site/_dogfooding/_pages tests/linking/nested/page-b.md>) diff --git a/website/_dogfooding/_pages tests/linking/page-a.md b/website/_dogfooding/_pages tests/linking/page-a.md new file mode 100644 index 000000000000..d8b9dd48c52f --- /dev/null +++ b/website/_dogfooding/_pages tests/linking/page-a.md @@ -0,0 +1,19 @@ +# Linking Test - Page A + +## Relative file path links + +- [`./index.md`](./index.md) +- [`./page-a.md`](./page-a.md) +- [`./nested/page-b.md`](./nested/page-b.md) + +## Absolute file path links + +- [`/index.md`](/linking/index.md) +- [`/page-a.md`](/linking/page-a.md) +- [`/nested/page-b.md`](/linking/nested/page-b.md) + +## Site alias file path links + +- [`@site/_dogfooding/_pages tests/linking/index.md`](<@site/_dogfooding/_pages tests/linking/index.md>) +- [`@site/_dogfooding/_pages tests/linking/page-a.md`](<@site/_dogfooding/_pages tests/linking/page-a.md>) +- [`@site/_dogfooding/_pages tests/linking/nested/page-b.md`](<@site/_dogfooding/_pages tests/linking/nested/page-b.md>) From dc0da34bcf60ce84cb45503b53909081cd05a658 Mon Sep 17 00:00:00 2001 From: Ivan Torres <40922354+torresgol10@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:54:07 +0100 Subject: [PATCH 2/2] refactor(create-docusaurus): replace lodash with native implementation (#11653) Co-authored-by: sebastien --- packages/create-docusaurus/package.json | 1 - .../src/__tests__/utils.test.ts | 42 +++++++++++++++++++ packages/create-docusaurus/src/index.ts | 14 +++++-- packages/create-docusaurus/src/utils.ts | 23 ++++++++++ 4 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 packages/create-docusaurus/src/__tests__/utils.test.ts create mode 100644 packages/create-docusaurus/src/utils.ts diff --git a/packages/create-docusaurus/package.json b/packages/create-docusaurus/package.json index a865c1052a4d..179bd121f4a0 100755 --- a/packages/create-docusaurus/package.json +++ b/packages/create-docusaurus/package.json @@ -27,7 +27,6 @@ "commander": "^5.1.0", "execa": "^5.1.1", "fs-extra": "^11.1.1", - "lodash": "^4.17.21", "prompts": "^2.4.2", "semver": "^7.5.4", "supports-color": "^9.4.0", diff --git a/packages/create-docusaurus/src/__tests__/utils.test.ts b/packages/create-docusaurus/src/__tests__/utils.test.ts new file mode 100644 index 000000000000..89fa327cecef --- /dev/null +++ b/packages/create-docusaurus/src/__tests__/utils.test.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {siteNameToPackageName} from '../utils'; + +describe('siteNameToPackageName', () => { + it('converts simple cases', () => { + const testCases: [string, string][] = [ + ['Foo Bar', 'foo-bar'], + ['fooBar', 'foo-bar'], + ['__FOO_BAR__', 'foo-bar'], + ['XMLHttpRequest', 'xml-http-request'], + ['sitemapXML', 'sitemap-xml'], + ['XMLHttp', 'xml-http'], + ['xml-http', 'xml-http'], + ]; + + testCases.forEach(([input, expected]) => { + expect(siteNameToPackageName(input)).toEqual(expected); + }); + }); + + it('converts ñ', () => { + expect(siteNameToPackageName('mañanaFoo')).toEqual('ma-ana-foo'); + }); + + it('converts __', () => { + expect(siteNameToPackageName('foo__bar')).toEqual('foo-bar'); + }); + + it('skips 🔥', () => { + expect(siteNameToPackageName('🔥')).toEqual('🔥'); + }); + + it('skips !!!', () => { + expect(siteNameToPackageName('!!!')).toEqual('!!!'); + }); +}); diff --git a/packages/create-docusaurus/src/index.ts b/packages/create-docusaurus/src/index.ts index ae9180746bc3..76b357b708d5 100755 --- a/packages/create-docusaurus/src/index.ts +++ b/packages/create-docusaurus/src/index.ts @@ -8,7 +8,6 @@ import fs from 'fs-extra'; import {fileURLToPath} from 'url'; import path from 'path'; -import _ from 'lodash'; import {logger} from '@docusaurus/logger'; import execa from 'execa'; import prompts, {type Choice} from 'prompts'; @@ -17,6 +16,7 @@ import supportsColor from 'supports-color'; // TODO remove dependency on large @docusaurus/utils // would be better to have a new smaller @docusaurus/utils-cli package import {askPreferredLanguage} from '@docusaurus/utils'; +import {siteNameToPackageName} from './utils.js'; type LanguagesOptions = { javascript?: boolean; @@ -164,7 +164,15 @@ async function readTemplates(): Promise { ); // Classic should be first in list! - return _.sortBy(templates, (t) => t.name !== recommendedTemplate); + return templates.sort((a, b) => { + if (a.name === recommendedTemplate) { + return -1; + } + if (b.name === recommendedTemplate) { + return 1; + } + return 0; + }); } async function copyTemplate( @@ -562,7 +570,7 @@ export default async function init( // Update package.json info. try { await updatePkg(path.join(dest, 'package.json'), { - name: _.kebabCase(siteName), + name: siteNameToPackageName(siteName), version: '0.0.0', private: true, }); diff --git a/packages/create-docusaurus/src/utils.ts b/packages/create-docusaurus/src/utils.ts new file mode 100644 index 000000000000..29bf0485b1e0 --- /dev/null +++ b/packages/create-docusaurus/src/utils.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * We use a simple kebab-case-like conversion + * It's not perfect, but good enough + * We don't want to depend on lodash in this package + * See https://github.com/facebook/docusaurus/pull/11653 + * @param siteName + */ +export function siteNameToPackageName(siteName: string): string { + const match = siteName.match( + /[A-Z]{2,}(?=[A-Z][a-z]+\d*|\b|_)|[A-Z]?[a-z]+\d*|[A-Z]|\d+/g, + ); + if (match) { + return match.map((x) => x.toLowerCase()).join('-'); + } + return siteName; +}