Compare commits

...

11 Commits

Author SHA1 Message Date
Sébastien Lorber 26e0bd928c
docs(website): Announce v3.2 on website/homepage (#10004) 2024-03-29 19:43:45 +01:00
Sébastien Lorber 70415a4cef
chore: fix codesandbox default privacy (#9993) 2024-03-29 18:30:47 +01:00
Sébastien Lorber a34e3f8f91
chore: update examples to v3.2.0 (#10003) 2024-03-29 18:27:36 +01:00
Sébastien Lorber 7edfe0e2d1
docs: fix 3.2 blog post headings (#10002) 2024-03-29 18:06:33 +01:00
Sébastien Lorber debfc87d34
chore: release Docusaurus v3.2.0 (#10000) 2024-03-29 17:51:27 +01:00
Sébastien Lorber 1a5fe5c412
fix(mdx-loader): Ignore contentTitle coming after Markdown thematicBreak (#9999) 2024-03-29 15:07:53 +01:00
ozaki 821247142e
refactor(utils): remove duplicated function (#9972)
Co-authored-by: sebastien <lorber.sebastien@gmail.com>
2024-03-29 14:45:30 +01:00
Sébastien Lorber efbe474e9c
refactor(core): improve dev perf, fine-grained site reloads - part 3 (#9975) 2024-03-28 12:39:07 +01:00
dependabot[bot] 06e70a4f9a
chore(deps): bump actions/dependency-review-action from 4.1.3 to 4.2.4 (#9981)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 11:37:03 +01:00
dependabot[bot] 1430c85a82
chore(deps): bump katex from 0.16.8 to 0.16.10 (#9982)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 11:36:54 +01:00
dependabot[bot] 8024d9b858
chore(deps): bump express from 4.18.2 to 4.19.2 (#9983)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 11:36:44 +01:00
196 changed files with 24010 additions and 2933 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@733dd5d4a5203f238c33806593ec0f5fc5343d8c # 4.2.4

View File

@ -1,5 +1,136 @@
# Docusaurus 2 Changelog
## 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.0",
"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.0",
"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.0",
"npmClient": "yarn",
"useWorkspaces": true,
"useNx": false,

View File

@ -1,6 +1,6 @@
{
"name": "create-docusaurus",
"version": "3.0.0",
"version": "3.2.0",
"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.0",
"@docusaurus/utils": "3.2.0",
"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.0",
"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.0",
"@docusaurus/preset-classic": "3.2.0",
"@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.0",
"@docusaurus/tsconfig": "3.2.0",
"@docusaurus/types": "3.2.0",
"typescript": "~5.2.2"
},
"browserslist": {

View File

@ -1,6 +1,6 @@
{
"name": "docusaurus-2-classic-template",
"version": "3.0.0",
"version": "3.2.0",
"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.0",
"@docusaurus/preset-classic": "3.2.0",
"@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.0",
"@docusaurus/types": "3.2.0"
},
"browserslist": {
"production": [

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/cssnano-preset",
"version": "3.0.0",
"version": "3.2.0",
"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.0",
"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.0",
"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.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"@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.0",
"@types/escape-html": "^1.0.2",
"@types/mdast": "^4.0.2",
"@types/stringify-object": "^3.3.1",

View File

@ -71,6 +71,21 @@ 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*
`);
expect(result.data.contentTitle).toBeUndefined();
});
it('is able to decently serialize Markdown syntax', async () => {
const result = await process(`
# some **markdown** \`content\` _italic_

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.0",
"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.0",
"@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.0",
"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.0",
"@docusaurus/logger": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-common": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"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.0"
},
"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.0",
"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.0",
"@docusaurus/logger": "3.2.0",
"@docusaurus/mdx-loader": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-common": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"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.0",
"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.0",
"@docusaurus/logger": "3.2.0",
"@docusaurus/mdx-loader": "3.2.0",
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-common": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"@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.0",
"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.0",
"@docusaurus/mdx-loader": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"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.0",
"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.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils": "3.2.0",
"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.0",
"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.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"tslib": "^2.6.0"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-google-gtag",
"version": "3.0.0",
"version": "3.2.0",
"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.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"@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.0",
"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.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"tslib": "^2.6.0"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-ideal-image",
"version": "3.0.0",
"version": "3.2.0",
"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.0",
"@docusaurus/lqip-loader": "3.2.0",
"@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.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"@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.0",
"fs-extra": "^11.1.0"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-pwa",
"version": "3.0.0",
"version": "3.2.0",
"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.0",
"@docusaurus/theme-common": "3.2.0",
"@docusaurus/theme-translations": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"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.0",
"fs-extra": "^11.1.0"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-sitemap",
"version": "3.0.0",
"version": "3.2.0",
"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.0",
"@docusaurus/logger": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-common": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"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.0",
"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.0",
"@docusaurus/logger": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"@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.0",
"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.0",
"@docusaurus/plugin-content-blog": "3.2.0",
"@docusaurus/plugin-content-docs": "3.2.0",
"@docusaurus/plugin-content-pages": "3.2.0",
"@docusaurus/plugin-debug": "3.2.0",
"@docusaurus/plugin-google-analytics": "3.2.0",
"@docusaurus/plugin-google-gtag": "3.2.0",
"@docusaurus/plugin-google-tag-manager": "3.2.0",
"@docusaurus/plugin-sitemap": "3.2.0",
"@docusaurus/theme-classic": "3.2.0",
"@docusaurus/theme-common": "3.2.0",
"@docusaurus/theme-search-algolia": "3.2.0",
"@docusaurus/types": "3.2.0"
},
"peerDependencies": {
"react": "^18.0.0",

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/remark-plugin-npm2yarn",
"version": "3.0.0",
"version": "3.2.0",
"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.0",
"description": "Classic theme for Docusaurus",
"main": "lib/index.js",
"types": "src/theme-classic.d.ts",
@ -20,18 +20,18 @@
"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/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.0",
"@docusaurus/mdx-loader": "3.2.0",
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/plugin-content-blog": "3.2.0",
"@docusaurus/plugin-content-docs": "3.2.0",
"@docusaurus/plugin-content-pages": "3.2.0",
"@docusaurus/theme-common": "3.2.0",
"@docusaurus/theme-translations": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-common": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"@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.0",
"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.0",
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/plugin-content-blog": "3.2.0",
"@docusaurus/plugin-content-docs": "3.2.0",
"@docusaurus/plugin-content-pages": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-common": "3.2.0",
"@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.0",
"@docusaurus/types": "3.2.0",
"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.0",
"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.0",
"@docusaurus/theme-common": "3.2.0",
"@docusaurus/theme-translations": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"@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.0",
"@types/buble": "^0.20.1"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-mermaid",
"version": "3.0.0",
"version": "3.2.0",
"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.0",
"@docusaurus/module-type-aliases": "3.2.0",
"@docusaurus/theme-common": "3.2.0",
"@docusaurus/types": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"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.0",
"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.0",
"@docusaurus/logger": "3.2.0",
"@docusaurus/plugin-content-docs": "3.2.0",
"@docusaurus/theme-common": "3.2.0",
"@docusaurus/theme-translations": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"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.0"
},
"peerDependencies": {
"react": "^18.0.0",

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/theme-translations",
"version": "3.0.0",
"version": "3.2.0",
"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.0",
"@docusaurus/logger": "3.2.0",
"lodash": "^4.17.21"
},
"engines": {

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/tsconfig",
"version": "3.0.0",
"version": "3.2.0",
"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.0",
"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';
@ -185,6 +185,7 @@ 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.0",
"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.0",
"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.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-common": "3.2.0",
"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.0",
"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.0",
"@docusaurus/utils-common": "3.2.0",
"@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.0",
"@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,13 +95,13 @@ 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.`,
);

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.0",
"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.0",
"@docusaurus/logger": "3.2.0",
"@docusaurus/mdx-loader": "3.2.0",
"@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.0",
"@docusaurus/utils-common": "3.2.0",
"@docusaurus/utils-validation": "3.2.0",
"@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.0",
"@docusaurus/types": "3.2.0",
"@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;
}

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,6 +36,7 @@ exports[`load loads props for site with custom i18n path 1`] = `
"plugins": [
{
"content": undefined,
"defaultCodeTranslations": {},
"getClientModules": [Function],
"globalData": undefined,
"injectHtmlTags": [Function],
@ -52,6 +53,7 @@ exports[`load loads props for site with custom i18n path 1`] = `
{
"configureWebpack": [Function],
"content": undefined,
"defaultCodeTranslations": {},
"globalData": undefined,
"name": "docusaurus-mdx-fallback-plugin",
"options": {
@ -132,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

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

@ -15,6 +15,7 @@ import {
aggregateAllContent,
aggregateGlobalData,
aggregateRoutes,
formatPluginName,
getPluginByIdentifier,
mergeGlobalData,
} from './pluginsUtils';
@ -73,46 +74,57 @@ async function executePluginContentLoading({
plugin: InitializedPlugin;
context: LoadContext;
}): Promise<LoadedPlugin> {
return PerfLogger.async(
`Plugins - single plugin content loading - ${plugin.name}@${plugin.options.id}`,
async () => {
let content = await plugin.loadContent?.();
return PerfLogger.async(`Load ${formatPluginName(plugin)}`, async () => {
let content = await PerfLogger.async('loadContent()', () =>
plugin.loadContent?.(),
);
content = await translatePluginContent({
content = await PerfLogger.async('translatePluginContent()', () =>
translatePluginContent({
plugin,
content,
context,
});
}),
);
if (!plugin.contentLoaded) {
return {
...plugin,
content,
routes: [],
globalData: undefined,
};
}
const pluginActionsUtils = await createPluginActionsUtils({
plugin,
generatedFilesDir: context.generatedFilesDir,
baseUrl: context.siteConfig.baseUrl,
trailingSlash: context.siteConfig.trailingSlash,
});
await plugin.contentLoaded({
content,
actions: pluginActionsUtils.getActions(),
});
const defaultCodeTranslations =
(await PerfLogger.async('getDefaultCodeTranslationMessages()', () =>
plugin.getDefaultCodeTranslationMessages?.(),
)) ?? {};
if (!plugin.contentLoaded) {
return {
...plugin,
content,
routes: pluginActionsUtils.getRoutes(),
globalData: pluginActionsUtils.getGlobalData(),
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 executeAllPluginsContentLoading({
@ -122,7 +134,7 @@ async function executeAllPluginsContentLoading({
plugins: InitializedPlugin[];
context: LoadContext;
}): Promise<LoadedPlugin[]> {
return PerfLogger.async(`Plugins - all plugins content loading`, () => {
return PerfLogger.async(`Load plugins content`, () => {
return Promise.all(
plugins.map((plugin) => executePluginContentLoading({plugin, context})),
);
@ -139,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};
@ -171,7 +183,7 @@ async function executeAllPluginsAllContentLoaded({
plugins: LoadedPlugin[];
context: LoadContext;
}): Promise<AllContentLoadedResult> {
return PerfLogger.async(`Plugins - allContentLoaded`, async () => {
return PerfLogger.async(`allContentLoaded()`, async () => {
const allContent = aggregateAllContent(plugins);
const routes: RouteConfig[] = [];
@ -199,6 +211,9 @@ async function executeAllPluginsAllContentLoaded({
});
}
// This merges plugins routes and global data created from both lifecycles:
// - contentLoaded()
// - allContentLoaded()
function mergeResults({
plugins,
allContentLoadedResult,
@ -232,9 +247,9 @@ export type LoadPluginsResult = {
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),
);
@ -272,36 +287,39 @@ export async function reloadPlugin({
plugins: LoadedPlugin[];
context: LoadContext;
}): Promise<LoadPluginsResult> {
return PerfLogger.async('Plugins - reloadPlugin', async () => {
const previousPlugin = getPluginByIdentifier({
plugins: previousPlugins,
pluginIdentifier,
});
const plugin = await executePluginContentLoading({
plugin: previousPlugin,
context,
});
return PerfLogger.async(
`Reload plugin ${formatPluginName(pluginIdentifier)}`,
async () => {
const previousPlugin = getPluginByIdentifier({
plugins: previousPlugins,
pluginIdentifier,
});
const plugin = await executePluginContentLoading({
plugin: previousPlugin,
context,
});
/*
/*
// TODO Docusaurus v4 - upgrade to Node 20, use array.with()
const plugins = previousPlugins.with(
previousPlugins.indexOf(previousPlugin),
plugin,
);
*/
const plugins = [...previousPlugins];
plugins[previousPlugins.indexOf(previousPlugin)] = plugin;
const plugins = [...previousPlugins];
plugins[previousPlugins.indexOf(previousPlugin)] = plugin;
const allContentLoadedResult = await executeAllPluginsAllContentLoaded({
plugins,
context,
});
const allContentLoadedResult = await executeAllPluginsAllContentLoaded({
plugins,
context,
});
const {routes, globalData} = mergeResults({
plugins,
allContentLoadedResult,
});
const {routes, globalData} = mergeResults({
plugins,
allContentLoadedResult,
});
return {plugins, routes, globalData};
});
return {plugins, routes, globalData};
},
);
}

View File

@ -29,7 +29,9 @@ export function getPluginByIdentifier<P extends InitializedPlugin>({
);
if (!plugin) {
throw new Error(
logger.interpolate`Plugin not found for identifier ${pluginIdentifier.name}@${pluginIdentifier.id}`,
logger.interpolate`Plugin not found for identifier ${formatPluginName(
pluginIdentifier,
)}`,
);
}
return plugin;
@ -85,3 +87,22 @@ export function mergeGlobalData(...globalDataList: GlobalData[]): GlobalData {
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

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

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
@ -17,6 +18,16 @@ const Thresholds = {
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;
@ -38,8 +49,6 @@ 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!`);
@ -54,7 +63,7 @@ function createPerfLogger(): PerfLoggerAPI {
if (duration < Thresholds.min) {
return;
}
console.log(`${prefix + label} - ${formatDuration(duration)}`);
console.log(`${PerfPrefix + label} - ${formatDuration(duration)}`);
};
const start: PerfLoggerAPI['start'] = (label) => performance.mark(label);
@ -62,18 +71,18 @@ function createPerfLogger(): PerfLoggerAPI {
const end: PerfLoggerAPI['end'] = (label) => {
const {duration} = performance.measure(label);
performance.clearMarks(label);
logDuration(label, duration);
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 finalLabel = applyParentPrefix(label);
const before = performance.now();
const result = await asyncFn();
const result = await ParentPrefix.run(finalLabel, () => asyncFn());
const duration = performance.now() - before;
logDuration(label, duration);
logDuration(finalLabel, duration);
return result;
};

View File

@ -1,6 +1,6 @@
{
"name": "@docusaurus/eslint-plugin",
"version": "3.0.0",
"version": "3.2.0",
"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.0",
"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.0",
"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.0",
"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

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

View File

@ -0,0 +1,112 @@
---
title: Docusaurus 3.2
authors: [slorber]
tags: [release]
image: ./img/social-card.png
date: 2024-03-29
---
We are happy to announce **Docusaurus 3.2**.
The upgrade should be easy: as explained in our [release process documentation](/community/release-process), minor versions respect [Semantic Versioning](https://semver.org/).
![Docusaurus blog post social card](./img/social-card.png)
<!--truncate-->
import BrowserWindow from '@site/src/components/BrowserWindow';
import IframeWindow from '@site/src/components/BrowserWindow/IframeWindow';
import ErrorBoundaryTestButton from '@site/src/components/ErrorBoundaryTestButton';
## Highlights
### Faster builds
We worked hard to reduce the time it takes to build a Docusaurus site in production mode.
Between v3.1.0 and v3.2.0, several changes have been made, leading to significantly faster production builds for many sites.
Let's take an example. Our benchmark on the [React Native website upgrading to v3.2](https://github.com/facebook/react-native-website/pull/4072) reports the following results:
- 🔥 Cold builds: 95s ➡️ 66s (~30% faster)
- 🔥 Incremental builds: 55s ➡️ 22s (~60% faster)
The results will vary depending on your site's topology and the options you turned on, but we expect the largest sites will see the most significant improvements.
Note that this is only the beginning, and Docusaurus performance can still be significantly improved, notably the bundling time and the memory consumption. Track our [performance issue](https://github.com/facebook/docusaurus/issues/4765) for upcoming improvements.
<details>
<summary>What is the difference between a cold build and an incremental build?</summary>
A cold build is when the Docusaurus caches are empty, generally after running `docusaurus clear`.
An incremental build happens when you run another time the `docusaurus build` command. Docusaurus automatically tries to "re-use" computations from former builds to make subsequent builds faster. In practice it's based on [Webpack persistent caching](https://webpack.js.org/guides/build-performance/#persistent-cache). To enable incremental builds on your CI server, you can persist the `node_modules/.cache` folder across builds.
</details>
### Faster Dev Server
We also worked on improving the performance of the dev server, so that you can get a faster feedback when editing Markdown/MDX files.
The way we initially implemented content reloading wasn't great. For example, editing a blog post file would also trigger a useless reload of the unrelated docs plugin. From now on, when editing a plugin's content, only that plugin will reload. It's hard to measure precisely the impact of this change, but I estimate edits should appear in your browser at least 50% faster 🔥.
We plan to keep improving the speed of our dev server, with even more granular hot reloads, ensuring we don't run useless computations that would always keep giving the same result.
### MDX partials table-of-contents
With [#9684](https://github.com/facebook/docusaurus/pull/9684), Docusaurus is now able to render headings coming from an imported partial into the table-of-contents.
Docusaurus and MDX allows you to [import one Markdown file into another](/docs/markdown-features/react#importing-markdown). We usually call the imported Markdown file a "partial", and use the `_` prefix so that this file does not lead to the creation of a new page.
```md title="myDoc.mdx"
# My Doc
## Doc heading
Content is imported from another MDX file:
import ImportedDoc from './\_importedDoc.mdx';
<ImportedDoc />
```
```md title="_myPartial.mdx"
## Partial heading
Some paragraph
```
Previously, the heading `Partial heading` did not appear in the table-of-contents, but now it will!
### Blog improvements
We improved the blog plugin with several new options to make it even more powerful and flexible:
- [#9912](https://github.com/facebook/docusaurus/pull/9912): you can now display the last update time and author of a blog post, a feature the docs plugin already had.
- [#9886](https://github.com/facebook/docusaurus/pull/9886): a new `processBlogPosts` option allow you to filter/transform/sort blog posts.
- [#9838](https://github.com/facebook/docusaurus/pull/9838): a new `pageBasePath` option allows you to customize the blog pagination URL segment (`/blog/page/2`)
### Sitemap lastmod
With [#9954](https://github.com/facebook/docusaurus/pull/9954), the sitemap plugin has a new `lastmod` option that can now emit a `<lastmod>` tag on the XML. The value is read from the Git history by default, but can be overridden with docs and blog `last_update` front matter.
We also made it possible to opt-out of emitting `<priority>` and `<frequency>` tags, which are generally ignored by crawlers (notably [Google](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping)).
We recommend using the following sitemap plugin config, that will become the default in Docusaurus V4:
```js
{
lastmod: 'date',
priority: null,
changefreq: null,
}
```
## Other changes
- [#9687](https://github.com/facebook/docusaurus/pull/9687): new Vercel Analytics plugin
- [#9681](https://github.com/facebook/docusaurus/pull/9681) and [#9442](https://github.com/facebook/docusaurus/pull/9442): `docusaurus swizzle` and `create-docusaurus` CLIs now ask users if they prefer to use TypeScript
- [#9928](https://github.com/facebook/docusaurus/pull/9928): new Icelandic translation
- [#9928](https://github.com/facebook/docusaurus/pull/9931): new `allContentLoaded` plugin lifecycle (experimental)
Check the **[3.2.0 changelog entry](/changelog/3.2.0)** for an exhaustive list of changes.

View File

@ -504,9 +504,9 @@ export default async function createConfigAsync() {
respectPrefersColorScheme: true,
},
announcementBar: {
id: 'announcementBar-3', // Increment on change
id: 'announcementBar-v3.2', // Increment on change
// content: `⭐️ If you like Docusaurus, give it a star on <a target="_blank" rel="noopener noreferrer" href="https://github.com/facebook/docusaurus">GitHub</a> and follow us on <a target="_blank" rel="noopener noreferrer" href="https://twitter.com/docusaurus">Twitter ${TwitterSvg}</a>`,
content: `🎉️ <b><a target="_blank" href="https://docusaurus.io/blog/releases/3.0">Docusaurus v3.0</a> is now out!</b> 🥳️`,
content: `🎉️ <b><a target="_blank" href="https://docusaurus.io/blog/releases/3.2">Docusaurus v3.2</a> is out!</b> 🥳️`,
},
prism: {
additionalLanguages: [

View File

@ -1,6 +1,6 @@
{
"name": "website",
"version": "3.0.0",
"version": "3.2.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@ -36,19 +36,19 @@
"dependencies": {
"@crowdin/cli": "^3.13.0",
"@crowdin/crowdin-api-client": "^1.29.5",
"@docusaurus/core": "3.0.0",
"@docusaurus/logger": "3.0.0",
"@docusaurus/plugin-client-redirects": "3.0.0",
"@docusaurus/plugin-ideal-image": "3.0.0",
"@docusaurus/plugin-pwa": "3.0.0",
"@docusaurus/preset-classic": "3.0.0",
"@docusaurus/remark-plugin-npm2yarn": "3.0.0",
"@docusaurus/theme-classic": "3.0.0",
"@docusaurus/theme-common": "3.0.0",
"@docusaurus/theme-live-codeblock": "3.0.0",
"@docusaurus/theme-mermaid": "3.0.0",
"@docusaurus/utils": "3.0.0",
"@docusaurus/utils-common": "3.0.0",
"@docusaurus/core": "3.2.0",
"@docusaurus/logger": "3.2.0",
"@docusaurus/plugin-client-redirects": "3.2.0",
"@docusaurus/plugin-ideal-image": "3.2.0",
"@docusaurus/plugin-pwa": "3.2.0",
"@docusaurus/preset-classic": "3.2.0",
"@docusaurus/remark-plugin-npm2yarn": "3.2.0",
"@docusaurus/theme-classic": "3.2.0",
"@docusaurus/theme-common": "3.2.0",
"@docusaurus/theme-live-codeblock": "3.2.0",
"@docusaurus/theme-mermaid": "3.2.0",
"@docusaurus/utils": "3.2.0",
"@docusaurus/utils-common": "3.2.0",
"@popperjs/core": "^2.11.8",
"@swc/core": "1.2.197",
"clsx": "^2.0.0",
@ -83,8 +83,8 @@
]
},
"devDependencies": {
"@docusaurus/eslint-plugin": "3.0.0",
"@docusaurus/tsconfig": "3.0.0",
"@docusaurus/eslint-plugin": "3.2.0",
"@docusaurus/tsconfig": "3.2.0",
"@types/color": "^3.0.4",
"@types/jest": "^29.5.3",
"cross-env": "^7.0.3",

View File

@ -222,9 +222,11 @@ function TopBanner() {
<div className={styles.topBanner}>
<div className={styles.topBannerTitle}>
{'🎉\xa0'}
<Link to="/blog/releases/3.0" className={styles.topBannerTitleText}>
<Translate id="homepage.banner.launch.3.0">
{'Docusaurus\xa03.0 is\xa0out!'}
<Link to="/blog/releases/3.2" className={styles.topBannerTitleText}>
<Translate
id="homepage.banner.launch.newVersion"
values={{newVersion: '3.2'}}>
{'Docusaurus\xa0{newVersion} is\xa0out!'}
</Translate>
</Link>
{'\xa0🥳'}

View File

@ -0,0 +1,28 @@
---
description: How Docusaurus works to build your app
---
# Architecture
```mdx-code-block
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import Zoom from 'react-medium-image-zoom';
```
<Zoom>
![Architecture overview](/img/architecture.png)
</Zoom>
This diagram shows how Docusaurus works to build your app. Plugins each collect their content and emit JSON data; themes provide layout components which receive the JSON data as route modules. The bundler bundles all the components and emits a server bundle and a client bundle.
Although you (either plugin authors or site creators) are writing JavaScript all the time, bear in mind that the JS is actually run in different environments:
- All plugin lifecycle methods are run in Node. Therefore, until we support ES Modules in our codebase, plugin source code must be provided as ES modules that can be imported, or CommonJS that can be `require`'d.
- The theme code is built with Webpack. They can be provided as ESM—following React conventions.
Plugin code and theme code never directly import each other: they only communicate through protocols (in our case, through JSON temp files and calls to `addRoute`). A useful mental model is to imagine that the plugins are not written in JavaScript, but in another language like Rust. The only way to interact with plugins for the user is through `docusaurus.config.js`, which itself is run in Node (hence you can use `require` and pass callbacks as plugin options).
During bundling, the config file itself is serialized and bundled, allowing the theme to access config options like `themeConfig` or `baseUrl` through [`useDocusaurusContext()`](../docusaurus-core.mdx#useDocusaurusContext). However, the `siteConfig` object only contains **serializable values** (values that are preserved after `JSON.stringify()`). Functions, regexes, etc. would be lost on the client side. The `themeConfig` is designed to be entirely serializable.

View File

@ -0,0 +1,184 @@
---
description: How the Docusaurus client is structured
---
# Client architecture
## Theme aliases {#theme-aliases}
A theme works by exporting a set of components, e.g. `Navbar`, `Layout`, `Footer`, to render the data passed down from plugins. Docusaurus and users use these components by importing them using the `@theme` webpack alias:
```js
import Navbar from '@theme/Navbar';
```
The alias `@theme` can refer to a few directories, in the following priority:
1. A user's `website/src/theme` directory, which is a special directory that has the higher precedence.
2. A Docusaurus theme package's `theme` directory.
3. Fallback components provided by Docusaurus core (usually not needed).
This is called a _layered architecture_: a higher-priority layer providing the component would shadow a lower-priority layer, making swizzling possible. Given the following structure:
```
website
├── node_modules
│ └── @docusaurus/theme-classic
│ └── theme
│ └── Navbar.js
└── src
└── theme
└── Navbar.js
```
`website/src/theme/Navbar.js` takes precedence whenever `@theme/Navbar` is imported. This behavior is called component swizzling. If you are familiar with Objective C where a function's implementation can be swapped during runtime, it's the exact same concept here with changing the target `@theme/Navbar` is pointing to!
We already talked about how the "userland theme" in `src/theme` can re-use a theme component through the [`@theme-original`](../swizzling.mdx#wrapping) alias. One theme package can also wrap a component from another theme, by importing the component from the initial theme, using the `@theme-init` import.
Here's an example of using this feature to enhance the default theme `CodeBlock` component with a `react-live` playground feature.
```js
import InitialCodeBlock from '@theme-init/CodeBlock';
import React from 'react';
export default function CodeBlock(props) {
return props.live ? (
<ReactLivePlayground {...props} />
) : (
<InitialCodeBlock {...props} />
);
}
```
Check the code of `@docusaurus/theme-live-codeblock` for details.
:::warning
Unless you want to publish a re-usable "theme enhancer" (like `@docusaurus/theme-live-codeblock`), you likely don't need `@theme-init`.
:::
It can be quite hard to wrap your mind around these aliases. Let's imagine the following case with a super convoluted setup with three themes/plugins and the site itself all trying to define the same component. Internally, Docusaurus loads these themes as a "stack".
```text
+-------------------------------------------------+
| `website/src/theme/CodeBlock.js` | <-- `@theme/CodeBlock` always points to the top
+-------------------------------------------------+
| `theme-live-codeblock/theme/CodeBlock/index.js` | <-- `@theme-original/CodeBlock` points to the topmost non-swizzled component
+-------------------------------------------------+
| `plugin-awesome-codeblock/theme/CodeBlock.js` |
+-------------------------------------------------+
| `theme-classic/theme/CodeBlock/index.js` | <-- `@theme-init/CodeBlock` always points to the bottom
+-------------------------------------------------+
```
The components in this "stack" are pushed in the order of `preset plugins > preset themes > plugins > themes > site`, so the swizzled component in `website/src/theme` always comes out on top because it's loaded last.
`@theme/*` always points to the topmost component—when `CodeBlock` is swizzled, all other components requesting `@theme/CodeBlock` receive the swizzled version.
`@theme-original/*` always points to the topmost non-swizzled component. That's why you can import `@theme-original/CodeBlock` in the swizzled component—it points to the next one in the "component stack", a theme-provided one. Plugin authors should not try to use this because your component could be the topmost component and cause a self-import.
`@theme-init/*` always points to the bottommost component—usually, this comes from the theme or plugin that first provides this component. Individual plugins / themes trying to enhance code block can safely use `@theme-init/CodeBlock` to get its basic version. Site creators should generally not use this because you likely want to enhance the _topmost_ instead of the _bottommost_ component. It's also possible that the `@theme-init/CodeBlock` alias does not exist at all—Docusaurus only creates it when it points to a different one from `@theme-original/CodeBlock`, i.e. when it's provided by more than one theme. We don't waste aliases!
## Client modules {#client-modules}
Client modules are part of your site's bundle, just like theme components. However, they are usually side-effect-ful. Client modules are anything that can be `import`ed by Webpack—CSS, JS, etc. JS scripts usually work on the global context, like registering event listeners, creating global variables...
These modules are imported globally before React even renders the initial UI.
```js title="@docusaurus/core/App.tsx"
// How it works under the hood
import '@generated/client-modules';
```
Plugins and sites can both declare client modules, through [`getClientModules`](../api/plugin-methods/lifecycle-apis.mdx#getClientModules) and [`siteConfig.clientModules`](../api/docusaurus.config.js.mdx#clientModules), respectively.
Client modules are called during server-side rendering as well, so remember to check the [execution environment](./ssg.mdx#escape-hatches) before accessing client-side globals.
```js title="mySiteGlobalJs.js"
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
if (ExecutionEnvironment.canUseDOM) {
// As soon as the site loads in the browser, register a global event listener
window.addEventListener('keydown', (e) => {
if (e.code === 'Period') {
location.assign(location.href.replace('.com', '.dev'));
}
});
}
```
CSS stylesheets imported as client modules are [global](../styling-layout.mdx#global-styles).
```css title="mySiteGlobalCss.css"
/* This stylesheet is global. */
.globalSelector {
color: red;
}
```
### Client module lifecycles {#client-module-lifecycles}
Besides introducing side-effects, client modules can optionally export two lifecycle functions: `onRouteUpdate` and `onRouteDidUpdate`.
Because Docusaurus builds a single-page application, `script` tags will only be executed the first time the page loads, but will not re-execute on page transitions. These lifecycles are useful if you have some imperative JS logic that should execute every time a new page has loaded, e.g., to manipulate DOM elements, to send analytics data, etc.
For every route transition, there will be several important timings:
1. The user clicks a link, which causes the router to change its current location.
2. Docusaurus preloads the next route's assets, while keeping displaying the current page's content.
3. The next route's assets have loaded.
4. The new location's route component gets rendered to DOM.
`onRouteUpdate` will be called at event (2), and `onRouteDidUpdate` will be called at (4). They both receive the current location and the previous location (which can be `null`, if this is the first screen).
`onRouteUpdate` can optionally return a "cleanup" callback, which will be called at (3). For example, if you want to display a progress bar, you can start a timeout in `onRouteUpdate`, and clear the timeout in the callback. (The classic theme already provides an `nprogress` integration this way.)
Note that the new page's DOM is only available during event (4). If you need to manipulate the new page's DOM, you'll likely want to use `onRouteDidUpdate`, which will be fired as soon as the DOM on the new page has mounted.
```js title="myClientModule.js"
export function onRouteDidUpdate({location, previousLocation}) {
// Don't execute if we are still on the same page; the lifecycle may be fired
// because the hash changes (e.g. when navigating between headings)
if (location.pathname !== previousLocation?.pathname) {
const title = document.getElementsByTagName('h1')[0];
if (title) {
title.innerText += '❤️';
}
}
}
export function onRouteUpdate({location, previousLocation}) {
if (location.pathname !== previousLocation?.pathname) {
const progressBarTimeout = window.setTimeout(() => {
nprogress.start();
}, delay);
return () => window.clearTimeout(progressBarTimeout);
}
return undefined;
}
```
Or, if you are using TypeScript and you want to leverage contextual typing:
```ts title="myClientModule.ts"
import type {ClientModule} from '@docusaurus/types';
const module: ClientModule = {
onRouteUpdate({location, previousLocation}) {
// ...
},
onRouteDidUpdate({location, previousLocation}) {
// ...
},
};
export default module;
```
Both lifecycles will fire on first render, but they will not fire on server-side, so you can safely access browser globals in them.
:::tip Prefer using React
Client module lifecycles are purely imperative, and you can't use React hooks or access React contexts within them. If your operations are state-driven or involve complicated DOM manipulations, you should consider [swizzling components](../swizzling.mdx) instead.
:::

View File

@ -0,0 +1,11 @@
# Advanced Tutorials
This section is not going to be very structured, but we will cover the following topics:
```mdx-code-block
import DocCardList from '@theme/DocCardList';
<DocCardList />
```
We will assume that you have finished the guides, and know the basics like how to configure plugins, how to write React components, etc. These sections will have plugin authors and code contributors in mind, so we may occasionally refer to [plugin APIs](../api/plugin-methods/README.mdx) or other architecture details. Don't panic if you don't understand everything😉

View File

@ -0,0 +1,129 @@
# Plugins
Plugins are the building blocks of features in a Docusaurus site. Each plugin handles its own individual feature. Plugins may work and be distributed as part of a bundle via presets.
## Creating plugins {#creating-plugins}
A plugin is a function that takes two parameters: `context` and `options`. It returns a plugin instance object (or a promise). You can create plugins as functions or modules. For more information, refer to the [plugin method references section](../api/plugin-methods/README.mdx).
### Function definition {#function-definition}
You can use a plugin as a function directly included in the Docusaurus config file:
```js title="docusaurus.config.js"
export default {
// ...
plugins: [
// highlight-start
async function myPlugin(context, options) {
// ...
return {
name: 'my-plugin',
async loadContent() {
// ...
},
async contentLoaded({content, actions}) {
// ...
},
/* other lifecycle API */
};
},
// highlight-end
],
};
```
### Module definition {#module-definition}
You can use a plugin as a module path referencing a separate file or npm package:
```js title="docusaurus.config.js"
export default {
// ...
plugins: [
// without options:
'./my-plugin',
// or with options:
['./my-plugin', options],
],
};
```
Then in the folder `my-plugin`, you can create an `index.js` such as this:
```js title="my-plugin/index.js"
export default async function myPlugin(context, options) {
// ...
return {
name: 'my-plugin',
async loadContent() {
/* ... */
},
async contentLoaded({content, actions}) {
/* ... */
},
/* other lifecycle API */
};
}
```
---
You can view all plugins installed in your site using the [debug plugin's metadata panel](/__docusaurus/debug/metadata).
Plugins come as several types:
- `package`: an external package you installed
- `project`: a plugin you created in your project, given to Docusaurus as a local file path
- `local`: a plugin created using the function definition
- `synthetic`: a "fake plugin" Docusaurus created internally, so we take advantage of our modular architecture and don't let the core do much special work. You won't see this in the metadata because it's an implementation detail.
You can access them on the client side with `useDocusaurusContext().siteMetadata.pluginVersions`.
## Plugin design {#plugin-design}
Docusaurus' implementation of the plugins system provides us with a convenient way to hook into the website's lifecycle to modify what goes on during development/build, which involves (but is not limited to) extending the webpack config, modifying the data loaded, and creating new components to be used in a page.
### Theme design {#theme-design}
When plugins have loaded their content, the data is made available to the client side through actions like [`createData` + `addRoute`](../api/plugin-methods/lifecycle-apis.mdx#addRoute) or [`setGlobalData`](../api/plugin-methods/lifecycle-apis.mdx#setGlobalData). This data has to be _serialized_ to plain strings, because [plugins and themes run in different environments](./architecture.mdx). Once the data arrives on the client side, the rest becomes familiar to React developers: data is passed along components, components are bundled with Webpack, and rendered to the window through `ReactDOM.render`...
**Themes provide the set of UI components to render the content.** Most content plugins need to be paired with a theme in order to be actually useful. The UI is a separate layer from the data schema, which makes swapping designs easy.
For example, a Docusaurus blog may consist of a blog plugin and a blog theme.
:::note
This is a contrived example: in practice, `@docusaurus/theme-classic` provides the theme for docs, blog, and layouts.
:::
```js title="docusaurus.config.js"
export default {
// highlight-next-line
themes: ['theme-blog'],
plugins: ['plugin-content-blog'],
};
```
And if you want to use Bootstrap styling, you can swap out the theme with `theme-blog-bootstrap` (another fictitious non-existing theme):
```js title="docusaurus.config.js"
export default {
// highlight-next-line
themes: ['theme-blog-bootstrap'],
plugins: ['plugin-content-blog'],
};
```
Now, although the theme receives the same data from the plugin, how the theme chooses to _render_ the data as UI can be drastically different.
While themes share the exact same lifecycle methods with plugins, themes' implementations can look very different from those of plugins based on themes' designed objectives.
Themes are designed to complete the build of your Docusaurus site and supply the components used by your site, plugins, and the themes themselves. A theme still acts like a plugin and exposes some lifecycle methods, but most likely they would not use [`loadContent`](../api/plugin-methods/lifecycle-apis.mdx#loadContent), since they only receive data from plugins, but don't generate data themselves; themes are typically also accompanied by an `src/theme` directory full of components, which are made known to the core through the [`getThemePath`](../api/plugin-methods/extend-infrastructure.mdx#getThemePath) lifecycle.
To summarize:
- Themes share the same lifecycle methods with Plugins
- Themes are run after all existing Plugins
- Themes add component aliases by providing `getThemePath`.

View File

@ -0,0 +1,289 @@
---
description: "Docusaurus' routing system follows single-page application conventions: one route, one component."
---
# Routing
```mdx-code-block
import Link from '@docusaurus/Link';
import {useLatestVersion, useActiveDocContext} from '@docusaurus/plugin-content-docs/client';
import {useLocation} from '@docusaurus/router';
import BrowserWindow from '@site/src/components/BrowserWindow';
```
Docusaurus' routing system follows single-page application conventions: one route, one component. In this section, we will begin by talking about routing within the three content plugins (docs, blog, and pages), and then go beyond to talk about the underlying routing system.
## Routing in content plugins {#routing-in-content-plugins}
Every content plugin provides a `routeBasePath` option. It defines where the plugins append their routes to. By default, the docs plugin puts its routes under `/docs`; the blog plugin, `/blog`; and the pages plugin, `/`. You can think about the route structure like this:
```mermaid
graph LR;
A(["https://example.com/"])
B(["/base-url/"])
C(["/docs/"])
D(["/blog/"])
E(["/"])
F["All docs <br/>routes"]
G["All blog <br/>routes"]
H["All pages <br/>routes"]
A---B;
B---C;
B---D;
B---E;
C---F;
D---G;
E---H;
```
Any route will be matched against this nested route config until a good match is found. For example, when given a route `/docs/configuration`, Docusaurus first enters the `/docs` branch, and then searches among the subroutes created by the docs plugin.
Changing `routeBasePath` can effectively alter your site's route structure. For example, in [Docs-only mode](../guides/docs/docs-introduction.mdx#docs-only-mode), we mentioned that configuring `routeBasePath: '/'` for docs means that all routes that the docs plugin create would not have the `/docs` prefix, yet it doesn't prevent you from having more subroutes like `/blog` created by other plugins.
Next, let's look at how the three plugins structure their own "boxes of subroutes".
### Pages routing {#pages-routing}
Pages routing are straightforward: the file paths directly map to URLs, without any other way to customize. See the [pages docs](../guides/creating-pages.mdx#routing) for more information.
The component used for Markdown pages is `@theme/MDXPage`. React pages are directly used as the route's component.
### Blog routing {#blog-routing}
The blog creates the following routes:
- **Posts list pages**: `/`, `/page/2`, `/page/3`...
- The route is customizable through the `pageBasePath` option.
- The component is `@theme/BlogListPage`.
- **Post pages**: `/2021/11/21/algolia-docsearch-migration`, `/2021/05/12/announcing-docusaurus-two-beta`...
- Generated from each Markdown post.
- The routes are fully customizable through the `slug` front matter.
- The component is `@theme/BlogPostPage`.
- **Tags list page**: `/tags`
- The route is customizable through the `tagsBasePath` option.
- The component is `@theme/BlogTagsListPage`.
- **Tag pages**: `/tags/adoption`, `/tags/beta`...
- Generated through the tags defined in each post's front matter.
- The routes always have base defined in `tagsBasePath`, but the subroutes are customizable through the tag's `permalink` field.
- The component is `@theme/BlogTagsPostsPage`.
- **Archive page**: `/archive`
- The route is customizable through the `archiveBasePath` option.
- The component is `@theme/BlogArchivePage`.
### Docs routing {#docs-routing}
The docs is the only plugin that creates **nested routes**. At the top, it registers [**version paths**](../guides/docs/versioning.mdx): `/`, `/next`, `/2.0.0-beta.13`... which provide the version context, including the layout and sidebar. This ensures that when switching between individual docs, the sidebar's state is preserved, and that you can switch between versions through the navbar dropdown while staying on the same doc. The component used is `@theme/DocPage`.
```mdx-code-block
export const URLPath = () => <code>{useLocation().pathname}</code>;
export const FilePath = () => {
const currentVersion = useActiveDocContext('default').activeVersion.name;
return <code>{currentVersion === 'current' ? './docs/' : `./versioned_docs/version-${currentVersion}/`}advanced/routing.md</code>;
}
```
The individual docs are rendered in the remaining space after the navbar, footer, sidebar, etc. have all been provided by the `DocPage` component. For example, this page, <URLPath />, is generated from the file at <FilePath />. The component used is `@theme/DocItem`.
The doc's `slug` front matter customizes the last part of the route, but the base route is always defined by the plugin's `routeBasePath` and the version's `path`.
### File paths and URL paths {#file-paths-and-url-paths}
Throughout the documentation, we always try to be unambiguous about whether we are talking about file paths or URL paths. Content plugins usually map file paths directly to URL paths, for example, `./docs/advanced/routing.md` will become `/docs/advanced/routing`. However, with `slug`, you can make URLs totally decoupled from the file structure.
When writing links in Markdown, you could either mean a _file path_, or a _URL path_, which Docusaurus would use several heuristics to determine.
- If the path has a `@site` prefix, it is _always_ an asset file path.
- If the path has an `http(s)://` prefix, it is _always_ a URL path.
- If the path doesn't have an extension, it is a URL path. For example, a link `[page](../plugins)` on a page with URL `/docs/advanced/routing` will link to `/docs/plugins`. Docusaurus will only detect broken links when building your site (when it knows the full route structure), but will make no assumptions about the existence of a file. It is exactly equivalent to writing `<a href="../plugins">page</a>` in a JSX file.
- If the path has an `.md(x)` extension, Docusaurus would try to resolve that Markdown file to a URL, and replace the file path with a URL path.
- If the path has any other extension, Docusaurus would treat it as [an asset](../guides/markdown-features/markdown-features-assets.mdx) and bundle it.
The following directory structure may help you visualize this file → URL mapping. Assume that there's no slug customization in any page.
<details>
<summary>A sample site structure</summary>
```bash
.
├── blog # blog plugin has routeBasePath: '/blog'
│ ├── 2019-05-28-first-blog-post.md # -> /blog/2019/05/28/first-blog-post
│ ├── 2019-05-29-long-blog-post.md # -> /blog/2019/05/29/long-blog-post
│ ├── 2021-08-01-mdx-blog-post.mdx # -> /blog/2021/08/01/mdx-blog-post
│ └── 2021-08-26-welcome
│ ├── docusaurus-plushie-banner.jpeg
│ └── index.md # -> /blog/2021/08/26/welcome
├── docs # docs plugin has routeBasePath: '/docs'; current version has base path '/'
│ ├── intro.md # -> /docs/intro
│ ├── tutorial-basics
│ │ ├── _category_.json
│ │ ├── congratulations.md # -> /docs/tutorial-basics/congratulations
│ │ └── markdown-features.mdx # -> /docs/tutorial-basics/congratulations
│ └── tutorial-extras
│ ├── _category_.json
│ ├── manage-docs-versions.md # -> /docs/tutorial-extras/manage-docs-versions
│ └── translate-your-site.md # -> /docs/tutorial-extras/translate-your-site
├── src
│ └── pages # pages plugin has routeBasePath: '/'
│ ├── index.module.css
│ ├── index.tsx # -> /
│ └── markdown-page.md # -> /markdown-page
└── versioned_docs
└── version-1.0.0 # version has base path '/1.0.0'
├── intro.md # -> /docs/1.0.0/intro
├── tutorial-basics
│ ├── _category_.json
│ ├── congratulations.md # -> /docs/1.0.0/tutorial-basics/congratulations
│ └── markdown-features.mdx # -> /docs/1.0.0/tutorial-basics/markdown-features
└── tutorial-extras
├── _category_.json
├── manage-docs-versions.md # -> /docs/1.0.0/tutorial-extras/manage-docs-versions
└── translate-your-site.md # -> /docs/1.0.0/tutorial-extras/translate-your-site
```
</details>
So much about content plugins. Let's take one step back and talk about how routing works in a Docusaurus app in general.
## Routes become HTML files {#routes-become-html-files}
Because Docusaurus is a server-side rendering framework, all routes generated will be server-side rendered into static HTML files. If you are familiar with the behavior of HTTP servers like [Apache2](https://httpd.apache.org/docs/trunk/getting-started.html), you will understand how this is done: when the browser sends a request to the route `/docs/advanced/routing`, the server interprets that as request for the HTML file `/docs/advanced/routing/index.html`, and returns that.
The `/docs/advanced/routing` route can correspond to either `/docs/advanced/routing/index.html` or `/docs/advanced/routing.html`. Some hosting providers differentiate between them using the presence of a trailing slash, and may or may not tolerate the other. Read more in the [trailing slash guide](https://github.com/slorber/trailing-slash-guide).
For example, the build output of the directory above is (ignoring other assets and JS bundle):
<details>
<summary>Output of the above workspace</summary>
```bash
build
├── 404.html # /404/
├── blog
│ ├── archive
│ │ └── index.html # /blog/archive/
│ ├── first-blog-post
│ │ └── index.html # /blog/first-blog-post/
│ ├── index.html # /blog/
│ ├── long-blog-post
│ │ └── index.html # /blog/long-blog-post/
│ ├── mdx-blog-post
│ │ └── index.html # /blog/mdx-blog-post/
│ ├── tags
│ │ ├── docusaurus
│ │ │ └── index.html # /blog/tags/docusaurus/
│ │ ├── hola
│ │ │ └── index.html # /blog/tags/hola/
│ │ └── index.html # /blog/tags/
│ └── welcome
│ └── index.html # /blog/welcome/
├── docs
│ ├── 1.0.0
│ │ ├── intro
│ │ │ └── index.html # /docs/1.0.0/intro/
│ │ ├── tutorial-basics
│ │ │ ├── congratulations
│ │ │ │ └── index.html # /docs/1.0.0/tutorial-basics/congratulations/
│ │ │ └── markdown-features
│ │ │ └── index.html # /docs/1.0.0/tutorial-basics/markdown-features/
│ │ └── tutorial-extras
│ │ ├── manage-docs-versions
│ │ │ └── index.html # /docs/1.0.0/tutorial-extras/manage-docs-versions/
│ │ └── translate-your-site
│ │ └── index.html # /docs/1.0.0/tutorial-extras/translate-your-site/
│ ├── intro
│ │ └── index.html # /docs/1.0.0/intro/
│ ├── tutorial-basics
│ │ ├── congratulations
│ │ │ └── index.html # /docs/tutorial-basics/congratulations/
│ │ └── markdown-features
│ │ └── index.html # /docs/tutorial-basics/markdown-features/
│ └── tutorial-extras
│ ├── manage-docs-versions
│ │ └── index.html # /docs/tutorial-extras/manage-docs-versions/
│ └── translate-your-site
│ └── index.html # /docs/tutorial-extras/translate-your-site/
├── index.html # /
└── markdown-page
└── index.html # /markdown-page/
```
</details>
If `trailingSlash` is set to `false`, the build would emit `intro.html` instead of `intro/index.html`.
All HTML files will reference its JS assets using absolute URLs, so in order for the correct assets to be located, you have to configure the `baseUrl` field. Note that `baseUrl` doesn't affect the emitted bundle's file structure: the base URL is one level above the Docusaurus routing system. You can see the aggregate of `url` and `baseUrl` as the actual location of your Docusaurus site.
For example, the emitted HTML would contain links like `<link rel="preload" href="/assets/js/runtime~main.7ed5108a.js" as="script">`. Because absolute URLs are resolved from the host, if the bundle placed under the path `https://example.com/base/`, the link will point to `https://example.com/assets/js/runtime~main.7ed5108a.js`, which is, well, non-existent. By specifying `/base/` as base URL, the link will correctly point to `/base/assets/js/runtime~main.7ed5108a.js`.
Localized sites have the locale as part of the base URL as well. For example, `https://docusaurus.io/zh-CN/docs/advanced/routing/` has base URL `/zh-CN/`.
## Generating and accessing routes {#generating-and-accessing-routes}
The `addRoute` lifecycle action is used to generate routes. It registers a piece of route config to the route tree, giving a route, a component, and props that the component needs. The props and the component are both provided as paths for the bundler to `require`, because as explained in the [architecture overview](architecture.mdx), server and client only communicate through temp files.
All routes are aggregated in `.docusaurus/routes.js`, which you can view with the debug plugin's [routes panel](/__docusaurus/debug/routes).
On the client side, we offer `@docusaurus/router` to access the page's route. `@docusaurus/router` is a re-export of the [`react-router-dom`](https://www.npmjs.com/package/react-router-dom/v/5.3.0) package. For example, you can use `useLocation` to get the current page's [location](https://developer.mozilla.org/en-US/docs/Web/API/Location), and `useHistory` to access the [history object](https://developer.mozilla.org/en-US/docs/Web/API/History). (They are not the same as the browser API, although similar in functionality. Refer to the React Router documentation for specific APIs.)
This API is **SSR safe**, as opposed to the browser-only `window.location`.
```jsx title="myComponent.js"
import React from 'react';
import {useLocation} from '@docusaurus/router';
export function PageRoute() {
// React router provides the current component's route, even in SSR
const location = useLocation();
return (
<span>
We are currently on <code>{location.pathname}</code>
</span>
);
}
```
```mdx-code-block
export function PageRoute() {
const location = useLocation();
return (
<span>
We are currently on <code>{location.pathname}</code>
</span>
);
}
<BrowserWindow>
<PageRoute />
</BrowserWindow>
```
## Escaping from SPA redirects {#escaping-from-spa-redirects}
Docusaurus builds a [single-page application](https://developer.mozilla.org/en-US/docs/Glossary/SPA), where route transitions are done through the `history.push()` method of React router. This operation is done on the client side. However, the prerequisite for a route transition to happen this way is that the target URL is known to our router. Otherwise, the router catches this path and displays a 404 page instead.
If you put some HTML pages under the `static` folder, they will be copied to the build output and therefore become accessible as part of your website, yet it's not part of the Docusaurus route system. We provide a `pathname://` protocol that allows you to redirect to another part of your domain in a non-SPA fashion, as if this route is an external link.
```md
- [pathname:///pure-html](pathname:///pure-html)
```
<BrowserWindow>
- [`pathname:///pure-html`](pathname:///pure-html)
</BrowserWindow>
The `pathname://` protocol is useful for referencing any content in the static folder. For example, Docusaurus would convert [all Markdown static assets to require() calls](../guides/markdown-features/markdown-features-assets.mdx#static-assets). You can use `pathname://` to keep it a regular link instead of being hashed by Webpack.
```md title="my-doc.md"
![An image from the static](pathname:///img/docusaurus.png)
[An asset from the static](pathname:///files/asset.pdf)
```
Docusaurus will only strip the `pathname://` prefix without processing the content.

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