Merge branch 'main' into ozaki/showcase

This commit is contained in:
ozakione 2024-04-11 14:54:51 +02:00
commit c89f8bed6e
288 changed files with 24854 additions and 3432 deletions

View file

@ -15,4 +15,4 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Dependency Review
uses: actions/dependency-review-action@9129d7d40b8c12c1ed0f60400d00c92d437adcce # 4.1.3
uses: actions/dependency-review-action@5bbc3ba658137598168acb2ab73b21c432dd411b # 4.2.5

View file

@ -1,5 +1,158 @@
# Docusaurus 2 Changelog
## 3.2.1 (2024-04-04)
#### :bug: Bug Fix
- `docusaurus`
- [#10012](https://github.com/facebook/docusaurus/pull/10012) fix(core): fix configurePostCss v3.2 regression ([@slorber](https://github.com/slorber))
#### :memo: Documentation
- [#9980](https://github.com/facebook/docusaurus/pull/9980) docs: remove old github action description ([@OzakIOne](https://github.com/OzakIOne))
- [#10014](https://github.com/facebook/docusaurus/pull/10014) docs(website): fix SEO docs headTags example ([@OzakIOne](https://github.com/OzakIOne))
- [#10004](https://github.com/facebook/docusaurus/pull/10004) docs(website): Announce v3.2 on website/homepage ([@slorber](https://github.com/slorber))
#### :robot: Dependencies
- [#10006](https://github.com/facebook/docusaurus/pull/10006) chore(deps): bump actions/dependency-review-action from 4.2.4 to 4.2.5 ([@dependabot[bot]](https://github.com/apps/dependabot))
#### Committers: 2
- Sébastien Lorber ([@slorber](https://github.com/slorber))
- ozaki ([@OzakIOne](https://github.com/OzakIOne))
## 3.2.0 (2024-03-29)
#### :rocket: New Feature
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-sitemap`, `docusaurus-types`, `docusaurus-utils`, `docusaurus`
- [#9954](https://github.com/facebook/docusaurus/pull/9954) feat(sitemap): add support for "lastmod" ([@slorber](https://github.com/slorber))
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-utils-validation`, `docusaurus-utils`
- [#9912](https://github.com/facebook/docusaurus/pull/9912) feat(blog): add LastUpdateAuthor & LastUpdateTime ([@OzakIOne](https://github.com/OzakIOne))
- `docusaurus-plugin-debug`, `docusaurus-types`, `docusaurus`
- [#9931](https://github.com/facebook/docusaurus/pull/9931) feat(core): add new plugin allContentLoaded lifecycle ([@slorber](https://github.com/slorber))
- `docusaurus-theme-translations`
- [#9928](https://github.com/facebook/docusaurus/pull/9928) feat(theme-translations) Icelandic (is) ([@Hallinn](https://github.com/Hallinn))
- `docusaurus-plugin-content-blog`
- [#9886](https://github.com/facebook/docusaurus/pull/9886) feat(blog): allow processing blog posts through a processBlogPosts function ([@OzakIOne](https://github.com/OzakIOne))
- [#9838](https://github.com/facebook/docusaurus/pull/9838) feat(blog): add blog pageBasePath plugin option ([@ilg-ul](https://github.com/ilg-ul))
- `docusaurus`
- [#9681](https://github.com/facebook/docusaurus/pull/9681) feat(swizzle): ask user preferred language if no language CLI option provided ([@yixiaojiu](https://github.com/yixiaojiu))
- `create-docusaurus`, `docusaurus-utils`
- [#9442](https://github.com/facebook/docusaurus/pull/9442) feat(create-docusaurus): ask user for preferred language when no language CLI option provided ([@Rafael-Martins](https://github.com/Rafael-Martins))
- `docusaurus-plugin-vercel-analytics`
- [#9687](https://github.com/facebook/docusaurus/pull/9687) feat(plugin-vercel-analytics): add new vercel analytics plugin ([@OzakIOne](https://github.com/OzakIOne))
- `docusaurus-mdx-loader`
- [#9684](https://github.com/facebook/docusaurus/pull/9684) feat(mdx-loader): the table-of-contents should display toc/headings of imported MDX partials ([@anatolykopyl](https://github.com/anatolykopyl))
#### :bug: Bug Fix
- `docusaurus-mdx-loader`
- [#9999](https://github.com/facebook/docusaurus/pull/9999) fix(mdx-loader): Ignore contentTitle coming after Markdown thematicBreak ([@slorber](https://github.com/slorber))
- `docusaurus-theme-search-algolia`
- [#9945](https://github.com/facebook/docusaurus/pull/9945) fix(a11y): move focus algolia-search focus back to search input on Escape ([@mxschmitt](https://github.com/mxschmitt))
- `docusaurus-plugin-content-blog`
- [#9920](https://github.com/facebook/docusaurus/pull/9920) fix(blog): apply trailing slash to blog feed ([@OzakIOne](https://github.com/OzakIOne))
- `docusaurus-theme-classic`
- [#9944](https://github.com/facebook/docusaurus/pull/9944) fix(theme): improve a11y of DocSidebarItemCategory expand/collapsed button ([@mxschmitt](https://github.com/mxschmitt))
- `docusaurus-theme-translations`
- [#9915](https://github.com/facebook/docusaurus/pull/9915) fix(theme-translations): complete and modify Japanese translations ([@Suenaga-Ryuya](https://github.com/Suenaga-Ryuya))
- [#9910](https://github.com/facebook/docusaurus/pull/9910) fix(theme-translations): add Japanese translations ([@Suenaga-Ryuya](https://github.com/Suenaga-Ryuya))
- [#9872](https://github.com/facebook/docusaurus/pull/9872) fix(theme-translations): complete and improve Spanish theme translations ([@4troDev](https://github.com/4troDev))
- [#9812](https://github.com/facebook/docusaurus/pull/9812) fix(i18n): add missing theme translations for fa locale ([@VahidNaderi](https://github.com/VahidNaderi))
- `docusaurus-utils`
- [#9897](https://github.com/facebook/docusaurus/pull/9897) fix(mdx-loader): mdx-code-block should support CRLF ([@slorber](https://github.com/slorber))
- `docusaurus`
- [#9878](https://github.com/facebook/docusaurus/pull/9878) fix(core): fix default i18n calendar used, infer it from locale if possible ([@slorber](https://github.com/slorber))
- [#9852](https://github.com/facebook/docusaurus/pull/9852) fix(core): ensure core error boundary is able to render theme layout ([@slorber](https://github.com/slorber))
- `docusaurus-remark-plugin-npm2yarn`
- [#9861](https://github.com/facebook/docusaurus/pull/9861) fix(remark-npm2yarn): update npm-to-yarn from 2.0.0 to 2.2.1, fix pnpm extra args syntax ([@OzakIOne](https://github.com/OzakIOne))
- `docusaurus-theme-classic`, `docusaurus-theme-translations`
- [#9851](https://github.com/facebook/docusaurus/pull/9851) fix(theme-classic): should use plurals for category items description ([@baradusov](https://github.com/baradusov))
#### :running_woman: Performance
- `docusaurus-types`, `docusaurus-utils`, `docusaurus`
- [#9975](https://github.com/facebook/docusaurus/pull/9975) refactor(core): improve dev perf, fine-grained site reloads - part 3 ([@slorber](https://github.com/slorber))
- `docusaurus-types`, `docusaurus`
- [#9968](https://github.com/facebook/docusaurus/pull/9968) refactor(core): improve dev perf, fine-grained site reloads - part2 ([@slorber](https://github.com/slorber))
- `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-types`, `docusaurus`
- [#9903](https://github.com/facebook/docusaurus/pull/9903) refactor(core): improve dev perf, fine-grained site reloads - part1 ([@slorber](https://github.com/slorber))
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-utils`
- [#9890](https://github.com/facebook/docusaurus/pull/9890) perf: optimize getFileCommitDate, make it async ([@slorber](https://github.com/slorber))
- `docusaurus`
- [#9798](https://github.com/facebook/docusaurus/pull/9798) refactor(core): internalize, simplify and optimize the SSG logic ([@slorber](https://github.com/slorber))
#### :nail_care: Polish
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`
- [#9868](https://github.com/facebook/docusaurus/pull/9868) refactor(theme): dates should be formatted on the client-side instead of in nodejs code ([@OzakIOne](https://github.com/OzakIOne))
- `docusaurus-plugin-content-blog`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-types`
- [#9669](https://github.com/facebook/docusaurus/pull/9669) refactor(theme): use JSON-LD instead of microdata for blog structured data ([@johnnyreilly](https://github.com/johnnyreilly))
- `docusaurus-plugin-content-docs`
- [#9839](https://github.com/facebook/docusaurus/pull/9839) refactor(blog): improve doc global data hook error message + add doc warning to blogOnly mode ([@OzakIOne](https://github.com/OzakIOne))
#### :memo: Documentation
- [#9937](https://github.com/facebook/docusaurus/pull/9937) docs: use official GitHub Action to deploy to GitHub Pages ([@vlad-nestorov](https://github.com/vlad-nestorov))
- [#9971](https://github.com/facebook/docusaurus/pull/9971) docs: replace VuePress by VitePress on tool comparison section ([@sunkanmii](https://github.com/sunkanmii))
- [#9914](https://github.com/facebook/docusaurus/pull/9914) docs: update legacy MDX v1 links to markdown links ([@OzakIOne](https://github.com/OzakIOne))
- [#9913](https://github.com/facebook/docusaurus/pull/9913) docs: update legacy MDX v1 links to markdown links ([@OzakIOne](https://github.com/OzakIOne))
- [#9906](https://github.com/facebook/docusaurus/pull/9906) docs: emphasize "index slug" convention ([@Josh-Cena](https://github.com/Josh-Cena))
- [#9877](https://github.com/facebook/docusaurus/pull/9877) docs: fix typos in deployment.mdx ([@Oreoxmt](https://github.com/Oreoxmt))
- [#9845](https://github.com/facebook/docusaurus/pull/9845) docs: typo ([@OzakIOne](https://github.com/OzakIOne))
- [#9816](https://github.com/facebook/docusaurus/pull/9816) docs: Add docs for Mermaid Component ([@Its-Just-Nans](https://github.com/Its-Just-Nans))
#### :robot: Dependencies
- [#9981](https://github.com/facebook/docusaurus/pull/9981) chore(deps): bump actions/dependency-review-action from 4.1.3 to 4.2.4 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9982](https://github.com/facebook/docusaurus/pull/9982) chore(deps): bump katex from 0.16.8 to 0.16.10 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9983](https://github.com/facebook/docusaurus/pull/9983) chore(deps): bump express from 4.18.2 to 4.19.2 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9977](https://github.com/facebook/docusaurus/pull/9977) chore(deps): bump webpack-dev-middleware from 5.3.3 to 5.3.4 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9958](https://github.com/facebook/docusaurus/pull/9958) chore(deps): bump follow-redirects from 1.15.4 to 1.15.6 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9892](https://github.com/facebook/docusaurus/pull/9892) chore(deps): bump actions/dependency-review-action from 4.1.2 to 4.1.3 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9869](https://github.com/facebook/docusaurus/pull/9869) chore(deps): bump actions/dependency-review-action from 4.0.0 to 4.1.2 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9874](https://github.com/facebook/docusaurus/pull/9874) chore(deps): bump ip from 2.0.0 to 2.0.1 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9843](https://github.com/facebook/docusaurus/pull/9843) chore(deps): bump actions/setup-node from 4.0.1 to 4.0.2 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9824](https://github.com/facebook/docusaurus/pull/9824) chore(deps): bump treosh/lighthouse-ci-action from 10.1.0 to 11.4.0 ([@dependabot[bot]](https://github.com/apps/dependabot))
- [#9823](https://github.com/facebook/docusaurus/pull/9823) chore(deps): bump marocchino/sticky-pull-request-comment from 2.8.0 to 2.9.0 ([@dependabot[bot]](https://github.com/apps/dependabot))
#### :wrench: Maintenance
- `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-docs`, `docusaurus-utils-common`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`
- [#9972](https://github.com/facebook/docusaurus/pull/9972) refactor(utils): remove duplicated function ([@OzakIOne](https://github.com/OzakIOne))
- Other
- [#9965](https://github.com/facebook/docusaurus/pull/9965) refactor(website): organise blog posts by year ([@GingerGeek](https://github.com/GingerGeek))
- [#9865](https://github.com/facebook/docusaurus/pull/9865) chore(website): update @crowdin/crowdin-api-client ([@chris-bateman](https://github.com/chris-bateman))
- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-utils`
- [#9963](https://github.com/facebook/docusaurus/pull/9963) refactor(docs,blog): last update timestamp should be in milliseconds instead of seconds ([@slorber](https://github.com/slorber))
#### Committers: 22
- Aolin ([@Oreoxmt](https://github.com/Oreoxmt))
- Anatoly Kopyl ([@anatolykopyl](https://github.com/anatolykopyl))
- Chris Bateman ([@chris-bateman](https://github.com/chris-bateman))
- Fafowora Sunkanmi ([@sunkanmii](https://github.com/sunkanmii))
- Hallbjörn Magnússon ([@Hallinn](https://github.com/Hallinn))
- John Reilly ([@johnnyreilly](https://github.com/johnnyreilly))
- Joshua Chen ([@Josh-Cena](https://github.com/Josh-Cena))
- Josue [4tro] A ([@4troDev](https://github.com/4troDev))
- Liviu Ionescu ([@ilg-ul](https://github.com/ilg-ul))
- Max Schmitt ([@mxschmitt](https://github.com/mxschmitt))
- Rafael Martins ([@Rafael-Martins](https://github.com/Rafael-Martins))
- Sébastien Lorber ([@slorber](https://github.com/slorber))
- Vahid Naderi ([@VahidNaderi](https://github.com/VahidNaderi))
- Vlad Nestorov ([@vlad-nestorov](https://github.com/vlad-nestorov))
- Zed Spencer-Milnes ([@GingerGeek](https://github.com/GingerGeek))
- axel7083 ([@axel7083](https://github.com/axel7083))
- krinza.eth ([@kaymomin](https://github.com/kaymomin))
- n4n5 ([@Its-Just-Nans](https://github.com/Its-Just-Nans))
- ozaki ([@OzakIOne](https://github.com/OzakIOne))
- suenryu ([@Suenaga-Ryuya](https://github.com/Suenaga-Ryuya))
- Нуриль Барадусов ([@baradusov](https://github.com/baradusov))
- 翊小久 ([@yixiaojiu](https://github.com/yixiaojiu))
## 3.1.1 (2024-01-26)
#### :bug: Bug Fix

View file

@ -11,9 +11,9 @@ const CookieName = 'DocusaurusPlaygroundName';
const PlaygroundConfigs = {
codesandbox:
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic?file=%2FREADME.md',
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic?file=%2FREADME.md&privacy=public',
'codesandbox-ts':
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic-typescript?file=%2FREADME.md',
'https://codesandbox.io/p/sandbox/github/facebook/docusaurus/tree/main/examples/classic-typescript?file=%2FREADME.md&privacy=public',
// Slow to load
// stackblitz: 'https://stackblitz.com/github/facebook/docusaurus/tree/main/examples/classic',

View file

@ -1,6 +1,6 @@
{
"name": "new.docusaurus.io",
"version": "3.0.0",
"version": "3.2.1",
"private": true,
"scripts": {
"start": "npx --package netlify-cli netlify dev"

View file

@ -1,6 +1,6 @@
{
"name": "argos",
"version": "3.0.0",
"version": "3.2.1",
"description": "Argos visual diff tests",
"license": "MIT",
"private": true,

View file

@ -16,8 +16,8 @@
"dev": "docusaurus start"
},
"dependencies": {
"@docusaurus/core": "3.1.1",
"@docusaurus/preset-classic": "3.1.1",
"@docusaurus/core": "3.2.0",
"@docusaurus/preset-classic": "3.2.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
@ -25,9 +25,9 @@
"react-dom": "^18.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.1.1",
"@docusaurus/tsconfig": "3.1.1",
"@docusaurus/types": "3.1.1",
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/tsconfig": "3.2.0",
"@docusaurus/types": "3.2.0",
"typescript": "~5.2.2"
},
"browserslist": {

File diff suppressed because it is too large Load diff

View file

@ -15,8 +15,8 @@
"dev": "docusaurus start"
},
"dependencies": {
"@docusaurus/core": "3.1.1",
"@docusaurus/preset-classic": "3.1.1",
"@docusaurus/core": "3.2.0",
"@docusaurus/preset-classic": "3.2.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
@ -24,8 +24,8 @@
"react-dom": "^18.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.1.1",
"@docusaurus/types": "3.1.1"
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/types": "3.2.0"
},
"browserslist": {
"production": [

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
{
"version": "3.0.0",
"version": "3.2.1",
"npmClient": "yarn",
"useWorkspaces": true,
"useNx": false,

View file

@ -1,6 +1,6 @@
{
"name": "create-docusaurus",
"version": "3.0.0",
"version": "3.2.1",
"description": "Create Docusaurus apps easily.",
"type": "module",
"repository": {
@ -22,8 +22,8 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/logger": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/logger": "3.2.1",
"@docusaurus/utils": "3.2.1",
"commander": "^5.1.0",
"fs-extra": "^11.1.1",
"lodash": "^4.17.21",

View file

@ -1,6 +1,6 @@
{
"name": "docusaurus-2-classic-typescript-template",
"version": "3.0.0",
"version": "3.2.1",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@ -15,8 +15,8 @@
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/preset-classic": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/preset-classic": "3.2.1",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
@ -24,9 +24,9 @@
"react-dom": "^18.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/tsconfig": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/module-type-aliases": "3.2.1",
"@docusaurus/tsconfig": "3.2.1",
"@docusaurus/types": "3.2.1",
"typescript": "~5.2.2"
},
"browserslist": {

View file

@ -1,6 +1,6 @@
{
"name": "docusaurus-2-classic-template",
"version": "3.0.0",
"version": "3.2.1",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@ -14,8 +14,8 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/preset-classic": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/preset-classic": "3.2.1",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
@ -23,8 +23,8 @@
"react-dom": "^18.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/types": "3.0.0"
"@docusaurus/module-type-aliases": "3.2.1",
"@docusaurus/types": "3.2.1"
},
"browserslist": {
"production": [

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/cssnano-preset",
"version": "3.0.0",
"version": "3.2.1",
"description": "Advanced cssnano preset for maximum optimization.",
"main": "lib/index.js",
"license": "MIT",

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/logger",
"version": "3.0.0",
"version": "3.2.1",
"description": "An encapsulated logger for semantically formatting console messages.",
"main": "./lib/index.js",
"repository": {

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/mdx-loader",
"version": "3.0.0",
"version": "3.2.1",
"description": "Docusaurus Loader for MDX",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,9 +18,9 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/logger": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/logger": "3.2.1",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"@mdx-js/mdx": "^3.0.0",
"@slorber/remark-comment": "^1.0.0",
"escape-html": "^1.0.3",
@ -44,7 +44,7 @@
"webpack": "^5.88.1"
},
"devDependencies": {
"@docusaurus/types": "3.0.0",
"@docusaurus/types": "3.2.1",
"@types/escape-html": "^1.0.2",
"@types/mdast": "^4.0.2",
"@types/stringify-object": "^3.3.1",

View file

@ -65,6 +65,21 @@ some **markdown** *content*
# contentTitle 1
some **markdown** *content*
`);
expect(result.data.contentTitle).toBeUndefined();
});
it('ignore contentTitle if after thematic break', async () => {
const result = await process(`
Hey
---
# contentTitle 1
some **markdown** *content*
`);

View file

@ -34,17 +34,24 @@ const plugin: Plugin = function plugin(
const {toString} = await import('mdast-util-to-string');
const {visit, EXIT} = await import('unist-util-visit');
visit(root, 'heading', (headingNode: Heading, index, parent) => {
if (headingNode.depth === 1) {
vfile.data.contentTitle = toString(headingNode);
if (removeContentTitle) {
// @ts-expect-error: TODO how to fix?
parent!.children.splice(index, 1);
visit(root, ['heading', 'thematicBreak'], (node, index, parent) => {
if (node.type === 'heading') {
const headingNode = node as Heading;
if (headingNode.depth === 1) {
vfile.data.contentTitle = toString(headingNode);
if (removeContentTitle) {
// @ts-expect-error: TODO how to fix?
parent!.children.splice(index, 1);
}
return EXIT; // We only handle the very first heading
}
// We only handle contentTitle if it's the very first heading found
if (headingNode.depth >= 1) {
return EXIT;
}
return EXIT; // We only handle the very first heading
}
// We only handle contentTitle if it's the very first heading found
if (headingNode.depth >= 1) {
// We only handle contentTitle when it's above the first thematic break
if (node.type === 'thematicBreak') {
return EXIT;
}
return undefined;

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/module-type-aliases",
"version": "3.0.0",
"version": "3.2.1",
"description": "Docusaurus module type aliases.",
"types": "./src/index.d.ts",
"publishConfig": {
@ -13,7 +13,7 @@
},
"dependencies": {
"@docusaurus/react-loadable": "5.5.2",
"@docusaurus/types": "3.0.0",
"@docusaurus/types": "3.2.1",
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router-config": "*",

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-client-redirects",
"version": "3.0.0",
"version": "3.2.1",
"description": "Client redirects plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,18 +18,18 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/logger": "3.2.1",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-common": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"eta": "^2.2.0",
"fs-extra": "^11.1.1",
"lodash": "^4.17.21",
"tslib": "^2.6.0"
},
"devDependencies": {
"@docusaurus/types": "3.0.0"
"@docusaurus/types": "3.2.1"
},
"peerDependencies": {
"react": "^18.0.0",

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {removeTrailingSlash} from '@docusaurus/utils';
import {removeTrailingSlash} from '@docusaurus/utils-common';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import collectRedirects from '../collectRedirects';
import {validateOptions} from '../options';

View file

@ -7,8 +7,11 @@
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils';
import {applyTrailingSlash} from '@docusaurus/utils-common';
import {
applyTrailingSlash,
addTrailingSlash,
removeTrailingSlash,
} from '@docusaurus/utils-common';
import {
createFromExtensionsRedirects,
createToExtensionsRedirects,

View file

@ -9,7 +9,7 @@ import {
addTrailingSlash,
removeSuffix,
removeTrailingSlash,
} from '@docusaurus/utils';
} from '@docusaurus/utils-common';
import type {RedirectItem} from './types';
const ExtensionAdditionalMessage =

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
import {addLeadingSlash, removePrefix} from '@docusaurus/utils-common';
import collectRedirects from './collectRedirects';
import writeRedirectFiles, {
toRedirectFiles,

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-blog",
"version": "3.0.0",
"version": "3.2.1",
"description": "Blog plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-content-blog.d.ts",
@ -31,13 +31,13 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/logger": "3.2.1",
"@docusaurus/mdx-loader": "3.2.1",
"@docusaurus/types": "3.2.1",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-common": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"cheerio": "^1.0.0-rc.12",
"feed": "^4.2.2",
"fs-extra": "^11.1.1",

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-docs",
"version": "3.0.0",
"version": "3.2.1",
"description": "Docs plugin for Docusaurus.",
"main": "lib/index.js",
"sideEffects": false,
@ -35,13 +35,14 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/logger": "3.2.1",
"@docusaurus/mdx-loader": "3.2.1",
"@docusaurus/module-type-aliases": "3.2.1",
"@docusaurus/types": "3.2.1",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-common": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"@types/react-router-config": "^5.0.7",
"combine-promises": "^1.1.0",
"fs-extra": "^11.1.1",

View file

@ -8,7 +8,7 @@
import path from 'path';
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {addTrailingSlash} from '@docusaurus/utils';
import {addTrailingSlash} from '@docusaurus/utils-common';
import {createDocsByIdIndex, toCategoryIndexMatcherParam} from '../docs';
import type {
SidebarItemDoc,

View file

@ -5,12 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import {
addLeadingSlash,
addTrailingSlash,
isValidPathname,
resolvePathname,
} from '@docusaurus/utils';
import {isValidPathname, resolvePathname} from '@docusaurus/utils';
import {addLeadingSlash, addTrailingSlash} from '@docusaurus/utils-common';
import {
DefaultNumberPrefixParser,
stripPathNumberPrefixes,

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-pages",
"version": "3.0.0",
"version": "3.2.1",
"description": "Pages plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-content-pages.d.ts",
@ -18,11 +18,11 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/mdx-loader": "3.2.1",
"@docusaurus/types": "3.2.1",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"fs-extra": "^11.1.1",
"tslib": "^2.6.0",
"webpack": "^5.88.1"

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-debug",
"version": "3.0.0",
"version": "3.2.1",
"description": "Debug plugin for Docusaurus.",
"main": "lib/index.js",
"types": "src/plugin-debug.d.ts",
@ -20,9 +20,9 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/types": "3.2.1",
"@docusaurus/utils": "3.2.1",
"fs-extra": "^11.1.1",
"react-json-view-lite": "^1.2.0",
"tslib": "^2.6.0"

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-google-analytics",
"version": "3.0.0",
"version": "3.2.1",
"description": "Global analytics (analytics.js) plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,9 +18,9 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/types": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"tslib": "^2.6.0"
},
"peerDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-google-gtag",
"version": "3.0.0",
"version": "3.2.1",
"description": "Global Site Tag (gtag.js) plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,9 +18,9 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/types": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"@types/gtag.js": "^0.0.12",
"tslib": "^2.6.0"
},

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-google-tag-manager",
"version": "3.0.0",
"version": "3.2.1",
"description": "Google Tag Manager (gtm.js) plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,9 +18,9 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/types": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"tslib": "^2.6.0"
},
"peerDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-ideal-image",
"version": "3.0.0",
"version": "3.2.1",
"description": "Docusaurus Plugin to generate an almost ideal image (responsive, lazy-loading, and low quality placeholder).",
"main": "lib/index.js",
"types": "src/plugin-ideal-image.d.ts",
@ -20,12 +20,12 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/lqip-loader": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/lqip-loader": "3.2.1",
"@docusaurus/responsive-loader": "^1.7.0",
"@docusaurus/theme-translations": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/theme-translations": "3.2.1",
"@docusaurus/types": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"@slorber/react-ideal-image": "^0.0.12",
"react-waypoint": "^10.3.0",
"sharp": "^0.32.3",
@ -33,7 +33,7 @@
"webpack": "^5.88.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/module-type-aliases": "3.2.1",
"fs-extra": "^11.1.0"
},
"peerDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-pwa",
"version": "3.0.0",
"version": "3.2.1",
"description": "Docusaurus Plugin to add PWA support.",
"main": "lib/index.js",
"types": "src/plugin-pwa.d.ts",
@ -22,12 +22,12 @@
"dependencies": {
"@babel/core": "^7.23.3",
"@babel/preset-env": "^7.23.3",
"@docusaurus/core": "3.0.0",
"@docusaurus/theme-common": "3.0.0",
"@docusaurus/theme-translations": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/theme-common": "3.2.1",
"@docusaurus/theme-translations": "3.2.1",
"@docusaurus/types": "3.2.1",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"babel-loader": "^9.1.3",
"clsx": "^2.0.0",
"core-js": "^3.31.1",
@ -41,7 +41,7 @@
"workbox-window": "^7.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/module-type-aliases": "3.2.1",
"fs-extra": "^11.1.0"
},
"peerDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-sitemap",
"version": "3.0.0",
"version": "3.2.1",
"description": "Simple sitemap generation plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,12 +18,12 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/logger": "3.2.1",
"@docusaurus/types": "3.2.1",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-common": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"fs-extra": "^11.1.1",
"sitemap": "^7.1.1",
"tslib": "^2.6.0"

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-vercel-analytics",
"version": "3.0.0",
"version": "3.2.1",
"description": "Global vercel analytics plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,11 +18,11 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/logger": "3.2.1",
"@docusaurus/types": "3.2.1",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"@vercel/analytics": "^1.1.1",
"tslib": "^2.6.0"
},

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/preset-classic",
"version": "3.0.0",
"version": "3.2.1",
"description": "Classic preset for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -18,19 +18,19 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/plugin-content-blog": "3.0.0",
"@docusaurus/plugin-content-docs": "3.0.0",
"@docusaurus/plugin-content-pages": "3.0.0",
"@docusaurus/plugin-debug": "3.0.0",
"@docusaurus/plugin-google-analytics": "3.0.0",
"@docusaurus/plugin-google-gtag": "3.0.0",
"@docusaurus/plugin-google-tag-manager": "3.0.0",
"@docusaurus/plugin-sitemap": "3.0.0",
"@docusaurus/theme-classic": "3.0.0",
"@docusaurus/theme-common": "3.0.0",
"@docusaurus/theme-search-algolia": "3.0.0",
"@docusaurus/types": "3.0.0"
"@docusaurus/core": "3.2.1",
"@docusaurus/plugin-content-blog": "3.2.1",
"@docusaurus/plugin-content-docs": "3.2.1",
"@docusaurus/plugin-content-pages": "3.2.1",
"@docusaurus/plugin-debug": "3.2.1",
"@docusaurus/plugin-google-analytics": "3.2.1",
"@docusaurus/plugin-google-gtag": "3.2.1",
"@docusaurus/plugin-google-tag-manager": "3.2.1",
"@docusaurus/plugin-sitemap": "3.2.1",
"@docusaurus/theme-classic": "3.2.1",
"@docusaurus/theme-common": "3.2.1",
"@docusaurus/theme-search-algolia": "3.2.1",
"@docusaurus/types": "3.2.1"
},
"peerDependencies": {
"react": "^18.0.0",

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/remark-plugin-npm2yarn",
"version": "3.0.0",
"version": "3.2.1",
"description": "Remark plugin for converting npm commands to Yarn commands as tabs.",
"main": "lib/index.js",
"publishConfig": {

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-classic",
"version": "3.0.0",
"version": "3.2.1",
"description": "Classic theme for Docusaurus",
"main": "lib/index.js",
"types": "src/theme-classic.d.ts",
@ -20,19 +20,19 @@
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
},
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/plugin-content-blog": "3.0.0",
"@docusaurus/plugin-content-docs": "3.0.0",
"@docusaurus/plugin-content-pages": "3.0.0",
"@docusaurus/plugin-content-showcase": "3.0.0",
"@docusaurus/theme-common": "3.0.0",
"@docusaurus/theme-translations": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/mdx-loader": "3.2.1",
"@docusaurus/module-type-aliases": "3.2.1",
"@docusaurus/plugin-content-blog": "3.2.1",
"@docusaurus/plugin-content-docs": "3.2.1",
"@docusaurus/plugin-content-pages": "3.2.1",
"@docusaurus/plugin-content-showcase": "3.2.1",
"@docusaurus/theme-common": "3.2.1",
"@docusaurus/theme-translations": "3.2.1",
"@docusaurus/types": "3.2.1",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-common": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"copy-text-to-clipboard": "^3.2.0",

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-common",
"version": "3.0.0",
"version": "3.2.1",
"description": "Common code for Docusaurus themes.",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
@ -30,13 +30,13 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/plugin-content-blog": "3.0.0",
"@docusaurus/plugin-content-docs": "3.0.0",
"@docusaurus/plugin-content-pages": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/mdx-loader": "3.2.1",
"@docusaurus/module-type-aliases": "3.2.1",
"@docusaurus/plugin-content-blog": "3.2.1",
"@docusaurus/plugin-content-docs": "3.2.1",
"@docusaurus/plugin-content-pages": "3.2.1",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-common": "3.2.1",
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router-config": "*",
@ -47,8 +47,8 @@
"utility-types": "^3.10.0"
},
"devDependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/types": "3.2.1",
"fs-extra": "^11.1.1",
"lodash": "^4.17.21",
"schema-dts": "^1.1.2"

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-live-codeblock",
"version": "3.0.0",
"version": "3.2.1",
"description": "Docusaurus live code block component.",
"main": "lib/index.js",
"types": "src/theme-live-codeblock.d.ts",
@ -23,10 +23,10 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/theme-common": "3.0.0",
"@docusaurus/theme-translations": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/theme-common": "3.2.1",
"@docusaurus/theme-translations": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"@philpl/buble": "^0.19.7",
"clsx": "^2.0.0",
"fs-extra": "^11.1.1",
@ -34,7 +34,7 @@
"tslib": "^2.6.0"
},
"devDependencies": {
"@docusaurus/types": "3.0.0",
"@docusaurus/types": "3.2.1",
"@types/buble": "^0.20.1"
},
"peerDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-mermaid",
"version": "3.0.0",
"version": "3.2.1",
"description": "Mermaid components for Docusaurus.",
"main": "lib/index.js",
"types": "src/theme-mermaid.d.ts",
@ -33,11 +33,11 @@
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
},
"dependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/theme-common": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/module-type-aliases": "3.2.1",
"@docusaurus/theme-common": "3.2.1",
"@docusaurus/types": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"mermaid": "^10.4.0",
"tslib": "^2.6.0"
},

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-search-algolia",
"version": "3.0.0",
"version": "3.2.1",
"description": "Algolia search component for Docusaurus.",
"main": "lib/index.js",
"sideEffects": [
@ -34,13 +34,13 @@
},
"dependencies": {
"@docsearch/react": "^3.5.2",
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/plugin-content-docs": "3.0.0",
"@docusaurus/theme-common": "3.0.0",
"@docusaurus/theme-translations": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/logger": "3.2.1",
"@docusaurus/plugin-content-docs": "3.2.1",
"@docusaurus/theme-common": "3.2.1",
"@docusaurus/theme-translations": "3.2.1",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"algoliasearch": "^4.18.0",
"algoliasearch-helper": "^3.13.3",
"clsx": "^2.0.0",
@ -51,7 +51,7 @@
"utility-types": "^3.10.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.0"
"@docusaurus/module-type-aliases": "3.2.1"
},
"peerDependencies": {
"react": "^18.0.0",

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-translations",
"version": "3.0.0",
"version": "3.2.1",
"description": "Docusaurus theme translations.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -23,8 +23,8 @@
"tslib": "^2.6.0"
},
"devDependencies": {
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/core": "3.2.1",
"@docusaurus/logger": "3.2.1",
"lodash": "^4.17.21"
},
"engines": {

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/tsconfig",
"version": "3.0.0",
"version": "3.2.1",
"description": "Docusaurus base TypeScript configuration.",
"main": "tsconfig.json",
"publishConfig": {

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/types",
"version": "3.0.0",
"version": "3.2.1",
"description": "Common types for Docusaurus packages.",
"types": "./src/index.d.ts",
"publishConfig": {

View file

@ -31,6 +31,7 @@ export type GlobalData = {[pluginName: string]: {[pluginId: string]: unknown}};
export type LoadContext = {
siteDir: string;
siteVersion: string | undefined;
generatedFilesDir: string;
siteConfig: DocusaurusConfig;
siteConfigPath: string;

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import type {TranslationFile} from './i18n';
import type {CodeTranslations, TranslationFile} from './i18n';
import type {RuleSetRule, Configuration as WebpackConfiguration} from 'webpack';
import type {CustomizeRuleString} from 'webpack-merge/dist/types';
import type {CommanderStatic} from 'commander';
@ -183,6 +183,9 @@ export type InitializedPlugin = Plugin & {
export type LoadedPlugin = InitializedPlugin & {
readonly content: unknown;
readonly globalData: unknown;
readonly routes: RouteConfig[];
readonly defaultCodeTranslations: CodeTranslations;
};
export type PluginModule<Content = unknown> = {

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/utils-common",
"version": "3.0.0",
"version": "3.2.1",
"description": "Common (Node/Browser) utility functions for Docusaurus packages.",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",

View file

@ -6,7 +6,10 @@
*/
import applyTrailingSlash, {
addTrailingSlash,
type ApplyTrailingSlashParams,
addLeadingSlash,
removeTrailingSlash,
} from '../applyTrailingSlash';
function params(
@ -176,3 +179,30 @@ describe('applyTrailingSlash', () => {
).toBe('https://xyz.com/abc/?search#anchor');
});
});
describe('addTrailingSlash', () => {
it('is no-op for path with trailing slash', () => {
expect(addTrailingSlash('/abcd/')).toBe('/abcd/');
});
it('adds / for path without trailing slash', () => {
expect(addTrailingSlash('/abcd')).toBe('/abcd/');
});
});
describe('addLeadingSlash', () => {
it('is no-op for path with leading slash', () => {
expect(addLeadingSlash('/abc')).toBe('/abc');
});
it('adds / for path without leading slash', () => {
expect(addLeadingSlash('abc')).toBe('/abc');
});
});
describe('removeTrailingSlash', () => {
it('is no-op for path without trailing slash', () => {
expect(removeTrailingSlash('/abcd')).toBe('/abcd');
});
it('removes / for path with trailing slash', () => {
expect(removeTrailingSlash('/abcd/')).toBe('/abcd');
});
});

View file

@ -0,0 +1,55 @@
/**
* 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 {addPrefix, addSuffix, removePrefix, removeSuffix} from '../stringUtils';
describe('removePrefix', () => {
it("is no-op when prefix doesn't exist", () => {
expect(removePrefix('abcdef', 'ijk')).toBe('abcdef');
expect(removePrefix('abcdef', 'def')).toBe('abcdef');
expect(removePrefix('abcdef', '')).toBe('abcdef');
});
it('removes prefix', () => {
expect(removePrefix('prefix', 'pre')).toBe('fix');
});
});
describe('removeSuffix', () => {
it("is no-op when suffix doesn't exist", () => {
expect(removeSuffix('abcdef', 'ijk')).toBe('abcdef');
expect(removeSuffix('abcdef', 'abc')).toBe('abcdef');
expect(removeSuffix('abcdef', '')).toBe('abcdef');
});
it('removes suffix', () => {
expect(removeSuffix('abcdef', 'ef')).toBe('abcd');
});
it('removes empty suffix', () => {
expect(removeSuffix('abcdef', '')).toBe('abcdef');
});
});
describe('addPrefix', () => {
it('is no-op when prefix already exists', () => {
expect(addPrefix('abcdef', 'abc')).toBe('abcdef');
expect(addPrefix('abc', '')).toBe('abc');
expect(addPrefix('', '')).toBe('');
});
it('adds prefix', () => {
expect(addPrefix('def', 'abc')).toBe('abcdef');
});
});
describe('addSuffix', () => {
it('is no-op when suffix already exists', () => {
expect(addSuffix('abcdef', 'def')).toBe('abcdef');
expect(addSuffix('abc', '')).toBe('abc');
expect(addSuffix('', '')).toBe('');
});
it('adds suffix', () => {
expect(addSuffix('abc', 'def')).toBe('abcdef');
});
});

View file

@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {addPrefix, removeSuffix} from './stringUtils';
import type {DocusaurusConfig} from '@docusaurus/types';
export type ApplyTrailingSlashParams = Pick<
@ -12,6 +13,10 @@ export type ApplyTrailingSlashParams = Pick<
'trailingSlash' | 'baseUrl'
>;
export function addTrailingSlash(str: string): string {
return str.endsWith('/') ? str : `${str}/`;
}
// Trailing slash handling depends in some site configuration options
export default function applyTrailingSlash(
path: string,
@ -24,13 +29,6 @@ export default function applyTrailingSlash(
return path;
}
// TODO deduplicate: also present in @docusaurus/utils
function addTrailingSlash(str: string): string {
return str.endsWith('/') ? str : `${str}/`;
}
function removeTrailingSlash(str: string): string {
return str.endsWith('/') ? str.slice(0, -1) : str;
}
function handleTrailingSlash(str: string, trailing: boolean): string {
return trailing ? addTrailingSlash(str) : removeTrailingSlash(str);
}
@ -55,3 +53,13 @@ export default function applyTrailingSlash(
return path.replace(pathname, newPathname);
}
/** Appends a leading slash to `str`, if one doesn't exist. */
export function addLeadingSlash(str: string): string {
return addPrefix(str, '/');
}
/** Removes the trailing slash from `str`. */
export function removeTrailingSlash(str: string): string {
return removeSuffix(str, '/');
}

View file

@ -11,6 +11,10 @@ export const blogPostContainerID = '__blog-post-container';
export {
default as applyTrailingSlash,
addTrailingSlash,
addLeadingSlash,
removeTrailingSlash,
type ApplyTrailingSlashParams,
} from './applyTrailingSlash';
export {addPrefix, removeSuffix, addSuffix, removePrefix} from './stringUtils';
export {getErrorCausalChain} from './errorUtils';

View file

@ -0,0 +1,30 @@
/**
* 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.
*/
/** Adds a given string prefix to `str`. */
export function addPrefix(str: string, prefix: string): string {
return str.startsWith(prefix) ? str : `${prefix}${str}`;
}
/** Removes a given string suffix from `str`. */
export function removeSuffix(str: string, suffix: string): string {
if (suffix === '') {
// str.slice(0, 0) is ""
return str;
}
return str.endsWith(suffix) ? str.slice(0, -suffix.length) : str;
}
/** Adds a given string suffix to `str`. */
export function addSuffix(str: string, suffix: string): string {
return str.endsWith(suffix) ? str : `${str}${suffix}`;
}
/** Removes a given string prefix from `str`. */
export function removePrefix(str: string, prefix: string): string {
return str.startsWith(prefix) ? str.slice(prefix.length) : str;
}

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/utils-validation",
"version": "3.0.0",
"version": "3.2.1",
"description": "Node validation utility functions for Docusaurus packages.",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
@ -18,8 +18,9 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/logger": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/logger": "3.2.1",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-common": "3.2.1",
"joi": "^17.9.2",
"js-yaml": "^4.1.0",
"tslib": "^2.6.0"

View file

@ -5,12 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import {
isValidPathname,
DEFAULT_PLUGIN_ID,
type Tag,
addLeadingSlash,
} from '@docusaurus/utils';
import {isValidPathname, DEFAULT_PLUGIN_ID, type Tag} from '@docusaurus/utils';
import {addLeadingSlash} from '@docusaurus/utils-common';
import Joi from './Joi';
import {JoiFrontMatter} from './JoiFrontMatter';

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/utils",
"version": "3.0.0",
"version": "3.2.1",
"description": "Node utility functions for Docusaurus packages.",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
@ -18,7 +18,8 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/logger": "3.0.0",
"@docusaurus/logger": "3.2.1",
"@docusaurus/utils-common": "3.2.1",
"@svgr/webpack": "^6.5.1",
"escape-string-regexp": "^4.0.0",
"file-loader": "^6.2.0",
@ -41,7 +42,7 @@
"node": ">=18.0"
},
"devDependencies": {
"@docusaurus/types": "3.0.0",
"@docusaurus/types": "3.2.1",
"@types/dedent": "^0.7.0",
"@types/github-slugger": "^1.3.0",
"@types/micromatch": "^4.0.2",

View file

@ -7,34 +7,7 @@
import {jest} from '@jest/globals';
import _ from 'lodash';
import {
removeSuffix,
removePrefix,
mapAsyncSequential,
findAsyncSequential,
} from '../jsUtils';
describe('removeSuffix', () => {
it("is no-op when suffix doesn't exist", () => {
expect(removeSuffix('abcdef', 'ijk')).toBe('abcdef');
expect(removeSuffix('abcdef', 'abc')).toBe('abcdef');
expect(removeSuffix('abcdef', '')).toBe('abcdef');
});
it('removes suffix', () => {
expect(removeSuffix('abcdef', 'ef')).toBe('abcd');
});
});
describe('removePrefix', () => {
it("is no-op when prefix doesn't exist", () => {
expect(removePrefix('abcdef', 'ijk')).toBe('abcdef');
expect(removePrefix('abcdef', 'def')).toBe('abcdef');
expect(removePrefix('abcdef', '')).toBe('abcdef');
});
it('removes prefix', () => {
expect(removePrefix('prefix', 'pre')).toBe('fix');
});
});
import {mapAsyncSequential, findAsyncSequential} from '../jsUtils';
describe('mapAsyncSequential', () => {
function sleep(timeout: number): Promise<void> {

View file

@ -10,9 +10,6 @@ import {
getEditUrl,
fileToPath,
isValidPathname,
addTrailingSlash,
addLeadingSlash,
removeTrailingSlash,
resolvePathname,
encodePath,
buildSshUrl,
@ -207,33 +204,6 @@ describe('isValidPathname', () => {
});
});
describe('addTrailingSlash', () => {
it('is no-op for path with trailing slash', () => {
expect(addTrailingSlash('/abcd/')).toBe('/abcd/');
});
it('adds / for path without trailing slash', () => {
expect(addTrailingSlash('/abcd')).toBe('/abcd/');
});
});
describe('addLeadingSlash', () => {
it('is no-op for path with leading slash', () => {
expect(addLeadingSlash('/abc')).toBe('/abc');
});
it('adds / for path without leading slash', () => {
expect(addLeadingSlash('abc')).toBe('/abc');
});
});
describe('removeTrailingSlash', () => {
it('is no-op for path without trailing slash', () => {
expect(removeTrailingSlash('/abcd')).toBe('/abcd');
});
it('removes / for path with trailing slash', () => {
expect(removeTrailingSlash('/abcd/')).toBe('/abcd');
});
});
describe('parseURLPath', () => {
it('parse and resolve pathname', () => {
expect(parseURLPath('')).toEqual({

View file

@ -12,6 +12,10 @@ import {findAsyncSequential} from './jsUtils';
const fileHash = new Map<string, string>();
const hashContent = (content: string): string => {
return createHash('md5').update(content).digest('hex');
};
/**
* Outputs a file to the generated files directory. Only writes files if content
* differs from cache (for hot reload performance).
@ -38,7 +42,7 @@ export async function generate(
// first "A" remains in cache. But if the file never existed in cache, no
// need to register it.
if (fileHash.get(filepath)) {
fileHash.set(filepath, createHash('md5').update(content).digest('hex'));
fileHash.set(filepath, hashContent(content));
}
return;
}
@ -50,11 +54,11 @@ export async function generate(
// overwriting and we can reuse old file.
if (!lastHash && (await fs.pathExists(filepath))) {
const lastContent = await fs.readFile(filepath, 'utf8');
lastHash = createHash('md5').update(lastContent).digest('hex');
lastHash = hashContent(lastContent);
fileHash.set(filepath, lastHash);
}
const currentHash = createHash('md5').update(content).digest('hex');
const currentHash = hashContent(content);
if (lastHash !== currentHash) {
await fs.outputFile(filepath, content);

View file

@ -6,7 +6,16 @@
*/
import path from 'path';
import shell from 'shelljs';
import fs from 'fs-extra';
import _ from 'lodash';
import shell from 'shelljs'; // TODO replace with async-first version
const realHasGitFn = () => !!shell.which('git');
// The hasGit call is synchronous IO so we memoize it
// The user won't install Git in the middle of a build anyway...
const hasGit =
process.env.NODE_ENV === 'test' ? realHasGitFn : _.memoize(realHasGitFn);
/** Custom error thrown when git is not found in `PATH`. */
export class GitNotFoundError extends Error {}
@ -86,33 +95,41 @@ export async function getFileCommitDate(
timestamp: number;
author?: string;
}> {
if (!shell.which('git')) {
if (!hasGit()) {
throw new GitNotFoundError(
`Failed to retrieve git history for "${file}" because git is not installed.`,
);
}
if (!shell.test('-f', file)) {
if (!(await fs.pathExists(file))) {
throw new Error(
`Failed to retrieve git history for "${file}" because the file does not exist.`,
);
}
// We add a "RESULT:" prefix to make parsing easier
// See why: https://github.com/facebook/docusaurus/pull/10022
const resultFormat = includeAuthor ? 'RESULT:%ct,%an' : 'RESULT:%ct';
const args = [
`--format=%ct${includeAuthor ? ',%an' : ''}`,
`--format=${resultFormat}`,
'--max-count=1',
age === 'oldest' ? '--follow --diff-filter=A' : undefined,
]
.filter(Boolean)
.join(' ');
const command = `git -c log.showSignature=false log ${args} -- "${path.basename(
file,
)}"`;
const result = await new Promise<{
code: number;
stdout: string;
stderr: string;
}>((resolve) => {
shell.exec(
`git log ${args} -- "${path.basename(file)}"`,
command,
{
// Setting cwd is important, see: https://github.com/facebook/docusaurus/pull/5048
cwd: path.dirname(file),
@ -129,10 +146,12 @@ export async function getFileCommitDate(
`Failed to retrieve the git history for file "${file}" with exit code ${result.code}: ${result.stderr}`,
);
}
let regex = /^(?<timestamp>\d+)$/;
if (includeAuthor) {
regex = /^(?<timestamp>\d+),(?<author>.+)$/;
}
// We only parse the output line starting with our "RESULT:" prefix
// See why https://github.com/facebook/docusaurus/pull/10022
const regex = includeAuthor
? /(?:^|\n)RESULT:(?<timestamp>\d+),(?<author>.+)(?:$|\n)/
: /(?:^|\n)RESULT:(?<timestamp>\d+)(?:$|\n)/;
const output = result.stdout.trim();

View file

@ -9,8 +9,7 @@
import path from 'path';
import Micromatch from 'micromatch'; // Note: Micromatch is used by Globby
import {addSuffix} from './jsUtils';
import {addSuffix} from '@docusaurus/utils-common';
/** A re-export of the globby instance. */
export {default as Globby} from 'globby';

View file

@ -35,12 +35,7 @@ export {
getPluginI18nPath,
localizePath,
} from './i18nUtils';
export {
removeSuffix,
removePrefix,
mapAsyncSequential,
findAsyncSequential,
} from './jsUtils';
export {mapAsyncSequential, findAsyncSequential} from './jsUtils';
export {
normalizeUrl,
getEditUrl,
@ -50,9 +45,6 @@ export {
resolvePathname,
parseURLPath,
serializeURLPath,
addLeadingSlash,
addTrailingSlash,
removeTrailingSlash,
hasSSHProtocol,
buildHttpsUrl,
buildSshUrl,

View file

@ -5,30 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
/** Adds a given string prefix to `str`. */
export function addPrefix(str: string, prefix: string): string {
return str.startsWith(prefix) ? str : `${prefix}${str}`;
}
/** Adds a given string suffix to `str`. */
export function addSuffix(str: string, suffix: string): string {
return str.endsWith(suffix) ? str : `${str}${suffix}`;
}
/** Removes a given string suffix from `str`. */
export function removeSuffix(str: string, suffix: string): string {
if (suffix === '') {
// str.slice(0, 0) is ""
return str;
}
return str.endsWith(suffix) ? str.slice(0, -suffix.length) : str;
}
/** Removes a given string prefix from `str`. */
export function removePrefix(str: string, prefix: string): string {
return str.startsWith(prefix) ? str.slice(prefix.length) : str;
}
/**
* `Array#map` for async operations where order matters.
* @param array The array to traverse.

View file

@ -6,7 +6,6 @@
*/
import resolvePathnameUnsafe from 'resolve-pathname';
import {addPrefix, addSuffix, removeSuffix} from './jsUtils';
/**
* Much like `path.join`, but much better. Takes an array of URL segments, and
@ -232,22 +231,6 @@ export function resolvePathname(to: string, from?: string): string {
return resolvePathnameUnsafe(to, from);
}
/** Appends a leading slash to `str`, if one doesn't exist. */
export function addLeadingSlash(str: string): string {
return addPrefix(str, '/');
}
// TODO deduplicate: also present in @docusaurus/utils-common
/** Appends a trailing slash to `str`, if one doesn't exist. */
export function addTrailingSlash(str: string): string {
return addSuffix(str, '/');
}
/** Removes the trailing slash from `str`. */
export function removeTrailingSlash(str: string): string {
return removeSuffix(str, '/');
}
/** Constructs an SSH URL that can be used to push to GitHub. */
export function buildSshUrl(
githubHost: string,

View file

@ -1,7 +1,7 @@
{
"name": "@docusaurus/core",
"description": "Easy to Maintain Open Source Documentation Websites",
"version": "3.0.0",
"version": "3.2.1",
"license": "MIT",
"publishConfig": {
"access": "public"
@ -43,13 +43,13 @@
"@babel/runtime": "^7.22.6",
"@babel/runtime-corejs3": "^7.22.6",
"@babel/traverse": "^7.22.8",
"@docusaurus/cssnano-preset": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/mdx-loader": "3.0.0",
"@docusaurus/cssnano-preset": "3.2.1",
"@docusaurus/logger": "3.2.1",
"@docusaurus/mdx-loader": "3.2.1",
"@docusaurus/react-loadable": "5.5.2",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/utils-validation": "3.0.0",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-common": "3.2.1",
"@docusaurus/utils-validation": "3.2.1",
"@svgr/webpack": "^6.5.1",
"autoprefixer": "^10.4.14",
"babel-loader": "^9.1.3",
@ -69,8 +69,8 @@
"del": "^6.1.1",
"detect-port": "^1.5.1",
"escape-html": "^1.0.3",
"eval": "^0.1.8",
"eta": "^2.2.0",
"eval": "^0.1.8",
"file-loader": "^6.2.0",
"fs-extra": "^11.1.1",
"html-minifier-terser": "^7.2.0",
@ -105,8 +105,8 @@
"webpackbar": "^5.0.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/types": "3.0.0",
"@docusaurus/module-type-aliases": "3.2.1",
"@docusaurus/types": "3.2.1",
"@total-typescript/shoehorn": "^0.1.2",
"@types/detect-port": "^1.3.3",
"@types/react-dom": "^18.2.7",

View file

@ -64,23 +64,15 @@ export async function build(
process.on(sig, () => process.exit());
});
async function tryToBuildLocale({
locale,
isLastLocale,
}: {
locale: string;
isLastLocale: boolean;
}) {
async function tryToBuildLocale({locale}: {locale: string}) {
try {
PerfLogger.start(`Building site for locale ${locale}`);
await buildLocale({
siteDir,
locale,
cliOptions,
forceTerminate,
isLastLocale,
});
PerfLogger.end(`Building site for locale ${locale}`);
await PerfLogger.async(`${logger.name(locale)}`, () =>
buildLocale({
siteDir,
locale,
cliOptions,
}),
);
} catch (err) {
throw new Error(
logger.interpolate`Unable to build website for locale name=${locale}.`,
@ -91,20 +83,28 @@ export async function build(
}
}
PerfLogger.start(`Get locales to build`);
const locales = await getLocalesToBuild({siteDir, cliOptions});
PerfLogger.end(`Get locales to build`);
const locales = await PerfLogger.async('Get locales to build', () =>
getLocalesToBuild({siteDir, cliOptions}),
);
if (locales.length > 1) {
logger.info`Website will be built for all these locales: ${locales}`;
}
PerfLogger.start(`Building ${locales.length} locales`);
await mapAsyncSequential(locales, (locale) => {
const isLastLocale = locales.indexOf(locale) === locales.length - 1;
return tryToBuildLocale({locale, isLastLocale});
});
PerfLogger.end(`Building ${locales.length} locales`);
await PerfLogger.async(`Build`, () =>
mapAsyncSequential(locales, async (locale) => {
const isLastLocale = locales.indexOf(locale) === locales.length - 1;
await tryToBuildLocale({locale});
if (isLastLocale) {
logger.info`Use code=${'npm run serve'} command to test your build locally.`;
}
// TODO do we really need this historical forceTerminate exit???
if (forceTerminate && isLastLocale && !cliOptions.bundleAnalyzer) {
process.exit(0);
}
}),
);
}
async function getLocalesToBuild({
@ -144,14 +144,10 @@ async function buildLocale({
siteDir,
locale,
cliOptions,
forceTerminate,
isLastLocale,
}: {
siteDir: string;
locale: string;
cliOptions: Partial<BuildCLIOptions>;
forceTerminate: boolean;
isLastLocale: boolean;
}): Promise<string> {
// Temporary workaround to unlock the ability to translate the site config
// We'll remove it if a better official API can be designed
@ -160,81 +156,66 @@ async function buildLocale({
logger.info`name=${`[${locale}]`} Creating an optimized production build...`;
PerfLogger.start('Loading site');
const site = await loadSite({
siteDir,
outDir: cliOptions.outDir,
config: cliOptions.config,
locale,
localizePath: cliOptions.locale ? false : undefined,
});
PerfLogger.end('Loading site');
const site = await PerfLogger.async('Load site', () =>
loadSite({
siteDir,
outDir: cliOptions.outDir,
config: cliOptions.config,
locale,
localizePath: cliOptions.locale ? false : undefined,
}),
);
const {props} = site;
const {outDir, plugins} = props;
// We can build the 2 configs in parallel
PerfLogger.start('Creating webpack configs');
const [{clientConfig, clientManifestPath}, {serverConfig, serverBundlePath}] =
await Promise.all([
getBuildClientConfig({
props,
cliOptions,
}),
getBuildServerConfig({
props,
}),
]);
PerfLogger.end('Creating webpack configs');
// Make sure generated client-manifest is cleaned first, so we don't reuse
// the one from previous builds.
// TODO do we really need this? .docusaurus folder is cleaned between builds
PerfLogger.start('Deleting previous client manifest');
await ensureUnlink(clientManifestPath);
PerfLogger.end('Deleting previous client manifest');
await PerfLogger.async('Creating webpack configs', () =>
Promise.all([
getBuildClientConfig({
props,
cliOptions,
}),
getBuildServerConfig({
props,
}),
]),
);
// Run webpack to build JS bundle (client) and static html files (server).
PerfLogger.start('Bundling');
await compile([clientConfig, serverConfig]);
PerfLogger.end('Bundling');
await PerfLogger.async('Bundling with Webpack', () =>
compile([clientConfig, serverConfig]),
);
PerfLogger.start('Executing static site generation');
const {collectedData} = await executeSSG({
props,
serverBundlePath,
clientManifestPath,
});
PerfLogger.end('Executing static site generation');
const {collectedData} = await PerfLogger.async('SSG', () =>
executeSSG({
props,
serverBundlePath,
clientManifestPath,
}),
);
// Remove server.bundle.js because it is not needed.
PerfLogger.start('Deleting server bundle');
await ensureUnlink(serverBundlePath);
PerfLogger.end('Deleting server bundle');
await PerfLogger.async('Deleting server bundle', () =>
ensureUnlink(serverBundlePath),
);
// Plugin Lifecycle - postBuild.
PerfLogger.start('Executing postBuild()');
await executePluginsPostBuild({plugins, props, collectedData});
PerfLogger.end('Executing postBuild()');
await PerfLogger.async('postBuild()', () =>
executePluginsPostBuild({plugins, props, collectedData}),
);
// TODO execute this in parallel to postBuild?
PerfLogger.start('Executing broken links checker');
await executeBrokenLinksCheck({props, collectedData});
PerfLogger.end('Executing broken links checker');
await PerfLogger.async('Broken links checker', () =>
executeBrokenLinksCheck({props, collectedData}),
);
logger.success`Generated static files in path=${path.relative(
process.cwd(),
outDir,
)}.`;
if (isLastLocale) {
logger.info`Use code=${'npm run serve'} command to test your build locally.`;
}
if (forceTerminate && isLastLocale && !cliOptions.bundleAnalyzer) {
process.exit(0);
}
return outDir;
}
@ -247,40 +228,39 @@ async function executeSSG({
serverBundlePath: string;
clientManifestPath: string;
}) {
PerfLogger.start('Reading client manifest');
const manifest: Manifest = await fs.readJSON(clientManifestPath, 'utf-8');
PerfLogger.end('Reading client manifest');
PerfLogger.start('Compiling SSR template');
const ssrTemplate = await compileSSRTemplate(
props.siteConfig.ssrTemplate ?? defaultSSRTemplate,
const manifest: Manifest = await PerfLogger.async(
'Read client manifest',
() => fs.readJSON(clientManifestPath, 'utf-8'),
);
PerfLogger.end('Compiling SSR template');
PerfLogger.start('Loading App renderer');
const renderer = await loadAppRenderer({
serverBundlePath,
});
PerfLogger.end('Loading App renderer');
const ssrTemplate = await PerfLogger.async('Compile SSR template', () =>
compileSSRTemplate(props.siteConfig.ssrTemplate ?? defaultSSRTemplate),
);
PerfLogger.start('Generate static files');
const ssgResult = await generateStaticFiles({
pathnames: props.routesPaths,
renderer,
params: {
trailingSlash: props.siteConfig.trailingSlash,
outDir: props.outDir,
baseUrl: props.baseUrl,
manifest,
headTags: props.headTags,
preBodyTags: props.preBodyTags,
postBodyTags: props.postBodyTags,
ssrTemplate,
noIndex: props.siteConfig.noIndex,
DOCUSAURUS_VERSION,
},
});
PerfLogger.end('Generate static files');
const renderer = await PerfLogger.async('Load App renderer', () =>
loadAppRenderer({
serverBundlePath,
}),
);
const ssgResult = await PerfLogger.async('Generate static files', () =>
generateStaticFiles({
pathnames: props.routesPaths,
renderer,
params: {
trailingSlash: props.siteConfig.trailingSlash,
outDir: props.outDir,
baseUrl: props.baseUrl,
manifest,
headTags: props.headTags,
preBodyTags: props.preBodyTags,
postBodyTags: props.postBodyTags,
ssrTemplate,
noIndex: props.siteConfig.noIndex,
DOCUSAURUS_VERSION,
},
}),
);
return ssgResult;
}
@ -345,6 +325,10 @@ async function getBuildClientConfig({
bundleAnalyzer: cliOptions.bundleAnalyzer ?? false,
});
let {config} = result;
config = executePluginsConfigurePostCss({
plugins,
config,
});
config = executePluginsConfigureWebpack({
plugins,
config,
@ -360,10 +344,6 @@ async function getBuildServerConfig({props}: {props: Props}) {
props,
});
let {config} = result;
config = executePluginsConfigurePostCss({
plugins,
config,
});
config = executePluginsConfigureWebpack({
plugins,
config,

View file

@ -18,6 +18,7 @@ import {
reloadSite,
reloadSitePlugin,
} from '../../server/site';
import {formatPluginName} from '../../server/plugins/pluginsUtils';
import type {StartCLIOptions} from './start';
import type {LoadedPlugin} from '@docusaurus/types';
@ -69,10 +70,13 @@ async function createLoadSiteParams({
export async function createReloadableSite(startParams: StartParams) {
const openUrlContext = await createOpenUrlContext(startParams);
let site = await PerfLogger.async('Loading site', async () => {
const params = await createLoadSiteParams(startParams);
return loadSite(params);
});
const loadSiteParams = await PerfLogger.async('createLoadSiteParams', () =>
createLoadSiteParams(startParams),
);
let site = await PerfLogger.async('Load site', () =>
loadSite(loadSiteParams),
);
const get = () => site;
@ -89,7 +93,7 @@ export async function createReloadableSite(startParams: StartParams) {
const reloadBase = async () => {
try {
const oldSite = site;
site = await PerfLogger.async('Reloading site', () => reloadSite(site));
site = await PerfLogger.async('Reload site', () => reloadSite(site));
if (oldSite.props.baseUrl !== site.props.baseUrl) {
printOpenUrlMessage();
}
@ -108,7 +112,7 @@ export async function createReloadableSite(startParams: StartParams) {
const reloadPlugin = async (plugin: LoadedPlugin) => {
try {
site = await PerfLogger.async(
`Reloading site plugin ${plugin.name}@${plugin.options.id}`,
`Reload site plugin ${formatPluginName(plugin)}`,
() => {
const pluginIdentifier = {name: plugin.name, id: plugin.options.id};
return reloadSitePlugin(site, pluginIdentifier);
@ -116,7 +120,7 @@ export async function createReloadableSite(startParams: StartParams) {
);
} catch (e) {
logger.error(
`Site plugin reload failure - Plugin ${plugin.name}@${plugin.options.id}`,
`Site plugin reload failure - Plugin ${formatPluginName(plugin)}`,
);
console.error(e);
}

View file

@ -13,7 +13,7 @@ import {
writePluginTranslations,
writeCodeTranslations,
type WriteTranslationsOptions,
getPluginsDefaultCodeTranslationMessages,
loadPluginsDefaultCodeTranslationMessages,
applyDefaultCodeTranslations,
} from '../server/translations/translations';
import {
@ -114,7 +114,7 @@ Available locales are: ${context.i18n.locales.join(',')}.`,
await getExtraSourceCodeFilePaths(),
);
const defaultCodeMessages = await getPluginsDefaultCodeTranslationMessages(
const defaultCodeMessages = await loadPluginsDefaultCodeTranslationMessages(
plugins,
);

View file

@ -36,13 +36,16 @@ exports[`load loads props for site with custom i18n path 1`] = `
"plugins": [
{
"content": undefined,
"defaultCodeTranslations": {},
"getClientModules": [Function],
"globalData": undefined,
"injectHtmlTags": [Function],
"name": "docusaurus-bootstrap-plugin",
"options": {
"id": "default",
},
"path": "<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site",
"routes": [],
"version": {
"type": "synthetic",
},
@ -50,11 +53,14 @@ exports[`load loads props for site with custom i18n path 1`] = `
{
"configureWebpack": [Function],
"content": undefined,
"defaultCodeTranslations": {},
"globalData": undefined,
"name": "docusaurus-mdx-fallback-plugin",
"options": {
"id": "default",
},
"path": ".",
"routes": [],
"version": {
"type": "synthetic",
},
@ -128,5 +134,6 @@ exports[`load loads props for site with custom i18n path 1`] = `
"pluginVersions": {},
"siteVersion": undefined,
},
"siteVersion": undefined,
}
`;

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {loadClientModules} from '../clientModules';
import {getAllClientModules} from '../clientModules';
import type {LoadedPlugin} from '@docusaurus/types';
const pluginEmpty = {
@ -33,14 +33,14 @@ const pluginHelloWorld = {
},
} as unknown as LoadedPlugin;
describe('loadClientModules', () => {
describe('getAllClientModules', () => {
it('loads an empty plugin', () => {
const clientModules = loadClientModules([pluginEmpty]);
const clientModules = getAllClientModules([pluginEmpty]);
expect(clientModules).toMatchInlineSnapshot(`[]`);
});
it('loads a non-empty plugin', () => {
const clientModules = loadClientModules([pluginFooBar]);
const clientModules = getAllClientModules([pluginFooBar]);
expect(clientModules).toMatchInlineSnapshot(`
[
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/foo",
@ -50,7 +50,7 @@ describe('loadClientModules', () => {
});
it('loads multiple non-empty plugins', () => {
const clientModules = loadClientModules([pluginFooBar, pluginHelloWorld]);
const clientModules = getAllClientModules([pluginFooBar, pluginHelloWorld]);
expect(clientModules).toMatchInlineSnapshot(`
[
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/foo",
@ -62,7 +62,7 @@ describe('loadClientModules', () => {
});
it('loads multiple non-empty plugins in different order', () => {
const clientModules = loadClientModules([pluginHelloWorld, pluginFooBar]);
const clientModules = getAllClientModules([pluginHelloWorld, pluginFooBar]);
expect(clientModules).toMatchInlineSnapshot(`
[
"/hello",
@ -74,7 +74,7 @@ describe('loadClientModules', () => {
});
it('loads both empty and non-empty plugins', () => {
const clientModules = loadClientModules([
const clientModules = getAllClientModules([
pluginHelloWorld,
pluginEmpty,
pluginFooBar,
@ -90,7 +90,7 @@ describe('loadClientModules', () => {
});
it('loads empty and non-empty in a different order', () => {
const clientModules = loadClientModules([
const clientModules = getAllClientModules([
pluginHelloWorld,
pluginFooBar,
pluginEmpty,

View file

@ -7,13 +7,13 @@
import path from 'path';
import {DOCUSAURUS_VERSION} from '@docusaurus/utils';
import {getPluginVersion, loadSiteMetadata} from '../siteMetadata';
import {loadPluginVersion, createSiteMetadata} from '../siteMetadata';
import type {LoadedPlugin} from '@docusaurus/types';
describe('getPluginVersion', () => {
describe('loadPluginVersion', () => {
it('detects external packages plugins versions', async () => {
await expect(
getPluginVersion(
loadPluginVersion(
path.join(__dirname, '__fixtures__/siteMetadata/dummy-plugin.js'),
// Make the plugin appear external.
path.join(__dirname, '..', '..', '..', '..', '..', '..', 'website'),
@ -23,7 +23,7 @@ describe('getPluginVersion', () => {
it('detects project plugins versions', async () => {
await expect(
getPluginVersion(
loadPluginVersion(
path.join(__dirname, '__fixtures__/siteMetadata/dummy-plugin.js'),
// Make the plugin appear project local.
path.join(__dirname, '__fixtures__/siteMetadata'),
@ -32,14 +32,14 @@ describe('getPluginVersion', () => {
});
it('detects local packages versions', async () => {
await expect(getPluginVersion('/', '/')).resolves.toEqual({type: 'local'});
await expect(loadPluginVersion('/', '/')).resolves.toEqual({type: 'local'});
});
});
describe('loadSiteMetadata', () => {
it('throws if plugin versions mismatch', async () => {
await expect(
loadSiteMetadata({
describe('createSiteMetadata', () => {
it('throws if plugin versions mismatch', () => {
expect(() =>
createSiteMetadata({
plugins: [
{
name: 'docusaurus-plugin-content-docs',
@ -50,10 +50,9 @@ describe('loadSiteMetadata', () => {
},
},
] as LoadedPlugin[],
siteDir: path.join(__dirname, '__fixtures__/siteMetadata'),
siteVersion: 'some-random-version',
}),
).rejects
.toThrow(`Invalid name=docusaurus-plugin-content-docs version number=1.0.0.
).toThrow(`Invalid name=docusaurus-plugin-content-docs version number=1.0.0.
All official @docusaurus/* packages should have the exact same version as @docusaurus/core (number=${DOCUSAURUS_VERSION}).
Maybe you want to check, or regenerate your yarn.lock or package-lock.json file?`);
});

View file

@ -9,13 +9,12 @@ import _ from 'lodash';
import logger from '@docusaurus/logger';
import {matchRoutes as reactRouterMatchRoutes} from 'react-router-config';
import {
addTrailingSlash,
parseURLPath,
removeTrailingSlash,
serializeURLPath,
flattenRoutes,
type URLPath,
} from '@docusaurus/utils';
import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils-common';
import type {RouteConfig, ReportingSeverity} from '@docusaurus/types';
function matchRoutes(routeConfig: RouteConfig[], pathname: string) {

View file

@ -12,7 +12,7 @@ import type {LoadedPlugin} from '@docusaurus/types';
* Runs the `getClientModules` lifecycle. The returned file paths are all
* absolute.
*/
export function loadClientModules(plugins: LoadedPlugin[]): string[] {
export function getAllClientModules(plugins: LoadedPlugin[]): string[] {
return plugins.flatMap(
(plugin) =>
plugin.getClientModules?.().map((p) => path.resolve(plugin.path, p)) ??

View file

@ -9,11 +9,13 @@ import {
DEFAULT_PARSE_FRONT_MATTER,
DEFAULT_STATIC_DIR_NAME,
DEFAULT_I18N_DIR_NAME,
addLeadingSlash,
addTrailingSlash,
removeTrailingSlash,
} from '@docusaurus/utils';
import {Joi, printWarning} from '@docusaurus/utils-validation';
import {
addTrailingSlash,
addLeadingSlash,
removeTrailingSlash,
} from '@docusaurus/utils-common';
import type {
DocusaurusConfig,
I18nConfig,

View file

@ -7,15 +7,10 @@
import path from 'path';
import {fromPartial} from '@total-typescript/shoehorn';
import {loadPlugins, mergeGlobalData} from '../plugins';
import type {
GlobalData,
LoadContext,
Plugin,
PluginConfig,
} from '@docusaurus/types';
import {loadPlugins, reloadPlugin} from '../plugins';
import type {LoadContext, Plugin, PluginConfig} from '@docusaurus/types';
function testLoad({
async function testLoad({
plugins,
themes,
}: {
@ -39,7 +34,9 @@ function testLoad({
},
});
return loadPlugins(context);
const result = await loadPlugins(context);
return {context, ...result};
}
const SyntheticPluginNames = [
@ -50,7 +47,7 @@ const SyntheticPluginNames = [
async function testPlugin<Content = unknown>(
pluginConfig: PluginConfig<Content>,
) {
const {plugins, routes, globalData} = await testLoad({
const {context, plugins, routes, globalData} = await testLoad({
plugins: [pluginConfig],
themes: [],
});
@ -62,204 +59,9 @@ async function testPlugin<Content = unknown>(
const plugin = nonSyntheticPlugins[0]!;
expect(plugin).toBeDefined();
return {plugin, routes, globalData};
return {context, plugin, routes, globalData};
}
describe('mergeGlobalData', () => {
it('no global data', () => {
expect(mergeGlobalData()).toEqual({});
});
it('1 global data', () => {
const globalData: GlobalData = {
plugin: {
default: {someData: 'val'},
},
};
expect(mergeGlobalData(globalData)).toEqual(globalData);
});
it('1 global data - primitive value', () => {
// For retro-compatibility we allow primitive values to be kept as is
// Not sure anyone is using primitive global data though...
const globalData: GlobalData = {
plugin: {
default: 42,
},
};
expect(mergeGlobalData(globalData)).toEqual(globalData);
});
it('3 distinct plugins global data', () => {
const globalData1: GlobalData = {
plugin1: {
default: {someData1: 'val1'},
},
};
const globalData2: GlobalData = {
plugin2: {
default: {someData2: 'val2'},
},
};
const globalData3: GlobalData = {
plugin3: {
default: {someData3: 'val3'},
},
};
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
plugin1: {
default: {someData1: 'val1'},
},
plugin2: {
default: {someData2: 'val2'},
},
plugin3: {
default: {someData3: 'val3'},
},
});
});
it('3 plugin instances of same plugin', () => {
const globalData1: GlobalData = {
plugin: {
id1: {someData1: 'val1'},
},
};
const globalData2: GlobalData = {
plugin: {
id2: {someData2: 'val2'},
},
};
const globalData3: GlobalData = {
plugin: {
id3: {someData3: 'val3'},
},
};
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
plugin: {
id1: {someData1: 'val1'},
id2: {someData2: 'val2'},
id3: {someData3: 'val3'},
},
});
});
it('3 times the same plugin', () => {
const globalData1: GlobalData = {
plugin: {
id: {someData1: 'val1', shared: 'shared1'},
},
};
const globalData2: GlobalData = {
plugin: {
id: {someData2: 'val2', shared: 'shared2'},
},
};
const globalData3: GlobalData = {
plugin: {
id: {someData3: 'val3', shared: 'shared3'},
},
};
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
plugin: {
id: {
someData1: 'val1',
someData2: 'val2',
someData3: 'val3',
shared: 'shared3',
},
},
});
});
it('3 times same plugin - including primitive values', () => {
// Very unlikely to happen, but we can't merge primitive values together
// Since we use Object.assign(), the primitive values are simply ignored
const globalData1: GlobalData = {
plugin: {
default: 42,
},
};
const globalData2: GlobalData = {
plugin: {
default: {hey: 'val'},
},
};
const globalData3: GlobalData = {
plugin: {
default: 84,
},
};
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
plugin: {
default: {hey: 'val'},
},
});
});
it('real world case', () => {
const globalData1: GlobalData = {
plugin1: {
id1: {someData1: 'val1', shared: 'globalData1'},
},
};
const globalData2: GlobalData = {
plugin1: {
id1: {someData2: 'val2', shared: 'globalData2'},
},
};
const globalData3: GlobalData = {
plugin1: {
id2: {someData3: 'val3', shared: 'globalData3'},
},
};
const globalData4: GlobalData = {
plugin2: {
id1: {someData1: 'val1', shared: 'globalData4'},
},
};
const globalData5: GlobalData = {
plugin2: {
id2: {someData1: 'val1', shared: 'globalData5'},
},
};
const globalData6: GlobalData = {
plugin3: {
id1: {someData1: 'val1', shared: 'globalData6'},
},
};
expect(
mergeGlobalData(
globalData1,
globalData2,
globalData3,
globalData4,
globalData5,
globalData6,
),
).toEqual({
plugin1: {
id1: {someData1: 'val1', someData2: 'val2', shared: 'globalData2'},
id2: {someData3: 'val3', shared: 'globalData3'},
},
plugin2: {
id1: {someData1: 'val1', shared: 'globalData4'},
id2: {someData1: 'val1', shared: 'globalData5'},
},
plugin3: {
id1: {someData1: 'val1', shared: 'globalData6'},
},
});
});
});
describe('loadPlugins', () => {
it('registers default synthetic plugins', async () => {
const {plugins, routes, globalData} = await testLoad({
@ -526,3 +328,272 @@ describe('loadPlugins', () => {
`);
});
});
describe('reloadPlugin', () => {
it('can reload a single complex plugin with same content', async () => {
const plugin: PluginConfig = () => ({
name: 'plugin-name',
contentLoaded({actions}) {
actions.addRoute({
path: '/contentLoadedRouteParent',
component: 'Comp',
routes: [
{path: '/contentLoadedRouteParent/child', component: 'Comp'},
],
});
actions.addRoute({
path: '/contentLoadedRouteSingle',
component: 'Comp',
});
actions.setGlobalData({
globalContentLoaded: 'val1',
globalOverridden: 'initial-value',
});
},
allContentLoaded({actions}) {
actions.addRoute({
path: '/allContentLoadedRouteParent',
component: 'Comp',
routes: [
{path: '/allContentLoadedRouteParent/child', component: 'Comp'},
],
});
actions.addRoute({
path: '/allContentLoadedRouteSingle',
component: 'Comp',
});
actions.setGlobalData({
globalAllContentLoaded: 'val2',
globalOverridden: 'override-value',
});
},
});
const loadResult = await testLoad({
plugins: [plugin],
themes: [],
});
const reloadResult = await reloadPlugin({
context: loadResult.context,
plugins: loadResult.plugins,
pluginIdentifier: {name: 'plugin-name', id: 'default'},
});
expect(loadResult.routes).toEqual(reloadResult.routes);
expect(loadResult.globalData).toEqual(reloadResult.globalData);
expect(reloadResult.routes).toMatchInlineSnapshot(`
[
{
"component": "Comp",
"context": {
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name/default/plugin-route-context-module-100.json",
},
"path": "/allContentLoadedRouteSingle/",
},
{
"component": "Comp",
"context": {
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name/default/plugin-route-context-module-100.json",
},
"path": "/contentLoadedRouteSingle/",
},
{
"component": "Comp",
"context": {
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name/default/plugin-route-context-module-100.json",
},
"path": "/allContentLoadedRouteParent/",
"routes": [
{
"component": "Comp",
"path": "/allContentLoadedRouteParent/child/",
},
],
},
{
"component": "Comp",
"context": {
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name/default/plugin-route-context-module-100.json",
},
"path": "/contentLoadedRouteParent/",
"routes": [
{
"component": "Comp",
"path": "/contentLoadedRouteParent/child/",
},
],
},
]
`);
expect(reloadResult.globalData).toMatchInlineSnapshot(`
{
"plugin-name": {
"default": {
"globalAllContentLoaded": "val2",
"globalContentLoaded": "val1",
"globalOverridden": "override-value",
},
},
}
`);
});
it('can reload plugins in real-world setup', async () => {
let isPlugin1Reload = false;
const plugin1: PluginConfig = () => ({
name: 'plugin-name-1',
contentLoaded({actions}) {
actions.addRoute({
path: isPlugin1Reload
? '/contentLoaded-route-reload'
: '/contentLoaded-route-initial',
component: 'Comp',
});
actions.setGlobalData({
contentLoadedVal: isPlugin1Reload
? 'contentLoaded-val-reload'
: 'contentLoaded-val-initial',
});
},
allContentLoaded({actions}) {
actions.addRoute({
path: isPlugin1Reload
? '/allContentLoaded-route-reload'
: '/allContentLoaded-route-initial',
component: 'Comp',
});
actions.setGlobalData({
allContentLoadedVal: isPlugin1Reload
? 'allContentLoaded-val-reload'
: 'allContentLoaded-val-initial',
});
},
});
const plugin2: PluginConfig = () => ({
name: 'plugin-name-2',
contentLoaded({actions}) {
actions.addRoute({
path: '/plugin-2-route',
component: 'Comp',
});
actions.setGlobalData({plugin2Val: 'val'});
},
});
const loadResult = await testLoad({
plugins: [plugin1, plugin2],
themes: [],
});
isPlugin1Reload = true;
const reloadResult = await reloadPlugin({
context: loadResult.context,
plugins: loadResult.plugins,
pluginIdentifier: {name: 'plugin-name-1', id: 'default'},
});
expect(loadResult.routes).not.toEqual(reloadResult.routes);
expect(loadResult.globalData).not.toEqual(reloadResult.globalData);
expect(loadResult.routes).toMatchInlineSnapshot(`
[
{
"component": "Comp",
"context": {
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name-1/default/plugin-route-context-module-100.json",
},
"path": "/allContentLoaded-route-initial/",
},
{
"component": "Comp",
"context": {
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name-1/default/plugin-route-context-module-100.json",
},
"path": "/contentLoaded-route-initial/",
},
{
"component": "Comp",
"context": {
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name-2/default/plugin-route-context-module-100.json",
},
"path": "/plugin-2-route/",
},
]
`);
expect(loadResult.globalData).toMatchInlineSnapshot(`
{
"plugin-name-1": {
"default": {
"allContentLoadedVal": "allContentLoaded-val-initial",
"contentLoadedVal": "contentLoaded-val-initial",
},
},
"plugin-name-2": {
"default": {
"plugin2Val": "val",
},
},
}
`);
expect(reloadResult.routes).toMatchInlineSnapshot(`
[
{
"component": "Comp",
"context": {
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name-1/default/plugin-route-context-module-100.json",
},
"path": "/allContentLoaded-route-reload/",
},
{
"component": "Comp",
"context": {
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name-1/default/plugin-route-context-module-100.json",
},
"path": "/contentLoaded-route-reload/",
},
{
"component": "Comp",
"context": {
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name-2/default/plugin-route-context-module-100.json",
},
"path": "/plugin-2-route/",
},
]
`);
expect(reloadResult.globalData).toMatchInlineSnapshot(`
{
"plugin-name-1": {
"default": {
"allContentLoadedVal": "allContentLoaded-val-reload",
"contentLoadedVal": "contentLoaded-val-reload",
},
},
"plugin-name-2": {
"default": {
"plugin2Val": "val",
},
},
}
`);
// Trying to reload again one plugin or the other should give
// the same result because the plugin content doesn't change
const reloadResult2 = await reloadPlugin({
context: loadResult.context,
plugins: reloadResult.plugins,
pluginIdentifier: {name: 'plugin-name-1', id: 'default'},
});
expect(reloadResult2.routes).toEqual(reloadResult.routes);
expect(reloadResult2.globalData).toEqual(reloadResult.globalData);
const reloadResult3 = await reloadPlugin({
context: loadResult.context,
plugins: reloadResult2.plugins,
pluginIdentifier: {name: 'plugin-name-2', id: 'default'},
});
expect(reloadResult3.routes).toEqual(reloadResult.routes);
expect(reloadResult3.globalData).toEqual(reloadResult.globalData);
});
});

View file

@ -0,0 +1,204 @@
/**
* 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 {mergeGlobalData} from '../pluginsUtils';
import type {GlobalData} from '@docusaurus/types';
describe('mergeGlobalData', () => {
it('no global data', () => {
expect(mergeGlobalData()).toEqual({});
});
it('1 global data', () => {
const globalData: GlobalData = {
plugin: {
default: {someData: 'val'},
},
};
expect(mergeGlobalData(globalData)).toEqual(globalData);
});
it('1 global data - primitive value', () => {
// For retro-compatibility we allow primitive values to be kept as is
// Not sure anyone is using primitive global data though...
const globalData: GlobalData = {
plugin: {
default: 42,
},
};
expect(mergeGlobalData(globalData)).toEqual(globalData);
});
it('3 distinct plugins global data', () => {
const globalData1: GlobalData = {
plugin1: {
default: {someData1: 'val1'},
},
};
const globalData2: GlobalData = {
plugin2: {
default: {someData2: 'val2'},
},
};
const globalData3: GlobalData = {
plugin3: {
default: {someData3: 'val3'},
},
};
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
plugin1: {
default: {someData1: 'val1'},
},
plugin2: {
default: {someData2: 'val2'},
},
plugin3: {
default: {someData3: 'val3'},
},
});
});
it('3 plugin instances of same plugin', () => {
const globalData1: GlobalData = {
plugin: {
id1: {someData1: 'val1'},
},
};
const globalData2: GlobalData = {
plugin: {
id2: {someData2: 'val2'},
},
};
const globalData3: GlobalData = {
plugin: {
id3: {someData3: 'val3'},
},
};
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
plugin: {
id1: {someData1: 'val1'},
id2: {someData2: 'val2'},
id3: {someData3: 'val3'},
},
});
});
it('3 times the same plugin', () => {
const globalData1: GlobalData = {
plugin: {
id: {someData1: 'val1', shared: 'shared1'},
},
};
const globalData2: GlobalData = {
plugin: {
id: {someData2: 'val2', shared: 'shared2'},
},
};
const globalData3: GlobalData = {
plugin: {
id: {someData3: 'val3', shared: 'shared3'},
},
};
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
plugin: {
id: {
someData1: 'val1',
someData2: 'val2',
someData3: 'val3',
shared: 'shared3',
},
},
});
});
it('3 times same plugin - including primitive values', () => {
// Very unlikely to happen, but we can't merge primitive values together
// Since we use Object.assign(), the primitive values are simply ignored
const globalData1: GlobalData = {
plugin: {
default: 42,
},
};
const globalData2: GlobalData = {
plugin: {
default: {hey: 'val'},
},
};
const globalData3: GlobalData = {
plugin: {
default: 84,
},
};
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
plugin: {
default: {hey: 'val'},
},
});
});
it('real world case', () => {
const globalData1: GlobalData = {
plugin1: {
id1: {someData1: 'val1', shared: 'globalData1'},
},
};
const globalData2: GlobalData = {
plugin1: {
id1: {someData2: 'val2', shared: 'globalData2'},
},
};
const globalData3: GlobalData = {
plugin1: {
id2: {someData3: 'val3', shared: 'globalData3'},
},
};
const globalData4: GlobalData = {
plugin2: {
id1: {someData1: 'val1', shared: 'globalData4'},
},
};
const globalData5: GlobalData = {
plugin2: {
id2: {someData1: 'val1', shared: 'globalData5'},
},
};
const globalData6: GlobalData = {
plugin3: {
id1: {someData1: 'val1', shared: 'globalData6'},
},
};
expect(
mergeGlobalData(
globalData1,
globalData2,
globalData3,
globalData4,
globalData5,
globalData6,
),
).toEqual({
plugin1: {
id1: {someData1: 'val1', someData2: 'val2', shared: 'globalData2'},
id2: {someData3: 'val3', shared: 'globalData3'},
},
plugin2: {
id1: {someData1: 'val1', shared: 'globalData4'},
id2: {someData1: 'val1', shared: 'globalData5'},
},
plugin3: {
id1: {someData1: 'val1', shared: 'globalData6'},
},
});
});
});

View file

@ -9,7 +9,7 @@ import path from 'path';
import {docuHash, generate} from '@docusaurus/utils';
import {applyRouteTrailingSlash} from './routeConfig';
import type {
LoadedPlugin,
InitializedPlugin,
PluginContentLoadedActions,
PluginRouteContext,
RouteConfig,
@ -31,7 +31,7 @@ export async function createPluginActionsUtils({
baseUrl,
trailingSlash,
}: {
plugin: LoadedPlugin;
plugin: InitializedPlugin;
generatedFilesDir: string;
baseUrl: string;
trailingSlash: boolean | undefined;
@ -48,6 +48,7 @@ export async function createPluginActionsUtils({
dataDir,
`${docuHash('pluginRouteContextModule')}.json`,
);
// TODO not ideal place to generate that file
await generate(
'/',
pluginRouteContextModulePath,

View file

@ -12,7 +12,7 @@ import {
normalizePluginOptions,
normalizeThemeConfig,
} from '@docusaurus/utils-validation';
import {getPluginVersion} from '../siteMetadata';
import {loadPluginVersion} from '../siteMetadata';
import {ensureUniquePluginInstanceIds} from './pluginIds';
import {loadPluginConfigs, type NormalizedPluginConfig} from './configs';
import type {
@ -61,14 +61,14 @@ export async function initPlugins(
const pluginRequire = createRequire(context.siteConfigPath);
const pluginConfigs = await loadPluginConfigs(context);
async function doGetPluginVersion(
async function doLoadPluginVersion(
normalizedPluginConfig: NormalizedPluginConfig,
): Promise<PluginVersionInformation> {
if (normalizedPluginConfig.pluginModule?.path) {
const pluginPath = pluginRequire.resolve(
normalizedPluginConfig.pluginModule.path,
);
return getPluginVersion(pluginPath, context.siteDir);
return loadPluginVersion(pluginPath, context.siteDir);
}
return {type: 'local'};
}
@ -109,7 +109,7 @@ export async function initPlugins(
async function initializePlugin(
normalizedPluginConfig: NormalizedPluginConfig,
): Promise<InitializedPlugin> {
const pluginVersion: PluginVersionInformation = await doGetPluginVersion(
const pluginVersion: PluginVersionInformation = await doLoadPluginVersion(
normalizedPluginConfig,
);
const pluginOptions = doValidatePluginOptions(normalizedPluginConfig);

View file

@ -5,14 +5,20 @@
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {initPlugins} from './init';
import {createBootstrapPlugin, createMDXFallbackPlugin} from './synthetic';
import {localizePluginTranslationFile} from '../translations/translations';
import {sortRoutes} from './routeConfig';
import {PerfLogger} from '../../utils';
import {createPluginActionsUtils} from './actions';
import {
aggregateAllContent,
aggregateGlobalData,
aggregateRoutes,
formatPluginName,
getPluginByIdentifier,
mergeGlobalData,
} from './pluginsUtils';
import type {
LoadContext,
RouteConfig,
@ -23,17 +29,17 @@ import type {
InitializedPlugin,
} from '@docusaurus/types';
async function translatePlugin({
async function translatePluginContent({
plugin,
content,
context,
}: {
plugin: LoadedPlugin;
plugin: InitializedPlugin;
content: unknown;
context: LoadContext;
}): Promise<LoadedPlugin> {
const {content} = plugin;
}): Promise<unknown> {
const rawTranslationFiles =
(await plugin.getTranslationFiles?.({content: plugin.content})) ?? [];
(await plugin.getTranslationFiles?.({content})) ?? [];
const translationFiles = await Promise.all(
rawTranslationFiles.map((translationFile) =>
@ -58,81 +64,81 @@ async function translatePlugin({
// translate its own slice of theme config and should make no assumptions
// about other plugins' keys, so this is safe to run in parallel.
Object.assign(context.siteConfig.themeConfig, translatedThemeConfigSlice);
return {...plugin, content: translatedContent};
return translatedContent;
}
async function executePluginLoadContent({
async function executePluginContentLoading({
plugin,
context,
}: {
plugin: InitializedPlugin;
context: LoadContext;
}): Promise<LoadedPlugin> {
return PerfLogger.async(
`Plugin - loadContent - ${plugin.name}@${plugin.options.id}`,
async () => {
const content = await plugin.loadContent?.();
const loadedPlugin: LoadedPlugin = {...plugin, content};
return translatePlugin({plugin: loadedPlugin, context});
},
);
return PerfLogger.async(`Load ${formatPluginName(plugin)}`, async () => {
let content = await PerfLogger.async('loadContent()', () =>
plugin.loadContent?.(),
);
content = await PerfLogger.async('translatePluginContent()', () =>
translatePluginContent({
plugin,
content,
context,
}),
);
const defaultCodeTranslations =
(await PerfLogger.async('getDefaultCodeTranslationMessages()', () =>
plugin.getDefaultCodeTranslationMessages?.(),
)) ?? {};
if (!plugin.contentLoaded) {
return {
...plugin,
content,
defaultCodeTranslations,
routes: [],
globalData: undefined,
};
}
const pluginActionsUtils = await createPluginActionsUtils({
plugin,
generatedFilesDir: context.generatedFilesDir,
baseUrl: context.siteConfig.baseUrl,
trailingSlash: context.siteConfig.trailingSlash,
});
await PerfLogger.async('contentLoaded()', () =>
// @ts-expect-error: should autofix with TS 5.4
plugin.contentLoaded({
content,
actions: pluginActionsUtils.getActions(),
}),
);
return {
...plugin,
content,
defaultCodeTranslations,
routes: pluginActionsUtils.getRoutes(),
globalData: pluginActionsUtils.getGlobalData(),
};
});
}
async function executePluginsLoadContent({
async function executeAllPluginsContentLoading({
plugins,
context,
}: {
plugins: InitializedPlugin[];
context: LoadContext;
}) {
return PerfLogger.async(`Plugins - loadContent`, () =>
Promise.all(
plugins.map((plugin) => executePluginLoadContent({plugin, context})),
),
);
}
function aggregateAllContent(loadedPlugins: LoadedPlugin[]): AllContent {
return _.chain(loadedPlugins)
.groupBy((item) => item.name)
.mapValues((nameItems) =>
_.chain(nameItems)
.groupBy((item) => item.options.id)
.mapValues((idItems) => idItems[0]!.content)
.value(),
)
.value();
}
async function executePluginContentLoaded({
plugin,
context,
}: {
plugin: LoadedPlugin;
context: LoadContext;
}): Promise<{routes: RouteConfig[]; globalData: unknown}> {
return PerfLogger.async(
`Plugins - contentLoaded - ${plugin.name}@${plugin.options.id}`,
async () => {
if (!plugin.contentLoaded) {
return {routes: [], globalData: undefined};
}
const pluginActionsUtils = await createPluginActionsUtils({
plugin,
generatedFilesDir: context.generatedFilesDir,
baseUrl: context.siteConfig.baseUrl,
trailingSlash: context.siteConfig.trailingSlash,
});
await plugin.contentLoaded({
content: plugin.content,
actions: pluginActionsUtils.getActions(),
});
return {
routes: pluginActionsUtils.getRoutes(),
globalData: pluginActionsUtils.getGlobalData(),
};
},
);
}): Promise<LoadedPlugin[]> {
return PerfLogger.async(`Load plugins content`, () => {
return Promise.all(
plugins.map((plugin) => executePluginContentLoading({plugin, context})),
);
});
}
async function executePluginAllContentLoaded({
@ -145,7 +151,7 @@ async function executePluginAllContentLoaded({
allContent: AllContent;
}): Promise<{routes: RouteConfig[]; globalData: unknown}> {
return PerfLogger.async(
`Plugins - allContentLoaded - ${plugin.name}@${plugin.options.id}`,
`allContentLoaded() - ${formatPluginName(plugin)}`,
async () => {
if (!plugin.allContentLoaded) {
return {routes: [], globalData: undefined};
@ -168,50 +174,16 @@ async function executePluginAllContentLoaded({
);
}
async function executePluginsContentLoaded({
type AllContentLoadedResult = {routes: RouteConfig[]; globalData: GlobalData};
async function executeAllPluginsAllContentLoaded({
plugins,
context,
}: {
plugins: LoadedPlugin[];
context: LoadContext;
}): Promise<{routes: RouteConfig[]; globalData: GlobalData}> {
return PerfLogger.async(`Plugins - contentLoaded`, async () => {
const routes: RouteConfig[] = [];
const globalData: GlobalData = {};
await Promise.all(
plugins.map(async (plugin) => {
const {routes: pluginRoutes, globalData: pluginGlobalData} =
await executePluginContentLoaded({
plugin,
context,
});
routes.push(...pluginRoutes);
if (pluginGlobalData !== undefined) {
globalData[plugin.name] ??= {};
globalData[plugin.name]![plugin.options.id] = pluginGlobalData;
}
}),
);
// Sort the route config.
// This ensures that route with sub routes are always placed last.
sortRoutes(routes, context.siteConfig.baseUrl);
return {routes, globalData};
});
}
async function executePluginsAllContentLoaded({
plugins,
context,
}: {
plugins: LoadedPlugin[];
context: LoadContext;
}): Promise<{routes: RouteConfig[]; globalData: GlobalData}> {
return PerfLogger.async(`Plugins - allContentLoaded`, async () => {
}): Promise<AllContentLoadedResult> {
return PerfLogger.async(`allContentLoaded()`, async () => {
const allContent = aggregateAllContent(plugins);
const routes: RouteConfig[] = [];
@ -235,100 +207,70 @@ async function executePluginsAllContentLoaded({
}),
);
// Sort the route config.
// This ensures that route with sub routes are always placed last.
sortRoutes(routes, context.siteConfig.baseUrl);
return {routes, globalData};
});
}
// This merges plugins routes and global data created from both lifecycles:
// - contentLoaded()
// - allContentLoaded()
function mergeResults({
plugins,
allContentLoadedResult,
}: {
plugins: LoadedPlugin[];
allContentLoadedResult: AllContentLoadedResult;
}) {
const routes: RouteConfig[] = [
...aggregateRoutes(plugins),
...allContentLoadedResult.routes,
];
sortRoutes(routes);
const globalData: GlobalData = mergeGlobalData(
aggregateGlobalData(plugins),
allContentLoadedResult.globalData,
);
return {routes, globalData};
}
export type LoadPluginsResult = {
plugins: LoadedPlugin[];
routes: RouteConfig[];
globalData: GlobalData;
};
type ContentLoadedResult = {routes: RouteConfig[]; globalData: GlobalData};
export function mergeGlobalData(...globalDataList: GlobalData[]): GlobalData {
const result: GlobalData = {};
const allPluginIdentifiers: PluginIdentifier[] = globalDataList.flatMap(
(gd) =>
Object.keys(gd).flatMap((name) =>
Object.keys(gd[name]!).map((id) => ({name, id})),
),
);
allPluginIdentifiers.forEach(({name, id}) => {
const allData = globalDataList
.map((gd) => gd?.[name]?.[id])
.filter((d) => typeof d !== 'undefined');
const mergedData =
allData.length === 1 ? allData[0] : Object.assign({}, ...allData);
result[name] ??= {};
result[name]![id] = mergedData;
});
return result;
}
function mergeResults({
contentLoadedResult,
allContentLoadedResult,
}: {
contentLoadedResult: ContentLoadedResult;
allContentLoadedResult: ContentLoadedResult;
}): ContentLoadedResult {
const routes = [
...contentLoadedResult.routes,
...allContentLoadedResult.routes,
];
sortRoutes(routes);
const globalData = mergeGlobalData(
contentLoadedResult.globalData,
allContentLoadedResult.globalData,
);
return {routes, globalData};
}
/**
* Initializes the plugins and run their lifecycle functions.
*/
export async function loadPlugins(
context: LoadContext,
): Promise<LoadPluginsResult> {
return PerfLogger.async('Plugins - loadPlugins', async () => {
return PerfLogger.async('Load plugins', async () => {
const initializedPlugins: InitializedPlugin[] = await PerfLogger.async(
'Plugins - initPlugins',
'Init plugins',
() => initPlugins(context),
);
// TODO probably not the ideal place to hardcode those plugins
initializedPlugins.push(
createBootstrapPlugin(context),
createMDXFallbackPlugin(context),
);
const plugins = await executePluginsLoadContent({
const plugins = await executeAllPluginsContentLoading({
plugins: initializedPlugins,
context,
});
const contentLoadedResult = await executePluginsContentLoaded({
plugins,
context,
});
const allContentLoadedResult = await executePluginsAllContentLoaded({
const allContentLoadedResult = await executeAllPluginsAllContentLoaded({
plugins,
context,
});
const {routes, globalData} = mergeResults({
contentLoadedResult,
plugins,
allContentLoadedResult,
});
@ -336,25 +278,6 @@ export async function loadPlugins(
});
}
export function getPluginByIdentifier({
plugins,
pluginIdentifier,
}: {
pluginIdentifier: PluginIdentifier;
plugins: LoadedPlugin[];
}): LoadedPlugin {
const plugin = plugins.find(
(p) =>
p.name === pluginIdentifier.name && p.options.id === pluginIdentifier.id,
);
if (!plugin) {
throw new Error(
logger.interpolate`Plugin not found for identifier ${pluginIdentifier.name}@${pluginIdentifier.id}`,
);
}
return plugin;
}
export async function reloadPlugin({
pluginIdentifier,
plugins: previousPlugins,
@ -364,34 +287,39 @@ export async function reloadPlugin({
plugins: LoadedPlugin[];
context: LoadContext;
}): Promise<LoadPluginsResult> {
return PerfLogger.async('Plugins - reloadPlugin', async () => {
const plugin = getPluginByIdentifier({
plugins: previousPlugins,
pluginIdentifier,
});
return PerfLogger.async(
`Reload plugin ${formatPluginName(pluginIdentifier)}`,
async () => {
const previousPlugin = getPluginByIdentifier({
plugins: previousPlugins,
pluginIdentifier,
});
const plugin = await executePluginContentLoading({
plugin: previousPlugin,
context,
});
const reloadedPlugin = await executePluginLoadContent({plugin, context});
/*
// TODO Docusaurus v4 - upgrade to Node 20, use array.with()
const plugins = previousPlugins.with(
previousPlugins.indexOf(plugin),
reloadedPlugin,
previousPlugins.indexOf(previousPlugin),
plugin,
);
*/
const plugins = [...previousPlugins];
plugins[previousPlugins.indexOf(previousPlugin)] = plugin;
// TODO optimize this, we shouldn't need to re-run this lifecycle
const contentLoadedResult = await executePluginsContentLoaded({
plugins,
context,
});
const allContentLoadedResult = await executeAllPluginsAllContentLoaded({
plugins,
context,
});
const allContentLoadedResult = await executePluginsAllContentLoaded({
plugins,
context,
});
const {routes, globalData} = mergeResults({
plugins,
allContentLoadedResult,
});
const {routes, globalData} = mergeResults({
contentLoadedResult,
allContentLoadedResult,
});
return {plugins, routes, globalData};
});
return {plugins, routes, globalData};
},
);
}

View file

@ -0,0 +1,108 @@
/**
* 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 _ from 'lodash';
import logger from '@docusaurus/logger';
import type {
AllContent,
GlobalData,
InitializedPlugin,
LoadedPlugin,
PluginIdentifier,
RouteConfig,
} from '@docusaurus/types';
export function getPluginByIdentifier<P extends InitializedPlugin>({
plugins,
pluginIdentifier,
}: {
pluginIdentifier: PluginIdentifier;
plugins: P[];
}): P {
const plugin = plugins.find(
(p) =>
p.name === pluginIdentifier.name && p.options.id === pluginIdentifier.id,
);
if (!plugin) {
throw new Error(
logger.interpolate`Plugin not found for identifier ${formatPluginName(
pluginIdentifier,
)}`,
);
}
return plugin;
}
export function aggregateAllContent(loadedPlugins: LoadedPlugin[]): AllContent {
return _.chain(loadedPlugins)
.groupBy((item) => item.name)
.mapValues((nameItems) =>
_.chain(nameItems)
.groupBy((item) => item.options.id)
.mapValues((idItems) => idItems[0]!.content)
.value(),
)
.value();
}
export function aggregateRoutes(loadedPlugins: LoadedPlugin[]): RouteConfig[] {
return loadedPlugins.flatMap((p) => p.routes);
}
export function aggregateGlobalData(loadedPlugins: LoadedPlugin[]): GlobalData {
const globalData: GlobalData = {};
loadedPlugins.forEach((plugin) => {
if (plugin.globalData !== undefined) {
globalData[plugin.name] ??= {};
globalData[plugin.name]![plugin.options.id] = plugin.globalData;
}
});
return globalData;
}
export function mergeGlobalData(...globalDataList: GlobalData[]): GlobalData {
const result: GlobalData = {};
const allPluginIdentifiers: PluginIdentifier[] = globalDataList.flatMap(
(gd) =>
Object.keys(gd).flatMap((name) =>
Object.keys(gd[name]!).map((id) => ({name, id})),
),
);
allPluginIdentifiers.forEach(({name, id}) => {
const allData = globalDataList
.map((gd) => gd?.[name]?.[id])
.filter((d) => typeof d !== 'undefined');
const mergedData =
allData.length === 1 ? allData[0] : Object.assign({}, ...allData);
result[name] ??= {};
result[name]![id] = mergedData;
});
return result;
}
// This is primarily useful for colored logging purpose
// Do not rely on this for logic
export function formatPluginName(
plugin: InitializedPlugin | PluginIdentifier,
): string {
let formattedName = plugin.name;
// Hacky way to reduce string size for logging purpose
formattedName = formattedName.replace('docusaurus-plugin-content-', '');
formattedName = formattedName.replace('docusaurus-plugin-', '');
formattedName = formattedName.replace('docusaurus-theme-', '');
formattedName = formattedName.replace('-plugin', '');
formattedName = logger.name(formattedName);
const id = 'id' in plugin ? plugin.id : plugin.options.id;
const formattedId = logger.subdue(id);
return `${formattedName}@${formattedId}`;
}

View file

@ -7,7 +7,11 @@
import path from 'path';
import type {RuleSetRule} from 'webpack';
import type {HtmlTagObject, LoadedPlugin, LoadContext} from '@docusaurus/types';
import type {
HtmlTagObject,
LoadContext,
InitializedPlugin,
} from '@docusaurus/types';
import type {Options as MDXLoaderOptions} from '@docusaurus/mdx-loader';
/**
@ -18,7 +22,7 @@ import type {Options as MDXLoaderOptions} from '@docusaurus/mdx-loader';
export function createBootstrapPlugin({
siteDir,
siteConfig,
}: LoadContext): LoadedPlugin {
}: LoadContext): InitializedPlugin {
const {
stylesheets,
scripts,
@ -27,7 +31,6 @@ export function createBootstrapPlugin({
} = siteConfig;
return {
name: 'docusaurus-bootstrap-plugin',
content: null,
options: {
id: 'default',
},
@ -75,10 +78,9 @@ export function createBootstrapPlugin({
export function createMDXFallbackPlugin({
siteDir,
siteConfig,
}: LoadContext): LoadedPlugin {
}: LoadContext): InitializedPlugin {
return {
name: 'docusaurus-mdx-fallback-plugin',
content: null,
options: {
id: 'default',
},

View file

@ -13,14 +13,14 @@ import {
} from '@docusaurus/utils';
import combinePromises from 'combine-promises';
import {loadSiteConfig} from './config';
import {loadClientModules} from './clientModules';
import {getAllClientModules} from './clientModules';
import {loadPlugins, reloadPlugin} from './plugins/plugins';
import {loadHtmlTags} from './htmlTags';
import {loadSiteMetadata} from './siteMetadata';
import {createSiteMetadata, loadSiteVersion} from './siteMetadata';
import {loadI18n} from './i18n';
import {
loadSiteCodeTranslations,
getPluginsDefaultCodeTranslationMessages,
getPluginsDefaultCodeTranslations,
} from './translations/translations';
import {PerfLogger} from '../utils';
import {generateSiteFiles} from './codegen/codegen';
@ -76,9 +76,15 @@ export async function loadContext(
} = params;
const generatedFilesDir = path.resolve(siteDir, GENERATED_FILES_DIR_NAME);
const {siteConfig: initialSiteConfig, siteConfigPath} = await loadSiteConfig({
siteDir,
customConfigFilePath,
const {
siteVersion,
loadSiteConfig: {siteConfig: initialSiteConfig, siteConfigPath},
} = await combinePromises({
siteVersion: loadSiteVersion(siteDir),
loadSiteConfig: loadSiteConfig({
siteDir,
customConfigFilePath,
}),
});
const i18n = await loadI18n(initialSiteConfig, {locale});
@ -107,6 +113,7 @@ export async function loadContext(
return {
siteDir,
siteVersion,
generatedFilesDir,
localizationDir,
siteConfig,
@ -118,13 +125,14 @@ export async function loadContext(
};
}
async function createSiteProps(
function createSiteProps(
params: LoadPluginsResult & {context: LoadContext},
): Promise<Props> {
): Props {
const {plugins, routes, context} = params;
const {
generatedFilesDir,
siteDir,
siteVersion,
siteConfig,
siteConfigPath,
outDir,
@ -136,19 +144,12 @@ async function createSiteProps(
const {headTags, preBodyTags, postBodyTags} = loadHtmlTags(plugins);
const {codeTranslations, siteMetadata} = await combinePromises({
// TODO code translations should be loaded as part of LoadedPlugin?
codeTranslations: PerfLogger.async(
'Load - loadCodeTranslations',
async () => ({
...(await getPluginsDefaultCodeTranslationMessages(plugins)),
...siteCodeTranslations,
}),
),
siteMetadata: PerfLogger.async('Load - loadSiteMetadata', () =>
loadSiteMetadata({plugins, siteDir}),
),
});
const siteMetadata = createSiteMetadata({plugins, siteVersion});
const codeTranslations = {
...getPluginsDefaultCodeTranslations({plugins}),
...siteCodeTranslations,
};
handleDuplicateRoutes(routes, siteConfig.onDuplicateRoutes);
const routesPaths = getRoutesPaths(routes, baseUrl);
@ -157,6 +158,7 @@ async function createSiteProps(
siteConfig,
siteConfigPath,
siteMetadata,
siteVersion,
siteDir,
outDir,
baseUrl,
@ -181,7 +183,7 @@ async function createSiteFiles({
site: Site;
globalData: GlobalData;
}) {
return PerfLogger.async('Load - createSiteFiles', async () => {
return PerfLogger.async('Create site files', async () => {
const {
props: {
plugins,
@ -194,7 +196,7 @@ async function createSiteFiles({
baseUrl,
},
} = site;
const clientModules = loadClientModules(plugins);
const clientModules = getAllClientModules(plugins);
await generateSiteFiles({
generatedFilesDir,
clientModules,
@ -216,13 +218,11 @@ async function createSiteFiles({
* it generates temp files in the `.docusaurus` folder for the bundler.
*/
export async function loadSite(params: LoadContextParams): Promise<Site> {
PerfLogger.start('Load - loadContext');
const context = await loadContext(params);
PerfLogger.end('Load - loadContext');
const context = await PerfLogger.async('Load context', () =>
loadContext(params),
);
PerfLogger.start('Load - loadPlugins');
const {plugins, routes, globalData} = await loadPlugins(context);
PerfLogger.end('Load - loadPlugins');
const props = await createSiteProps({plugins, routes, globalData, context});
@ -247,10 +247,6 @@ export async function reloadSitePlugin(
site: Site,
pluginIdentifier: PluginIdentifier,
): Promise<Site> {
console.log(
`reloadSitePlugin ${pluginIdentifier.name}@${pluginIdentifier.id}`,
);
const {plugins, routes, globalData} = await reloadPlugin({
pluginIdentifier,
plugins: site.props.plugins,

View file

@ -14,7 +14,7 @@ import type {
SiteMetadata,
} from '@docusaurus/types';
async function getPackageJsonVersion(
async function loadPackageJsonVersion(
packageJsonPath: string,
): Promise<string | undefined> {
if (await fs.pathExists(packageJsonPath)) {
@ -24,14 +24,20 @@ async function getPackageJsonVersion(
return undefined;
}
async function getPackageJsonName(
async function loadPackageJsonName(
packageJsonPath: string,
): Promise<string | undefined> {
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require
return (require(packageJsonPath) as {name?: string}).name;
}
export async function getPluginVersion(
export async function loadSiteVersion(
siteDir: string,
): Promise<string | undefined> {
return loadPackageJsonVersion(path.join(siteDir, 'package.json'));
}
export async function loadPluginVersion(
pluginPath: string,
siteDir: string,
): Promise<PluginVersionInformation> {
@ -52,8 +58,8 @@ export async function getPluginVersion(
}
return {
type: 'package',
name: await getPackageJsonName(packageJsonPath),
version: await getPackageJsonVersion(packageJsonPath),
name: await loadPackageJsonName(packageJsonPath),
version: await loadPackageJsonVersion(packageJsonPath),
};
}
potentialPluginPackageJsonDirectory = path.dirname(
@ -89,18 +95,16 @@ Maybe you want to check, or regenerate your yarn.lock or package-lock.json file?
);
}
export async function loadSiteMetadata({
export function createSiteMetadata({
siteVersion,
plugins,
siteDir,
}: {
siteVersion: string | undefined;
plugins: LoadedPlugin[];
siteDir: string;
}): Promise<SiteMetadata> {
}): SiteMetadata {
const siteMetadata: SiteMetadata = {
docusaurusVersion: DOCUSAURUS_VERSION,
siteVersion: await getPackageJsonVersion(
path.join(siteDir, 'package.json'),
),
siteVersion,
pluginVersions: Object.fromEntries(
plugins
.filter(({version: {type}}) => type !== 'synthetic')

View file

@ -15,7 +15,7 @@ import {
readCodeTranslationFileContent,
type WriteTranslationsOptions,
localizePluginTranslationFile,
getPluginsDefaultCodeTranslationMessages,
loadPluginsDefaultCodeTranslationMessages,
applyDefaultCodeTranslations,
} from '../translations';
import type {
@ -537,7 +537,7 @@ describe('readCodeTranslationFileContent', () => {
});
});
describe('getPluginsDefaultCodeTranslationMessages', () => {
describe('loadPluginsDefaultCodeTranslationMessages', () => {
function createTestPlugin(
fn: InitializedPlugin['getDefaultCodeTranslationMessages'],
): InitializedPlugin {
@ -547,14 +547,14 @@ describe('getPluginsDefaultCodeTranslationMessages', () => {
it('works for empty plugins', async () => {
const plugins: InitializedPlugin[] = [];
await expect(
getPluginsDefaultCodeTranslationMessages(plugins),
loadPluginsDefaultCodeTranslationMessages(plugins),
).resolves.toEqual({});
});
it('works for 1 plugin without lifecycle', async () => {
const plugins: InitializedPlugin[] = [createTestPlugin(undefined)];
await expect(
getPluginsDefaultCodeTranslationMessages(plugins),
loadPluginsDefaultCodeTranslationMessages(plugins),
).resolves.toEqual({});
});
@ -566,7 +566,7 @@ describe('getPluginsDefaultCodeTranslationMessages', () => {
})),
];
await expect(
getPluginsDefaultCodeTranslationMessages(plugins),
loadPluginsDefaultCodeTranslationMessages(plugins),
).resolves.toEqual({
a: '1',
b: '2',
@ -585,7 +585,7 @@ describe('getPluginsDefaultCodeTranslationMessages', () => {
})),
];
await expect(
getPluginsDefaultCodeTranslationMessages(plugins),
loadPluginsDefaultCodeTranslationMessages(plugins),
).resolves.toEqual({
a: '1',
b: '2',
@ -613,7 +613,7 @@ describe('getPluginsDefaultCodeTranslationMessages', () => {
createTestPlugin(undefined),
];
await expect(
getPluginsDefaultCodeTranslationMessages(plugins),
loadPluginsDefaultCodeTranslationMessages(plugins),
).resolves.toEqual({
// merge, last plugin wins
b: '2',

View file

@ -20,6 +20,7 @@ import type {
TranslationFile,
CodeTranslations,
InitializedPlugin,
LoadedPlugin,
} from '@docusaurus/types';
export type WriteTranslationsOptions = {
@ -242,17 +243,33 @@ export async function localizePluginTranslationFile({
return translationFile;
}
export async function getPluginsDefaultCodeTranslationMessages(
export function mergeCodeTranslations(
codeTranslations: CodeTranslations[],
): CodeTranslations {
return codeTranslations.reduce(
(allCodeTranslations, current) => ({
...allCodeTranslations,
...current,
}),
{},
);
}
export async function loadPluginsDefaultCodeTranslationMessages(
plugins: InitializedPlugin[],
): Promise<CodeTranslations> {
const pluginsMessages = await Promise.all(
plugins.map((plugin) => plugin.getDefaultCodeTranslationMessages?.() ?? {}),
);
return mergeCodeTranslations(pluginsMessages);
}
return pluginsMessages.reduce(
(allMessages, pluginMessages) => ({...allMessages, ...pluginMessages}),
{},
);
export function getPluginsDefaultCodeTranslations({
plugins,
}: {
plugins: LoadedPlugin[];
}): CodeTranslations {
return mergeCodeTranslations(plugins.map((p) => p.defaultCodeTranslations));
}
export function applyDefaultCodeTranslations({

View file

@ -47,12 +47,11 @@ export async function loadAppRenderer({
}: {
serverBundlePath: string;
}): Promise<AppRenderer> {
console.log(`SSG - Load server bundle`);
PerfLogger.start(`SSG - Load server bundle`);
const source = await fs.readFile(serverBundlePath);
PerfLogger.end(`SSG - Load server bundle`);
const source = await PerfLogger.async(`Load server bundle`, () =>
fs.readFile(serverBundlePath),
);
PerfLogger.log(
`SSG - Server bundle size = ${(source.length / 1024000).toFixed(3)} MB`,
`Server bundle size = ${(source.length / 1024000).toFixed(3)} MB`,
);
const filename = path.basename(serverBundlePath);
@ -69,14 +68,16 @@ export async function loadAppRenderer({
require: createRequire(serverBundlePath),
};
PerfLogger.start(`SSG - Evaluate server bundle`);
const serverEntry = evaluate(
source,
/* filename: */ filename,
/* scope: */ globals,
/* includeGlobals: */ true,
) as {default?: AppRenderer};
PerfLogger.end(`SSG - Evaluate server bundle`);
const serverEntry = await PerfLogger.async(
`Evaluate server bundle`,
() =>
evaluate(
source,
/* filename: */ filename,
/* scope: */ globals,
/* includeGlobals: */ true,
) as {default?: AppRenderer},
);
if (!serverEntry?.default || typeof serverEntry.default !== 'function') {
throw new Error(

View file

@ -4,6 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {AsyncLocalStorage} from 'async_hooks';
import logger from '@docusaurus/logger';
// For now this is a private env variable we use internally
@ -11,6 +12,22 @@ import logger from '@docusaurus/logger';
export const PerfDebuggingEnabled: boolean =
!!process.env.DOCUSAURUS_PERF_LOGGER;
const Thresholds = {
min: 5,
yellow: 100,
red: 1000,
};
const PerfPrefix = logger.yellow(`[PERF] `);
// This is what enables to "see the parent stack" for each log
// Parent1 > Parent2 > Parent3 > child trace
const ParentPrefix = new AsyncLocalStorage<string>();
function applyParentPrefix(label: string) {
const parentPrefix = ParentPrefix.getStore();
return parentPrefix ? `${parentPrefix} > ${label}` : label;
}
type PerfLoggerAPI = {
start: (label: string) => void;
end: (label: string) => void;
@ -32,19 +49,40 @@ function createPerfLogger(): PerfLoggerAPI {
};
}
const prefix = logger.yellow(`[PERF] `);
const formatDuration = (duration: number): string => {
if (duration > Thresholds.red) {
return logger.red(`${(duration / 1000).toFixed(2)} seconds!`);
} else if (duration > Thresholds.yellow) {
return logger.yellow(`${duration.toFixed(2)} ms`);
} else {
return logger.green(`${duration.toFixed(2)} ms`);
}
};
const start: PerfLoggerAPI['start'] = (label) => console.time(prefix + label);
const logDuration = (label: string, duration: number) => {
if (duration < Thresholds.min) {
return;
}
console.log(`${PerfPrefix + label} - ${formatDuration(duration)}`);
};
const end: PerfLoggerAPI['end'] = (label) => console.timeEnd(prefix + label);
const start: PerfLoggerAPI['start'] = (label) => performance.mark(label);
const end: PerfLoggerAPI['end'] = (label) => {
const {duration} = performance.measure(label);
performance.clearMarks(label);
logDuration(applyParentPrefix(label), duration);
};
const log: PerfLoggerAPI['log'] = (label: string) =>
console.log(prefix + label);
console.log(PerfPrefix + applyParentPrefix(label));
const async: PerfLoggerAPI['async'] = async (label, asyncFn) => {
start(label);
const result = await asyncFn();
end(label);
const finalLabel = applyParentPrefix(label);
const before = performance.now();
const result = await ParentPrefix.run(finalLabel, () => asyncFn());
const duration = performance.now() - before;
logDuration(finalLabel, duration);
return result;
};

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/eslint-plugin",
"version": "3.0.0",
"version": "3.2.1",
"description": "ESLint plugin to enforce best Docusaurus practices.",
"main": "lib/index.js",
"keywords": [

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/lqip-loader",
"version": "3.0.0",
"version": "3.2.1",
"description": "Low Quality Image Placeholders (LQIP) loader for webpack.",
"main": "lib/index.js",
"publishConfig": {
@ -17,7 +17,7 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/logger": "3.0.0",
"@docusaurus/logger": "3.2.1",
"file-loader": "^6.2.0",
"lodash": "^4.17.21",
"sharp": "^0.32.3",

View file

@ -1,6 +1,6 @@
{
"name": "stylelint-copyright",
"version": "3.0.0",
"version": "3.2.1",
"description": "Stylelint plugin to check CSS files for a copyright header.",
"main": "lib/index.js",
"license": "MIT",

View file

@ -16,6 +16,7 @@ architecting
Astro
atrule
Autoconverted
autofix
Autogen
autogen
autogenerating
@ -243,7 +244,6 @@ Paraiso
paraiso
pathinfo
paularmstrong
peaceiris
philpl
Photoshop
photoshop
@ -328,6 +328,7 @@ solana
spâce
stackblitz
stackblitzrc
Stormkit
Strikethrough
strikethroughs
stylelint

Some files were not shown because too many files have changed in this diff Show more