Merge branch 'main' into tinyglobby

This commit is contained in:
Ben McCann 2025-05-25 12:08:05 -07:00
commit 175cfbf0ee
86 changed files with 1764 additions and 334 deletions

View File

@ -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

View File

@ -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@da24556b548a50705dd671f47852072ea4c105d9 # 4.7.1

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

3
.gitignore vendored
View File

@ -48,3 +48,6 @@ website/i18n/**/*
website/rspack-tracing.json
website/bundler-cpu-profile.json
website/profile.json.gz

View File

@ -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",

View File

@ -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=<Your GitHub username> yarn deploy
GIT_USER=<Your GitHub username> 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.

View File

@ -104,3 +104,46 @@ export async function getProgressBarPlugin({
return WebpackBar;
}
export async function registerBundlerTracing({
currentBundler,
}: {
currentBundler: CurrentBundler;
}): Promise<() => Promise<void>> {
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
};
}

View File

@ -12,6 +12,7 @@ export {
getCSSExtractPlugin,
getCopyPlugin,
getProgressBarPlugin,
registerBundlerTracing,
} from './currentBundler';
export {getMinimizers} from './minification';

View File

@ -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",

View File

@ -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 = ({

View File

@ -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);

View File

@ -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}${

View File

@ -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 ?? '';

View File

@ -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",

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,23 @@
<!doctype html>
<html lang="en" dir="ltr" class="plugin-native plugin-id-default" data-has-hydrated="false">
<head>
<meta charset="UTF-8">
<meta name="generator" content="Docusaurus v3.7.0">
<title data-rh="true">Page Not Found | Docusaurus blog website fixture</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:image" content="https://docusaurus.io/img/docusaurus-social-card.jpg"><meta data-rh="true" name="twitter:image" content="https://docusaurus.io/img/docusaurus-social-card.jpg"><meta data-rh="true" property="og:url" content="https://docusaurus.io/blog/tags/global-tag-permalink (en)"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docusaurus_tag" content="default"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="docsearch:docusaurus_tag" content="default"><meta data-rh="true" property="og:title" content="Page Not Found | Docusaurus blog website fixture"><link data-rh="true" rel="icon" href="/img/docusaurus.ico"><link data-rh="true" rel="canonical" href="https://docusaurus.io/blog/tags/global-tag-permalink (en)"><link data-rh="true" rel="alternate" href="https://docusaurus.io/blog/tags/global-tag-permalink (en)" hreflang="en"><link data-rh="true" rel="alternate" href="https://docusaurus.io/blog/tags/global-tag-permalink (en)" hreflang="x-default"><link data-rh="true" rel="preconnect" href="https://X1Z85QJPUV-dsn.algolia.net" crossorigin="anonymous"><link rel="alternate" type="application/rss+xml" href="/blog/rss.xml" title="Docusaurus blog website fixture RSS Feed">
<link rel="alternate" type="application/atom+xml" href="/blog/atom.xml" title="Docusaurus blog website fixture Atom Feed">
<link rel="alternate" type="application/json" href="/blog/feed.json" title="Docusaurus blog website fixture JSON Feed">
<link rel="search" type="application/opensearchdescription+xml" title="Docusaurus blog website fixture" href="/opensearch.xml"><link rel="stylesheet" href="/assets/css/styles.5eca0ed4.css">
<script src="/assets/js/runtime~main.972f9f8c.js" defer="defer"></script>
<script src="/assets/js/main.52184938.js" defer="defer"></script>
</head>
<body class="navigation-with-keyboard">
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><defs>
<symbol id="theme-svg-external-link" viewBox="0 0 24 24"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"/></symbol>
</defs></svg>
<script>!function(){var t="light";var e=function(){try{return new URLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}}()||function(){try{return window.localStorage.getItem("theme")}catch(t){}}();document.documentElement.setAttribute("data-theme",e||t),document.documentElement.setAttribute("data-theme-choice",e||t)}(),function(){try{const c=new URLSearchParams(window.location.search).entries();for(var[t,e]of c)if(t.startsWith("docusaurus-data-")){var a=t.replace("docusaurus-data-","data-");document.documentElement.setAttribute(a,e)}}catch(t){}}()</script><div id="__docusaurus"><div role="region" aria-label="Skip to main content"><a class="skipToContent_c5VT" href="#__docusaurus_skipToContent_fallback">Skip to main content</a></div><nav aria-label="Main" class="theme-layout-navbar navbar navbar--fixed-top navbarHideable_gzXY"><div class="navbar__inner"><div class="theme-layout-navbar-left navbar__items"><a class="navbar__brand" href="/"><div class="navbar__logo"><img src="/img/docusaurus.svg" alt="Docusaurus Logo" class="themedComponent_NoEC themedComponent--light_xEpK"><img src="/img/docusaurus_keytar.svg" alt="Docusaurus Logo" class="themedComponent_NoEC themedComponent--dark__8yu"></div><b class="navbar__title text--truncate">Docusaurus</b></a></div><div class="theme-layout-navbar-right navbar__items navbar__items--right"><div class="toggle_vYTj colorModeToggle_XGli"><button class="clean-btn toggleButton_dQHP toggleButtonDisabled_T3qH" type="button" disabled="" title="system mode" aria-label="Switch between dark and light mode (currently system mode)"><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_dOhG lightToggleIcon_VoLy"><path fill="currentColor" d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"></path></svg><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_dOhG darkToggleIcon_Y09b"><path fill="currentColor" d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"></path></svg><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_dOhG systemToggleIcon_hBI2"><path fill="currentColor" d="m12 21c4.971 0 9-4.029 9-9s-4.029-9-9-9-9 4.029-9 9 4.029 9 9 9zm4.95-13.95c1.313 1.313 2.05 3.093 2.05 4.95s-0.738 3.637-2.05 4.95c-1.313 1.313-3.093 2.05-4.95 2.05v-14c1.857 0 3.637 0.737 4.95 2.05z"></path></svg></button></div><div class="navbarSearchContainer_J_Aa"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search (Command+K)"><span class="DocSearch-Button-Container"><svg width="20" height="20" class="DocSearch-Search-Icon" viewBox="0 0 20 20" aria-hidden="true"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"></span></button></div></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div id="__docusaurus_skipToContent_fallback" class="theme-layout-main main-wrapper mainWrapper_cF7l"><main class="container margin-vert--xl"><div class="row"><div class="col col--6 col--offset-3"><h1 class="hero__title">Page Not Found</h1><p>We could not find what you were looking for.</p><p>Please contact the owner of the site that linked you to the original URL and let them know their link is broken.</p></div></div></main></div></div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -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"
`;

View File

@ -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": [
{

View File

@ -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: /<!--\s*truncate\s*-->/,
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: /<!--\s*truncate\s*-->/,
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: /<!--\s*truncate\s*-->/,
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: /<!--\s*truncate\s*-->/,
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: /<!--\s*truncate\s*-->/,
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: /<!--\s*truncate\s*-->/,
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: /<!--\s*truncate\s*-->/,
onInlineTags: 'ignore',
onInlineAuthors: 'ignore',

View File

@ -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`,

View File

@ -0,0 +1,54 @@
/**
* 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('', 'en')).toBe(0);
});
it('calculates reading time for short content', () => {
const content = 'This is a short test content.';
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, 'en')).toBe(2.5);
});
it('respects custom words per minute', () => {
const content = 'This is a test content. '.repeat(100);
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, 'en')).toBe(0.04);
});
it('handles content with multiple lines', () => {
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 = '<p>This is a <strong>test</strong> content.</p>';
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, 'en')).toBe(0.04);
});
it('handles CJK content', () => {
const content = '你好,世界!这是一段测试内容。';
expect(calculateReadingTime(content, 'zh')).toBe(0.04);
});
});

View File

@ -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 {
@ -210,8 +210,8 @@ async function parseBlogPostMarkdownFile({
}
}
const defaultReadingTime: ReadingTimeFunction = ({content, options}) =>
readingTime(content, options).minutes;
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),

View File

@ -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,

View File

@ -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<Omit<Parameters<ReadingTimeFunction>[0], 'options'>> & {
/**
* The default reading time implementation from ngryman/reading-time.
* The default reading time implementation.
*/
defaultReadingTime: ReadingTimeFunction;
},

