mirror of
https://github.com/facebook/docusaurus.git
synced 2025-12-29 05:12:52 +00:00
Merge branch 'main' into tinyglobby
This commit is contained in:
commit
175cfbf0ee
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -48,3 +48,6 @@ website/i18n/**/*
|
|||
|
||||
website/rspack-tracing.json
|
||||
website/bundler-cpu-profile.json
|
||||
website/profile.json.gz
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export {
|
|||
getCSSExtractPlugin,
|
||||
getCopyPlugin,
|
||||
getProgressBarPlugin,
|
||||
registerBundlerTracing,
|
||||
} from './currentBundler';
|
||||
|
||||
export {getMinimizers} from './minification';
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 = ({
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}${
|
||||
|
|
|
|||
|
|
@ -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 ?? '';
|
||||
|
||||
|
|
|
|||
|
|
@ -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
File diff suppressed because one or more lines are too long
|
|
@ -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
|
|
@ -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"
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
.tsbuildinfo*
|
||||
tsconfig*
|
||||
__tests__
|
||||
|
|
@ -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).
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
@ -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"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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};
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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" />
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": false
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["**/__tests__/**"]
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ export type FasterConfig = {
|
|||
|
||||
export type FutureV4Config = {
|
||||
removeLegacyPostBuildHeadAttribute: boolean;
|
||||
useCssCascadeLayers: boolean;
|
||||
};
|
||||
|
||||
export type FutureConfig = {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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}`,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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": [],
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ exports[`load loads props for site with custom i18n path 1`] = `
|
|||
},
|
||||
"v4": {
|
||||
"removeLegacyPostBuildHeadAttribute": false,
|
||||
"useCssCascadeLayers": false,
|
||||
},
|
||||
},
|
||||
"headTags": [],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -19,14 +19,6 @@ Respecting Semantic Versioning is important for multiple reasons:
|
|||
- A new major version is an opportunity to thoroughly document breaking changes
|
||||
- A new major/minor version is an opportunity to communicate new features through a blog post
|
||||
|
||||
:::note
|
||||
|
||||
Releasing Docusaurus 2.0 took a very long time. From now on, Docusaurus will **release new major versions more regularly**. In practice, you can expect a new major version every 2–4 months.
|
||||
|
||||
[Major version numbers are not sacred](https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html), but we still group breaking changes together and avoid releasing major versions too often.
|
||||
|
||||
:::
|
||||
|
||||
### Major versions {#major-versions}
|
||||
|
||||
The `major` version number is incremented on **every breaking change**.
|
||||
|
|
@ -69,66 +61,36 @@ Whenever a new patch version is released, we publish:
|
|||
|
||||
```mdx-code-block
|
||||
import {
|
||||
StableMajorVersion,
|
||||
NextMajorVersion,
|
||||
CurrentMajorVersionNumber,
|
||||
StableMajorBranchLink,
|
||||
NextMajorBranchLink,
|
||||
MainBranchLink,
|
||||
} from "@site/src/components/Versions";
|
||||
```
|
||||
|
||||
The Docusaurus team is usually working on 2 major versions at the same time:
|
||||
The Docusaurus team uses a simple development process and only works on a single major version and a single Git branch at a same time:
|
||||
|
||||
- **Docusaurus <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.
|
||||
|
|
|
|||
|
|
@ -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)).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
},
|
||||
};
|
||||
```
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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/).
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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" />;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
205
yarn.lock
|
|
@ -2565,48 +2565,48 @@
|
|||
dependencies:
|
||||
langium "3.3.1"
|
||||
|
||||
"@module-federation/error-codes@0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@module-federation/error-codes/-/error-codes-0.11.2.tgz#880cbaf370bacb5d27e5149a93228aebe7ed084c"
|
||||
integrity sha512-ik1Qnn0I+WyEdprTck9WGlH41vGsVdUg8cfO+ZM02qOb2cZm5Vu3SlxGAobj6g7uAj0g8yINnd7h7Dci40BxQA==
|
||||
"@module-federation/error-codes@0.13.1":
|
||||
version "0.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@module-federation/error-codes/-/error-codes-0.13.1.tgz#8a1697f8e5e62baf135f8a96832f55e0afc31ead"
|
||||
integrity sha512-azgGDBnFRfqlivHOl96ZjlFUFlukESz2Rnnz/pINiSqoBBNjUE0fcAZP4X6jgrVITuEg90YkruZa7pW9I3m7Uw==
|
||||
|
||||
"@module-federation/runtime-core@0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@module-federation/runtime-core/-/runtime-core-0.11.2.tgz#677aced902d56afd3e44f4033e8d78d57c8aa029"
|
||||
integrity sha512-dia5kKybi6MFU0s5PgglJwN27k7n9Sf69Cy5xZ4BWaP0qlaXTsxHKO0PECHNt2Pt8jDdyU29sQ4DwAQfxpnXJQ==
|
||||
"@module-federation/runtime-core@0.13.1":
|
||||
version "0.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@module-federation/runtime-core/-/runtime-core-0.13.1.tgz#e9c8002eed251feeae1a04688dff1fb6c9aa32d1"
|
||||
integrity sha512-TfyKfkSAentKeuvSsAItk8s5tqQSMfIRTPN2e1aoaq/kFhE+7blps719csyWSX5Lg5Es7WXKMsXHy40UgtBtuw==
|
||||
dependencies:
|
||||
"@module-federation/error-codes" "0.11.2"
|
||||
"@module-federation/sdk" "0.11.2"
|
||||
"@module-federation/error-codes" "0.13.1"
|
||||
"@module-federation/sdk" "0.13.1"
|
||||
|
||||
"@module-federation/runtime-tools@0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@module-federation/runtime-tools/-/runtime-tools-0.11.2.tgz#d6a5c4f61b93b647de656b08ba465590631a1316"
|
||||
integrity sha512-4MJTGAxVq6vxQRkTtTlH7Mm9AVqgn0X9kdu+7RsL7T/qU+jeYsbrntN2CWG3GVVA8r5JddXyTI1iJ0VXQZLV1w==
|
||||
"@module-federation/runtime-tools@0.13.1":
|
||||
version "0.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@module-federation/runtime-tools/-/runtime-tools-0.13.1.tgz#69fc13660b189f516e56ff9f3d55a1ddfe93d2fb"
|
||||
integrity sha512-GEF1pxqLc80osIMZmE8j9UKZSaTm2hX2lql8tgIH/O9yK4wnF06k6LL5Ah+wJt+oJv6Dj55ri/MoxMP4SXoPNA==
|
||||
dependencies:
|
||||
"@module-federation/runtime" "0.11.2"
|
||||
"@module-federation/webpack-bundler-runtime" "0.11.2"
|
||||
"@module-federation/runtime" "0.13.1"
|
||||
"@module-federation/webpack-bundler-runtime" "0.13.1"
|
||||
|
||||
"@module-federation/runtime@0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@module-federation/runtime/-/runtime-0.11.2.tgz#e623136774599ce202bb4ea1e396f18fbaeee19a"
|
||||
integrity sha512-Ya9u/L6z2LvhgpqxuKCB7LcigIIRf1BbaxAZIH7mzbq/A7rZtTP7v+73E433jvgiAlbAfPSZkeoYGele6hfRwA==
|
||||
"@module-federation/runtime@0.13.1":
|
||||
version "0.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@module-federation/runtime/-/runtime-0.13.1.tgz#bd103f3e62dc335f5d7ab6e0b8f086febe5bb487"
|
||||
integrity sha512-ZHnYvBquDm49LiHfv6fgagMo/cVJneijNJzfPh6S0CJrPS2Tay1bnTXzy8VA5sdIrESagYPaskKMGIj7YfnPug==
|
||||
dependencies:
|
||||
"@module-federation/error-codes" "0.11.2"
|
||||
"@module-federation/runtime-core" "0.11.2"
|
||||
"@module-federation/sdk" "0.11.2"
|
||||
"@module-federation/error-codes" "0.13.1"
|
||||
"@module-federation/runtime-core" "0.13.1"
|
||||
"@module-federation/sdk" "0.13.1"
|
||||
|
||||
"@module-federation/sdk@0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@module-federation/sdk/-/sdk-0.11.2.tgz#965b0dcf8fb036dda9b1e6812d6ae0a394ea827d"
|
||||
integrity sha512-SBFe5xOamluT900J4AGBx+2/kCH/JbfqXoUwPSAC6PRzb8Y7LB0posnOGzmqYsLZXT37vp3d6AmJDsVoajDqxw==
|
||||
"@module-federation/sdk@0.13.1":
|
||||
version "0.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@module-federation/sdk/-/sdk-0.13.1.tgz#5b8d63719c452e6f691ce1b839f3b09079dfe4c9"
|
||||
integrity sha512-bmf2FGQ0ymZuxYnw9bIUfhV3y6zDhaqgydEjbl4msObKMLGXZqhse2pTIIxBFpIxR1oONKX/y2FAolDCTlWKiw==
|
||||
|
||||
"@module-federation/webpack-bundler-runtime@0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.11.2.tgz#ef4a21e0ff8aefce9c264a57aa882ee72ecfe6aa"
|
||||
integrity sha512-WdwIE6QF+MKs/PdVu0cKPETF743JB9PZ62/qf7Uo3gU4fjsUMc37RnbJZ/qB60EaHHfjwp1v6NnhZw1r4eVsnw==
|
||||
"@module-federation/webpack-bundler-runtime@0.13.1":
|
||||
version "0.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.13.1.tgz#4a14f8626cfbbd9f4d0ec03b5c90b0aa14ba8cbd"
|
||||
integrity sha512-QSuSIGa09S8mthbB1L6xERqrz+AzPlHR6D7RwAzssAc+IHf40U6NiTLPzUqp9mmKDhC5Tm0EISU0ZHNeJpnpBQ==
|
||||
dependencies:
|
||||
"@module-federation/runtime" "0.11.2"
|
||||
"@module-federation/sdk" "0.11.2"
|
||||
"@module-federation/runtime" "0.13.1"
|
||||
"@module-federation/sdk" "0.13.1"
|
||||
|
||||
"@netlify/functions@^1.6.0":
|
||||
version "1.6.0"
|
||||
|
|
@ -3262,75 +3262,75 @@
|
|||
fs-extra "^11.1.1"
|
||||
lodash "^4.17.21"
|
||||
|
||||
"@rspack/binding-darwin-arm64@1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.3.3.tgz#f62473fa7df23f649144d61dc40d09c44a1c26b6"
|
||||
integrity sha512-vbzEdpRCZl5+HXWsVjzSDqB9ZVIlqldV+udHp4YDD8qiwdQznVaBZke0eMzZ7kaInqRPsZ+UHQuVk6JaH/JkMQ==
|
||||
"@rspack/binding-darwin-arm64@1.3.10":
|
||||
version "1.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.3.10.tgz#676ac33fc66a7da4669e68175bf267f40b99a282"
|
||||
integrity sha512-0k/j8OeMSVm5u5Nzckp9Ie7S7hprnvNegebnGr+L6VCyD7sMqm4m+4rLHs99ZklYdH0dZtY2+LrzrtjUZCqfew==
|
||||
|
||||
"@rspack/binding-darwin-x64@1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.3.3.tgz#472ad3b6ae6c687bff1f4bb9a6f2d2ddbe3d1c19"
|
||||
integrity sha512-OXtY2s4nlYtUXkeJt8TQKKNIcN7PI8yDq0nqI75OfJoS4u1ZmRXJ8IMeSALLo8I+xD2RAF79tf7yhM/Y/AaiKQ==
|
||||
"@rspack/binding-darwin-x64@1.3.10":
|
||||
version "1.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.3.10.tgz#79102a085443617bf70ced9dc5f7cfe2fb3dd6a6"
|
||||
integrity sha512-jOyqYW/18cgxw60wK5oqJvM194pbD4H99xaif89McNtLkH3npFvBkXBHVWWuOHGoXNX0LhRpHcI89p9b9THQZQ==
|
||||
|
||||
"@rspack/binding-linux-arm64-gnu@1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.3.3.tgz#0df2ac7ab945df162073bdcdea20171df9ee5942"
|
||||
integrity sha512-Lluq3RLYzyCMdXr/HyALKEPGsr+196x8Ccuy5AmIRosOdWuwtSiomSRH1Ka8REUFNHfYy5y9SzfmIZo/E0QEmg==
|
||||
"@rspack/binding-linux-arm64-gnu@1.3.10":
|
||||
version "1.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.3.10.tgz#192c7f27013119f6fc082fca03525897726eda21"
|
||||
integrity sha512-zhF5ZNaT/7pxrm8xD3dWG1b4x+FO3LbVeZZG448CjpSo5T57kPD+SaGUU1GcPpn6mexf795x0SVS49aH7/e3Dg==
|
||||
|
||||
"@rspack/binding-linux-arm64-musl@1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.3.3.tgz#082d45d2a92255656cadf4ebe801dc09726140fa"
|
||||
integrity sha512-PIsicXWjOqzmoOutUqxpMNkCoKo+8/wxDyKxHFeu+5WIAxVFphe2d3H5qvEjc2MasWSdRmAVn9XiuIj2LIXFzA==
|
||||
"@rspack/binding-linux-arm64-musl@1.3.10":
|
||||
version "1.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.3.10.tgz#2be001351a7f2e93d7a189b8607137343648a682"
|
||||
integrity sha512-o3x7IrOSCHK6lcRvdZgsSuOG1CHRQR00xiyLW7kkWmNm7t417LC9xdFWKIK62C5fKXGC5YcTbUkDMnQujespkg==
|
||||
|
||||
"@rspack/binding-linux-x64-gnu@1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.3.3.tgz#2be5a5eff56c3dd2f58ff7e42abe3c9484d42b20"
|
||||
integrity sha512-BtksK73ZFdny2T/wU1x0kxBF4ruYUUArZDyeGfpO+vd/1nNYqzzdhGvOksKmtdvsO38ETr2gZ9+XZyr1vpy9uQ==
|
||||
"@rspack/binding-linux-x64-gnu@1.3.10":
|
||||
version "1.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.3.10.tgz#ad44d1dee50688ed81e87235d774bfe22528ce20"
|
||||
integrity sha512-FMSi28VZhXMr15picOHFynULhqZ/FODPxRIS6uNrvPRYcbNuiO1v+VHV6X88mhOMmJ/aVF6OwjUO/o2l1FVa9Q==
|
||||
|
||||
"@rspack/binding-linux-x64-musl@1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.3.3.tgz#019d02dbcc25e0c16ca01f67bebf4b6bde3f4ae9"
|
||||
integrity sha512-jx86CxkTmyBz/eHDqZp1mCqBwY+UTEtaPlPoWFyGkJUR5ey6nQnxS+fhG34Rqz63chW+q/afwpGNGyALYdgc8g==
|
||||
"@rspack/binding-linux-x64-musl@1.3.10":
|
||||
version "1.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.3.10.tgz#24ff3df3017c44e570a604e64312b0a3c603a133"
|
||||
integrity sha512-e0xbY9SlbRGIFz41v1yc0HfREvmgMnLV1bLmTSPK8wI2suIEJ7iYYqsocHOAOk86qLZcxVrTnL6EjUcNaRTWlg==
|
||||
|
||||
"@rspack/binding-win32-arm64-msvc@1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.3.3.tgz#204b33735d9b5e2c96ec0419f1e2151d1c2c4bcf"
|
||||
integrity sha512-uXAdDzajFToVrH3fCNVDP/uKQ9i5FQjJc2aYxsnhS9Su/CZB+UQsOecbq6MnIN2s0B9GBKBG8QdQEtS3RtC6Hg==
|
||||
"@rspack/binding-win32-arm64-msvc@1.3.10":
|
||||
version "1.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.3.10.tgz#d853250fee6b066a815c3d29abd115db331a4b6b"
|
||||
integrity sha512-YHJPvEujWeWjU6EUF6sDpaec9rsOtKVvy16YCtGaxRpDQXqfuxibnp6Ge0ZTTrY+joRiWehRA9OUI+3McqI+QA==
|
||||
|
||||
"@rspack/binding-win32-ia32-msvc@1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.3.3.tgz#66fe0ebd3b44c34393862693d7bbb8fc68624504"
|
||||
integrity sha512-VBE6XsJ3IiAlozAywAIxAZ1Aqc2QVnEwBo0gP9998KkwL7wxB6Bg/OJnPbH3Q0ZaNWAQViC99rPC+5hSIdeSxw==
|
||||
"@rspack/binding-win32-ia32-msvc@1.3.10":
|
||||
version "1.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.3.10.tgz#40d401aa1c7550f2b5688b68aee54de3abca67b1"
|
||||
integrity sha512-2iwSBzVBC89ZSk56MYwgirh3bda2WKmL9I3qPajiTEivChXpX7jp83jAtGE6CPqPYcccYz6nrURTHNUZhqXxVw==
|
||||
|
||||
"@rspack/binding-win32-x64-msvc@1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.3.3.tgz#d17a96dbb7cb5730742127c58dc3bec3f6f39333"
|
||||
integrity sha512-rOsNz4/DFgSENjEh0t9kFn89feuXK14/9wbmmFlT8VMpYOCcj4tKcAHjWg+Nzzj4FL+NSOC/81SrUF9J+C2R7w==
|
||||
"@rspack/binding-win32-x64-msvc@1.3.10":
|
||||
version "1.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.3.10.tgz#4eefa2d1d851c25ba0e0b1eee0ee525be224c1cd"
|
||||
integrity sha512-ehWJ9Y5Zezj/+uJpiWbt29RZaRIM00f91PWuabM6/sKmHJhdCEE21u5iI3B8DeW/EjJsH8zkI69YYAxJWwGn9A==
|
||||
|
||||
"@rspack/binding@1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding/-/binding-1.3.3.tgz#19c104b7eed3bb01ec6089cee9f08d41acc035a4"
|
||||
integrity sha512-zdwJ801tyC8k+Gu5RjNoc7bEtX0MgJzzVv9qpaMwcAUfUfwZgCzXPTqcGMDoNI+Z47Fw59/2fKCmgZhZn60AgA==
|
||||
"@rspack/binding@1.3.10":
|
||||
version "1.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/binding/-/binding-1.3.10.tgz#b14d9a21a7bae8c7025e6c2d9707e81c311147d5"
|
||||
integrity sha512-9TjO+Ig5U4VqdYWpBsv01n4d2KsgFfdXGIE7zdHXDDbry0avL0J4109ESqSeP9uC+Bi7ZCF53iaxJRvZDflNVQ==
|
||||
optionalDependencies:
|
||||
"@rspack/binding-darwin-arm64" "1.3.3"
|
||||
"@rspack/binding-darwin-x64" "1.3.3"
|
||||
"@rspack/binding-linux-arm64-gnu" "1.3.3"
|
||||
"@rspack/binding-linux-arm64-musl" "1.3.3"
|
||||
"@rspack/binding-linux-x64-gnu" "1.3.3"
|
||||
"@rspack/binding-linux-x64-musl" "1.3.3"
|
||||
"@rspack/binding-win32-arm64-msvc" "1.3.3"
|
||||
"@rspack/binding-win32-ia32-msvc" "1.3.3"
|
||||
"@rspack/binding-win32-x64-msvc" "1.3.3"
|
||||
"@rspack/binding-darwin-arm64" "1.3.10"
|
||||
"@rspack/binding-darwin-x64" "1.3.10"
|
||||
"@rspack/binding-linux-arm64-gnu" "1.3.10"
|
||||
"@rspack/binding-linux-arm64-musl" "1.3.10"
|
||||
"@rspack/binding-linux-x64-gnu" "1.3.10"
|
||||
"@rspack/binding-linux-x64-musl" "1.3.10"
|
||||
"@rspack/binding-win32-arm64-msvc" "1.3.10"
|
||||
"@rspack/binding-win32-ia32-msvc" "1.3.10"
|
||||
"@rspack/binding-win32-x64-msvc" "1.3.10"
|
||||
|
||||
"@rspack/core@^1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/core/-/core-1.3.3.tgz#1eba8c52878ab898e2c06cfbffd3ee32881e8054"
|
||||
integrity sha512-+mXVlFcYr0tWezZfJ/gR0fj8njRc7pzEMtTFa2NO5cfsNAKPF/SXm4rb55kfa63r0b3U3N7f2nKrJG9wyG7zMQ==
|
||||
"@rspack/core@^1.3.10":
|
||||
version "1.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@rspack/core/-/core-1.3.10.tgz#6b00365c3ccfd1008d598d1ce5a82bab466dc777"
|
||||
integrity sha512-YomvSRGuMUQgCE2rNMdff2q1Z0YpZw/z6m5+PVTMSs9l/q69YKUzpbpSD8YyB5i1DddrRxC2RE34DkrBuwlREQ==
|
||||
dependencies:
|
||||
"@module-federation/runtime-tools" "0.11.2"
|
||||
"@rspack/binding" "1.3.3"
|
||||
"@module-federation/runtime-tools" "0.13.1"
|
||||
"@rspack/binding" "1.3.10"
|
||||
"@rspack/lite-tapable" "1.0.1"
|
||||
caniuse-lite "^1.0.30001707"
|
||||
caniuse-lite "^1.0.30001717"
|
||||
|
||||
"@rspack/lite-tapable@1.0.1":
|
||||
version "1.0.1"
|
||||
|
|
@ -5911,10 +5911,10 @@ caniuse-api@^3.0.0:
|
|||
lodash.memoize "^4.1.2"
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669, caniuse-lite@^1.0.30001707:
|
||||
version "1.0.30001712"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz#41ee150f12de11b5f57c5889d4f30deb451deedf"
|
||||
integrity sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669, caniuse-lite@^1.0.30001717:
|
||||
version "1.0.30001718"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz#dae13a9c80d517c30c6197515a96131c194d8f82"
|
||||
integrity sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==
|
||||
|
||||
ccount@^2.0.0:
|
||||
version "2.0.1"
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue