From 16ebd55ef6ddc7154a31a11b71170c3a202660fd Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 29 Apr 2025 10:16:22 +0200
Subject: [PATCH 01/25] chore(deps): bump http-proxy-middleware from 2.0.7 to
2.0.9 (#11135)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
yarn.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index 4f67ec4371..78765fc47a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10002,9 +10002,9 @@ http-proxy-agent@^5.0.0:
debug "4"
http-proxy-middleware@^2.0.3:
- version "2.0.7"
- resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6"
- integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==
+ version "2.0.9"
+ resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz#e9e63d68afaa4eee3d147f39149ab84c0c2815ef"
+ integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==
dependencies:
"@types/http-proxy" "^1.17.8"
http-proxy "^1.18.1"
From fadb6d2607a73d597875541f4abcc05d9cac265a Mon Sep 17 00:00:00 2001
From: Shreedhar Bhat <57760562+shreedharbhat98@users.noreply.github.com>
Date: Wed, 30 Apr 2025 16:17:51 +0530
Subject: [PATCH 02/25] test(reading-time): Unit tests for calculating reading
time (#11116)
---
.../build-snap/blog/authors/index.html | 23 +++++++
.../index.html | 23 +++++++
.../tags/global-tag-permalink (en)/index.html | 23 +++++++
.../blog/tags/inline-tag/index.html | 23 +++++++
.../__tests__/__snapshots__/feed.test.ts.snap | 60 +++++++++++++++++--
.../src/__tests__/readingTime.test.ts | 56 +++++++++++++++++
.../src/blogUtils.ts | 4 +-
.../src/readingTime.ts | 29 +++++++++
8 files changed, 233 insertions(+), 8 deletions(-)
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/authors/index.html
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/authors/slorber-custom-permalink-localized/index.html
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/tags/global-tag-permalink (en)/index.html
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/tags/inline-tag/index.html
create mode 100644 packages/docusaurus-plugin-content-blog/src/__tests__/readingTime.test.ts
create mode 100644 packages/docusaurus-plugin-content-blog/src/readingTime.ts
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/authors/index.html b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/authors/index.html
new file mode 100644
index 0000000000..be7763d064
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/authors/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+Authors | Docusaurus blog website fixture
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Authors Docusaurus maintainer (translated)
+
+
\ No newline at end of file
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/authors/slorber-custom-permalink-localized/index.html b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/authors/slorber-custom-permalink-localized/index.html
new file mode 100644
index 0000000000..76471ccd02
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/authors/slorber-custom-permalink-localized/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+Sébastien Lorber (translated) - One post | Docusaurus blog website fixture
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Happy birthday! (translated)
+
+
\ No newline at end of file
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/tags/global-tag-permalink (en)/index.html b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/tags/global-tag-permalink (en)/index.html
new file mode 100644
index 0000000000..ccfb79d4e1
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/tags/global-tag-permalink (en)/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+Page Not Found | Docusaurus blog website fixture
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Page Not Found We could not find what you were looking for.
Please contact the owner of the site that linked you to the original URL and let them know their link is broken.
+
+
\ No newline at end of file
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/tags/inline-tag/index.html b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/tags/inline-tag/index.html
new file mode 100644
index 0000000000..f9639abb14
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/tags/inline-tag/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+One post tagged with "inlineTag" | Docusaurus blog website fixture
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Happy birthday! (translated)
+
+
\ No newline at end of file
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap
index 2c90688e2c..0b4609ede3 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap
@@ -237,6 +237,10 @@ exports[`atom has custom xslt files for feed: blog tree 1`] = `
├── atom.css
├── atom.xml
├── atom.xsl
+├── authors
+│ ├── index.html
+│ └── slorber-custom-permalink-localized
+│ └── index.html
├── blog-with-links
│ └── index.html
├── custom-atom.css
@@ -273,7 +277,11 @@ exports[`atom has custom xslt files for feed: blog tree 1`] = `
│ │ └── index.html
│ ├── date
│ │ └── index.html
-│ └── index.html
+│ ├── global-tag-permalink (en)
+│ │ └── index.html
+│ ├── index.html
+│ └── inline-tag
+│ └── index.html
└── unlisted
└── index.html"
`;
@@ -683,6 +691,10 @@ exports[`atom has xslt files for feed: blog tree 1`] = `
├── atom.css
├── atom.xml
├── atom.xsl
+├── authors
+│ ├── index.html
+│ └── slorber-custom-permalink-localized
+│ └── index.html
├── blog-with-links
│ └── index.html
├── custom-atom.css
@@ -719,7 +731,11 @@ exports[`atom has xslt files for feed: blog tree 1`] = `
│ │ └── index.html
│ ├── date
│ │ └── index.html
-│ └── index.html
+│ ├── global-tag-permalink (en)
+│ │ └── index.html
+│ ├── index.html
+│ └── inline-tag
+│ └── index.html
└── unlisted
└── index.html"
`;
@@ -900,6 +916,10 @@ exports[`json has custom xslt files for feed: blog tree 1`] = `
├── atom.css
├── atom.xml
├── atom.xsl
+├── authors
+│ ├── index.html
+│ └── slorber-custom-permalink-localized
+│ └── index.html
├── blog-with-links
│ └── index.html
├── custom-atom.css
@@ -936,7 +956,11 @@ exports[`json has custom xslt files for feed: blog tree 1`] = `
│ │ └── index.html
│ ├── date
│ │ └── index.html
-│ └── index.html
+│ ├── global-tag-permalink (en)
+│ │ └── index.html
+│ ├── index.html
+│ └── inline-tag
+│ └── index.html
└── unlisted
└── index.html"
`;
@@ -1253,6 +1277,10 @@ exports[`json has xslt files for feed: blog tree 1`] = `
├── atom.css
├── atom.xml
├── atom.xsl
+├── authors
+│ ├── index.html
+│ └── slorber-custom-permalink-localized
+│ └── index.html
├── blog-with-links
│ └── index.html
├── custom-atom.css
@@ -1289,7 +1317,11 @@ exports[`json has xslt files for feed: blog tree 1`] = `
│ │ └── index.html
│ ├── date
│ │ └── index.html
-│ └── index.html
+│ ├── global-tag-permalink (en)
+│ │ └── index.html
+│ ├── index.html
+│ └── inline-tag
+│ └── index.html
└── unlisted
└── index.html"
`;
@@ -1527,6 +1559,10 @@ exports[`rss has custom xslt files for feed: blog tree 1`] = `
├── atom.css
├── atom.xml
├── atom.xsl
+├── authors
+│ ├── index.html
+│ └── slorber-custom-permalink-localized
+│ └── index.html
├── blog-with-links
│ └── index.html
├── custom-atom.css
@@ -1563,7 +1599,11 @@ exports[`rss has custom xslt files for feed: blog tree 1`] = `
│ │ └── index.html
│ ├── date
│ │ └── index.html
-│ └── index.html
+│ ├── global-tag-permalink (en)
+│ │ └── index.html
+│ ├── index.html
+│ └── inline-tag
+│ └── index.html
└── unlisted
└── index.html"
`;
@@ -1949,6 +1989,10 @@ exports[`rss has xslt files for feed: blog tree 1`] = `
├── atom.css
├── atom.xml
├── atom.xsl
+├── authors
+│ ├── index.html
+│ └── slorber-custom-permalink-localized
+│ └── index.html
├── blog-with-links
│ └── index.html
├── custom-atom.css
@@ -1985,7 +2029,11 @@ exports[`rss has xslt files for feed: blog tree 1`] = `
│ │ └── index.html
│ ├── date
│ │ └── index.html
-│ └── index.html
+│ ├── global-tag-permalink (en)
+│ │ └── index.html
+│ ├── index.html
+│ └── inline-tag
+│ └── index.html
└── unlisted
└── index.html"
`;
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/readingTime.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/readingTime.test.ts
new file mode 100644
index 0000000000..98b130418e
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/readingTime.test.ts
@@ -0,0 +1,56 @@
+/**
+ * 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 {calculateReadingTime} from '../readingTime';
+
+describe('calculateReadingTime', () => {
+ it('calculates reading time for empty content', () => {
+ expect(calculateReadingTime('')).toBe(0);
+ });
+
+ it('calculates reading time for short content', () => {
+ const content = 'This is a short test content.';
+ expect(calculateReadingTime(content)).toBe(0.03);
+ });
+
+ it('calculates reading time for long content', () => {
+ const content = 'This is a test content. '.repeat(100);
+ expect(calculateReadingTime(content)).toBe(2.5);
+ });
+
+ it('respects custom words per minute', () => {
+ const content = 'This is a test content. '.repeat(100);
+ expect(calculateReadingTime(content, {wordsPerMinute: 100})).toBe(5);
+ });
+
+ it('handles content with special characters', () => {
+ const content = 'Hello! How are you? This is a test...';
+ expect(calculateReadingTime(content)).toBe(0.04);
+ });
+
+ it('handles content with multiple lines', () => {
+ const content = `This is line 1.
+ This is line 2.
+ This is line 3.`;
+ expect(calculateReadingTime(content)).toBe(0.06);
+ });
+
+ it('handles content with HTML tags', () => {
+ const content = 'This is a test content.
';
+ expect(calculateReadingTime(content)).toBe(0.025);
+ });
+
+ it('handles content with markdown', () => {
+ const content = '# Title\n\nThis is **bold** and *italic* text.';
+ expect(calculateReadingTime(content)).toBe(0.04);
+ });
+
+ it('handles CJK content', () => {
+ const content = '你好,世界!这是一段测试内容。';
+ expect(calculateReadingTime(content)).toBe(0.06);
+ });
+});
diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts
index ab7426eac5..2cc84bac76 100644
--- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts
+++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts
@@ -9,7 +9,6 @@ import fs from 'fs-extra';
import path from 'path';
import _ from 'lodash';
import logger from '@docusaurus/logger';
-import readingTime from 'reading-time';
import {
parseMarkdownFile,
normalizeUrl,
@@ -32,6 +31,7 @@ import {getTagsFile} from '@docusaurus/utils-validation';
import {validateBlogPostFrontMatter} from './frontMatter';
import {getBlogPostAuthors} from './authors';
import {reportAuthorsProblems} from './authorsProblems';
+import {calculateReadingTime} from './readingTime';
import type {TagsFile} from '@docusaurus/utils';
import type {LoadContext, ParseFrontMatter} from '@docusaurus/types';
import type {
@@ -211,7 +211,7 @@ async function parseBlogPostMarkdownFile({
}
const defaultReadingTime: ReadingTimeFunction = ({content, options}) =>
- readingTime(content, options).minutes;
+ calculateReadingTime(content, options);
async function processBlogSourceFile(
blogSourceRelative: string,
diff --git a/packages/docusaurus-plugin-content-blog/src/readingTime.ts b/packages/docusaurus-plugin-content-blog/src/readingTime.ts
new file mode 100644
index 0000000000..76e4513537
--- /dev/null
+++ b/packages/docusaurus-plugin-content-blog/src/readingTime.ts
@@ -0,0 +1,29 @@
+/**
+ * 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 readingTime from 'reading-time';
+
+const DEFAULT_WORDS_PER_MINUTE = 200;
+
+interface ReadingTimeOptions {
+ wordsPerMinute?: number;
+ wordBound?: (char: string) => boolean;
+}
+
+/**
+ * Calculates the reading time for a given content string.
+ * Uses the reading-time package under the hood.
+ */
+export function calculateReadingTime(
+ content: string,
+ options: ReadingTimeOptions = {},
+): number {
+ const wordsPerMinute = options.wordsPerMinute ?? DEFAULT_WORDS_PER_MINUTE;
+ const {wordBound} = options;
+ return readingTime(content, {wordsPerMinute, ...(wordBound && {wordBound})})
+ .minutes;
+}
From 5d0df96554d482562db254eb96fb656d5f9c5ec3 Mon Sep 17 00:00:00 2001
From: JiPai <3956400+PaiJi@users.noreply.github.com>
Date: Sat, 3 May 2025 01:21:54 +0800
Subject: [PATCH 03/25] docs(deploy): remove `layer0` from deployment docs
(#11137)
---
website/docs/deployment.mdx | 4 ----
1 file changed, 4 deletions(-)
diff --git a/website/docs/deployment.mdx b/website/docs/deployment.mdx
index 1d001e79fe..a7ebeb5052 100644
--- a/website/docs/deployment.mdx
+++ b/website/docs/deployment.mdx
@@ -834,10 +834,6 @@ You can deploy your Docusaurus project to [Stormkit](https://www.stormkit.io), a
See [docs](https://docs.quantcdn.io/docs/cli/continuous-integration) and [blog](https://www.quantcdn.io/blog) for more examples and use cases for deploying to QuantCDN.
-## Deploying to Layer0 {#deploying-to-layer0}
-
-[Layer0](https://www.layer0.co) is an all-in-one platform to develop, deploy, preview, experiment on, monitor, and run your headless frontend. It is focused on large, dynamic websites and best-in-class performance through EdgeJS (a JavaScript-based Content Delivery Network), predictive prefetching, and performance monitoring. Layer0 offers a free tier. Get started in just a few minutes by following [Layer0's guide to deploying Docusaurus](https://docs.layer0.co/guides/docusaurus).
-
## Deploying to Cloudflare Pages {#deploying-to-cloudflare-pages}
[Cloudflare Pages](https://pages.cloudflare.com/) is a Jamstack platform for frontend developers to collaborate and deploy websites. Get started within a few minutes by following [this page](https://developers.cloudflare.com/pages/framework-guides/deploy-a-docusaurus-site/).
From fef6c4c8cb22a45fa4d3dff016618a3d10fdd552 Mon Sep 17 00:00:00 2001
From: Jack Howard
Date: Fri, 2 May 2025 10:52:38 -0700
Subject: [PATCH 04/25] fix(theme): improve color contrast of live code block
header (#11120)
fix: Ensure accessibility of live code block contrast with background update
---
.../src/theme/Playground/Header/styles.module.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Header/styles.module.css b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Header/styles.module.css
index 092af7825d..39214d88e9 100644
--- a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Header/styles.module.css
+++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Header/styles.module.css
@@ -16,6 +16,6 @@
}
.playgroundHeader:first-of-type {
- background: var(--ifm-color-emphasis-600);
+ background: var(--ifm-color-emphasis-700);
color: var(--ifm-color-content-inverse);
}
From 42223ef9eb967cfcaffe44f1e19ed4f2ce703fa4 Mon Sep 17 00:00:00 2001
From: David King Roderos
Date: Tue, 6 May 2025 16:34:48 +0800
Subject: [PATCH 05/25] docs: Use tabs on choosing a GitHub action file
(#11145)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Sébastien Lorber
---
website/docs/deployment.mdx | 90 ++++++++++++++++++++++++++++++++++++-
1 file changed, 88 insertions(+), 2 deletions(-)
diff --git a/website/docs/deployment.mdx b/website/docs/deployment.mdx
index a7ebeb5052..24460c4b9e 100644
--- a/website/docs/deployment.mdx
+++ b/website/docs/deployment.mdx
@@ -374,12 +374,96 @@ Add these two workflow files:
:::warning Tweak the parameters for your setup
-These files assume you are using Yarn. If you use npm, change `cache: yarn`, `yarn install --frozen-lockfile`, `yarn build` to `cache: npm`, `npm ci`, `npm run build` accordingly.
-
If your Docusaurus project is not at the root of your repo, you may need to configure a [default working directory](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-set-the-default-shell-and-working-directory), and adjust the paths accordingly.
:::
+
+
+```yml title=".github/workflows/deploy.yml"
+name: Deploy to GitHub Pages
+
+on:
+ push:
+ branches:
+ - main
+ # Review gh actions docs if you want to further define triggers, paths, etc
+ # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on
+
+jobs:
+ build:
+ name: Build Docusaurus
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 18
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+ - name: Build website
+ run: npm build
+
+ - name: Upload Build Artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: build
+
+ deploy:
+ name: Deploy to GitHub Pages
+ needs: build
+
+ # Grant GITHUB_TOKEN the permissions required to make a Pages deployment
+ permissions:
+ pages: write # to deploy to Pages
+ id-token: write # to verify the deployment originates from an appropriate source
+
+ # Deploy to the github-pages environment
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+
+ runs-on: ubuntu-latest
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
+```
+
+```yml title=".github/workflows/test-deploy.yml"
+name: Test deployment
+
+on:
+ pull_request:
+ branches:
+ - main
+ # Review gh actions docs if you want to further define triggers, paths, etc
+ # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on
+
+jobs:
+ test-deploy:
+ name: Test deployment
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 18
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+ - name: Test build website
+ run: npm build
+```
+
+
```yml title=".github/workflows/deploy.yml"
name: Deploy to GitHub Pages
@@ -462,6 +546,8 @@ jobs:
- name: Test build website
run: yarn build
```
+
+
From 91d95a9ac13ca661f9993e4d11a4bb27c610f6ce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Tue, 6 May 2025 11:20:14 +0200
Subject: [PATCH 06/25] fix: fix docs lint issue (#11154)
---
website/docs/deployment.mdx | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/website/docs/deployment.mdx b/website/docs/deployment.mdx
index 24460c4b9e..5a6cfb00ee 100644
--- a/website/docs/deployment.mdx
+++ b/website/docs/deployment.mdx
@@ -380,6 +380,7 @@ If your Docusaurus project is not at the root of your repo, you may need to conf
+
```yml title=".github/workflows/deploy.yml"
name: Deploy to GitHub Pages
@@ -462,8 +463,10 @@ jobs:
- name: Test build website
run: npm build
```
+
+
```yml title=".github/workflows/deploy.yml"
name: Deploy to GitHub Pages
@@ -546,6 +549,7 @@ jobs:
- name: Test build website
run: yarn build
```
+
From 59e9eb20facda32998a0d3b4d748fa74999af876 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Tue, 6 May 2025 11:34:19 +0200
Subject: [PATCH 07/25] fix(theme): restore former code block theme-common
internal APIs (#11153)
Co-authored-by: slorber <749374+slorber@users.noreply.github.com>
---
packages/docusaurus-theme-common/src/internal.ts | 8 ++++++++
.../docusaurus-theme-common/src/utils/codeBlockUtils.tsx | 6 ++++++
2 files changed, 14 insertions(+)
diff --git a/packages/docusaurus-theme-common/src/internal.ts b/packages/docusaurus-theme-common/src/internal.ts
index b1c116a0c6..d8d5821c95 100644
--- a/packages/docusaurus-theme-common/src/internal.ts
+++ b/packages/docusaurus-theme-common/src/internal.ts
@@ -39,6 +39,14 @@ export {
getPrismCssVariables,
CodeBlockContextProvider,
useCodeBlockContext,
+
+ // TODO Docusaurus v4: remove, only kept for internal retro-compatibility
+ // See https://github.com/facebook/docusaurus/pull/11153
+ parseCodeBlockTitle,
+ parseClassNameLanguage as parseLanguage,
+ parseLines,
+ getLineNumbersStart,
+ containsLineNumbers,
} from './utils/codeBlockUtils';
export {DEFAULT_SEARCH_TAG} from './utils/searchUtils';
diff --git a/packages/docusaurus-theme-common/src/utils/codeBlockUtils.tsx b/packages/docusaurus-theme-common/src/utils/codeBlockUtils.tsx
index 914fc5de35..fc18e4d44b 100644
--- a/packages/docusaurus-theme-common/src/utils/codeBlockUtils.tsx
+++ b/packages/docusaurus-theme-common/src/utils/codeBlockUtils.tsx
@@ -188,6 +188,12 @@ export function getLineNumbersStart({
return getMetaLineNumbersStart(metastring);
}
+// TODO Docusaurus v4: remove, only kept for internal retro-compatibility
+// See https://github.com/facebook/docusaurus/pull/11153
+export function containsLineNumbers(metastring?: string): boolean {
+ return Boolean(metastring?.includes('showLineNumbers'));
+}
+
type ParseCodeLinesParam = {
/**
* The full metastring, as received from MDX. Line ranges declared here
From e905b93c90bd2c467a8bac5ecc3d6f941215c631 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Tue, 6 May 2025 16:30:35 +0200
Subject: [PATCH 08/25] feat(core): add internal flag to skip bundling (#11156)
---
.../src/commands/build/buildLocale.ts | 28 ++++++++++++-------
website/docusaurus.config.ts | 2 +-
2 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/packages/docusaurus/src/commands/build/buildLocale.ts b/packages/docusaurus/src/commands/build/buildLocale.ts
index 84f65a63d3..a996c4bccd 100644
--- a/packages/docusaurus/src/commands/build/buildLocale.ts
+++ b/packages/docusaurus/src/commands/build/buildLocale.ts
@@ -34,6 +34,8 @@ export type BuildLocaleParams = {
cliOptions: Partial;
};
+const SkipBundling = process.env.DOCUSAURUS_SKIP_BUNDLING === 'true';
+
export async function buildLocale({
siteDir,
locale,
@@ -82,19 +84,25 @@ export async function buildLocale({
// We also clear website/build dir
// returns void, no useful result needed before compilation
// See also https://github.com/facebook/docusaurus/pull/11037
- clearPath(outDir),
+ SkipBundling ? undefined : clearPath(outDir),
]),
);
- // Run webpack to build JS bundle (client) and static html files (server).
- await PerfLogger.async(`Bundling with ${props.currentBundler.name}`, () => {
- return compile({
- configs:
- // For hash router we don't do SSG and can skip the server bundle
- router === 'hash' ? [clientConfig] : [clientConfig, serverConfig],
- currentBundler: configureWebpackUtils.currentBundler,
+ if (SkipBundling) {
+ console.warn(
+ `Skipping the Docusaurus bundling step because DOCUSAURUS_SKIP_BUNDLING='true'`,
+ );
+ } else {
+ // Run webpack to build JS bundle (client) and static html files (server).
+ await PerfLogger.async(`Bundling with ${props.currentBundler.name}`, () => {
+ return compile({
+ configs:
+ // For hash router we don't do SSG and can skip the server bundle
+ router === 'hash' ? [clientConfig] : [clientConfig, serverConfig],
+ currentBundler: configureWebpackUtils.currentBundler,
+ });
});
- });
+ }
const {collectedData} = await PerfLogger.async('SSG', () =>
executeSSG({
@@ -231,7 +239,7 @@ async function getBuildServerConfig({
async function cleanupServerBundle(serverBundlePath: string) {
if (process.env.DOCUSAURUS_KEEP_SERVER_BUNDLE === 'true') {
logger.warn(
- "Will NOT delete server bundle because DOCUSAURUS_KEEP_SERVER_BUNDLE is set to 'true'",
+ "Will NOT delete server bundle because DOCUSAURUS_KEEP_SERVER_BUNDLE='true'",
);
} else {
await PerfLogger.async('Deleting server bundle', async () => {
diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts
index a1ce845153..a6155291af 100644
--- a/website/docusaurus.config.ts
+++ b/website/docusaurus.config.ts
@@ -324,7 +324,7 @@ export default async function createConfigAsync() {
showLastUpdateTime: true,
} satisfies DocsOptions,
],
- [
+ !process.env.DOCUSAURUS_SKIP_BUNDLING && [
'client-redirects',
{
fromExtensions: ['html'],
From 53fa0ecb1f87047b014fd315ea1ff82aaacc2625 Mon Sep 17 00:00:00 2001
From: Arien Shibani
Date: Thu, 8 May 2025 18:10:21 +0200
Subject: [PATCH 09/25] fix(create-docusaurus): Improve init template README,
fix headings and remove $ in bash code blocks (#11157)
---
.../templates/shared/README.md | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/packages/create-docusaurus/templates/shared/README.md b/packages/create-docusaurus/templates/shared/README.md
index 35ad92cc2a..b28211a9bb 100644
--- a/packages/create-docusaurus/templates/shared/README.md
+++ b/packages/create-docusaurus/templates/shared/README.md
@@ -2,40 +2,40 @@
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
-### Installation
+## Installation
```bash
-$ yarn
+yarn
```
-### Local Development
+## Local Development
```bash
-$ yarn start
+yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
-### Build
+## Build
```bash
-$ yarn build
+yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
-### Deployment
+## Deployment
Using SSH:
```bash
-$ USE_SSH=true yarn deploy
+USE_SSH=true yarn deploy
```
Not using SSH:
```bash
-$ GIT_USER= yarn deploy
+GIT_USER= yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
From 33811e38fe882e63c31265c71f3e1a1b0098f33d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Fri, 9 May 2025 13:34:02 +0200
Subject: [PATCH 10/25] perf(core): optimize SSG collected data memory and
worker thread communication (#11162)
---
packages/docusaurus-logger/src/perfLogger.ts | 24 ++++++---
.../docusaurus/src/client/serverEntry.tsx | 8 +--
.../src/client/serverHelmetUtils.tsx | 6 +--
packages/docusaurus/src/common.d.ts | 30 +++++++++--
packages/docusaurus/src/ssg/ssgExecutor.ts | 50 +++++++++----------
packages/docusaurus/src/ssg/ssgRenderer.ts | 33 ++++++++++--
6 files changed, 105 insertions(+), 46 deletions(-)
diff --git a/packages/docusaurus-logger/src/perfLogger.ts b/packages/docusaurus-logger/src/perfLogger.ts
index 7f9575c18c..2bc94b7730 100644
--- a/packages/docusaurus-logger/src/perfLogger.ts
+++ b/packages/docusaurus-logger/src/perfLogger.ts
@@ -72,12 +72,22 @@ function createPerfLogger(): PerfLoggerAPI {
}
};
- const formatMemory = (memory: Memory): string => {
- const fmtHead = (bytes: number) =>
- logger.cyan(`${(bytes / 1000000).toFixed(0)}mb`);
+ const formatBytesToMb = (bytes: number) =>
+ logger.cyan(`${(bytes / 1024 / 1024).toFixed(0)}mb`);
+
+ const formatMemoryDelta = (memory: Memory): string => {
return logger.dim(
- `(${fmtHead(memory.before.heapUsed)} -> ${fmtHead(
+ `(Heap ${formatBytesToMb(memory.before.heapUsed)} -> ${formatBytesToMb(
memory.after.heapUsed,
+ )} / Total ${formatBytesToMb(memory.after.heapTotal)})`,
+ );
+ };
+
+ const formatMemoryCurrent = (): string => {
+ const memory = getMemory();
+ return logger.dim(
+ `(Heap ${formatBytesToMb(memory.heapUsed)} / Total ${formatBytesToMb(
+ memory.heapTotal,
)})`,
);
};
@@ -103,7 +113,7 @@ function createPerfLogger(): PerfLoggerAPI {
console.log(
`${PerfPrefix}${formatStatus(error)} ${label} - ${formatDuration(
duration,
- )} - ${formatMemory(memory)}`,
+ )} - ${formatMemoryDelta(memory)}`,
);
};
@@ -144,7 +154,9 @@ function createPerfLogger(): PerfLoggerAPI {
};
const log: PerfLoggerAPI['log'] = (label: string) =>
- console.log(`${PerfPrefix} ${applyParentPrefix(label)}`);
+ console.log(
+ `${PerfPrefix} ${applyParentPrefix(label)} - ${formatMemoryCurrent()}`,
+ );
const async: PerfLoggerAPI['async'] = async (label, asyncFn) => {
const finalLabel = applyParentPrefix(label);
diff --git a/packages/docusaurus/src/client/serverEntry.tsx b/packages/docusaurus/src/client/serverEntry.tsx
index 75e7258d76..161e512cb5 100644
--- a/packages/docusaurus/src/client/serverEntry.tsx
+++ b/packages/docusaurus/src/client/serverEntry.tsx
@@ -16,8 +16,8 @@ import {
createStatefulBrokenLinks,
BrokenLinksProvider,
} from './BrokenLinksContext';
-import {toPageCollectedMetadata} from './serverHelmetUtils';
-import type {PageCollectedData, AppRenderer} from '../common';
+import {toPageCollectedMetadataInternal} from './serverHelmetUtils';
+import type {AppRenderer, PageCollectedDataInternal} from '../common';
const render: AppRenderer['render'] = async ({
pathname,
@@ -47,7 +47,7 @@ const render: AppRenderer['render'] = async ({
const {helmet} = helmetContext as FilledContext;
- const metadata = toPageCollectedMetadata({helmet});
+ const metadata = toPageCollectedMetadataInternal({helmet});
// TODO Docusaurus v4 remove with deprecated postBuild({head}) API
// the returned collectedData must be serializable to run in workers
@@ -55,7 +55,7 @@ const render: AppRenderer['render'] = async ({
metadata.helmet = null;
}
- const collectedData: PageCollectedData = {
+ const collectedData: PageCollectedDataInternal = {
metadata,
anchors: statefulBrokenLinks.getCollectedAnchors(),
links: statefulBrokenLinks.getCollectedLinks(),
diff --git a/packages/docusaurus/src/client/serverHelmetUtils.tsx b/packages/docusaurus/src/client/serverHelmetUtils.tsx
index 14d062785f..9ee582a231 100644
--- a/packages/docusaurus/src/client/serverHelmetUtils.tsx
+++ b/packages/docusaurus/src/client/serverHelmetUtils.tsx
@@ -6,7 +6,7 @@
*/
import type {ReactElement} from 'react';
-import type {PageCollectedMetadata} from '../common';
+import type {PageCollectedMetadataInternal} from '../common';
import type {HelmetServerState} from 'react-helmet-async';
type BuildMetaTag = {name?: string; content?: string};
@@ -30,11 +30,11 @@ function isNoIndexTag(tag: BuildMetaTag): boolean {
);
}
-export function toPageCollectedMetadata({
+export function toPageCollectedMetadataInternal({
helmet,
}: {
helmet: HelmetServerState;
-}): PageCollectedMetadata {
+}): PageCollectedMetadataInternal {
const tags = getBuildMetaTags(helmet);
const noIndex = tags.some(isNoIndexTag);
diff --git a/packages/docusaurus/src/common.d.ts b/packages/docusaurus/src/common.d.ts
index 5ad881b29f..dc6892676d 100644
--- a/packages/docusaurus/src/common.d.ts
+++ b/packages/docusaurus/src/common.d.ts
@@ -13,7 +13,7 @@ import type {RouteBuildMetadata} from '@docusaurus/types';
export type AppRenderResult = {
html: string;
- collectedData: PageCollectedData;
+ collectedData: PageCollectedDataInternal;
};
export type AppRenderer = {
@@ -40,23 +40,43 @@ export type RouteBuildMetadataInternal = {
script: string;
};
-// This data structure must remain serializable!
-// See why: https://github.com/facebook/docusaurus/pull/10826
export type PageCollectedMetadata = {
public: RouteBuildMetadata;
- internal: RouteBuildMetadataInternal;
// TODO Docusaurus v4 remove legacy unserializable helmet data structure
// See https://github.com/facebook/docusaurus/pull/10850
helmet: HelmetServerState | null;
};
+// This data structure must remain serializable!
+// See why: https://github.com/facebook/docusaurus/pull/10826
+export type PageCollectedMetadataInternal = PageCollectedMetadata & {
+ internal: {
+ htmlAttributes: string;
+ bodyAttributes: string;
+ title: string;
+ meta: string;
+ link: string;
+ script: string;
+ };
+};
+
+export type PageCollectedDataInternal = {
+ metadata: PageCollectedMetadataInternal;
+ modules: string[];
+ links: string[];
+ anchors: string[];
+};
+
+// Keep this data structure as small as possible
+// See https://github.com/facebook/docusaurus/pull/11162
export type PageCollectedData = {
metadata: PageCollectedMetadata;
links: string[];
anchors: string[];
- modules: string[];
};
+// Keep this data structure as small as possible
+// See https://github.com/facebook/docusaurus/pull/11162
export type SiteCollectedData = {
[pathname: string]: PageCollectedData;
};
diff --git a/packages/docusaurus/src/ssg/ssgExecutor.ts b/packages/docusaurus/src/ssg/ssgExecutor.ts
index 3b094a7697..46b9c89fc8 100644
--- a/packages/docusaurus/src/ssg/ssgExecutor.ts
+++ b/packages/docusaurus/src/ssg/ssgExecutor.ts
@@ -38,16 +38,13 @@ const createSimpleSSGExecutor: CreateSSGExecutor = async ({
}) => {
return {
run: () => {
- return PerfLogger.async(
- 'Generate static files (current thread)',
- async () => {
- const ssgResults = await executeSSGInlineTask({
- pathnames,
- params,
- });
- return createGlobalSSGResult(ssgResults);
- },
- );
+ return PerfLogger.async('SSG (current thread)', async () => {
+ const ssgResults = await executeSSGInlineTask({
+ pathnames,
+ params,
+ });
+ return createGlobalSSGResult(ssgResults);
+ });
},
destroy: async () => {
@@ -111,7 +108,7 @@ const createPooledSSGExecutor: CreateSSGExecutor = async ({
}
const pool = await PerfLogger.async(
- `Create SSG pool - ${logger.cyan(numberOfThreads)} threads`,
+ `Create SSG thread pool - ${logger.cyan(numberOfThreads)} threads`,
async () => {
const Tinypool = await import('tinypool').then((m) => m.default);
@@ -134,23 +131,26 @@ const createPooledSSGExecutor: CreateSSGExecutor = async ({
const pathnamesChunks = _.chunk(pathnames, SSGWorkerThreadTaskSize);
// Tiny wrapper for type-safety
- const submitTask: ExecuteSSGWorkerThreadTask = (task) => pool.run(task);
+ const submitTask: ExecuteSSGWorkerThreadTask = async (task) => {
+ const result = await pool.run(task);
+ // Note, we don't use PerfLogger.async() because all tasks are submitted
+ // immediately at once and queued, while results are received progressively
+ PerfLogger.log(`Result for task ${logger.name(task.id)}`);
+ return result;
+ };
return {
run: async () => {
- const results = await PerfLogger.async(
- `Generate static files (${numberOfThreads} worker threads)`,
- async () => {
- return Promise.all(
- pathnamesChunks.map((taskPathnames, taskIndex) => {
- return submitTask({
- id: taskIndex + 1,
- pathnames: taskPathnames,
- });
- }),
- );
- },
- );
+ const results = await PerfLogger.async(`Thread pool`, async () => {
+ return Promise.all(
+ pathnamesChunks.map((taskPathnames, taskIndex) => {
+ return submitTask({
+ id: taskIndex + 1,
+ pathnames: taskPathnames,
+ });
+ }),
+ );
+ });
const allResults = results.flat();
return createGlobalSSGResult(allResults);
},
diff --git a/packages/docusaurus/src/ssg/ssgRenderer.ts b/packages/docusaurus/src/ssg/ssgRenderer.ts
index 3e0c2cf253..0955f0367d 100644
--- a/packages/docusaurus/src/ssg/ssgRenderer.ts
+++ b/packages/docusaurus/src/ssg/ssgRenderer.ts
@@ -22,14 +22,18 @@ import {SSGConcurrency} from './ssgEnv';
import {writeStaticFile} from './ssgUtils';
import {createSSGRequire} from './ssgNodeRequire';
import type {SSGParams} from './ssgParams';
-import type {AppRenderer, AppRenderResult} from '../common';
+import type {
+ AppRenderer,
+ PageCollectedData,
+ PageCollectedDataInternal,
+} from '../common';
import type {HtmlMinifier} from '@docusaurus/bundler';
export type SSGSuccess = {
success: true;
pathname: string;
result: {
- collectedData: AppRenderResult['collectedData'];
+ collectedData: PageCollectedData;
warnings: string[];
// html: we don't include it on purpose!
// we don't need to aggregate all html contents in memory!
@@ -144,6 +148,26 @@ export async function loadSSGRenderer({
};
}
+// We reduce the page collected data structure after the HTML file is written
+// Some data (modules, metadata.internal) is only useful to create the HTML file
+// It's not useful to aggregate that collected data in memory
+// Keep this data structure as small as possible
+// See https://github.com/facebook/docusaurus/pull/11162
+function reduceCollectedData(
+ pageCollectedData: PageCollectedDataInternal,
+): PageCollectedData {
+ // We re-create the object from scratch
+ // We absolutely want to avoid TS duck typing
+ return {
+ anchors: pageCollectedData.anchors,
+ metadata: {
+ public: pageCollectedData.metadata.public,
+ helmet: pageCollectedData.metadata.helmet,
+ },
+ links: pageCollectedData.links,
+ };
+}
+
async function generateStaticFile({
pathname,
appRenderer,
@@ -176,11 +200,14 @@ async function generateStaticFile({
content: minifierResult.code,
params,
});
+
+ const collectedData = reduceCollectedData(appRenderResult.collectedData);
+
return {
success: true,
pathname,
result: {
- collectedData: appRenderResult.collectedData,
+ collectedData,
// As of today, only the html minifier can emit SSG warnings
warnings: minifierResult.warnings,
},
From b7cd1061fd9dfee0bcfda83a344f399a58ab52d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Fri, 9 May 2025 16:31:05 +0200
Subject: [PATCH 11/25] perf(utils): implement git command queue (#11163)
Co-authored-by: slorber <749374+slorber@users.noreply.github.com>
---
packages/docusaurus-utils/package.json | 1 +
packages/docusaurus-utils/src/gitUtils.ts | 36 ++++++++++++++++++++---
yarn.lock | 2 +-
3 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/packages/docusaurus-utils/package.json b/packages/docusaurus-utils/package.json
index 7dde60ae3a..a8a74fa2ad 100644
--- a/packages/docusaurus-utils/package.json
+++ b/packages/docusaurus-utils/package.json
@@ -33,6 +33,7 @@
"lodash": "^4.17.21",
"micromatch": "^4.0.5",
"prompts": "^2.4.2",
+ "p-queue": "^6.6.2",
"resolve-pathname": "^3.0.0",
"tslib": "^2.6.0",
"url-loader": "^4.1.1",
diff --git a/packages/docusaurus-utils/src/gitUtils.ts b/packages/docusaurus-utils/src/gitUtils.ts
index 104bf73634..fff2659922 100644
--- a/packages/docusaurus-utils/src/gitUtils.ts
+++ b/packages/docusaurus-utils/src/gitUtils.ts
@@ -7,8 +7,33 @@
import path from 'path';
import fs from 'fs-extra';
+import os from 'os';
import _ from 'lodash';
import execa from 'execa';
+import PQueue from 'p-queue';
+
+// Quite high/conservative concurrency value (it was previously "Infinity")
+// See https://github.com/facebook/docusaurus/pull/10915
+const DefaultGitCommandConcurrency =
+ // TODO Docusaurus v4: bump node, availableParallelism() now always exists
+ (typeof os.availableParallelism === 'function'
+ ? os.availableParallelism()
+ : os.cpus().length) * 4;
+
+const GitCommandConcurrencyEnv = process.env.DOCUSAURUS_GIT_COMMAND_CONCURRENCY
+ ? parseInt(process.env.DOCUSAURUS_GIT_COMMAND_CONCURRENCY, 10)
+ : undefined;
+
+const GitCommandConcurrency =
+ GitCommandConcurrencyEnv && GitCommandConcurrencyEnv > 0
+ ? GitCommandConcurrencyEnv
+ : DefaultGitCommandConcurrency;
+
+// We use a queue to avoid running too many concurrent Git commands at once
+// See https://github.com/facebook/docusaurus/issues/10348
+const GitCommandQueue = new PQueue({
+ concurrency: GitCommandConcurrency,
+});
const realHasGitFn = () => {
try {
@@ -129,10 +154,13 @@ export async function getFileCommitDate(
file,
)}"`;
- const result = await execa(command, {
- cwd: path.dirname(file),
- shell: true,
- });
+ const result = (await GitCommandQueue.add(() =>
+ execa(command, {
+ cwd: path.dirname(file),
+ shell: true,
+ }),
+ ))!;
+
if (result.exitCode !== 0) {
throw new Error(
`Failed to retrieve the git history for file "${file}" with exit code ${result.exitCode}: ${result.stderr}`,
diff --git a/yarn.lock b/yarn.lock
index 78765fc47a..d61d8a7ae5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -13909,7 +13909,7 @@ p-pipe@3.1.0:
resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e"
integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==
-p-queue@6.6.2:
+p-queue@6.6.2, p-queue@^6.6.2:
version "6.6.2"
resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426"
integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==
From 8061f2267be8ebd66ff29ce7227bf4916ae85657 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Fri, 9 May 2025 17:40:58 +0200
Subject: [PATCH 12/25] feat(core): expose opt-in env variable for SSG thread
recycling (#11166)
---
packages/docusaurus/src/ssg/ssgEnv.ts | 11 +++++++++++
packages/docusaurus/src/ssg/ssgExecutor.ts | 16 +++++++++++++++-
2 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/packages/docusaurus/src/ssg/ssgEnv.ts b/packages/docusaurus/src/ssg/ssgEnv.ts
index 869be2bca7..b05eba6f93 100644
--- a/packages/docusaurus/src/ssg/ssgEnv.ts
+++ b/packages/docusaurus/src/ssg/ssgEnv.ts
@@ -27,3 +27,14 @@ export const SSGWorkerThreadTaskSize: number = process.env
.DOCUSAURUS_SSG_WORKER_THREAD_TASK_SIZE
? parseInt(process.env.DOCUSAURUS_SSG_WORKER_THREAD_TASK_SIZE, 10)
: 10; // TODO need fine-tuning
+
+// Controls worker thread recycling behavior (maxMemoryLimitBeforeRecycle)
+// See https://github.com/facebook/docusaurus/pull/11166
+// See https://github.com/facebook/docusaurus/issues/11161
+export const SSGWorkerThreadRecyclerMaxMemory: number | undefined = process.env
+ .DOCUSAURUS_SSG_WORKER_THREAD_RECYCLER_MAX_MEMORY
+ ? parseInt(process.env.DOCUSAURUS_SSG_WORKER_THREAD_RECYCLER_MAX_MEMORY, 10)
+ : // TODO we should probably provide a default value here
+ // 2gb is a quite reasonable max that should work well even for large sites
+ // we'd rather ask community feedback first
+ undefined;
diff --git a/packages/docusaurus/src/ssg/ssgExecutor.ts b/packages/docusaurus/src/ssg/ssgExecutor.ts
index 46b9c89fc8..0b4f7c703a 100644
--- a/packages/docusaurus/src/ssg/ssgExecutor.ts
+++ b/packages/docusaurus/src/ssg/ssgExecutor.ts
@@ -12,7 +12,11 @@ import _ from 'lodash';
import logger, {PerfLogger} from '@docusaurus/logger';
import {createSSGParams} from './ssgParams';
import {renderHashRouterTemplate} from './ssgTemplate';
-import {SSGWorkerThreadCount, SSGWorkerThreadTaskSize} from './ssgEnv';
+import {
+ SSGWorkerThreadCount,
+ SSGWorkerThreadRecyclerMaxMemory,
+ SSGWorkerThreadTaskSize,
+} from './ssgEnv';
import {generateHashRouterEntrypoint} from './ssgUtils';
import {createGlobalSSGResult} from './ssgGlobalResult';
import {executeSSGInlineTask} from './ssgWorkerInline';
@@ -124,6 +128,16 @@ const createPooledSSGExecutor: CreateSSGExecutor = async ({
runtime: 'worker_threads',
isolateWorkers: false,
workerData: {params},
+
+ // WORKER MEMORY MANAGEMENT
+ // Allows containing SSG memory leaks with a thread recycling workaround
+ // See https://github.com/facebook/docusaurus/pull/11166
+ // See https://github.com/facebook/docusaurus/issues/11161
+ maxMemoryLimitBeforeRecycle: SSGWorkerThreadRecyclerMaxMemory,
+ resourceLimits: {
+ // For some reason I can't figure out how to limit memory on a worker
+ // See https://x.com/sebastienlorber/status/1920781195618513143
+ },
});
},
);
From 4af898227892dca826b767ffc9f09bc48dd78233 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Fri, 9 May 2025 18:29:07 +0200
Subject: [PATCH 13/25] chore: add Node 24 to CI + fix deprecation warnings
(#11168)
---
.github/workflows/argos.yml | 1 +
.github/workflows/lighthouse-report.yml | 1 +
.github/workflows/tests-e2e.yml | 2 +-
.github/workflows/tests-windows.yml | 3 ++-
.github/workflows/tests.yml | 2 +-
.../docusaurus-mdx-loader/src/remark/transformImage/index.ts | 3 ++-
.../docusaurus-mdx-loader/src/remark/transformLinks/index.ts | 3 ++-
packages/docusaurus-utils/src/urlUtils.ts | 3 ++-
8 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/argos.yml b/.github/workflows/argos.yml
index 1caf9f471d..c1c6be47ef 100644
--- a/.github/workflows/argos.yml
+++ b/.github/workflows/argos.yml
@@ -33,6 +33,7 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: lts/*
+ cache: yarn
- name: Install dependencies
run: yarn || yarn || yarn
diff --git a/.github/workflows/lighthouse-report.yml b/.github/workflows/lighthouse-report.yml
index f62d49ce03..f588cb4c48 100644
--- a/.github/workflows/lighthouse-report.yml
+++ b/.github/workflows/lighthouse-report.yml
@@ -27,6 +27,7 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: lts/*
+ cache: yarn
- name: Install dependencies
run: yarn || yarn || yarn
diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml
index 82d2d9a8f4..1e2054dfb9 100644
--- a/.github/workflows/tests-e2e.yml
+++ b/.github/workflows/tests-e2e.yml
@@ -38,7 +38,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- node: ['18.0', '20', '22']
+ node: ['18.0', '20', '22', '24']
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
diff --git a/.github/workflows/tests-windows.yml b/.github/workflows/tests-windows.yml
index 31999b8bb9..018eb41268 100644
--- a/.github/workflows/tests-windows.yml
+++ b/.github/workflows/tests-windows.yml
@@ -27,7 +27,7 @@ jobs:
runs-on: windows-latest
strategy:
matrix:
- node: ['18.0', '20', '22']
+ node: ['18.0', '20', '22', '24']
steps:
- name: Support longpaths
run: git config --system core.longpaths true
@@ -37,6 +37,7 @@ jobs:
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node }}
+ cache: yarn
- name: Installation
run: yarn || yarn || yarn
- name: Docusaurus Jest Tests
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 7ff69a08d3..4163b3131f 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -27,7 +27,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- node: ['18.0', '20', '22']
+ node: ['18.0', '20', '22', '24']
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts b/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts
index adf59a40ad..8cc4af821d 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts
@@ -14,6 +14,7 @@ import {
escapePath,
findAsyncSequential,
getFileLoaderUtils,
+ parseURLOrPath,
} from '@docusaurus/utils';
import escapeHtml from 'escape-html';
import {imageSizeFromFile} from 'image-size/fromFile';
@@ -50,7 +51,7 @@ async function toImageRequireNode(
);
relativeImagePath = `./${relativeImagePath}`;
- const parsedUrl = url.parse(node.url);
+ const parsedUrl = parseURLOrPath(node.url, 'https://example.com');
const hash = parsedUrl.hash ?? '';
const search = parsedUrl.search ?? '';
const requireString = `${context.inlineMarkdownImageFileLoader}${
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts
index 705bad93c6..625d838433 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts
@@ -14,6 +14,7 @@ import {
escapePath,
findAsyncSequential,
getFileLoaderUtils,
+ parseURLOrPath,
} from '@docusaurus/utils';
import escapeHtml from 'escape-html';
import {assetRequireAttributeValue, transformNode} from '../utils';
@@ -51,7 +52,7 @@ async function toAssetRequireNode(
path.relative(path.dirname(context.filePath), assetPath),
)}`;
- const parsedUrl = url.parse(node.url);
+ const parsedUrl = parseURLOrPath(node.url);
const hash = parsedUrl.hash ?? '';
const search = parsedUrl.search ?? '';
diff --git a/packages/docusaurus-utils/src/urlUtils.ts b/packages/docusaurus-utils/src/urlUtils.ts
index 742a7b1681..8cbe926202 100644
--- a/packages/docusaurus-utils/src/urlUtils.ts
+++ b/packages/docusaurus-utils/src/urlUtils.ts
@@ -166,7 +166,8 @@ export function isValidPathname(str: string): boolean {
export function parseURLOrPath(url: string, base?: string | URL): URL {
try {
- // TODO when Node supports it, use URL.parse could be faster?
+ // TODO Docusaurus v4: use URL.parse()
+ // Node 24 supports it, use URL.parse could be faster?
// see https://kilianvalkhof.com/2024/javascript/the-problem-with-new-url-and-how-url-parse-fixes-that/
return new URL(url, base ?? 'https://example.com');
} catch (e) {
From dee76f8042048c798e2d16195f8e11902c1247f7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Fri, 9 May 2025 19:26:15 +0200
Subject: [PATCH 14/25] perf(core): add default for
DOCUSAURUS_SSG_WORKER_THREAD_RECYCLER_MAX_MEMORY (#11170)
---
packages/docusaurus/src/ssg/ssgEnv.ts | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/packages/docusaurus/src/ssg/ssgEnv.ts b/packages/docusaurus/src/ssg/ssgEnv.ts
index b05eba6f93..8927bb7c71 100644
--- a/packages/docusaurus/src/ssg/ssgEnv.ts
+++ b/packages/docusaurus/src/ssg/ssgEnv.ts
@@ -34,7 +34,6 @@ export const SSGWorkerThreadTaskSize: number = process.env
export const SSGWorkerThreadRecyclerMaxMemory: number | undefined = process.env
.DOCUSAURUS_SSG_WORKER_THREAD_RECYCLER_MAX_MEMORY
? parseInt(process.env.DOCUSAURUS_SSG_WORKER_THREAD_RECYCLER_MAX_MEMORY, 10)
- : // TODO we should probably provide a default value here
- // 2gb is a quite reasonable max that should work well even for large sites
- // we'd rather ask community feedback first
- undefined;
+ : // 1 GB is a quite reasonable max value
+ // It should work well even for large sites
+ 1_000_000_000;
From 2371ca7b74447f7175d20e918a680e8c5ad90981 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Tue, 13 May 2025 13:48:11 +0200
Subject: [PATCH 15/25] perf(bundler): fine-tuning of Webpack/Rspack
optimizations (#11176)
---
.../docusaurus-bundler/src/currentBundler.ts | 43 ++++
packages/docusaurus-bundler/src/index.ts | 1 +
packages/docusaurus-faster/package.json | 2 +-
packages/docusaurus-faster/src/index.ts | 10 -
.../src/commands/build/buildLocale.ts | 10 +-
packages/docusaurus/src/webpack/base.ts | 14 ++
website/docusaurus.config.ts | 14 +-
yarn.lock | 186 +++++++++---------
8 files changed, 174 insertions(+), 106 deletions(-)
diff --git a/packages/docusaurus-bundler/src/currentBundler.ts b/packages/docusaurus-bundler/src/currentBundler.ts
index 9d1730d352..7d23d82860 100644
--- a/packages/docusaurus-bundler/src/currentBundler.ts
+++ b/packages/docusaurus-bundler/src/currentBundler.ts
@@ -104,3 +104,46 @@ export async function getProgressBarPlugin({
return WebpackBar;
}
+
+export async function registerBundlerTracing({
+ currentBundler,
+}: {
+ currentBundler: CurrentBundler;
+}): Promise<() => Promise> {
+ if (currentBundler.name === 'rspack') {
+ const Rspack = await importRspack();
+
+ // See https://rspack.dev/contribute/development/profiling
+ // File can be opened with https://ui.perfetto.dev/
+ if (process.env.DOCUSAURUS_RSPACK_TRACE) {
+ // We use the env variable as the "filter" attribute
+ // See values here: https://rspack.dev/contribute/development/tracing#tracing-filter
+ let filter = process.env.DOCUSAURUS_RSPACK_TRACE;
+
+ if (filter === 'true' || filter === '1') {
+ // Default value recommended by the Rspack team
+ // It's also what the CLI uses for the "overview" preset:
+ // https://github.com/web-infra-dev/rspack/blob/v1.3.10/packages/rspack-cli/src/utils/profile.ts
+ filter = 'info';
+ }
+
+ await Rspack.experiments.globalTrace.register(
+ filter,
+ 'chrome',
+ './rspack-tracing.json',
+ );
+
+ console.info(`Rspack tracing registered, filter=${filter}`);
+
+ return async () => {
+ await Rspack.experiments.globalTrace.cleanup();
+ console.log(`Rspack tracing cleaned up, filter=${filter}`);
+ };
+ }
+ }
+
+ // We don't support Webpack tracing at the moment
+ return async () => {
+ // noop
+ };
+}
diff --git a/packages/docusaurus-bundler/src/index.ts b/packages/docusaurus-bundler/src/index.ts
index 65b2d58033..0f26ae2c15 100644
--- a/packages/docusaurus-bundler/src/index.ts
+++ b/packages/docusaurus-bundler/src/index.ts
@@ -12,6 +12,7 @@ export {
getCSSExtractPlugin,
getCopyPlugin,
getProgressBarPlugin,
+ registerBundlerTracing,
} from './currentBundler';
export {getMinimizers} from './minification';
diff --git a/packages/docusaurus-faster/package.json b/packages/docusaurus-faster/package.json
index 9bf846fbad..3d1e0bbebf 100644
--- a/packages/docusaurus-faster/package.json
+++ b/packages/docusaurus-faster/package.json
@@ -19,7 +19,7 @@
"license": "MIT",
"dependencies": {
"@docusaurus/types": "3.7.0",
- "@rspack/core": "^1.3.3",
+ "@rspack/core": "^1.3.10",
"@swc/core": "^1.7.39",
"@swc/html": "^1.7.39",
"browserslist": "^4.24.2",
diff --git a/packages/docusaurus-faster/src/index.ts b/packages/docusaurus-faster/src/index.ts
index ba0b2ae8c9..cba2c68730 100644
--- a/packages/docusaurus-faster/src/index.ts
+++ b/packages/docusaurus-faster/src/index.ts
@@ -11,16 +11,6 @@ import browserslist from 'browserslist';
import {minify as swcHtmlMinifier} from '@swc/html';
import type {JsMinifyOptions, Options as SwcOptions} from '@swc/core';
-// See https://rspack.dev/contribute/development/profiling
-// File can be opened with https://ui.perfetto.dev/
-if (process.env.DOCUSAURUS_RSPACK_TRACE) {
- Rspack.experiments.globalTrace.register(
- 'trace',
- 'chrome',
- './rspack-tracing.json',
- );
-}
-
export const swcLoader = require.resolve('swc-loader');
export const getSwcLoaderOptions = ({
diff --git a/packages/docusaurus/src/commands/build/buildLocale.ts b/packages/docusaurus/src/commands/build/buildLocale.ts
index a996c4bccd..f6b5822394 100644
--- a/packages/docusaurus/src/commands/build/buildLocale.ts
+++ b/packages/docusaurus/src/commands/build/buildLocale.ts
@@ -8,7 +8,7 @@
import fs from 'fs-extra';
import path from 'path';
import _ from 'lodash';
-import {compile} from '@docusaurus/bundler';
+import {compile, registerBundlerTracing} from '@docusaurus/bundler';
import logger, {PerfLogger} from '@docusaurus/logger';
import {loadSite} from '../../server/site';
import {handleBrokenLinks} from '../../server/brokenLinks';
@@ -35,6 +35,7 @@ export type BuildLocaleParams = {
};
const SkipBundling = process.env.DOCUSAURUS_SKIP_BUNDLING === 'true';
+const ExitAfterBundling = process.env.DOCUSAURUS_EXIT_AFTER_BUNDLING === 'true';
export async function buildLocale({
siteDir,
@@ -93,6 +94,9 @@ export async function buildLocale({
`Skipping the Docusaurus bundling step because DOCUSAURUS_SKIP_BUNDLING='true'`,
);
} else {
+ const cleanupBundlerTracing = await registerBundlerTracing({
+ currentBundler: props.currentBundler,
+ });
// Run webpack to build JS bundle (client) and static html files (server).
await PerfLogger.async(`Bundling with ${props.currentBundler.name}`, () => {
return compile({
@@ -102,6 +106,10 @@ export async function buildLocale({
currentBundler: configureWebpackUtils.currentBundler,
});
});
+ await cleanupBundlerTracing();
+ }
+ if (ExitAfterBundling) {
+ return process.exit(0);
}
const {collectedData} = await PerfLogger.async('SSG', () =>
diff --git a/packages/docusaurus/src/webpack/base.ts b/packages/docusaurus/src/webpack/base.ts
index 1eecf97d27..993e80c9fe 100644
--- a/packages/docusaurus/src/webpack/base.ts
+++ b/packages/docusaurus/src/webpack/base.ts
@@ -249,6 +249,20 @@ export async function createBaseConfig({
modules: ['node_modules', path.join(siteDir, 'node_modules')],
},
optimization: {
+ // The optimizations.concatenateModules is expensive
+ // - On the server, it's not useful to run it at all
+ // - On the client, it leads to a ~3% JS assets total size decrease
+ // Let's keep it by default, but large sites may prefer faster builds
+ // See also https://github.com/facebook/docusaurus/pull/11176
+ concatenateModules: !isServer,
+
+ // The optimizations.mergeDuplicateChunks is expensive
+ // - On the server, it's not useful to run it at all
+ // - On the client, we compared assets/js before/after and see 0 change
+ // `du -sk js-before js-after` => the JS assets have the exact same size
+ // See also https://github.com/facebook/docusaurus/pull/11176
+ mergeDuplicateChunks: true,
+
// Only minimize client bundle in production because server bundle is only
// used for static site generation
minimize: minimizeEnabled,
diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts
index a6155291af..0476dfc1f3 100644
--- a/website/docusaurus.config.ts
+++ b/website/docusaurus.config.ts
@@ -164,7 +164,19 @@ export default async function createConfigAsync() {
url: 'https://docusaurus.io',
future: {
v4: !isSlower, // Not accurate, but good enough
- experimental_faster: !isSlower,
+ experimental_faster: isSlower
+ ? false
+ : {
+ // Verbose object: easier to independently test single attributes
+ swcJsLoader: true,
+ swcJsMinimizer: true,
+ swcHtmlMinimizer: true,
+ lightningCssMinimizer: true,
+ mdxCrossCompilerCache: true,
+ rspackBundler: true,
+ rspackPersistentCache: true,
+ ssgWorkerThreads: true,
+ },
experimental_storage: {
namespace: true,
},
diff --git a/yarn.lock b/yarn.lock
index d61d8a7ae5..7d0aa88c9e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2565,48 +2565,48 @@
dependencies:
langium "3.3.1"
-"@module-federation/error-codes@0.11.2":
- version "0.11.2"
- resolved "https://registry.yarnpkg.com/@module-federation/error-codes/-/error-codes-0.11.2.tgz#880cbaf370bacb5d27e5149a93228aebe7ed084c"
- integrity sha512-ik1Qnn0I+WyEdprTck9WGlH41vGsVdUg8cfO+ZM02qOb2cZm5Vu3SlxGAobj6g7uAj0g8yINnd7h7Dci40BxQA==
+"@module-federation/error-codes@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@module-federation/error-codes/-/error-codes-0.13.1.tgz#8a1697f8e5e62baf135f8a96832f55e0afc31ead"
+ integrity sha512-azgGDBnFRfqlivHOl96ZjlFUFlukESz2Rnnz/pINiSqoBBNjUE0fcAZP4X6jgrVITuEg90YkruZa7pW9I3m7Uw==
-"@module-federation/runtime-core@0.11.2":
- version "0.11.2"
- resolved "https://registry.yarnpkg.com/@module-federation/runtime-core/-/runtime-core-0.11.2.tgz#677aced902d56afd3e44f4033e8d78d57c8aa029"
- integrity sha512-dia5kKybi6MFU0s5PgglJwN27k7n9Sf69Cy5xZ4BWaP0qlaXTsxHKO0PECHNt2Pt8jDdyU29sQ4DwAQfxpnXJQ==
+"@module-federation/runtime-core@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@module-federation/runtime-core/-/runtime-core-0.13.1.tgz#e9c8002eed251feeae1a04688dff1fb6c9aa32d1"
+ integrity sha512-TfyKfkSAentKeuvSsAItk8s5tqQSMfIRTPN2e1aoaq/kFhE+7blps719csyWSX5Lg5Es7WXKMsXHy40UgtBtuw==
dependencies:
- "@module-federation/error-codes" "0.11.2"
- "@module-federation/sdk" "0.11.2"
+ "@module-federation/error-codes" "0.13.1"
+ "@module-federation/sdk" "0.13.1"
-"@module-federation/runtime-tools@0.11.2":
- version "0.11.2"
- resolved "https://registry.yarnpkg.com/@module-federation/runtime-tools/-/runtime-tools-0.11.2.tgz#d6a5c4f61b93b647de656b08ba465590631a1316"
- integrity sha512-4MJTGAxVq6vxQRkTtTlH7Mm9AVqgn0X9kdu+7RsL7T/qU+jeYsbrntN2CWG3GVVA8r5JddXyTI1iJ0VXQZLV1w==
+"@module-federation/runtime-tools@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@module-federation/runtime-tools/-/runtime-tools-0.13.1.tgz#69fc13660b189f516e56ff9f3d55a1ddfe93d2fb"
+ integrity sha512-GEF1pxqLc80osIMZmE8j9UKZSaTm2hX2lql8tgIH/O9yK4wnF06k6LL5Ah+wJt+oJv6Dj55ri/MoxMP4SXoPNA==
dependencies:
- "@module-federation/runtime" "0.11.2"
- "@module-federation/webpack-bundler-runtime" "0.11.2"
+ "@module-federation/runtime" "0.13.1"
+ "@module-federation/webpack-bundler-runtime" "0.13.1"
-"@module-federation/runtime@0.11.2":
- version "0.11.2"
- resolved "https://registry.yarnpkg.com/@module-federation/runtime/-/runtime-0.11.2.tgz#e623136774599ce202bb4ea1e396f18fbaeee19a"
- integrity sha512-Ya9u/L6z2LvhgpqxuKCB7LcigIIRf1BbaxAZIH7mzbq/A7rZtTP7v+73E433jvgiAlbAfPSZkeoYGele6hfRwA==
+"@module-federation/runtime@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@module-federation/runtime/-/runtime-0.13.1.tgz#bd103f3e62dc335f5d7ab6e0b8f086febe5bb487"
+ integrity sha512-ZHnYvBquDm49LiHfv6fgagMo/cVJneijNJzfPh6S0CJrPS2Tay1bnTXzy8VA5sdIrESagYPaskKMGIj7YfnPug==
dependencies:
- "@module-federation/error-codes" "0.11.2"
- "@module-federation/runtime-core" "0.11.2"
- "@module-federation/sdk" "0.11.2"
+ "@module-federation/error-codes" "0.13.1"
+ "@module-federation/runtime-core" "0.13.1"
+ "@module-federation/sdk" "0.13.1"
-"@module-federation/sdk@0.11.2":
- version "0.11.2"
- resolved "https://registry.yarnpkg.com/@module-federation/sdk/-/sdk-0.11.2.tgz#965b0dcf8fb036dda9b1e6812d6ae0a394ea827d"
- integrity sha512-SBFe5xOamluT900J4AGBx+2/kCH/JbfqXoUwPSAC6PRzb8Y7LB0posnOGzmqYsLZXT37vp3d6AmJDsVoajDqxw==
+"@module-federation/sdk@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@module-federation/sdk/-/sdk-0.13.1.tgz#5b8d63719c452e6f691ce1b839f3b09079dfe4c9"
+ integrity sha512-bmf2FGQ0ymZuxYnw9bIUfhV3y6zDhaqgydEjbl4msObKMLGXZqhse2pTIIxBFpIxR1oONKX/y2FAolDCTlWKiw==
-"@module-federation/webpack-bundler-runtime@0.11.2":
- version "0.11.2"
- resolved "https://registry.yarnpkg.com/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.11.2.tgz#ef4a21e0ff8aefce9c264a57aa882ee72ecfe6aa"
- integrity sha512-WdwIE6QF+MKs/PdVu0cKPETF743JB9PZ62/qf7Uo3gU4fjsUMc37RnbJZ/qB60EaHHfjwp1v6NnhZw1r4eVsnw==
+"@module-federation/webpack-bundler-runtime@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.13.1.tgz#4a14f8626cfbbd9f4d0ec03b5c90b0aa14ba8cbd"
+ integrity sha512-QSuSIGa09S8mthbB1L6xERqrz+AzPlHR6D7RwAzssAc+IHf40U6NiTLPzUqp9mmKDhC5Tm0EISU0ZHNeJpnpBQ==
dependencies:
- "@module-federation/runtime" "0.11.2"
- "@module-federation/sdk" "0.11.2"
+ "@module-federation/runtime" "0.13.1"
+ "@module-federation/sdk" "0.13.1"
"@netlify/functions@^1.6.0":
version "1.6.0"
@@ -3262,75 +3262,75 @@
fs-extra "^11.1.1"
lodash "^4.17.21"
-"@rspack/binding-darwin-arm64@1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.3.3.tgz#f62473fa7df23f649144d61dc40d09c44a1c26b6"
- integrity sha512-vbzEdpRCZl5+HXWsVjzSDqB9ZVIlqldV+udHp4YDD8qiwdQznVaBZke0eMzZ7kaInqRPsZ+UHQuVk6JaH/JkMQ==
+"@rspack/binding-darwin-arm64@1.3.10":
+ version "1.3.10"
+ resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.3.10.tgz#676ac33fc66a7da4669e68175bf267f40b99a282"
+ integrity sha512-0k/j8OeMSVm5u5Nzckp9Ie7S7hprnvNegebnGr+L6VCyD7sMqm4m+4rLHs99ZklYdH0dZtY2+LrzrtjUZCqfew==
-"@rspack/binding-darwin-x64@1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.3.3.tgz#472ad3b6ae6c687bff1f4bb9a6f2d2ddbe3d1c19"
- integrity sha512-OXtY2s4nlYtUXkeJt8TQKKNIcN7PI8yDq0nqI75OfJoS4u1ZmRXJ8IMeSALLo8I+xD2RAF79tf7yhM/Y/AaiKQ==
+"@rspack/binding-darwin-x64@1.3.10":
+ version "1.3.10"
+ resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.3.10.tgz#79102a085443617bf70ced9dc5f7cfe2fb3dd6a6"
+ integrity sha512-jOyqYW/18cgxw60wK5oqJvM194pbD4H99xaif89McNtLkH3npFvBkXBHVWWuOHGoXNX0LhRpHcI89p9b9THQZQ==
-"@rspack/binding-linux-arm64-gnu@1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.3.3.tgz#0df2ac7ab945df162073bdcdea20171df9ee5942"
- integrity sha512-Lluq3RLYzyCMdXr/HyALKEPGsr+196x8Ccuy5AmIRosOdWuwtSiomSRH1Ka8REUFNHfYy5y9SzfmIZo/E0QEmg==
+"@rspack/binding-linux-arm64-gnu@1.3.10":
+ version "1.3.10"
+ resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.3.10.tgz#192c7f27013119f6fc082fca03525897726eda21"
+ integrity sha512-zhF5ZNaT/7pxrm8xD3dWG1b4x+FO3LbVeZZG448CjpSo5T57kPD+SaGUU1GcPpn6mexf795x0SVS49aH7/e3Dg==
-"@rspack/binding-linux-arm64-musl@1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.3.3.tgz#082d45d2a92255656cadf4ebe801dc09726140fa"
- integrity sha512-PIsicXWjOqzmoOutUqxpMNkCoKo+8/wxDyKxHFeu+5WIAxVFphe2d3H5qvEjc2MasWSdRmAVn9XiuIj2LIXFzA==
+"@rspack/binding-linux-arm64-musl@1.3.10":
+ version "1.3.10"
+ resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.3.10.tgz#2be001351a7f2e93d7a189b8607137343648a682"
+ integrity sha512-o3x7IrOSCHK6lcRvdZgsSuOG1CHRQR00xiyLW7kkWmNm7t417LC9xdFWKIK62C5fKXGC5YcTbUkDMnQujespkg==
-"@rspack/binding-linux-x64-gnu@1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.3.3.tgz#2be5a5eff56c3dd2f58ff7e42abe3c9484d42b20"
- integrity sha512-BtksK73ZFdny2T/wU1x0kxBF4ruYUUArZDyeGfpO+vd/1nNYqzzdhGvOksKmtdvsO38ETr2gZ9+XZyr1vpy9uQ==
+"@rspack/binding-linux-x64-gnu@1.3.10":
+ version "1.3.10"
+ resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.3.10.tgz#ad44d1dee50688ed81e87235d774bfe22528ce20"
+ integrity sha512-FMSi28VZhXMr15picOHFynULhqZ/FODPxRIS6uNrvPRYcbNuiO1v+VHV6X88mhOMmJ/aVF6OwjUO/o2l1FVa9Q==
-"@rspack/binding-linux-x64-musl@1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.3.3.tgz#019d02dbcc25e0c16ca01f67bebf4b6bde3f4ae9"
- integrity sha512-jx86CxkTmyBz/eHDqZp1mCqBwY+UTEtaPlPoWFyGkJUR5ey6nQnxS+fhG34Rqz63chW+q/afwpGNGyALYdgc8g==
+"@rspack/binding-linux-x64-musl@1.3.10":
+ version "1.3.10"
+ resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.3.10.tgz#24ff3df3017c44e570a604e64312b0a3c603a133"
+ integrity sha512-e0xbY9SlbRGIFz41v1yc0HfREvmgMnLV1bLmTSPK8wI2suIEJ7iYYqsocHOAOk86qLZcxVrTnL6EjUcNaRTWlg==
-"@rspack/binding-win32-arm64-msvc@1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.3.3.tgz#204b33735d9b5e2c96ec0419f1e2151d1c2c4bcf"
- integrity sha512-uXAdDzajFToVrH3fCNVDP/uKQ9i5FQjJc2aYxsnhS9Su/CZB+UQsOecbq6MnIN2s0B9GBKBG8QdQEtS3RtC6Hg==
+"@rspack/binding-win32-arm64-msvc@1.3.10":
+ version "1.3.10"
+ resolved "https://registry.yarnpkg.com/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.3.10.tgz#d853250fee6b066a815c3d29abd115db331a4b6b"
+ integrity sha512-YHJPvEujWeWjU6EUF6sDpaec9rsOtKVvy16YCtGaxRpDQXqfuxibnp6Ge0ZTTrY+joRiWehRA9OUI+3McqI+QA==
-"@rspack/binding-win32-ia32-msvc@1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.3.3.tgz#66fe0ebd3b44c34393862693d7bbb8fc68624504"
- integrity sha512-VBE6XsJ3IiAlozAywAIxAZ1Aqc2QVnEwBo0gP9998KkwL7wxB6Bg/OJnPbH3Q0ZaNWAQViC99rPC+5hSIdeSxw==
+"@rspack/binding-win32-ia32-msvc@1.3.10":
+ version "1.3.10"
+ resolved "https://registry.yarnpkg.com/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.3.10.tgz#40d401aa1c7550f2b5688b68aee54de3abca67b1"
+ integrity sha512-2iwSBzVBC89ZSk56MYwgirh3bda2WKmL9I3qPajiTEivChXpX7jp83jAtGE6CPqPYcccYz6nrURTHNUZhqXxVw==
-"@rspack/binding-win32-x64-msvc@1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.3.3.tgz#d17a96dbb7cb5730742127c58dc3bec3f6f39333"
- integrity sha512-rOsNz4/DFgSENjEh0t9kFn89feuXK14/9wbmmFlT8VMpYOCcj4tKcAHjWg+Nzzj4FL+NSOC/81SrUF9J+C2R7w==
+"@rspack/binding-win32-x64-msvc@1.3.10":
+ version "1.3.10"
+ resolved "https://registry.yarnpkg.com/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.3.10.tgz#4eefa2d1d851c25ba0e0b1eee0ee525be224c1cd"
+ integrity sha512-ehWJ9Y5Zezj/+uJpiWbt29RZaRIM00f91PWuabM6/sKmHJhdCEE21u5iI3B8DeW/EjJsH8zkI69YYAxJWwGn9A==
-"@rspack/binding@1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@rspack/binding/-/binding-1.3.3.tgz#19c104b7eed3bb01ec6089cee9f08d41acc035a4"
- integrity sha512-zdwJ801tyC8k+Gu5RjNoc7bEtX0MgJzzVv9qpaMwcAUfUfwZgCzXPTqcGMDoNI+Z47Fw59/2fKCmgZhZn60AgA==
+"@rspack/binding@1.3.10":
+ version "1.3.10"
+ resolved "https://registry.yarnpkg.com/@rspack/binding/-/binding-1.3.10.tgz#b14d9a21a7bae8c7025e6c2d9707e81c311147d5"
+ integrity sha512-9TjO+Ig5U4VqdYWpBsv01n4d2KsgFfdXGIE7zdHXDDbry0avL0J4109ESqSeP9uC+Bi7ZCF53iaxJRvZDflNVQ==
optionalDependencies:
- "@rspack/binding-darwin-arm64" "1.3.3"
- "@rspack/binding-darwin-x64" "1.3.3"
- "@rspack/binding-linux-arm64-gnu" "1.3.3"
- "@rspack/binding-linux-arm64-musl" "1.3.3"
- "@rspack/binding-linux-x64-gnu" "1.3.3"
- "@rspack/binding-linux-x64-musl" "1.3.3"
- "@rspack/binding-win32-arm64-msvc" "1.3.3"
- "@rspack/binding-win32-ia32-msvc" "1.3.3"
- "@rspack/binding-win32-x64-msvc" "1.3.3"
+ "@rspack/binding-darwin-arm64" "1.3.10"
+ "@rspack/binding-darwin-x64" "1.3.10"
+ "@rspack/binding-linux-arm64-gnu" "1.3.10"
+ "@rspack/binding-linux-arm64-musl" "1.3.10"
+ "@rspack/binding-linux-x64-gnu" "1.3.10"
+ "@rspack/binding-linux-x64-musl" "1.3.10"
+ "@rspack/binding-win32-arm64-msvc" "1.3.10"
+ "@rspack/binding-win32-ia32-msvc" "1.3.10"
+ "@rspack/binding-win32-x64-msvc" "1.3.10"
-"@rspack/core@^1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@rspack/core/-/core-1.3.3.tgz#1eba8c52878ab898e2c06cfbffd3ee32881e8054"
- integrity sha512-+mXVlFcYr0tWezZfJ/gR0fj8njRc7pzEMtTFa2NO5cfsNAKPF/SXm4rb55kfa63r0b3U3N7f2nKrJG9wyG7zMQ==
+"@rspack/core@^1.3.10":
+ version "1.3.10"
+ resolved "https://registry.yarnpkg.com/@rspack/core/-/core-1.3.10.tgz#6b00365c3ccfd1008d598d1ce5a82bab466dc777"
+ integrity sha512-YomvSRGuMUQgCE2rNMdff2q1Z0YpZw/z6m5+PVTMSs9l/q69YKUzpbpSD8YyB5i1DddrRxC2RE34DkrBuwlREQ==
dependencies:
- "@module-federation/runtime-tools" "0.11.2"
- "@rspack/binding" "1.3.3"
+ "@module-federation/runtime-tools" "0.13.1"
+ "@rspack/binding" "1.3.10"
"@rspack/lite-tapable" "1.0.1"
- caniuse-lite "^1.0.30001707"
+ caniuse-lite "^1.0.30001717"
"@rspack/lite-tapable@1.0.1":
version "1.0.1"
@@ -5911,10 +5911,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669, caniuse-lite@^1.0.30001707:
- version "1.0.30001712"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz#41ee150f12de11b5f57c5889d4f30deb451deedf"
- integrity sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669, caniuse-lite@^1.0.30001717:
+ version "1.0.30001718"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz#dae13a9c80d517c30c6197515a96131c194d8f82"
+ integrity sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==
ccount@^2.0.0:
version "2.0.1"
From d6be84a1e665df55042dfea1dd5c3ba1f7e022d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Tue, 13 May 2025 14:38:03 +0200
Subject: [PATCH 16/25] perf(core): fix bad value for mergeDuplicateChunks
(typo) (#11177)
---
packages/docusaurus/src/webpack/base.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/docusaurus/src/webpack/base.ts b/packages/docusaurus/src/webpack/base.ts
index 993e80c9fe..ab20bbc51e 100644
--- a/packages/docusaurus/src/webpack/base.ts
+++ b/packages/docusaurus/src/webpack/base.ts
@@ -261,7 +261,7 @@ export async function createBaseConfig({
// - On the client, we compared assets/js before/after and see 0 change
// `du -sk js-before js-after` => the JS assets have the exact same size
// See also https://github.com/facebook/docusaurus/pull/11176
- mergeDuplicateChunks: true,
+ mergeDuplicateChunks: false,
// Only minimize client bundle in production because server bundle is only
// used for static site generation
From dbc4ed2c1583d1180ef03b06a518e63c932b2c53 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 13 May 2025 14:39:38 +0200
Subject: [PATCH 17/25] chore(deps): bump actions/dependency-review-action from
4.6.0 to 4.7.0 (#11174)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/dependency-review.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index 872531af0a..cd9647f2cc 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -15,4 +15,4 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Dependency Review
- uses: actions/dependency-review-action@ce3cf9537a52e8119d91fd484ab5b8a807627bf8 # 4.6.0
+ uses: actions/dependency-review-action@38ecb5b593bf0eb19e335c03f97670f792489a8b # 4.7.0
From e839b67034e05f09bd68cfe8e73df95a98e8e989 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Wed, 14 May 2025 17:01:54 +0200
Subject: [PATCH 18/25] perf(core): disable Rspack `parallelCodeSplitting`
temporarily (#11178)
Co-authored-by: slorber <749374+slorber@users.noreply.github.com>
---
.gitignore | 3 +++
package.json | 3 ++-
packages/docusaurus-utils/src/lastUpdateUtils.ts | 5 ++++-
packages/docusaurus/src/webpack/base.ts | 8 ++++++--
project-words.txt | 2 ++
website/package.json | 3 ++-
website/profileSamply.sh | 6 ++++++
7 files changed, 25 insertions(+), 5 deletions(-)
create mode 100755 website/profileSamply.sh
diff --git a/.gitignore b/.gitignore
index d44178396a..5776519d88 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,3 +48,6 @@ website/i18n/**/*
website/rspack-tracing.json
website/bundler-cpu-profile.json
+website/profile.json.gz
+
+
diff --git a/package.json b/package.json
index e48928ab6b..0e9d63abf9 100644
--- a/package.json
+++ b/package.json
@@ -31,8 +31,9 @@
"build:website:deployPreview": "yarn build:website:deployPreview:testWrap && yarn build:website:deployPreview:build",
"build:website:fast": "yarn workspace website build:fast",
"build:website:fast:rsdoctor": "yarn workspace website build:fast:rsdoctor",
- "build:website:fast:profile": "yarn workspace website build:fast:profile",
"build:website:en": "yarn workspace website build --locale en",
+ "profile:bundle:cpu": "yarn workspace website profile:bundle:cpu:profile",
+ "profile:bundle:samply": "yarn workspace website profile:bundle:samply",
"clear:website": "yarn workspace website clear",
"serve:website": "yarn workspace website serve",
"serve:website:baseUrl": "serve website",
diff --git a/packages/docusaurus-utils/src/lastUpdateUtils.ts b/packages/docusaurus-utils/src/lastUpdateUtils.ts
index a11dc782ef..5b024d0067 100644
--- a/packages/docusaurus-utils/src/lastUpdateUtils.ts
+++ b/packages/docusaurus-utils/src/lastUpdateUtils.ts
@@ -71,7 +71,10 @@ export const LAST_UPDATE_FALLBACK: LastUpdateData = {
export async function getLastUpdate(
filePath: string,
): Promise {
- if (process.env.NODE_ENV !== 'production') {
+ if (
+ process.env.NODE_ENV !== 'production' ||
+ process.env.DOCUSAURUS_DISABLE_LAST_UPDATE === 'true'
+ ) {
// Use fake data in dev/test for faster development.
return LAST_UPDATE_FALLBACK;
}
diff --git a/packages/docusaurus/src/webpack/base.ts b/packages/docusaurus/src/webpack/base.ts
index ab20bbc51e..c05e022fcf 100644
--- a/packages/docusaurus/src/webpack/base.ts
+++ b/packages/docusaurus/src/webpack/base.ts
@@ -187,6 +187,10 @@ export async function createBaseConfig({
// @ts-expect-error: Rspack-only, not available in Webpack typedefs
incremental: !isProd && !process.env.DISABLE_RSPACK_INCREMENTAL,
+ // TODO re-enable later, there's an Rspack performance issue
+ // see https://github.com/facebook/docusaurus/pull/11178
+ parallelCodeSplitting: false,
+
...PersistentCacheAttributes,
};
}
@@ -249,14 +253,14 @@ export async function createBaseConfig({
modules: ['node_modules', path.join(siteDir, 'node_modules')],
},
optimization: {
- // The optimizations.concatenateModules is expensive
+ // The optimization.concatenateModules is expensive
// - On the server, it's not useful to run it at all
// - On the client, it leads to a ~3% JS assets total size decrease
// Let's keep it by default, but large sites may prefer faster builds
// See also https://github.com/facebook/docusaurus/pull/11176
concatenateModules: !isServer,
- // The optimizations.mergeDuplicateChunks is expensive
+ // The optimization.mergeDuplicateChunks is expensive
// - On the server, it's not useful to run it at all
// - On the client, we compared assets/js before/after and see 0 change
// `du -sk js-before js-after` => the JS assets have the exact same size
diff --git a/project-words.txt b/project-words.txt
index 2151d48837..d048138934 100644
--- a/project-words.txt
+++ b/project-words.txt
@@ -266,6 +266,8 @@ Rspack
rspack
Rspress
rtcts
+Samply
+samply
saurus
Scaleway
Sebastien
diff --git a/website/package.json b/website/package.json
index 92cfcecfc5..b496d2d960 100644
--- a/website/package.json
+++ b/website/package.json
@@ -23,7 +23,8 @@
"build:blogOnly": "cross-env yarn build --config=docusaurus.config-blog-only.js",
"build:fast": "cross-env BUILD_FAST=true yarn build --locale en",
"build:fast:rsdoctor": "cross-env BUILD_FAST=true RSDOCTOR=true yarn build --locale en",
- "build:fast:profile": "cross-env BUILD_FAST=true node --cpu-prof --cpu-prof-dir .cpu-prof ./node_modules/.bin/docusaurus build --locale en",
+ "profile:bundle:cpu": "DOCUSAURUS_EXIT_AFTER_BUNDLING=true node --cpu-prof --cpu-prof-dir .cpu-prof ./node_modules/.bin/docusaurus build --locale en",
+ "profile:bundle:samply": "./profileSamply.sh",
"netlify:build:production": "yarn docusaurus write-translations && yarn netlify:crowdin:delay && yarn netlify:crowdin:uploadSources && yarn netlify:crowdin:downloadTranslations && yarn build",
"netlify:build:branchDeploy": "yarn build",
"netlify:build:deployPreview": "yarn build",
diff --git a/website/profileSamply.sh b/website/profileSamply.sh
new file mode 100755
index 0000000000..c0bb90fe07
--- /dev/null
+++ b/website/profileSamply.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+
+echo "This Samply profiling script doesn't work when it's run with Yarn, but you can run it manually instead"
+
+samply record -- DOCUSAURUS_EXIT_AFTER_BUNDLING=true node --perf-prof --perf-basic-prof --interpreted-frames-native-stack ./node_modules/.bin/docusaurus build --locale en
From f3e3f54e5c5d6ddf19d16ce525b0b3426882c114 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Wed, 14 May 2025 20:48:05 +0200
Subject: [PATCH 19/25] fix(mdx-loader): remove opt-in for mdx dependency file
(#11179)
---
packages/docusaurus-plugin-content-docs/src/index.ts | 7 -------
1 file changed, 7 deletions(-)
diff --git a/packages/docusaurus-plugin-content-docs/src/index.ts b/packages/docusaurus-plugin-content-docs/src/index.ts
index a774c2b52c..6c0b93f7b3 100644
--- a/packages/docusaurus-plugin-content-docs/src/index.ts
+++ b/packages/docusaurus-plugin-content-docs/src/index.ts
@@ -77,13 +77,6 @@ async function createMdxLoaderDependencyFile({
options: PluginOptions;
versionsMetadata: VersionMetadata[];
}): Promise {
- // TODO this has been temporarily made opt-in until Rspack cache bug is fixed
- // See https://github.com/facebook/docusaurus/pull/10931
- // See https://github.com/facebook/docusaurus/pull/10934#issuecomment-2672253145
- if (!process.env.DOCUSAURUS_ENABLE_MDX_DEPENDENCY_FILE) {
- return undefined;
- }
-
const filePath = path.join(dataDir, '__mdx-loader-dependency.json');
// the cache is invalidated whenever this file content changes
const fileContent = {
From c419d7ec88c142c6b3f602cdc842729cbc867e84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Thu, 15 May 2025 12:45:28 +0200
Subject: [PATCH 20/25] docs(ideal-image): Add warning for pnpm 10+ and `sharp`
install script (#11180)
---
website/docs/api/plugins/plugin-ideal-image.mdx | 14 ++++++++++++++
.../api/plugins/plugin-ideal-image.mdx | 12 ++++++++++++
2 files changed, 26 insertions(+)
diff --git a/website/docs/api/plugins/plugin-ideal-image.mdx b/website/docs/api/plugins/plugin-ideal-image.mdx
index 16f3a4d987..c6793466db 100644
--- a/website/docs/api/plugins/plugin-ideal-image.mdx
+++ b/website/docs/api/plugins/plugin-ideal-image.mdx
@@ -45,6 +45,20 @@ This plugin registers a [Webpack loader](https://webpack.js.org/loaders/) that c
:::
+:::warning For pnpm users
+
+Starting with [pnpm 10](https://github.com/pnpm/pnpm/releases/tag/v10.0.0), running `pnpm install` won't run dependency install scripts by default. You'll need additional pnpm configuration ([issue](https://github.com/lovell/sharp/issues/4343)) for our `sharp` image resizing dependency to install correctly, such as:
+
+```json title="package.json"
+{
+ "pnpm": {
+ "onlyBuiltDependencies": ["fsevents"]
+ }
+}
+```
+
+:::
+
## Configuration {#configuration}
Accepted fields:
diff --git a/website/versioned_docs/version-3.7.0/api/plugins/plugin-ideal-image.mdx b/website/versioned_docs/version-3.7.0/api/plugins/plugin-ideal-image.mdx
index 16f3a4d987..bbc5442861 100644
--- a/website/versioned_docs/version-3.7.0/api/plugins/plugin-ideal-image.mdx
+++ b/website/versioned_docs/version-3.7.0/api/plugins/plugin-ideal-image.mdx
@@ -45,6 +45,18 @@ This plugin registers a [Webpack loader](https://webpack.js.org/loaders/) that c
:::
+:::warning For pnpm users
+
+Starting with [pnpm 10](https://github.com/pnpm/pnpm/releases/tag/v10.0.0), running `pnpm install` won't run dependency install scripts by default. You'll need additional pnpm configuration ([issue](https://github.com/lovell/sharp/issues/4343)) for our `sharp` image resizing dependency to install correctly, such as:
+
+```json title="package.json"
+{
+ "pnpm": {
+ "onlyBuiltDependencies": ["fsevents"]
+ }
+}
+```
+
## Configuration {#configuration}
Accepted fields:
From 9f6360ba820899af9eb8192b8b7a03f116d40e7c Mon Sep 17 00:00:00 2001
From: Shreedhar Bhat <57760562+shreedharbhat98@users.noreply.github.com>
Date: Thu, 15 May 2025 16:52:00 +0530
Subject: [PATCH 21/25] refactor(content-blog): replace `reading-time` with
`Intl.Segmenter` API (#11138)
Co-authored-by: sebastien
---
.../package.json | 1 -
.../__snapshots__/index.test.ts.snap | 2 +-
.../src/__tests__/feed.test.ts | 14 +++---
.../src/__tests__/index.test.ts | 6 +--
.../src/__tests__/readingTime.test.ts | 22 +++++-----
.../src/blogUtils.ts | 5 ++-
.../src/options.ts | 3 +-
.../src/plugin-content-blog.d.ts | 21 +++------
.../src/readingTime.ts | 44 ++++++++++++++-----
website/_dogfooding/dogfooding.config.ts | 6 ++-
.../docs/api/plugins/plugin-content-blog.mdx | 3 +-
website/docs/blog.mdx | 33 ++++++++++----
yarn.lock | 5 ---
13 files changed, 96 insertions(+), 69 deletions(-)
diff --git a/packages/docusaurus-plugin-content-blog/package.json b/packages/docusaurus-plugin-content-blog/package.json
index 8d7e3c1179..4b5f96fcf0 100644
--- a/packages/docusaurus-plugin-content-blog/package.json
+++ b/packages/docusaurus-plugin-content-blog/package.json
@@ -43,7 +43,6 @@
"feed": "^4.2.2",
"fs-extra": "^11.1.1",
"lodash": "^4.17.21",
- "reading-time": "^1.5.0",
"schema-dts": "^1.1.2",
"srcset": "^4.0.0",
"tslib": "^2.6.0",
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap
index 30c2809187..0b0f653af2 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap
@@ -150,7 +150,7 @@ exports[`blog plugin process blog posts load content 2`] = `
"title": "Another With Tag",
},
"permalink": "/blog/simple/slug/another",
- "readingTime": 0.015,
+ "readingTime": 0.02,
"source": "@site/blog/another-simple-slug-with-tags.md",
"tags": [
{
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
index 96d0d857d7..7241282e1d 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
@@ -120,7 +120,7 @@ describe.each(['atom', 'rss', 'json'] as const)('%s', (feedType) => {
xslt: {atom: null, rss: null},
},
readingTime: ({content, defaultReadingTime}) =>
- defaultReadingTime({content}),
+ defaultReadingTime({content, locale: 'en'}),
truncateMarker: //,
onInlineTags: 'ignore',
onInlineAuthors: 'ignore',
@@ -164,7 +164,7 @@ describe.each(['atom', 'rss', 'json'] as const)('%s', (feedType) => {
xslt: {atom: null, rss: null},
},
readingTime: ({content, defaultReadingTime}) =>
- defaultReadingTime({content}),
+ defaultReadingTime({content, locale: 'en'}),
truncateMarker: //,
onInlineTags: 'ignore',
onInlineAuthors: 'ignore',
@@ -220,7 +220,7 @@ describe.each(['atom', 'rss', 'json'] as const)('%s', (feedType) => {
xslt: {atom: null, rss: null},
},
readingTime: ({content, defaultReadingTime}) =>
- defaultReadingTime({content}),
+ defaultReadingTime({content, locale: 'en'}),
truncateMarker: //,
onInlineTags: 'ignore',
onInlineAuthors: 'ignore',
@@ -267,7 +267,7 @@ describe.each(['atom', 'rss', 'json'] as const)('%s', (feedType) => {
xslt: {atom: null, rss: null},
},
readingTime: ({content, defaultReadingTime}) =>
- defaultReadingTime({content}),
+ defaultReadingTime({content, locale: 'en'}),
truncateMarker: //,
onInlineTags: 'ignore',
onInlineAuthors: 'ignore',
@@ -314,7 +314,7 @@ describe.each(['atom', 'rss', 'json'] as const)('%s', (feedType) => {
xslt: {atom: null, rss: null},
},
readingTime: ({content, defaultReadingTime}) =>
- defaultReadingTime({content}),
+ defaultReadingTime({content, locale: 'en'}),
truncateMarker: //,
onInlineTags: 'ignore',
onInlineAuthors: 'ignore',
@@ -360,7 +360,7 @@ describe.each(['atom', 'rss', 'json'] as const)('%s', (feedType) => {
xslt: true,
},
readingTime: ({content, defaultReadingTime}) =>
- defaultReadingTime({content}),
+ defaultReadingTime({content, locale: 'en'}),
truncateMarker: //,
onInlineTags: 'ignore',
onInlineAuthors: 'ignore',
@@ -409,7 +409,7 @@ describe.each(['atom', 'rss', 'json'] as const)('%s', (feedType) => {
},
},
readingTime: ({content, defaultReadingTime}) =>
- defaultReadingTime({content}),
+ defaultReadingTime({content, locale: 'en'}),
truncateMarker: //,
onInlineTags: 'ignore',
onInlineAuthors: 'ignore',
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts
index 5d8cff5778..ebbca9b18d 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts
@@ -211,7 +211,7 @@ describe('blog plugin', () => {
).toEqual({
editUrl: `${BaseEditUrl}/blog/2018-12-14-Happy-First-Birthday-Slash.md`,
permalink: '/blog/2018/12/14/Happy-First-Birthday-Slash',
- readingTime: 0.015,
+ readingTime: 0.02,
source: path.posix.join(
'@site',
path.posix.join('i18n', 'en', 'docusaurus-plugin-content-blog'),
@@ -276,7 +276,7 @@ describe('blog plugin', () => {
}).toEqual({
editUrl: `${BaseEditUrl}/blog/complex-slug.md`,
permalink: '/blog/hey/my super path/héllô',
- readingTime: 0.015,
+ readingTime: 0.02,
source: path.posix.join('@site', PluginPath, 'complex-slug.md'),
title: 'Complex Slug',
description: `complex url slug`,
@@ -318,7 +318,7 @@ describe('blog plugin', () => {
}).toEqual({
editUrl: `${BaseEditUrl}/blog/simple-slug.md`,
permalink: '/blog/simple/slug',
- readingTime: 0.015,
+ readingTime: 0.02,
source: path.posix.join('@site', PluginPath, 'simple-slug.md'),
title: 'Simple Slug',
description: `simple url slug`,
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/readingTime.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/readingTime.test.ts
index 98b130418e..1f62148d6b 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/readingTime.test.ts
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/readingTime.test.ts
@@ -9,48 +9,46 @@ import {calculateReadingTime} from '../readingTime';
describe('calculateReadingTime', () => {
it('calculates reading time for empty content', () => {
- expect(calculateReadingTime('')).toBe(0);
+ expect(calculateReadingTime('', 'en')).toBe(0);
});
it('calculates reading time for short content', () => {
const content = 'This is a short test content.';
- expect(calculateReadingTime(content)).toBe(0.03);
+ expect(calculateReadingTime(content, 'en')).toBe(0.03);
});
it('calculates reading time for long content', () => {
const content = 'This is a test content. '.repeat(100);
- expect(calculateReadingTime(content)).toBe(2.5);
+ expect(calculateReadingTime(content, 'en')).toBe(2.5);
});
it('respects custom words per minute', () => {
const content = 'This is a test content. '.repeat(100);
- expect(calculateReadingTime(content, {wordsPerMinute: 100})).toBe(5);
+ expect(calculateReadingTime(content, 'en', {wordsPerMinute: 100})).toBe(5);
});
it('handles content with special characters', () => {
const content = 'Hello! How are you? This is a test...';
- expect(calculateReadingTime(content)).toBe(0.04);
+ expect(calculateReadingTime(content, 'en')).toBe(0.04);
});
it('handles content with multiple lines', () => {
- const content = `This is line 1.
- This is line 2.
- This is line 3.`;
- expect(calculateReadingTime(content)).toBe(0.06);
+ const content = `This is line 1.\n This is line 2.\n This is line 3.`;
+ expect(calculateReadingTime(content, 'en')).toBe(0.06);
});
it('handles content with HTML tags', () => {
const content = 'This is a test content.
';
- expect(calculateReadingTime(content)).toBe(0.025);
+ expect(calculateReadingTime(content, 'en')).toBe(0.05);
});
it('handles content with markdown', () => {
const content = '# Title\n\nThis is **bold** and *italic* text.';
- expect(calculateReadingTime(content)).toBe(0.04);
+ expect(calculateReadingTime(content, 'en')).toBe(0.04);
});
it('handles CJK content', () => {
const content = '你好,世界!这是一段测试内容。';
- expect(calculateReadingTime(content)).toBe(0.06);
+ expect(calculateReadingTime(content, 'zh')).toBe(0.04);
});
});
diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts
index 2cc84bac76..cd3c1b8837 100644
--- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts
+++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts
@@ -210,8 +210,8 @@ async function parseBlogPostMarkdownFile({
}
}
-const defaultReadingTime: ReadingTimeFunction = ({content, options}) =>
- calculateReadingTime(content, options);
+const defaultReadingTime: ReadingTimeFunction = ({content, locale, options}) =>
+ calculateReadingTime(content, locale, options);
async function processBlogSourceFile(
blogSourceRelative: string,
@@ -373,6 +373,7 @@ async function processBlogSourceFile(
content,
frontMatter,
defaultReadingTime,
+ locale: i18n.currentLocale,
})
: undefined,
hasTruncateMarker: truncateMarker.test(content),
diff --git a/packages/docusaurus-plugin-content-blog/src/options.ts b/packages/docusaurus-plugin-content-blog/src/options.ts
index 20981aaa87..fe2d64e664 100644
--- a/packages/docusaurus-plugin-content-blog/src/options.ts
+++ b/packages/docusaurus-plugin-content-blog/src/options.ts
@@ -63,7 +63,8 @@ export const DEFAULT_OPTIONS: PluginOptions = {
path: 'blog',
editLocalizedFiles: false,
authorsMapPath: 'authors.yml',
- readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}),
+ readingTime: ({content, defaultReadingTime, locale}) =>
+ defaultReadingTime({content, locale}),
sortPosts: 'descending',
showLastUpdateTime: false,
showLastUpdateAuthor: false,
diff --git a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts
index a3056261cf..610fa1052a 100644
--- a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts
+++ b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts
@@ -387,15 +387,10 @@ declare module '@docusaurus/plugin-content-blog' {
};
/**
- * Duplicate from ngryman/reading-time to keep stability of API.
+ * Options for reading time calculation using Intl.Segmenter.
*/
type ReadingTimeOptions = {
wordsPerMinute?: number;
- /**
- * @param char The character to be matched.
- * @returns `true` if this character is a word bound.
- */
- wordBound?: (char: string) => boolean;
};
/**
@@ -405,24 +400,22 @@ declare module '@docusaurus/plugin-content-blog' {
export type ReadingTimeFunction = (params: {
/** Markdown content. */
content: string;
+ /** Locale for word segmentation. */
+ locale: string;
/** Front matter. */
frontMatter?: BlogPostFrontMatter & {[key: string]: unknown};
- /** Options accepted by ngryman/reading-time. */
+ /** Options for reading time calculation. */
options?: ReadingTimeOptions;
}) => number;
/**
- * @returns The reading time directly plugged into metadata. `undefined` to
- * hide reading time for a specific post.
+ * @returns The reading time directly plugged into metadata.
+ * `undefined` to hide reading time for a specific post.
*/
export type ReadingTimeFunctionOption = (
- /**
- * The `options` is not provided by the caller; the user can inject their
- * own option values into `defaultReadingTime`
- */
params: Required[0], 'options'>> & {
/**
- * The default reading time implementation from ngryman/reading-time.
+ * The default reading time implementation.
*/
defaultReadingTime: ReadingTimeFunction;
},
diff --git a/packages/docusaurus-plugin-content-blog/src/readingTime.ts b/packages/docusaurus-plugin-content-blog/src/readingTime.ts
index 76e4513537..7bdf10c767 100644
--- a/packages/docusaurus-plugin-content-blog/src/readingTime.ts
+++ b/packages/docusaurus-plugin-content-blog/src/readingTime.ts
@@ -5,25 +5,45 @@
* LICENSE file in the root directory of this source tree.
*/
-import readingTime from 'reading-time';
-
const DEFAULT_WORDS_PER_MINUTE = 200;
-interface ReadingTimeOptions {
- wordsPerMinute?: number;
- wordBound?: (char: string) => boolean;
+/**
+ * Counts the number of words in a string using Intl.Segmenter.
+ * @param content The text content to count words in.
+ * @param locale The locale to use for segmentation.
+ */
+function countWords(content: string, locale: string): number {
+ if (!content) {
+ return 0;
+ }
+ const segmenter = new Intl.Segmenter(locale, {granularity: 'word'});
+ let wordCount = 0;
+ for (const {isWordLike} of segmenter.segment(content)) {
+ if (isWordLike) {
+ wordCount += 1;
+ }
+ }
+ return wordCount;
}
/**
- * Calculates the reading time for a given content string.
- * Uses the reading-time package under the hood.
+ * Calculates the reading time for a given content string using Intl.Segmenter.
+ * @param content The text content to calculate reading time for.
+ * @param locale Required locale string for Intl.Segmenter
+ * @param options Options for reading time calculation.
+ * - wordsPerMinute: number of words per minute (default 200)
+ * @returns Estimated reading time in minutes (float, rounded to 2 decimals)
*/
export function calculateReadingTime(
content: string,
- options: ReadingTimeOptions = {},
+ locale: string,
+ options?: {wordsPerMinute?: number},
): number {
- const wordsPerMinute = options.wordsPerMinute ?? DEFAULT_WORDS_PER_MINUTE;
- const {wordBound} = options;
- return readingTime(content, {wordsPerMinute, ...(wordBound && {wordBound})})
- .minutes;
+ const wordsPerMinute = options?.wordsPerMinute ?? DEFAULT_WORDS_PER_MINUTE;
+ const words = countWords(content, locale);
+ if (words === 0) {
+ return 0;
+ }
+ // Calculate reading time in minutes and round to 2 decimal places
+ return Math.round((words / wordsPerMinute) * 100) / 100;
}
diff --git a/website/_dogfooding/dogfooding.config.ts b/website/_dogfooding/dogfooding.config.ts
index 192cb4a8c2..59e90dcc8a 100644
--- a/website/_dogfooding/dogfooding.config.ts
+++ b/website/_dogfooding/dogfooding.config.ts
@@ -104,7 +104,11 @@ export const dogfoodingPluginInstances: PluginConfig[] = [
readingTime: ({content, frontMatter, defaultReadingTime}) =>
frontMatter.hide_reading_time
? undefined
- : defaultReadingTime({content, options: {wordsPerMinute: 5}}),
+ : defaultReadingTime({
+ content,
+ locale: 'en',
+ options: {wordsPerMinute: 5},
+ }),
onInlineTags: 'warn',
onInlineAuthors: 'ignore',
onUntruncatedBlogPosts: 'ignore',
diff --git a/website/docs/api/plugins/plugin-content-blog.mdx b/website/docs/api/plugins/plugin-content-blog.mdx
index 1a1703b7c4..8dee6fdcda 100644
--- a/website/docs/api/plugins/plugin-content-blog.mdx
+++ b/website/docs/api/plugins/plugin-content-blog.mdx
@@ -109,17 +109,18 @@ type EditUrlFunction = (params: {
```ts
type ReadingTimeOptions = {
wordsPerMinute: number;
- wordBound: (char: string) => boolean;
};
type ReadingTimeCalculator = (params: {
content: string;
+ locale: string;
frontMatter?: BlogPostFrontMatter & Record;
options?: ReadingTimeOptions;
}) => number;
type ReadingTimeFn = (params: {
content: string;
+ locale: string;
frontMatter: BlogPostFrontMatter & Record;
defaultReadingTime: ReadingTimeCalculator;
}) => number | undefined;
diff --git a/website/docs/blog.mdx b/website/docs/blog.mdx
index 8c9b0dc0ef..aec3c7b3a9 100644
--- a/website/docs/blog.mdx
+++ b/website/docs/blog.mdx
@@ -476,8 +476,12 @@ export default {
blog: {
// highlight-start
showReadingTime: true, // When set to false, the "x min read" won't be shown
- readingTime: ({content, frontMatter, defaultReadingTime}) =>
- defaultReadingTime({content, options: {wordsPerMinute: 300}}),
+ readingTime: ({content, locale, frontMatter, defaultReadingTime}) =>
+ defaultReadingTime({
+ content,
+ locale,
+ options: {wordsPerMinute: 300},
+ }),
// highlight-end
},
},
@@ -486,9 +490,16 @@ export default {
};
```
-The `readingTime` callback receives three parameters: the blog content text as a string, front matter as a record of string keys and their values, and the default reading time function. It returns a number (reading time in minutes) or `undefined` (disable reading time for this page).
+The `readingTime` callback receives the following parameters:
-The default reading time is able to accept additional options: `wordsPerMinute` as a number (default: 300), and `wordBound` as a function from string to boolean. If the string passed to `wordBound` should be a word bound (spaces, tabs, and line breaks by default), the function should return `true`.
+- `content`: the blog content text as a string
+- `frontMatter`: the front matter as a record of string keys and their values
+- `locale`: the locale of the current Docusaurus site
+- `defaultReadingTime`: the default built-in reading time function. It returns a number (reading time in minutes) or `undefined` (disable reading time for this page).
+
+The default reading time is able to accept additional options:
+
+- `wordsPerMinute` as a number (default: 300)
:::tip
@@ -510,10 +521,10 @@ export default {
blog: {
showReadingTime: true,
// highlight-start
- readingTime: ({content, frontMatter, defaultReadingTime}) =>
+ readingTime: ({content, locale, frontMatter, defaultReadingTime}) =>
frontMatter.hide_reading_time
? undefined
- : defaultReadingTime({content}),
+ : defaultReadingTime({content, locale}),
// highlight-end
},
},
@@ -547,8 +558,12 @@ export default {
{
blog: {
// highlight-start
- readingTime: ({content, defaultReadingTime}) =>
- defaultReadingTime({content, options: {wordsPerMinute: 100}}),
+ readingTime: ({content, locale, defaultReadingTime}) =>
+ defaultReadingTime({
+ content,
+ locale,
+ options: {wordsPerMinute: 100},
+ }),
// highlight-end
},
},
@@ -574,7 +589,7 @@ export default {
{
blog: {
// highlight-next-line
- readingTime: ({content}) => myReadingTime(content),
+ readingTime: ({content, locale}) => myReadingTime(content, locale),
},
},
],
diff --git a/yarn.lock b/yarn.lock
index 7d0aa88c9e..1cbb35edac 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -15578,11 +15578,6 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
-reading-time@^1.5.0:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.5.0.tgz#d2a7f1b6057cb2e169beaf87113cc3411b5bc5bb"
- integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==
-
rechoir@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
From 484ffad4f226f6daa06c241f86052920b5086289 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Thu, 15 May 2025 17:07:13 +0200
Subject: [PATCH 22/25] docs: adjust the Docusaurus release process
documentation to our new simpler process (#11181)
Co-authored-by: slorber <749374+slorber@users.noreply.github.com>
---
project-words.txt | 1 -
website/blog/releases/3.0/index.mdx | 2 +-
website/community/4-canary.mdx | 2 +-
website/community/5-release-process.mdx | 64 +++++--------------------
website/src/components/Versions.tsx | 30 ++++--------
5 files changed, 25 insertions(+), 74 deletions(-)
diff --git a/project-words.txt b/project-words.txt
index d048138934..18363f6eec 100644
--- a/project-words.txt
+++ b/project-words.txt
@@ -183,7 +183,6 @@ navigations
navlink
netrc
newtab
-ngryman
Nisarag
noflash
noicon
diff --git a/website/blog/releases/3.0/index.mdx b/website/blog/releases/3.0/index.mdx
index 66b7043121..87496cd352 100644
--- a/website/blog/releases/3.0/index.mdx
+++ b/website/blog/releases/3.0/index.mdx
@@ -28,7 +28,7 @@ The simplest sites will only need to upgrade a few npm dependencies. For more co
:::info About Docusaurus v2
-According to our [release process](/community/release-process#stable-version), Docusaurus v2 has now entered **maintenance mode**. It will only receive support for major security issues for 3 months, until 31 January 2024. It is recommended to upgrade within that time frame to v3.
+According to our [release process](/community/release-process), Docusaurus v2 has now entered **maintenance mode**. It will only receive support for major security issues for 3 months, until 31 January 2024. It is recommended to upgrade within that time frame to v3.
:::
diff --git a/website/community/4-canary.mdx b/website/community/4-canary.mdx
index 698dd1f8ac..cf337558cb 100644
--- a/website/community/4-canary.mdx
+++ b/website/community/4-canary.mdx
@@ -16,7 +16,7 @@ import {
Docusaurus has a canary releases system.
-It permits you to **test new unreleased features** as soon as the pull requests are merged on the [next version](./5-release-process.mdx#next-version) of Docusaurus.
+It permits you to **test new unreleased features** as soon as the pull requests are merged.
It is a good way to **give feedback to maintainers**, ensuring the newly implemented feature works as intended.
diff --git a/website/community/5-release-process.mdx b/website/community/5-release-process.mdx
index fae6a1a046..5377f3dd65 100644
--- a/website/community/5-release-process.mdx
+++ b/website/community/5-release-process.mdx
@@ -19,14 +19,6 @@ Respecting Semantic Versioning is important for multiple reasons:
- A new major version is an opportunity to thoroughly document breaking changes
- A new major/minor version is an opportunity to communicate new features through a blog post
-:::note
-
-Releasing Docusaurus 2.0 took a very long time. From now on, Docusaurus will **release new major versions more regularly**. In practice, you can expect a new major version every 2–4 months.
-
-[Major version numbers are not sacred](https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html), but we still group breaking changes together and avoid releasing major versions too often.
-
-:::
-
### Major versions {#major-versions}
The `major` version number is incremented on **every breaking change**.
@@ -69,66 +61,36 @@ Whenever a new patch version is released, we publish:
```mdx-code-block
import {
- StableMajorVersion,
- NextMajorVersion,
+ CurrentMajorVersionNumber,
StableMajorBranchLink,
- NextMajorBranchLink,
+ MainBranchLink,
} from "@site/src/components/Versions";
```
-The Docusaurus team is usually working on 2 major versions at the same time:
+The Docusaurus team uses a simple development process and only works on a single major version and a single Git branch at a same time:
-- **Docusaurus **: the **stable** version, on the branch
-- **Docusaurus **: the **next** version, on the branch
+- **Docusaurus {CurrentMajorVersionNumber}**: the **stable** version, on the branch.
-:::note
+:::note How we will ship the next version
-The branch is created just before releasing the first v release candidate.
+Once we are ready ship **Docusaurus {CurrentMajorVersionNumber + 1}**, we will:
+
+- create a branch
+- merge breaking changes on the branch
+- release that new version directly from the branch
:::
-### Stable version {#stable-version}
+:::warning Security fixes policy
-The stable version (v , on ) is recommended for most Docusaurus users.
+After a new stable version has been released, the former stable version will continue to receive support for **major security issues** for **3 months**.
-We regularly backport retro-compatible features, bug and security fixes from to with `git cherry-pick` to make them available to those not ready for the next version.
-
-:::info
-
-After a new stable version has been released, the former stable version will continue to receive support only for **major security issues** for **3 months**. Otherwise, all features will be frozen and non-critical bugs will not be fixed.
+In practice, we will backport security fixes to the branch. Otherwise, all features will be frozen and non-critical bugs will not be fixed.
It is recommended to upgrade within that time frame to the new stable version.
:::
-### Next version {#next-version}
-
-The next version (v , on ) is the version the Docusaurus team is currently working on.
-
-The branch is the **default target branch** for all pull requests, including core team and external contributions.
-
-This version is recommended for **early adopters** that want to use the latest Docusaurus features as soon as possible. It is also a good way to help us by reporting bugs and giving some feedback.
-
-There are 3 ways to use the next version:
-
-- with an `alpha`, `beta` or `rc` pre-release
-- with the `@next` npm dist tag for the latest pre-release
-- with a [canary release](./4-canary.mdx) for the very latest features
-
-:::tip
-
-The next version passes all our automated tests and is used by the Docusaurus site itself. It is relatively safe: don't be afraid to give it a try.
-
-:::
-
-:::warning
-
-Breaking changes can happen on the next version: detailed upgrade instructions are available in the changelog and pull requests.
-
-At the `beta` and `rc` (release candidate) phases, we avoid introducing major breaking changes.
-
-:::
-
## Public API surface {#public-api-surface}
Docusaurus commits to respecting Semantic Versioning. This means that whenever changes occur in Docusaurus public APIs and break backward compatibility, we will increment the `major` version number.
diff --git a/website/src/components/Versions.tsx b/website/src/components/Versions.tsx
index 9ce7547273..78de5d43a8 100644
--- a/website/src/components/Versions.tsx
+++ b/website/src/components/Versions.tsx
@@ -19,6 +19,12 @@ import {
import Translate from '@docusaurus/Translate';
import Link from '@docusaurus/Link';
import CodeBlock from '@theme/CodeBlock';
+import versions from '@site/versions.json';
+
+export const CurrentMajorVersionNumber = parseInt(
+ versions[0]!.split('.')[0]!,
+ 10,
+);
type ContextValue = {
name: string;
@@ -91,23 +97,8 @@ export function StableVersion(): ReactNode {
return {currentVersion} ;
}
-// TODO temporary: assumes main is already on v3 (not the case yet)
-function useNextMajorVersionNumber(): number {
- return 3; // TODO later read from main@package.json or something...
-}
-function useStableMajorVersionNumber(): number {
- // -1 because website is published from main, which is the next version
- return useNextMajorVersionNumber() - 1;
-}
-
-export function NextMajorVersion(): ReactNode {
- const majorVersionNumber = useNextMajorVersionNumber();
- return {majorVersionNumber} ;
-}
-
export function StableMajorVersion(): ReactNode {
- const majorVersionNumber = useStableMajorVersionNumber();
- return {majorVersionNumber} ;
+ return {CurrentMajorVersionNumber} ;
}
function GitBranchLink({branch}: {branch: string}): ReactNode {
@@ -119,12 +110,11 @@ function GitBranchLink({branch}: {branch: string}): ReactNode {
}
export function StableMajorBranchLink(): ReactNode {
- const majorVersionNumber = useStableMajorVersionNumber();
- const branch = `docusaurus-v${majorVersionNumber}`;
- return ;
+ // Can't be a link until the branch actually exists...
+ return {`docusaurus-v${CurrentMajorVersionNumber}`};
}
-export function NextMajorBranchLink(): ReactNode {
+export function MainBranchLink(): ReactNode {
return ;
}
From d74290bcbb930320426ab96eaa6b7589fd360fa9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 17 May 2025 15:51:18 +0200
Subject: [PATCH 23/25] chore(deps): bump lockfile-lint-api from 5.9.1 to 5.9.2
(#11182)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
yarn.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index 1cbb35edac..3012501d78 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11834,9 +11834,9 @@ locate-path@^7.1.0:
p-locate "^6.0.0"
lockfile-lint-api@^5.9.1:
- version "5.9.1"
- resolved "https://registry.yarnpkg.com/lockfile-lint-api/-/lockfile-lint-api-5.9.1.tgz#12b10434792fa8b8dd0e332ddfbac55ea70a9e08"
- integrity sha512-us5IT1bGA6KXbq1WrhrSzk9mtPgHKz5nhvv3S4hwcYnhcVOKW2uK0W8+PN9oIgv4pI49WsD5wBdTQFTpNChF/Q==
+ version "5.9.2"
+ resolved "https://registry.yarnpkg.com/lockfile-lint-api/-/lockfile-lint-api-5.9.2.tgz#c9ca335d4aa46c90d8b8467a92ed6670b5db0ed9"
+ integrity sha512-3QhxWxl3jT9GcMxuCnTsU8Tz5U6U1lKBlKBu2zOYOz/x3ONUoojEtky3uzoaaDgExcLqIX0Aqv2I7TZXE383CQ==
dependencies:
"@yarnpkg/parsers" "^3.0.0-rc.48.1"
debug "^4.3.4"
From a301b24d6478f629ba9f21fbbae46727a82106dd Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 20 May 2025 13:36:40 +0200
Subject: [PATCH 24/25] chore(deps): bump actions/dependency-review-action from
4.7.0 to 4.7.1 (#11185)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/dependency-review.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index cd9647f2cc..5d9fc5e2ed 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -15,4 +15,4 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Dependency Review
- uses: actions/dependency-review-action@38ecb5b593bf0eb19e335c03f97670f792489a8b # 4.7.0
+ uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # 4.7.1
From abd04a2b7104ff988701b558d43a82c0f4680618 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Lorber?=
Date: Thu, 22 May 2025 19:55:02 +0200
Subject: [PATCH 25/25] feat(theme): new CSS cascade layers plugin + built-in
`v4.useCssCascadeLayers` future flag (#11142)
Co-authored-by: slorber <749374+slorber@users.noreply.github.com>
---
.../.npmignore | 3 +
.../README.md | 7 +
.../package.json | 29 +++
.../src/__tests__/layers.test.ts | 96 ++++++++++
.../src/__tests__/options.test.ts | 105 +++++++++++
.../src/index.ts | 68 +++++++
.../src/layers.ts | 27 +++
.../src/options.ts | 87 +++++++++
.../src/postCssPlugin.ts | 45 +++++
.../src/types.d.ts | 8 +
.../tsconfig.json | 8 +
.../docusaurus-preset-classic/package.json | 1 +
.../docusaurus-preset-classic/src/index.ts | 7 +
.../theme/Navbar/Content/styles.module.css | 8 +
packages/docusaurus-types/src/config.d.ts | 1 +
.../__snapshots__/config.test.ts.snap | 10 +
.../__tests__/__snapshots__/site.test.ts.snap | 1 +
.../server/__tests__/configValidation.test.ts | 78 ++++++++
.../docusaurus/src/server/configValidation.ts | 5 +
project-words.txt | 1 +
website/_dogfooding/_pages tests/index.mdx | 1 +
.../style-isolation/index.module.css | 26 +++
.../_pages tests/style-isolation/index.tsx | 174 ++++++++++++++++++
website/docs/api/docusaurus.config.js.mdx | 2 +
website/docs/api/plugins/overview.mdx | 1 +
.../api/plugins/plugin-css-cascade-layers.mdx | 95 ++++++++++
26 files changed, 894 insertions(+)
create mode 100644 packages/docusaurus-plugin-css-cascade-layers/.npmignore
create mode 100644 packages/docusaurus-plugin-css-cascade-layers/README.md
create mode 100644 packages/docusaurus-plugin-css-cascade-layers/package.json
create mode 100644 packages/docusaurus-plugin-css-cascade-layers/src/__tests__/layers.test.ts
create mode 100644 packages/docusaurus-plugin-css-cascade-layers/src/__tests__/options.test.ts
create mode 100644 packages/docusaurus-plugin-css-cascade-layers/src/index.ts
create mode 100644 packages/docusaurus-plugin-css-cascade-layers/src/layers.ts
create mode 100644 packages/docusaurus-plugin-css-cascade-layers/src/options.ts
create mode 100644 packages/docusaurus-plugin-css-cascade-layers/src/postCssPlugin.ts
create mode 100644 packages/docusaurus-plugin-css-cascade-layers/src/types.d.ts
create mode 100644 packages/docusaurus-plugin-css-cascade-layers/tsconfig.json
create mode 100644 website/_dogfooding/_pages tests/style-isolation/index.module.css
create mode 100644 website/_dogfooding/_pages tests/style-isolation/index.tsx
create mode 100644 website/docs/api/plugins/plugin-css-cascade-layers.mdx
diff --git a/packages/docusaurus-plugin-css-cascade-layers/.npmignore b/packages/docusaurus-plugin-css-cascade-layers/.npmignore
new file mode 100644
index 0000000000..03c9ae1e1b
--- /dev/null
+++ b/packages/docusaurus-plugin-css-cascade-layers/.npmignore
@@ -0,0 +1,3 @@
+.tsbuildinfo*
+tsconfig*
+__tests__
diff --git a/packages/docusaurus-plugin-css-cascade-layers/README.md b/packages/docusaurus-plugin-css-cascade-layers/README.md
new file mode 100644
index 0000000000..94e35be735
--- /dev/null
+++ b/packages/docusaurus-plugin-css-cascade-layers/README.md
@@ -0,0 +1,7 @@
+# `@docusaurus/plugin-css-cascade-layers`
+
+CSS Cascade Layer plugin for Docusaurus
+
+## Usage
+
+See [plugin-css-cascade-layers documentation](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-css-cascade-layers).
diff --git a/packages/docusaurus-plugin-css-cascade-layers/package.json b/packages/docusaurus-plugin-css-cascade-layers/package.json
new file mode 100644
index 0000000000..2d22fba616
--- /dev/null
+++ b/packages/docusaurus-plugin-css-cascade-layers/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "@docusaurus/plugin-css-cascade-layers",
+ "version": "3.7.0",
+ "description": "CSS Cascade Layer plugin for Docusaurus.",
+ "main": "lib/index.js",
+ "types": "lib/index.d.ts",
+ "scripts": {
+ "build": "tsc --build",
+ "watch": "tsc --build --watch"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/facebook/docusaurus.git",
+ "directory": "packages/docusaurus-plugin-css-cascade-layers"
+ },
+ "license": "MIT",
+ "dependencies": {
+ "@docusaurus/core": "3.7.0",
+ "@docusaurus/types": "3.7.0",
+ "@docusaurus/utils-validation": "3.7.0",
+ "tslib": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=18.0"
+ }
+}
diff --git a/packages/docusaurus-plugin-css-cascade-layers/src/__tests__/layers.test.ts b/packages/docusaurus-plugin-css-cascade-layers/src/__tests__/layers.test.ts
new file mode 100644
index 0000000000..e7dc82a49d
--- /dev/null
+++ b/packages/docusaurus-plugin-css-cascade-layers/src/__tests__/layers.test.ts
@@ -0,0 +1,96 @@
+/**
+ * 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 {
+ generateLayersDeclaration,
+ findLayer,
+ isValidLayerName,
+} from '../layers';
+import type {PluginOptions} from '../options';
+
+describe('isValidLayerName', () => {
+ it('accepts valid names', () => {
+ expect(isValidLayerName('layer1')).toBe(true);
+ expect(isValidLayerName('layer1.layer2')).toBe(true);
+ expect(isValidLayerName('layer-1.layer_2.layer3')).toBe(true);
+ });
+
+ it('rejects layer with coma', () => {
+ expect(isValidLayerName('lay,er1')).toBe(false);
+ });
+ it('rejects layer with space', () => {
+ expect(isValidLayerName('lay er1')).toBe(false);
+ });
+});
+
+describe('generateLayersDeclaration', () => {
+ it('for list of layers', () => {
+ expect(generateLayersDeclaration(['layer1', 'layer2'])).toBe(
+ '@layer layer1, layer2;',
+ );
+ });
+
+ it('for empty list of layers', () => {
+ // Not useful to generate it, but still valid CSS anyway
+ expect(generateLayersDeclaration([])).toBe('@layer ;');
+ });
+});
+
+describe('findLayer', () => {
+ const inputFilePath = 'filePath';
+
+ function testFor(layers: PluginOptions['layers']) {
+ return findLayer(inputFilePath, Object.entries(layers));
+ }
+
+ it('for empty layers', () => {
+ expect(testFor({})).toBeUndefined();
+ });
+
+ it('for single matching layer', () => {
+ expect(testFor({layer: (filePath) => filePath === inputFilePath})).toBe(
+ 'layer',
+ );
+ });
+
+ it('for single non-matching layer', () => {
+ expect(
+ testFor({layer: (filePath) => filePath !== inputFilePath}),
+ ).toBeUndefined();
+ });
+
+ it('for multiple matching layers', () => {
+ expect(
+ testFor({
+ layer1: (filePath) => filePath === inputFilePath,
+ layer2: (filePath) => filePath === inputFilePath,
+ layer3: (filePath) => filePath === inputFilePath,
+ }),
+ ).toBe('layer1');
+ });
+
+ it('for multiple non-matching layers', () => {
+ expect(
+ testFor({
+ layer1: (filePath) => filePath !== inputFilePath,
+ layer2: (filePath) => filePath !== inputFilePath,
+ layer3: (filePath) => filePath !== inputFilePath,
+ }),
+ ).toBeUndefined();
+ });
+
+ it('for multiple mixed matching layers', () => {
+ expect(
+ testFor({
+ layer1: (filePath) => filePath !== inputFilePath,
+ layer2: (filePath) => filePath === inputFilePath,
+ layer3: (filePath) => filePath !== inputFilePath,
+ layer4: (filePath) => filePath === inputFilePath,
+ }),
+ ).toBe('layer2');
+ });
+});
diff --git a/packages/docusaurus-plugin-css-cascade-layers/src/__tests__/options.test.ts b/packages/docusaurus-plugin-css-cascade-layers/src/__tests__/options.test.ts
new file mode 100644
index 0000000000..215ab167f0
--- /dev/null
+++ b/packages/docusaurus-plugin-css-cascade-layers/src/__tests__/options.test.ts
@@ -0,0 +1,105 @@
+/**
+ * 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 {normalizePluginOptions} from '@docusaurus/utils-validation';
+import {
+ validateOptions,
+ type PluginOptions,
+ type Options,
+ DEFAULT_OPTIONS,
+} from '../options';
+import type {Validate} from '@docusaurus/types';
+
+function testValidateOptions(options: Options) {
+ return validateOptions({
+ validate: normalizePluginOptions as Validate,
+ options,
+ });
+}
+
+describe('validateOptions', () => {
+ it('accepts undefined options', () => {
+ // @ts-expect-error: should error
+ expect(testValidateOptions(undefined)).toEqual(DEFAULT_OPTIONS);
+ });
+
+ it('accepts empty options', () => {
+ expect(testValidateOptions({})).toEqual(DEFAULT_OPTIONS);
+ });
+
+ describe('layers', () => {
+ it('accepts empty layers', () => {
+ expect(testValidateOptions({layers: {}})).toEqual({
+ ...DEFAULT_OPTIONS,
+ layers: {},
+ });
+ });
+
+ it('accepts undefined layers', () => {
+ const config: Options = {
+ layers: undefined,
+ };
+ expect(testValidateOptions(config)).toEqual(DEFAULT_OPTIONS);
+ });
+
+ it('accepts custom layers', () => {
+ const config: Options = {
+ layers: {
+ layer1: (filePath: string) => {
+ return !!filePath;
+ },
+ layer2: (filePath: string) => {
+ return !!filePath;
+ },
+ },
+ };
+ expect(testValidateOptions(config)).toEqual({
+ ...DEFAULT_OPTIONS,
+ layers: config.layers,
+ });
+ });
+
+ it('rejects layer with bad name', () => {
+ const config: Options = {
+ layers: {
+ 'layer 1': (filePath) => !!filePath,
+ },
+ };
+ expect(() =>
+ testValidateOptions(config),
+ ).toThrowErrorMatchingInlineSnapshot(`""layers.layer 1" is not allowed"`);
+ });
+
+ it('rejects layer with bad value', () => {
+ const config: Options = {
+ layers: {
+ // @ts-expect-error: should error
+ layer1: 'bad value',
+ },
+ };
+ expect(() =>
+ testValidateOptions(config),
+ ).toThrowErrorMatchingInlineSnapshot(
+ `""layers.layer1" must be of type function"`,
+ );
+ });
+
+ it('rejects layer with bad function arity', () => {
+ const config: Options = {
+ layers: {
+ // @ts-expect-error: should error
+ layer1: () => {},
+ },
+ };
+ expect(() =>
+ testValidateOptions(config),
+ ).toThrowErrorMatchingInlineSnapshot(
+ `""layers.layer1" must have an arity of 1"`,
+ );
+ });
+ });
+});
diff --git a/packages/docusaurus-plugin-css-cascade-layers/src/index.ts b/packages/docusaurus-plugin-css-cascade-layers/src/index.ts
new file mode 100644
index 0000000000..013b61ed06
--- /dev/null
+++ b/packages/docusaurus-plugin-css-cascade-layers/src/index.ts
@@ -0,0 +1,68 @@
+/**
+ * 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 path from 'path';
+import {PostCssPluginWrapInLayer} from './postCssPlugin';
+import {generateLayersDeclaration} from './layers';
+import type {LoadContext, Plugin} from '@docusaurus/types';
+import type {PluginOptions, Options} from './options';
+
+const PluginName = 'docusaurus-plugin-css-cascade-layers';
+
+const LayersDeclarationModule = 'layers.css';
+
+function getLayersDeclarationPath(
+ context: LoadContext,
+ options: PluginOptions,
+) {
+ const {generatedFilesDir} = context;
+ const pluginId = options.id;
+ if (pluginId !== 'default') {
+ // Since it's only possible to declare a single layer order
+ // using this plugin twice doesn't really make sense
+ throw new Error(
+ 'The CSS Cascade Layers plugin does not support multiple instances.',
+ );
+ }
+ return path.join(
+ generatedFilesDir,
+ PluginName,
+ pluginId,
+ LayersDeclarationModule,
+ );
+}
+
+export default function pluginCssCascadeLayers(
+ context: LoadContext,
+ options: PluginOptions,
+): Plugin | null {
+ const layersDeclarationPath = getLayersDeclarationPath(context, options);
+
+ return {
+ name: PluginName,
+
+ getClientModules() {
+ return [layersDeclarationPath];
+ },
+
+ async contentLoaded({actions}) {
+ await actions.createData(
+ LayersDeclarationModule,
+ generateLayersDeclaration(Object.keys(options.layers)),
+ );
+ },
+
+ configurePostCss(postCssOptions) {
+ postCssOptions.plugins.push(PostCssPluginWrapInLayer(options));
+ return postCssOptions;
+ },
+ };
+}
+
+export {validateOptions} from './options';
+
+export type {PluginOptions, Options};
diff --git a/packages/docusaurus-plugin-css-cascade-layers/src/layers.ts b/packages/docusaurus-plugin-css-cascade-layers/src/layers.ts
new file mode 100644
index 0000000000..c15d328917
--- /dev/null
+++ b/packages/docusaurus-plugin-css-cascade-layers/src/layers.ts
@@ -0,0 +1,27 @@
+/**
+ * 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.
+ */
+
+export type LayerEntry = [string, (filePath: string) => boolean];
+
+export function isValidLayerName(layer: string): boolean {
+ // TODO improve validation rule to match spec, not high priority
+ return !layer.includes(',') && !layer.includes(' ');
+}
+
+export function generateLayersDeclaration(layers: string[]): string {
+ return `@layer ${layers.join(', ')};`;
+}
+
+export function findLayer(
+ filePath: string,
+ layers: LayerEntry[],
+): string | undefined {
+ // Using find() => layers order matter
+ // The first layer that matches is used in priority even if others match too
+ const layerEntry = layers.find((layer) => layer[1](filePath));
+ return layerEntry?.[0]; // return layer name
+}
diff --git a/packages/docusaurus-plugin-css-cascade-layers/src/options.ts b/packages/docusaurus-plugin-css-cascade-layers/src/options.ts
new file mode 100644
index 0000000000..180e9d427e
--- /dev/null
+++ b/packages/docusaurus-plugin-css-cascade-layers/src/options.ts
@@ -0,0 +1,87 @@
+/**
+ * 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 {Joi} from '@docusaurus/utils-validation';
+import {isValidLayerName} from './layers';
+import type {OptionValidationContext} from '@docusaurus/types';
+
+export type PluginOptions = {
+ id: string; // plugin id
+ layers: Record boolean>;
+};
+
+export type Options = {
+ layers?: PluginOptions['layers'];
+};
+
+// Not ideal to compute layers using "filePath.includes()"
+// But this is mostly temporary until we add first-class layers everywhere
+function layerFor(...params: string[]) {
+ return (filePath: string) => params.some((p) => filePath.includes(p));
+}
+
+// Object order matters, it defines the layer order
+export const DEFAULT_LAYERS: PluginOptions['layers'] = {
+ 'docusaurus.infima': layerFor('node_modules/infima/dist'),
+ 'docusaurus.theme-common': layerFor(
+ 'packages/docusaurus-theme-common/lib',
+ 'node_modules/@docusaurus/theme-common/lib',
+ ),
+ 'docusaurus.theme-classic': layerFor(
+ 'packages/docusaurus-theme-classic/lib',
+ 'node_modules/@docusaurus/theme-classic/lib',
+ ),
+ 'docusaurus.core': layerFor(
+ 'packages/docusaurus/lib',
+ 'node_modules/@docusaurus/core/lib',
+ ),
+ 'docusaurus.plugin-debug': layerFor(
+ 'packages/docusaurus-plugin-debug/lib',
+ 'node_modules/@docusaurus/plugin-debug/lib',
+ ),
+ 'docusaurus.theme-mermaid': layerFor(
+ 'packages/docusaurus-theme-mermaid/lib',
+ 'node_modules/@docusaurus/theme-mermaid/lib',
+ ),
+ 'docusaurus.theme-live-codeblock': layerFor(
+ 'packages/docusaurus-theme-live-codeblock/lib',
+ 'node_modules/@docusaurus/theme-live-codeblock/lib',
+ ),
+ 'docusaurus.theme-search-algolia.docsearch': layerFor(
+ 'node_modules/@docsearch/css/dist',
+ ),
+ 'docusaurus.theme-search-algolia': layerFor(
+ 'packages/docusaurus-theme-search-algolia/lib',
+ 'node_modules/@docusaurus/theme-search-algolia/lib',
+ ),
+ // docusaurus.website layer ? (declare it, even if empty?)
+};
+
+export const DEFAULT_OPTIONS: Partial = {
+ id: 'default',
+ layers: DEFAULT_LAYERS,
+};
+
+const pluginOptionsSchema = Joi.object({
+ layers: Joi.object()
+ .pattern(
+ Joi.custom((val, helpers) => {
+ if (!isValidLayerName(val)) {
+ return helpers.error('any.invalid');
+ }
+ return val;
+ }),
+ Joi.function().arity(1).required(),
+ )
+ .default(DEFAULT_LAYERS),
+});
+
+export function validateOptions({
+ validate,
+ options,
+}: OptionValidationContext): PluginOptions {
+ return validate(pluginOptionsSchema, options);
+}
diff --git a/packages/docusaurus-plugin-css-cascade-layers/src/postCssPlugin.ts b/packages/docusaurus-plugin-css-cascade-layers/src/postCssPlugin.ts
new file mode 100644
index 0000000000..6b37b78b3d
--- /dev/null
+++ b/packages/docusaurus-plugin-css-cascade-layers/src/postCssPlugin.ts
@@ -0,0 +1,45 @@
+/**
+ * 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 {findLayer} from './layers';
+import type {Root, PluginCreator} from 'postcss';
+import type {PluginOptions} from './options';
+
+function wrapCssRootInLayer(root: Root, layer: string): void {
+ const rootBefore = root.clone();
+ root.removeAll();
+ root.append({
+ type: 'atrule',
+ name: 'layer',
+ params: layer,
+ nodes: rootBefore.nodes,
+ });
+}
+
+export const PostCssPluginWrapInLayer: PluginCreator<{
+ layers: PluginOptions['layers'];
+}> = (options) => {
+ if (!options) {
+ throw new Error('PostCssPluginWrapInLayer options are mandatory');
+ }
+ const layers = Object.entries(options.layers);
+ return {
+ postcssPlugin: 'postcss-wrap-in-layer',
+ Once(root) {
+ const filePath = root.source?.input.file;
+ if (!filePath) {
+ return;
+ }
+ const layer = findLayer(filePath, layers);
+ if (layer) {
+ wrapCssRootInLayer(root, layer);
+ }
+ },
+ };
+};
+
+PostCssPluginWrapInLayer.postcss = true;
diff --git a/packages/docusaurus-plugin-css-cascade-layers/src/types.d.ts b/packages/docusaurus-plugin-css-cascade-layers/src/types.d.ts
new file mode 100644
index 0000000000..6f6f99f127
--- /dev/null
+++ b/packages/docusaurus-plugin-css-cascade-layers/src/types.d.ts
@@ -0,0 +1,8 @@
+/**
+ * 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.
+ */
+
+///
diff --git a/packages/docusaurus-plugin-css-cascade-layers/tsconfig.json b/packages/docusaurus-plugin-css-cascade-layers/tsconfig.json
new file mode 100644
index 0000000000..343f87c70b
--- /dev/null
+++ b/packages/docusaurus-plugin-css-cascade-layers/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "noEmit": false
+ },
+ "include": ["src"],
+ "exclude": ["**/__tests__/**"]
+}
diff --git a/packages/docusaurus-preset-classic/package.json b/packages/docusaurus-preset-classic/package.json
index cfb770edaa..47ed48acff 100644
--- a/packages/docusaurus-preset-classic/package.json
+++ b/packages/docusaurus-preset-classic/package.json
@@ -19,6 +19,7 @@
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.7.0",
+ "@docusaurus/plugin-css-cascade-layers": "3.7.0",
"@docusaurus/plugin-content-blog": "3.7.0",
"@docusaurus/plugin-content-docs": "3.7.0",
"@docusaurus/plugin-content-pages": "3.7.0",
diff --git a/packages/docusaurus-preset-classic/src/index.ts b/packages/docusaurus-preset-classic/src/index.ts
index eac8c461b3..661be7e325 100644
--- a/packages/docusaurus-preset-classic/src/index.ts
+++ b/packages/docusaurus-preset-classic/src/index.ts
@@ -62,6 +62,13 @@ export default function preset(
}
const plugins: PluginConfig[] = [];
+
+ // TODO Docusaurus v4: temporary due to the opt-in flag
+ // In v4 we'd like to use layers everywhere natively
+ if (siteConfig.future.v4.useCssCascadeLayers) {
+ plugins.push(makePluginConfig('@docusaurus/plugin-css-cascade-layers'));
+ }
+
if (docs !== false) {
plugins.push(makePluginConfig('@docusaurus/plugin-content-docs', docs));
}
diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/Content/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Navbar/Content/styles.module.css
index 3cbe701f24..eee2127b69 100644
--- a/packages/docusaurus-theme-classic/src/theme/Navbar/Content/styles.module.css
+++ b/packages/docusaurus-theme-classic/src/theme/Navbar/Content/styles.module.css
@@ -13,3 +13,11 @@ Hide color mode toggle in small viewports
display: none;
}
}
+
+/*
+Restore some Infima style that broke with CSS Cascade Layers
+See https://github.com/facebook/docusaurus/pull/11142
+ */
+:global(.navbar__items--right) > :last-child {
+ padding-right: 0;
+}
diff --git a/packages/docusaurus-types/src/config.d.ts b/packages/docusaurus-types/src/config.d.ts
index 26b7022cf3..d77975a06b 100644
--- a/packages/docusaurus-types/src/config.d.ts
+++ b/packages/docusaurus-types/src/config.d.ts
@@ -136,6 +136,7 @@ export type FasterConfig = {
export type FutureV4Config = {
removeLegacyPostBuildHeadAttribute: boolean;
+ useCssCascadeLayers: boolean;
};
export type FutureConfig = {
diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap
index 1cce07a53a..bc5e437767 100644
--- a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap
+++ b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap
@@ -25,6 +25,7 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = `
},
"v4": {
"removeLegacyPostBuildHeadAttribute": false,
+ "useCssCascadeLayers": false,
},
},
"headTags": [],
@@ -99,6 +100,7 @@ exports[`loadSiteConfig website with ts + js config 1`] = `
},
"v4": {
"removeLegacyPostBuildHeadAttribute": false,
+ "useCssCascadeLayers": false,
},
},
"headTags": [],
@@ -173,6 +175,7 @@ exports[`loadSiteConfig website with valid JS CJS config 1`] = `
},
"v4": {
"removeLegacyPostBuildHeadAttribute": false,
+ "useCssCascadeLayers": false,
},
},
"headTags": [],
@@ -247,6 +250,7 @@ exports[`loadSiteConfig website with valid JS ESM config 1`] = `
},
"v4": {
"removeLegacyPostBuildHeadAttribute": false,
+ "useCssCascadeLayers": false,
},
},
"headTags": [],
@@ -321,6 +325,7 @@ exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = `
},
"v4": {
"removeLegacyPostBuildHeadAttribute": false,
+ "useCssCascadeLayers": false,
},
},
"headTags": [],
@@ -395,6 +400,7 @@ exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = `
},
"v4": {
"removeLegacyPostBuildHeadAttribute": false,
+ "useCssCascadeLayers": false,
},
},
"headTags": [],
@@ -469,6 +475,7 @@ exports[`loadSiteConfig website with valid async config 1`] = `
},
"v4": {
"removeLegacyPostBuildHeadAttribute": false,
+ "useCssCascadeLayers": false,
},
},
"headTags": [],
@@ -545,6 +552,7 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = `
},
"v4": {
"removeLegacyPostBuildHeadAttribute": false,
+ "useCssCascadeLayers": false,
},
},
"headTags": [],
@@ -621,6 +629,7 @@ exports[`loadSiteConfig website with valid config creator function 1`] = `
},
"v4": {
"removeLegacyPostBuildHeadAttribute": false,
+ "useCssCascadeLayers": false,
},
},
"headTags": [],
@@ -700,6 +709,7 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = `
},
"v4": {
"removeLegacyPostBuildHeadAttribute": false,
+ "useCssCascadeLayers": false,
},
},
"headTags": [],
diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap
index 91aea83bc3..3a85ce8cb2 100644
--- a/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap
+++ b/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap
@@ -99,6 +99,7 @@ exports[`load loads props for site with custom i18n path 1`] = `
},
"v4": {
"removeLegacyPostBuildHeadAttribute": false,
+ "useCssCascadeLayers": false,
},
},
"headTags": [],
diff --git a/packages/docusaurus/src/server/__tests__/configValidation.test.ts b/packages/docusaurus/src/server/__tests__/configValidation.test.ts
index 8d8f5058cc..861faa8cd5 100644
--- a/packages/docusaurus/src/server/__tests__/configValidation.test.ts
+++ b/packages/docusaurus/src/server/__tests__/configValidation.test.ts
@@ -50,6 +50,7 @@ describe('normalizeConfig', () => {
future: {
v4: {
removeLegacyPostBuildHeadAttribute: true,
+ useCssCascadeLayers: true,
},
experimental_faster: {
swcJsLoader: true,
@@ -754,6 +755,7 @@ describe('future', () => {
const future: DocusaurusConfig['future'] = {
v4: {
removeLegacyPostBuildHeadAttribute: true,
+ useCssCascadeLayers: true,
},
experimental_faster: {
swcJsLoader: true,
@@ -1861,6 +1863,7 @@ describe('future', () => {
it('accepts v4 - full', () => {
const v4: FutureV4Config = {
removeLegacyPostBuildHeadAttribute: true,
+ useCssCascadeLayers: true,
};
expect(
normalizeConfig({
@@ -1976,5 +1979,80 @@ describe('future', () => {
`);
});
});
+
+ describe('useCssCascadeLayers', () => {
+ it('accepts - undefined', () => {
+ const v4: Partial = {
+ useCssCascadeLayers: undefined,
+ };
+ expect(
+ normalizeConfig({
+ future: {
+ v4,
+ },
+ }),
+ ).toEqual(v4Containing({useCssCascadeLayers: false}));
+ });
+
+ it('accepts - true', () => {
+ const v4: Partial = {
+ useCssCascadeLayers: true,
+ };
+ expect(
+ normalizeConfig({
+ future: {
+ v4,
+ },
+ }),
+ ).toEqual(v4Containing({useCssCascadeLayers: true}));
+ });
+
+ it('accepts - false', () => {
+ const v4: Partial = {
+ useCssCascadeLayers: false,
+ };
+ expect(
+ normalizeConfig({
+ future: {
+ v4,
+ },
+ }),
+ ).toEqual(v4Containing({useCssCascadeLayers: false}));
+ });
+
+ it('rejects - null', () => {
+ const v4: Partial = {
+ // @ts-expect-error: invalid
+ useCssCascadeLayers: 42,
+ };
+ expect(() =>
+ normalizeConfig({
+ future: {
+ v4,
+ },
+ }),
+ ).toThrowErrorMatchingInlineSnapshot(`
+ ""future.v4.useCssCascadeLayers" must be a boolean
+ "
+ `);
+ });
+
+ it('rejects - number', () => {
+ const v4: Partial = {
+ // @ts-expect-error: invalid
+ useCssCascadeLayers: 42,
+ };
+ expect(() =>
+ normalizeConfig({
+ future: {
+ v4,
+ },
+ }),
+ ).toThrowErrorMatchingInlineSnapshot(`
+ ""future.v4.useCssCascadeLayers" must be a boolean
+ "
+ `);
+ });
+ });
});
});
diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts
index 33d7199143..67fe593f22 100644
--- a/packages/docusaurus/src/server/configValidation.ts
+++ b/packages/docusaurus/src/server/configValidation.ts
@@ -68,11 +68,13 @@ export const DEFAULT_FASTER_CONFIG_TRUE: FasterConfig = {
export const DEFAULT_FUTURE_V4_CONFIG: FutureV4Config = {
removeLegacyPostBuildHeadAttribute: false,
+ useCssCascadeLayers: false,
};
// When using the "v4: true" shortcut
export const DEFAULT_FUTURE_V4_CONFIG_TRUE: FutureV4Config = {
removeLegacyPostBuildHeadAttribute: true,
+ useCssCascadeLayers: true,
};
export const DEFAULT_FUTURE_CONFIG: FutureConfig = {
@@ -270,6 +272,9 @@ const FUTURE_V4_SCHEMA = Joi.alternatives()
removeLegacyPostBuildHeadAttribute: Joi.boolean().default(
DEFAULT_FUTURE_V4_CONFIG.removeLegacyPostBuildHeadAttribute,
),
+ useCssCascadeLayers: Joi.boolean().default(
+ DEFAULT_FUTURE_V4_CONFIG.useCssCascadeLayers,
+ ),
}),
Joi.boolean()
.required()
diff --git a/project-words.txt b/project-words.txt
index 18363f6eec..48a0ac9321 100644
--- a/project-words.txt
+++ b/project-words.txt
@@ -109,6 +109,7 @@ Héctor
héllô
IANAD
Infima
+infima
inlines
interactiveness
Interpolatable
diff --git a/website/_dogfooding/_pages tests/index.mdx b/website/_dogfooding/_pages tests/index.mdx
index 38f13ae9ae..0ac1884635 100644
--- a/website/_dogfooding/_pages tests/index.mdx
+++ b/website/_dogfooding/_pages tests/index.mdx
@@ -41,3 +41,4 @@ import Readme from "../README.mdx"
- [Analytics](/tests/pages/analytics)
- [History tests](/tests/pages/history-tests)
- [Embeds](/tests/pages/embeds)
+- [Style Isolation tests](/tests/pages/style-isolation)
diff --git a/website/_dogfooding/_pages tests/style-isolation/index.module.css b/website/_dogfooding/_pages tests/style-isolation/index.module.css
new file mode 100644
index 0000000000..cfa3773802
--- /dev/null
+++ b/website/_dogfooding/_pages tests/style-isolation/index.module.css
@@ -0,0 +1,26 @@
+/**
+ * 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.
+ */
+
+.exampleContainer {
+ border: solid thin;
+}
+
+.isolated:not(#a#b) {
+ &,
+ * {
+ @layer docusaurus {
+ all: revert-layer;
+ }
+ /*
+ Yes, unfortunately we need to revert sub-layers one by one
+ See https://bsky.app/profile/sebastienlorber.com/post/3lpqzuxat6s2v
+ */
+ @layer docusaurus.infima {
+ all: revert-layer;
+ }
+ }
+}
diff --git a/website/_dogfooding/_pages tests/style-isolation/index.tsx b/website/_dogfooding/_pages tests/style-isolation/index.tsx
new file mode 100644
index 0000000000..96ac211171
--- /dev/null
+++ b/website/_dogfooding/_pages tests/style-isolation/index.tsx
@@ -0,0 +1,174 @@
+/**
+ * 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 React, {type ReactNode} from 'react';
+import clsx from 'clsx';
+import Link from '@docusaurus/Link';
+import Layout from '@theme/Layout';
+import Heading from '@theme/Heading';
+
+import styles from './index.module.css';
+
+/* eslint-disable @docusaurus/prefer-docusaurus-heading */
+
+function ExampleContainer({
+ isolated,
+ children,
+}: {
+ isolated?: boolean;
+ children: ReactNode;
+}) {
+ return (
+
+ {children}
+
+ );
+}
+
+function ExampleRow({name, children}: {name: string; children: ReactNode}) {
+ return (
+
+ {name}
+
+ {children}
+
+
+ {children}
+
+
+ );
+}
+
+function ExamplesTable() {
+ return (
+
+
+
+ Example
+ Normal
+ Isolated
+
+
+
+
+ title
+
+
+
+ text
+
+
+
+ {/* eslint-disable-next-line */}
+ link
+
+
+
+ code
+
+
+
+ code
+
+
+
+
+ some text
+
+
+
+ {/* eslint-disable-next-line */}
+ button
+
+
+
+
+
+
+
+
+ item1
+ item2
+
+
+
+
+ kbd
+
+
+
+ shadow (KO)
+
+
+
+
+
+
+ Col1
+ Col2
+
+
+
+
+ Cell1
+ Cell2
+
+
+ Cell3
+ Cell3
+
+
+
+
+
+
+ {/* eslint-disable-next-line */}
+ button
+
+
+
+ danger
+
+
+ success
+
+
+
+ );
+}
+
+export default function StyleIsolation(): ReactNode {
+ return (
+
+
+ Style Isolation tests
+
+
+ This shows how to isolate your components from Docusaurus global
+ styles. A workaround for{' '}
+
+ this issue
+
+ .
+
+
+
+
+ );
+}
diff --git a/website/docs/api/docusaurus.config.js.mdx b/website/docs/api/docusaurus.config.js.mdx
index 41f5c50a9f..e746377ba7 100644
--- a/website/docs/api/docusaurus.config.js.mdx
+++ b/website/docs/api/docusaurus.config.js.mdx
@@ -199,6 +199,7 @@ export default {
future: {
v4: {
removeLegacyPostBuildHeadAttribute: true,
+ useCssCascadeLayers: true,
},
experimental_faster: {
swcJsLoader: true,
@@ -221,6 +222,7 @@ export default {
- `v4`: Permits to opt-in for upcoming Docusaurus v4 breaking changes and features, to prepare your site in advance for this new version. Use `true` as a shorthand to enable all the flags.
- [`removeLegacyPostBuildHeadAttribute`](https://github.com/facebook/docusaurus/pull/10435): Removes the legacy `plugin.postBuild({head})` API that prevents us from applying useful SSG optimizations ([explanations](https://github.com/facebook/docusaurus/pull/10850)).
+ - [`useCssCascadeLayers`](https://github.com/facebook/docusaurus/pull/11142): This enables the [Docusaurus CSS Cascade Layers plugin](./plugins/plugin-css-cascade-layers.mdx) with pre-configured layers that we plan to apply by default for Docusaurus v4.
- `experimental_faster`: An object containing feature flags to make the Docusaurus build faster. This requires adding the `@docusaurus/faster` package to your site's dependencies. Use `true` as a shorthand to enable all flags. Read more on the [Docusaurus Faster](https://github.com/facebook/docusaurus/issues/10556) issue. Available feature flags:
- [`swcJsLoader`](https://github.com/facebook/docusaurus/pull/10435): Use [SWC](https://swc.rs/) to transpile JS (instead of [Babel](https://babeljs.io/)).
- [`swcJsMinimizer`](https://github.com/facebook/docusaurus/pull/10441): Use [SWC](https://swc.rs/) to minify JS (instead of [Terser](https://github.com/terser/terser)).
diff --git a/website/docs/api/plugins/overview.mdx b/website/docs/api/plugins/overview.mdx
index 23eb708929..6109d4eb20 100644
--- a/website/docs/api/plugins/overview.mdx
+++ b/website/docs/api/plugins/overview.mdx
@@ -31,3 +31,4 @@ These plugins will add a useful behavior to your Docusaurus site.
- [@docusaurus/plugin-google-analytics](./plugin-google-analytics.mdx)
- [@docusaurus/plugin-google-gtag](./plugin-google-gtag.mdx)
- [@docusaurus/plugin-google-tag-manager](./plugin-google-tag-manager.mdx)
+- [@docusaurus/plugin-css-cascade-layers](./plugin-css-cascade-layers.mdx) u
diff --git a/website/docs/api/plugins/plugin-css-cascade-layers.mdx b/website/docs/api/plugins/plugin-css-cascade-layers.mdx
new file mode 100644
index 0000000000..a155a02260
--- /dev/null
+++ b/website/docs/api/plugins/plugin-css-cascade-layers.mdx
@@ -0,0 +1,95 @@
+---
+sidebar_position: 9
+slug: /api/plugins/@docusaurus/plugin-css-cascade-layers
+---
+
+# 📦 plugin-css-cascade-layers
+
+import APITable from '@site/src/components/APITable';
+
+:::caution Experimental
+
+This plugin is mostly designed to be used internally by the classic preset through the [Docusaurus `future.v4.useCssCascadeLayers` flag](../docusaurus.config.js.mdx#future), although it can also be used as a standalone plugin. Please [let us know here](https://github.com/facebook/docusaurus/pull/11142) if you have a use case for it and help us design an API that makes sense for the future of Docusaurus.
+
+:::
+
+A plugin for wrapping CSS modules of your Docusaurus site in [CSS Cascade Layers](https://css-tricks.com/css-cascade-layers/). This modern CSS feature is widely supported by all browsers. It allows grouping CSS rules in layers of specificity and gives you more control over the CSS cascade.
+
+Use this plugin to:
+
+- apply a top-level `@layer myLayer { ... }` block rule around any CSS module, including un-layered third-party CSS.
+- define an explicit layer ordering
+
+:::caution
+
+To use this plugin properly, it's recommended to have a solid understanding of [CSS Cascade Layers](https://css-tricks.com/css-cascade-layers/), the [CSS Cascade](https://developer.mozilla.org/docs/Web/CSS/CSS_cascade/Cascade) and [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascade/Specificity).
+
+:::
+
+## Installation {#installation}
+
+```bash npm2yarn
+npm install --save @docusaurus/plugin-css-cascade-layers
+```
+
+:::tip
+
+If you use the preset `@docusaurus/preset-classic`, this plugin is automatically configured for you with the [`siteConfig.future.v4.useCssCascadeLayers`](../docusaurus.config.js.mdx#future) flag.
+
+:::
+
+## Configuration {#configuration}
+
+Accepted fields:
+
+```mdx-code-block
+
+```
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `layers` | `Layers` | **Built-in layers** | An object representing all the CSS cascade layers you want to use, and whether the layer should be applied to a given file path. See examples and types below. |
+
+```mdx-code-block
+
+```
+
+### Types {#types}
+
+#### `Layers` {#EditUrlFunction}
+
+```ts
+type Layers = Record<
+ string, // layer name
+ (filePath: string) => boolean // layer matcher
+>;
+```
+
+The `layers` object is defined by:
+
+- key: the name of a layer
+- value: a function to define if a given CSS module file should be in that layer
+
+:::caution Order matters
+
+The object order matters:
+
+- the keys order defines an explicit CSS layer order
+- when multiple layers match a file path, only the first layer will apply
+
+:::
+
+### Example configuration {#ex-config}
+
+You can configure this plugin through plugin options.
+
+```js
+const options = {
+ layers: {
+ 'docusaurus.infima': (filePath) =>
+ filePath.includes('/node_modules/infima/dist'),
+ 'docusaurus.theme-classic': (filePath) =>
+ filePath.includes('/node_modules/@docusaurus/theme-classic/lib'),
+ },
+};
+```