View File

@ -0,0 +1,49 @@
/**
* 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.
*/
const DEFAULT_WORDS_PER_MINUTE = 200;
/**
* 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 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,
locale: string,
options?: {wordsPerMinute?: number},
): number {
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;
}

View File

@ -77,13 +77,6 @@ async function createMdxLoaderDependencyFile({
options: PluginOptions;
versionsMetadata: VersionMetadata[];
}): Promise<string | undefined> {
// 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 = {

View File

@ -0,0 +1,3 @@
.tsbuildinfo*
tsconfig*
__tests__

View File

@ -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).

View File

@ -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"
}
}

View File

@ -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');
});
});

View File

@ -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, PluginOptions>,
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"`,
);
});
});
});

View File

@ -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};

View File

@ -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
}

View File

@ -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<string, (filePath: string) => 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<PluginOptions> = {
id: 'default',
layers: DEFAULT_LAYERS,
};
const pluginOptionsSchema = Joi.object<PluginOptions>({
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<Options, PluginOptions>): PluginOptions {
return validate(pluginOptionsSchema, options);
}

View File

@ -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;

View File

@ -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.
*/
/// <reference types="@docusaurus/module-type-aliases" />

View File

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"noEmit": false
},
"include": ["src"],
"exclude": ["**/__tests__/**"]
}

