Merge branch 'main' into ozaki/showcase

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

View file

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

View file

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

View file

@ -11,9 +11,9 @@ const CookieName = 'DocusaurusPlaygroundName';
const PlaygroundConfigs = { const PlaygroundConfigs = {
codesandbox: 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': '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 // Slow to load
// stackblitz: 'https://stackblitz.com/github/facebook/docusaurus/tree/main/examples/classic', // stackblitz: 'https://stackblitz.com/github/facebook/docusaurus/tree/main/examples/classic',

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -34,7 +34,9 @@ const plugin: Plugin = function plugin(
const {toString} = await import('mdast-util-to-string'); const {toString} = await import('mdast-util-to-string');
const {visit, EXIT} = await import('unist-util-visit'); const {visit, EXIT} = await import('unist-util-visit');
visit(root, 'heading', (headingNode: Heading, index, parent) => { visit(root, ['heading', 'thematicBreak'], (node, index, parent) => {
if (node.type === 'heading') {
const headingNode = node as Heading;
if (headingNode.depth === 1) { if (headingNode.depth === 1) {
vfile.data.contentTitle = toString(headingNode); vfile.data.contentTitle = toString(headingNode);
if (removeContentTitle) { if (removeContentTitle) {
@ -47,6 +49,11 @@ const plugin: Plugin = function plugin(
if (headingNode.depth >= 1) { if (headingNode.depth >= 1) {
return EXIT; return EXIT;
} }
}
// We only handle contentTitle when it's above the first thematic break
if (node.type === 'thematicBreak') {
return EXIT;
}
return undefined; return undefined;
}); });
}; };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * 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 collectRedirects from './collectRedirects';
import writeRedirectFiles, { import writeRedirectFiles, {
toRedirectFiles, toRedirectFiles,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,10 @@
*/ */
import applyTrailingSlash, { import applyTrailingSlash, {
addTrailingSlash,
type ApplyTrailingSlashParams, type ApplyTrailingSlashParams,
addLeadingSlash,
removeTrailingSlash,
} from '../applyTrailingSlash'; } from '../applyTrailingSlash';
function params( function params(
@ -176,3 +179,30 @@ describe('applyTrailingSlash', () => {
).toBe('https://xyz.com/abc/?search#anchor'); ).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. * LICENSE file in the root directory of this source tree.
*/ */
import {addPrefix, removeSuffix} from './stringUtils';
import type {DocusaurusConfig} from '@docusaurus/types'; import type {DocusaurusConfig} from '@docusaurus/types';
export type ApplyTrailingSlashParams = Pick< export type ApplyTrailingSlashParams = Pick<
@ -12,6 +13,10 @@ export type ApplyTrailingSlashParams = Pick<
'trailingSlash' | 'baseUrl' 'trailingSlash' | 'baseUrl'
>; >;
export function addTrailingSlash(str: string): string {
return str.endsWith('/') ? str : `${str}/`;
}
// Trailing slash handling depends in some site configuration options // Trailing slash handling depends in some site configuration options
export default function applyTrailingSlash( export default function applyTrailingSlash(
path: string, path: string,
@ -24,13 +29,6 @@ export default function applyTrailingSlash(
return path; 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 { function handleTrailingSlash(str: string, trailing: boolean): string {
return trailing ? addTrailingSlash(str) : removeTrailingSlash(str); return trailing ? addTrailingSlash(str) : removeTrailingSlash(str);
} }
@ -55,3 +53,13 @@ export default function applyTrailingSlash(
return path.replace(pathname, newPathname); 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 { export {
default as applyTrailingSlash, default as applyTrailingSlash,
addTrailingSlash,
addLeadingSlash,
removeTrailingSlash,
type ApplyTrailingSlashParams, type ApplyTrailingSlashParams,
} from './applyTrailingSlash'; } from './applyTrailingSlash';
export {addPrefix, removeSuffix, addSuffix, removePrefix} from './stringUtils';
export {getErrorCausalChain} from './errorUtils'; 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", "name": "@docusaurus/utils-validation",
"version": "3.0.0", "version": "3.2.1",
"description": "Node validation utility functions for Docusaurus packages.", "description": "Node validation utility functions for Docusaurus packages.",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
@ -18,8 +18,9 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@docusaurus/logger": "3.0.0", "@docusaurus/logger": "3.2.1",
"@docusaurus/utils": "3.0.0", "@docusaurus/utils": "3.2.1",
"@docusaurus/utils-common": "3.2.1",
"joi": "^17.9.2", "joi": "^17.9.2",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"tslib": "^2.6.0" "tslib": "^2.6.0"

View file

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

View file

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

View file

@ -7,34 +7,7 @@
import {jest} from '@jest/globals'; import {jest} from '@jest/globals';
import _ from 'lodash'; import _ from 'lodash';
import { import {mapAsyncSequential, findAsyncSequential} from '../jsUtils';
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');
});
});
describe('mapAsyncSequential', () => { describe('mapAsyncSequential', () => {
function sleep(timeout: number): Promise<void> { function sleep(timeout: number): Promise<void> {

View file

@ -10,9 +10,6 @@ import {
getEditUrl, getEditUrl,
fileToPath, fileToPath,
isValidPathname, isValidPathname,
addTrailingSlash,
addLeadingSlash,
removeTrailingSlash,
resolvePathname, resolvePathname,
encodePath, encodePath,
buildSshUrl, 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', () => { describe('parseURLPath', () => {
it('parse and resolve pathname', () => { it('parse and resolve pathname', () => {
expect(parseURLPath('')).toEqual({ expect(parseURLPath('')).toEqual({

View file

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

View file

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

View file

@ -9,8 +9,7 @@
import path from 'path'; import path from 'path';
import Micromatch from 'micromatch'; // Note: Micromatch is used by Globby 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. */ /** A re-export of the globby instance. */
export {default as Globby} from 'globby'; export {default as Globby} from 'globby';

View file

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

View file

@ -5,30 +5,6 @@
* LICENSE file in the root directory of this source tree. * 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. * `Array#map` for async operations where order matters.
* @param array The array to traverse. * @param array The array to traverse.

View file

@ -6,7 +6,6 @@
*/ */
import resolvePathnameUnsafe from 'resolve-pathname'; 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 * 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); 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. */ /** Constructs an SSH URL that can be used to push to GitHub. */
export function buildSshUrl( export function buildSshUrl(
githubHost: string, githubHost: string,

View file

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

View file

@ -64,23 +64,15 @@ export async function build(
process.on(sig, () => process.exit()); process.on(sig, () => process.exit());
}); });
async function tryToBuildLocale({ async function tryToBuildLocale({locale}: {locale: string}) {
locale,
isLastLocale,
}: {
locale: string;
isLastLocale: boolean;
}) {
try { try {
PerfLogger.start(`Building site for locale ${locale}`); await PerfLogger.async(`${logger.name(locale)}`, () =>
await buildLocale({ buildLocale({
siteDir, siteDir,
locale, locale,
cliOptions, cliOptions,
forceTerminate, }),
isLastLocale, );
});
PerfLogger.end(`Building site for locale ${locale}`);
} catch (err) { } catch (err) {
throw new Error( throw new Error(
logger.interpolate`Unable to build website for locale name=${locale}.`, 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 PerfLogger.async('Get locales to build', () =>
const locales = await getLocalesToBuild({siteDir, cliOptions}); getLocalesToBuild({siteDir, cliOptions}),
PerfLogger.end(`Get locales to build`); );
if (locales.length > 1) { if (locales.length > 1) {
logger.info`Website will be built for all these locales: ${locales}`; logger.info`Website will be built for all these locales: ${locales}`;
} }
PerfLogger.start(`Building ${locales.length} locales`); await PerfLogger.async(`Build`, () =>
await mapAsyncSequential(locales, (locale) => { mapAsyncSequential(locales, async (locale) => {
const isLastLocale = locales.indexOf(locale) === locales.length - 1; const isLastLocale = locales.indexOf(locale) === locales.length - 1;
return tryToBuildLocale({locale, isLastLocale}); await tryToBuildLocale({locale});
}); if (isLastLocale) {
PerfLogger.end(`Building ${locales.length} locales`); 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({ async function getLocalesToBuild({
@ -144,14 +144,10 @@ async function buildLocale({
siteDir, siteDir,
locale, locale,
cliOptions, cliOptions,
forceTerminate,
isLastLocale,
}: { }: {
siteDir: string; siteDir: string;
locale: string; locale: string;
cliOptions: Partial<BuildCLIOptions>; cliOptions: Partial<BuildCLIOptions>;
forceTerminate: boolean;
isLastLocale: boolean;
}): Promise<string> { }): Promise<string> {
// Temporary workaround to unlock the ability to translate the site config // Temporary workaround to unlock the ability to translate the site config
// We'll remove it if a better official API can be designed // We'll remove it if a better official API can be designed
@ -160,23 +156,23 @@ async function buildLocale({
logger.info`name=${`[${locale}]`} Creating an optimized production build...`; logger.info`name=${`[${locale}]`} Creating an optimized production build...`;
PerfLogger.start('Loading site'); const site = await PerfLogger.async('Load site', () =>
const site = await loadSite({ loadSite({
siteDir, siteDir,
outDir: cliOptions.outDir, outDir: cliOptions.outDir,
config: cliOptions.config, config: cliOptions.config,
locale, locale,
localizePath: cliOptions.locale ? false : undefined, localizePath: cliOptions.locale ? false : undefined,
}); }),
PerfLogger.end('Loading site'); );
const {props} = site; const {props} = site;
const {outDir, plugins} = props; const {outDir, plugins} = props;
// We can build the 2 configs in parallel // We can build the 2 configs in parallel
PerfLogger.start('Creating webpack configs');
const [{clientConfig, clientManifestPath}, {serverConfig, serverBundlePath}] = const [{clientConfig, clientManifestPath}, {serverConfig, serverBundlePath}] =
await Promise.all([ await PerfLogger.async('Creating webpack configs', () =>
Promise.all([
getBuildClientConfig({ getBuildClientConfig({
props, props,
cliOptions, cliOptions,
@ -184,57 +180,42 @@ async function buildLocale({
getBuildServerConfig({ getBuildServerConfig({
props, 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');
// Run webpack to build JS bundle (client) and static html files (server). // Run webpack to build JS bundle (client) and static html files (server).
PerfLogger.start('Bundling'); await PerfLogger.async('Bundling with Webpack', () =>
await compile([clientConfig, serverConfig]); compile([clientConfig, serverConfig]),
PerfLogger.end('Bundling'); );
PerfLogger.start('Executing static site generation'); const {collectedData} = await PerfLogger.async('SSG', () =>
const {collectedData} = await executeSSG({ executeSSG({
props, props,
serverBundlePath, serverBundlePath,
clientManifestPath, clientManifestPath,
}); }),
PerfLogger.end('Executing static site generation'); );
// Remove server.bundle.js because it is not needed. // Remove server.bundle.js because it is not needed.
PerfLogger.start('Deleting server bundle'); await PerfLogger.async('Deleting server bundle', () =>
await ensureUnlink(serverBundlePath); ensureUnlink(serverBundlePath),
PerfLogger.end('Deleting server bundle'); );
// Plugin Lifecycle - postBuild. // Plugin Lifecycle - postBuild.
PerfLogger.start('Executing postBuild()'); await PerfLogger.async('postBuild()', () =>
await executePluginsPostBuild({plugins, props, collectedData}); executePluginsPostBuild({plugins, props, collectedData}),
PerfLogger.end('Executing postBuild()'); );
// TODO execute this in parallel to postBuild? // TODO execute this in parallel to postBuild?
PerfLogger.start('Executing broken links checker'); await PerfLogger.async('Broken links checker', () =>
await executeBrokenLinksCheck({props, collectedData}); executeBrokenLinksCheck({props, collectedData}),
PerfLogger.end('Executing broken links checker'); );
logger.success`Generated static files in path=${path.relative( logger.success`Generated static files in path=${path.relative(
process.cwd(), process.cwd(),
outDir, 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; return outDir;
} }
@ -247,24 +228,23 @@ async function executeSSG({
serverBundlePath: string; serverBundlePath: string;
clientManifestPath: string; clientManifestPath: string;
}) { }) {
PerfLogger.start('Reading client manifest'); const manifest: Manifest = await PerfLogger.async(
const manifest: Manifest = await fs.readJSON(clientManifestPath, 'utf-8'); 'Read client manifest',
PerfLogger.end('Reading client manifest'); () => fs.readJSON(clientManifestPath, 'utf-8'),
PerfLogger.start('Compiling SSR template');
const ssrTemplate = await compileSSRTemplate(
props.siteConfig.ssrTemplate ?? defaultSSRTemplate,
); );
PerfLogger.end('Compiling SSR template');
PerfLogger.start('Loading App renderer'); const ssrTemplate = await PerfLogger.async('Compile SSR template', () =>
const renderer = await loadAppRenderer({ compileSSRTemplate(props.siteConfig.ssrTemplate ?? defaultSSRTemplate),
);
const renderer = await PerfLogger.async('Load App renderer', () =>
loadAppRenderer({
serverBundlePath, serverBundlePath,
}); }),
PerfLogger.end('Loading App renderer'); );
PerfLogger.start('Generate static files'); const ssgResult = await PerfLogger.async('Generate static files', () =>
const ssgResult = await generateStaticFiles({ generateStaticFiles({
pathnames: props.routesPaths, pathnames: props.routesPaths,
renderer, renderer,
params: { params: {
@ -279,8 +259,8 @@ async function executeSSG({
noIndex: props.siteConfig.noIndex, noIndex: props.siteConfig.noIndex,
DOCUSAURUS_VERSION, DOCUSAURUS_VERSION,
}, },
}); }),
PerfLogger.end('Generate static files'); );
return ssgResult; return ssgResult;
} }
@ -345,6 +325,10 @@ async function getBuildClientConfig({
bundleAnalyzer: cliOptions.bundleAnalyzer ?? false, bundleAnalyzer: cliOptions.bundleAnalyzer ?? false,
}); });
let {config} = result; let {config} = result;
config = executePluginsConfigurePostCss({
plugins,
config,
});
config = executePluginsConfigureWebpack({ config = executePluginsConfigureWebpack({
plugins, plugins,
config, config,
@ -360,10 +344,6 @@ async function getBuildServerConfig({props}: {props: Props}) {
props, props,
}); });
let {config} = result; let {config} = result;
config = executePluginsConfigurePostCss({
plugins,
config,
});
config = executePluginsConfigureWebpack({ config = executePluginsConfigureWebpack({
plugins, plugins,
config, config,

View file

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

View file

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

View file

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

View file

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

View file

@ -7,13 +7,13 @@
import path from 'path'; import path from 'path';
import {DOCUSAURUS_VERSION} from '@docusaurus/utils'; import {DOCUSAURUS_VERSION} from '@docusaurus/utils';
import {getPluginVersion, loadSiteMetadata} from '../siteMetadata'; import {loadPluginVersion, createSiteMetadata} from '../siteMetadata';
import type {LoadedPlugin} from '@docusaurus/types'; import type {LoadedPlugin} from '@docusaurus/types';
describe('getPluginVersion', () => { describe('loadPluginVersion', () => {
it('detects external packages plugins versions', async () => { it('detects external packages plugins versions', async () => {
await expect( await expect(
getPluginVersion( loadPluginVersion(
path.join(__dirname, '__fixtures__/siteMetadata/dummy-plugin.js'), path.join(__dirname, '__fixtures__/siteMetadata/dummy-plugin.js'),
// Make the plugin appear external. // Make the plugin appear external.
path.join(__dirname, '..', '..', '..', '..', '..', '..', 'website'), path.join(__dirname, '..', '..', '..', '..', '..', '..', 'website'),
@ -23,7 +23,7 @@ describe('getPluginVersion', () => {
it('detects project plugins versions', async () => { it('detects project plugins versions', async () => {
await expect( await expect(
getPluginVersion( loadPluginVersion(
path.join(__dirname, '__fixtures__/siteMetadata/dummy-plugin.js'), path.join(__dirname, '__fixtures__/siteMetadata/dummy-plugin.js'),
// Make the plugin appear project local. // Make the plugin appear project local.
path.join(__dirname, '__fixtures__/siteMetadata'), path.join(__dirname, '__fixtures__/siteMetadata'),
@ -32,14 +32,14 @@ describe('getPluginVersion', () => {
}); });
it('detects local packages versions', async () => { it('detects local packages versions', async () => {
await expect(getPluginVersion('/', '/')).resolves.toEqual({type: 'local'}); await expect(loadPluginVersion('/', '/')).resolves.toEqual({type: 'local'});
}); });
}); });
describe('loadSiteMetadata', () => { describe('createSiteMetadata', () => {
it('throws if plugin versions mismatch', async () => { it('throws if plugin versions mismatch', () => {
await expect( expect(() =>
loadSiteMetadata({ createSiteMetadata({
plugins: [ plugins: [
{ {
name: 'docusaurus-plugin-content-docs', name: 'docusaurus-plugin-content-docs',
@ -50,10 +50,9 @@ describe('loadSiteMetadata', () => {
}, },
}, },
] as LoadedPlugin[], ] 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}). 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?`); 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 logger from '@docusaurus/logger';
import {matchRoutes as reactRouterMatchRoutes} from 'react-router-config'; import {matchRoutes as reactRouterMatchRoutes} from 'react-router-config';
import { import {
addTrailingSlash,
parseURLPath, parseURLPath,
removeTrailingSlash,
serializeURLPath, serializeURLPath,
flattenRoutes, flattenRoutes,
type URLPath, type URLPath,
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils-common';
import type {RouteConfig, ReportingSeverity} from '@docusaurus/types'; import type {RouteConfig, ReportingSeverity} from '@docusaurus/types';
function matchRoutes(routeConfig: RouteConfig[], pathname: string) { 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 * Runs the `getClientModules` lifecycle. The returned file paths are all
* absolute. * absolute.
*/ */
export function loadClientModules(plugins: LoadedPlugin[]): string[] { export function getAllClientModules(plugins: LoadedPlugin[]): string[] {
return plugins.flatMap( return plugins.flatMap(
(plugin) => (plugin) =>
plugin.getClientModules?.().map((p) => path.resolve(plugin.path, p)) ?? plugin.getClientModules?.().map((p) => path.resolve(plugin.path, p)) ??

View file

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

View file

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

View file

@ -0,0 +1,204 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {mergeGlobalData} from '../pluginsUtils';
import type {GlobalData} from '@docusaurus/types';
describe('mergeGlobalData', () => {
it('no global data', () => {
expect(mergeGlobalData()).toEqual({});
});
it('1 global data', () => {
const globalData: GlobalData = {
plugin: {
default: {someData: 'val'},
},
};
expect(mergeGlobalData(globalData)).toEqual(globalData);
});
it('1 global data - primitive value', () => {
// For retro-compatibility we allow primitive values to be kept as is
// Not sure anyone is using primitive global data though...
const globalData: GlobalData = {
plugin: {
default: 42,
},
};
expect(mergeGlobalData(globalData)).toEqual(globalData);
});
it('3 distinct plugins global data', () => {
const globalData1: GlobalData = {
plugin1: {
default: {someData1: 'val1'},
},
};
const globalData2: GlobalData = {
plugin2: {
default: {someData2: 'val2'},
},
};
const globalData3: GlobalData = {
plugin3: {
default: {someData3: 'val3'},
},
};
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
plugin1: {
default: {someData1: 'val1'},
},
plugin2: {
default: {someData2: 'val2'},
},
plugin3: {
default: {someData3: 'val3'},
},
});
});
it('3 plugin instances of same plugin', () => {
const globalData1: GlobalData = {
plugin: {
id1: {someData1: 'val1'},
},
};
const globalData2: GlobalData = {
plugin: {
id2: {someData2: 'val2'},
},
};
const globalData3: GlobalData = {
plugin: {
id3: {someData3: 'val3'},
},
};
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
plugin: {
id1: {someData1: 'val1'},
id2: {someData2: 'val2'},
id3: {someData3: 'val3'},
},
});
});
it('3 times the same plugin', () => {
const globalData1: GlobalData = {
plugin: {
id: {someData1: 'val1', shared: 'shared1'},
},
};
const globalData2: GlobalData = {
plugin: {
id: {someData2: 'val2', shared: 'shared2'},
},
};
const globalData3: GlobalData = {
plugin: {
id: {someData3: 'val3', shared: 'shared3'},
},
};
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
plugin: {
id: {
someData1: 'val1',
someData2: 'val2',
someData3: 'val3',
shared: 'shared3',
},
},
});
});
it('3 times same plugin - including primitive values', () => {
// Very unlikely to happen, but we can't merge primitive values together
// Since we use Object.assign(), the primitive values are simply ignored
const globalData1: GlobalData = {
plugin: {
default: 42,
},
};
const globalData2: GlobalData = {
plugin: {
default: {hey: 'val'},
},
};
const globalData3: GlobalData = {
plugin: {
default: 84,
},
};
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
plugin: {
default: {hey: 'val'},
},
});
});
it('real world case', () => {
const globalData1: GlobalData = {
plugin1: {
id1: {someData1: 'val1', shared: 'globalData1'},
},
};
const globalData2: GlobalData = {
plugin1: {
id1: {someData2: 'val2', shared: 'globalData2'},
},
};
const globalData3: GlobalData = {
plugin1: {
id2: {someData3: 'val3', shared: 'globalData3'},
},
};
const globalData4: GlobalData = {
plugin2: {
id1: {someData1: 'val1', shared: 'globalData4'},
},
};
const globalData5: GlobalData = {
plugin2: {
id2: {someData1: 'val1', shared: 'globalData5'},
},
};
const globalData6: GlobalData = {
plugin3: {
id1: {someData1: 'val1', shared: 'globalData6'},
},
};
expect(
mergeGlobalData(
globalData1,
globalData2,
globalData3,
globalData4,
globalData5,
globalData6,
),
).toEqual({
plugin1: {
id1: {someData1: 'val1', someData2: 'val2', shared: 'globalData2'},
id2: {someData3: 'val3', shared: 'globalData3'},
},
plugin2: {
id1: {someData1: 'val1', shared: 'globalData4'},
id2: {someData1: 'val1', shared: 'globalData5'},
},
plugin3: {
id1: {someData1: 'val1', shared: 'globalData6'},
},
});
});
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -14,7 +14,7 @@ import type {
SiteMetadata, SiteMetadata,
} from '@docusaurus/types'; } from '@docusaurus/types';
async function getPackageJsonVersion( async function loadPackageJsonVersion(
packageJsonPath: string, packageJsonPath: string,
): Promise<string | undefined> { ): Promise<string | undefined> {
if (await fs.pathExists(packageJsonPath)) { if (await fs.pathExists(packageJsonPath)) {
@ -24,14 +24,20 @@ async function getPackageJsonVersion(
return undefined; return undefined;
} }
async function getPackageJsonName( async function loadPackageJsonName(
packageJsonPath: string, packageJsonPath: string,
): Promise<string | undefined> { ): Promise<string | undefined> {
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require
return (require(packageJsonPath) as {name?: string}).name; 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, pluginPath: string,
siteDir: string, siteDir: string,
): Promise<PluginVersionInformation> { ): Promise<PluginVersionInformation> {
@ -52,8 +58,8 @@ export async function getPluginVersion(
} }
return { return {
type: 'package', type: 'package',
name: await getPackageJsonName(packageJsonPath), name: await loadPackageJsonName(packageJsonPath),
version: await getPackageJsonVersion(packageJsonPath), version: await loadPackageJsonVersion(packageJsonPath),
}; };
} }
potentialPluginPackageJsonDirectory = path.dirname( 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, plugins,
siteDir,
}: { }: {
siteVersion: string | undefined;
plugins: LoadedPlugin[]; plugins: LoadedPlugin[];
siteDir: string; }): SiteMetadata {
}): Promise<SiteMetadata> {
const siteMetadata: SiteMetadata = { const siteMetadata: SiteMetadata = {
docusaurusVersion: DOCUSAURUS_VERSION, docusaurusVersion: DOCUSAURUS_VERSION,
siteVersion: await getPackageJsonVersion( siteVersion,
path.join(siteDir, 'package.json'),
),
pluginVersions: Object.fromEntries( pluginVersions: Object.fromEntries(
plugins plugins
.filter(({version: {type}}) => type !== 'synthetic') .filter(({version: {type}}) => type !== 'synthetic')

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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