View File

@ -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",

View File

@ -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));
}

View File

@ -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;
}

View File

@ -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';

View File

@ -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

View File

@ -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);
}

View File

@ -136,6 +136,7 @@ export type FasterConfig = {
export type FutureV4Config = {
removeLegacyPostBuildHeadAttribute: boolean;
useCssCascadeLayers: boolean;
};
export type FutureConfig = {

View File

@ -32,6 +32,7 @@
"lodash": "^4.17.21",
"micromatch": "^4.0.5",
"prompts": "^2.4.2",
"p-queue": "^6.6.2",
"resolve-pathname": "^3.0.0",
"tinyglobby": "^0.2.14",
"tslib": "^2.6.0",

View File

@ -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}`,

View File

@ -71,7 +71,10 @@ export const LAST_UPDATE_FALLBACK: LastUpdateData = {
export async function getLastUpdate(
filePath: string,
): Promise<LastUpdateData | null> {
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;
}

View File

@ -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) {

View File

@ -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(),

View File

@ -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);

View File

@ -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';
@ -34,6 +34,9 @@ export type BuildLocaleParams = {
cliOptions: Partial<BuildCLIOptions>;
};
const SkipBundling = process.env.DOCUSAURUS_SKIP_BUNDLING === 'true';
const ExitAfterBundling = process.env.DOCUSAURUS_EXIT_AFTER_BUNDLING === 'true';
export async function buildLocale({
siteDir,
locale,
@ -82,19 +85,32 @@ 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 {
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({
configs:
// For hash router we don't do SSG and can skip the server bundle
router === 'hash' ? [clientConfig] : [clientConfig, serverConfig],
currentBundler: configureWebpackUtils.currentBundler,
});
});
await cleanupBundlerTracing();
}
if (ExitAfterBundling) {
return process.exit(0);
}
const {collectedData} = await PerfLogger.async('SSG', () =>
executeSSG({
@ -231,7 +247,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 () => {

View File

@ -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;
};

View File

@ -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": [],

View File

@ -99,6 +99,7 @@ exports[`load loads props for site with custom i18n path 1`] = `
},
"v4": {
"removeLegacyPostBuildHeadAttribute": false,
"useCssCascadeLayers": false,
},
},
"headTags": [],

View File

@ -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<FutureV4Config> = {
useCssCascadeLayers: undefined,
};
expect(
normalizeConfig({
future: {
v4,
},
}),
).toEqual(v4Containing({useCssCascadeLayers: false}));
});
it('accepts - true', () => {
const v4: Partial<FutureV4Config> = {
useCssCascadeLayers: true,
};
expect(
normalizeConfig({
future: {
v4,
},
}),
).toEqual(v4Containing({useCssCascadeLayers: true}));
});
it('accepts - false', () => {
const v4: Partial<FutureV4Config> = {
useCssCascadeLayers: false,
};
expect(
normalizeConfig({
future: {
v4,
},
}),
).toEqual(v4Containing({useCssCascadeLayers: false}));
});
it('rejects - null', () => {
const v4: Partial<FutureV4Config> = {
// @ts-expect-error: invalid
useCssCascadeLayers: 42,
};
expect(() =>
normalizeConfig({
future: {
v4,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
""future.v4.useCssCascadeLayers" must be a boolean
"
`);
});
it('rejects - number', () => {
const v4: Partial<FutureV4Config> = {
// @ts-expect-error: invalid
useCssCascadeLayers: 42,
};
expect(() =>
normalizeConfig({
future: {
v4,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
""future.v4.useCssCascadeLayers" must be a boolean
"
`);
});
});
});
});

View File

@ -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()

View File

@ -27,3 +27,13 @@ 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)
: // 1 GB is a quite reasonable max value
// It should work well even for large sites
1_000_000_000;

View File

@ -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';
@ -38,16 +42,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 +112,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);
@ -127,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
},
});
},
);
@ -134,23 +145,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);
},

View File

@ -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,
},

View File

@ -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,6 +253,20 @@ export async function createBaseConfig({
modules: ['node_modules', path.join(siteDir, 'node_modules')],
},
optimization: {
// 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 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
// See also https://github.com/facebook/docusaurus/pull/11176
mergeDuplicateChunks: false,
// Only minimize client bundle in production because server bundle is only
// used for static site generation
minimize: minimizeEnabled,

View File

@ -109,6 +109,7 @@ Héctor
héllô
IANAD
Infima
infima
inlines
interactiveness
Interpolatable
@ -183,7 +184,6 @@ navigations
navlink
netrc
newtab
ngryman
Nisarag
noflash
noicon
@ -266,6 +266,8 @@ Rspack
rspack
Rspress
rtcts
Samply
samply
saurus
Scaleway
Sebastien

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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 (
<div
className={clsx(
styles.exampleContainer,
isolated ? styles.isolated : undefined,
)}>
{children}
</div>
);
}
function ExampleRow({name, children}: {name: string; children: ReactNode}) {
return (
<tr>
<td>{name}</td>
<td>
<ExampleContainer>{children}</ExampleContainer>
</td>
<td>
<ExampleContainer isolated>{children}</ExampleContainer>
</td>
</tr>
);
}
function ExamplesTable() {
return (
<table className="table-auto border-collapse border border-gray-300">
<thead>
<tr>
<th>Example</th>
<th>Normal</th>
<th>Isolated</th>
</tr>
</thead>
<tbody>
<ExampleRow name="h1">
<h1>title</h1>
</ExampleRow>
<ExampleRow name="p">
<p>text</p>
</ExampleRow>
<ExampleRow name="a">
{/* eslint-disable-next-line */}
<a href="https://example.com">link</a>
</ExampleRow>
<ExampleRow name="code">
<code>code</code>
</ExampleRow>
<ExampleRow name="pre > code">
<pre>
<code>code</code>
</pre>
</ExampleRow>
<ExampleRow name="blockquote">
<blockquote>some text</blockquote>
</ExampleRow>
<ExampleRow name="button">
{/* eslint-disable-next-line */}
<button>button</button>
</ExampleRow>
<ExampleRow name="ul">
<ul>
<li>item1</li>
<li>item2</li>
</ul>
</ExampleRow>
<ExampleRow name="ol">
<ol>
<li>item1</li>
<li>item2</li>
</ol>
</ExampleRow>
<ExampleRow name="kbd">
<kbd>kbd</kbd>
</ExampleRow>
<ExampleRow name="shadow">
<div className="shadow--tl">shadow (KO)</div>
</ExampleRow>
<ExampleRow name="table">
<table>
<thead>
<tr>
<th>Col1</th>
<th>Col2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cell1</td>
<td>Cell2</td>
</tr>
<tr>
<td>Cell3</td>
<td>Cell3</td>
</tr>
</tbody>
</table>
</ExampleRow>
<ExampleRow name="Infima button primary">
{/* eslint-disable-next-line */}
<button className="button button--primary">button</button>
</ExampleRow>
<ExampleRow name="Infima alert danger">
<div className="alert alert--danger">danger</div>
</ExampleRow>
<ExampleRow name="Infima badge success">
<div className="badge badge--success">success</div>
</ExampleRow>
</tbody>
</table>
);
}
export default function StyleIsolation(): ReactNode {
return (
<Layout>
<main
style={{padding: 30}}
className="markdown" // class added on purpose, creates extra pollution
>
<Heading as="h1">Style Isolation tests</Heading>
<p>
This shows how to isolate your components from Docusaurus global
styles. A workaround for{' '}
<Link
target="_blank"
href="https://github.com/facebook/docusaurus/issues/6032">
this issue
</Link>
.
</p>
<ExamplesTable />
</main>
</Layout>
);
}

View File

@ -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',

View File

@ -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.
:::

View File

@ -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.

View File

@ -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 24 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 <StableMajorVersion/>**: the **stable** version, on the <StableMajorBranchLink/> branch
- **Docusaurus <NextMajorVersion/>**: the **next** version, on the <NextMajorBranchLink/> branch
- **Docusaurus {CurrentMajorVersionNumber}**: the **stable** version, on the <MainBranchLink/> branch.
:::note
:::note How we will ship the next version
The <StableMajorBranchLink/> branch is created just before releasing the first v<StableMajorVersion/> release candidate.
Once we are ready ship **Docusaurus {CurrentMajorVersionNumber + 1}**, we will:
- create a <StableMajorBranchLink/> branch
- merge breaking changes on the <MainBranchLink/> branch
- release that new version directly from the <MainBranchLink/> branch
:::
### Stable version {#stable-version}
:::warning Security fixes policy
The stable version (v<StableMajorVersion/>, on <StableMajorBranchLink/>) 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 <NextMajorBranchLink/> to <StableMajorBranchLink/> 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 <StableMajorBranchLink/> 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<NextMajorVersion/>, on <NextMajorBranchLink/>) is the version the Docusaurus team is currently working on.
The <NextMajorBranchLink/> 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.

View File

@ -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)).

View File

@ -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

View File

@ -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<string, unknown>;
options?: ReadingTimeOptions;
}) => number;
type ReadingTimeFn = (params: {
content: string;
locale: string;
frontMatter: BlogPostFrontMatter & Record<string, unknown>;
defaultReadingTime: ReadingTimeCalculator;
}) => number | undefined;

View File

@ -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
<APITable>
```
| 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
</APITable>
```
### 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'),
},
};
```

View File

@ -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:

View File

@ -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),
},
},
],

View File

@ -374,12 +374,99 @@ 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.
:::
<Tabs>
<TabItem value="npm" label="npm">
```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
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```yml title=".github/workflows/deploy.yml"
name: Deploy to GitHub Pages
@ -463,6 +550,9 @@ jobs:
run: yarn build
```
</TabItem>
</Tabs>
</details>
```mdx-code-block
@ -834,10 +924,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/).

View File

@ -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,
},
@ -324,7 +336,7 @@ export default async function createConfigAsync() {
showLastUpdateTime: true,
} satisfies DocsOptions,
],
[
!process.env.DOCUSAURUS_SKIP_BUNDLING && [
'client-redirects',
{
fromExtensions: ['html'],

View File

@ -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",

6
website/profileSamply.sh Executable file
View File

@ -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

View File

@ -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 <span>{currentVersion}</span>;
}
// 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 <span>{majorVersionNumber}</span>;
}
export function StableMajorVersion(): ReactNode {
const majorVersionNumber = useStableMajorVersionNumber();
return <span>{majorVersionNumber}</span>;
return <span>{CurrentMajorVersionNumber}</span>;
}
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 <GitBranchLink branch={branch} />;
// Can't be a link until the branch actually exists...
return <code>{`docusaurus-v${CurrentMajorVersionNumber}`}</code>;
}
export function NextMajorBranchLink(): ReactNode {
export function MainBranchLink(): ReactNode {
return <GitBranchLink branch="main" />;
}

View File

@ -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:

205
yarn.lock
View File

@ -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"
@ -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"
@ -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"
@ -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==
@ -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"