diff --git a/package.json b/package.json index 73e88a87ef..9f072d6ea9 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "lerna": "lerna", "test": "cross-env TZ=UTC jest", "test:build:website": "./admin/scripts/test-release.sh", - "watch": "yarn lerna run --parallel --no-private watch", + "watch": "yarn lerna run --parallel watch", "clear": "(yarn workspace website clear || echo 'Failure while running docusaurus clear') && yarn lerna exec --ignore docusaurus yarn rimraf lib lib-next", "test:baseUrl": "yarn build:website:baseUrl && yarn serve:website:baseUrl", "lock:update": "npx yarn-deduplicate" diff --git a/packages/docusaurus-init/templates/bootstrap/docusaurus.config.js b/packages/docusaurus-init/templates/bootstrap/docusaurus.config.js index 68c8f1ff6b..02be819935 100644 --- a/packages/docusaurus-init/templates/bootstrap/docusaurus.config.js +++ b/packages/docusaurus-init/templates/bootstrap/docusaurus.config.js @@ -1,5 +1,8 @@ -/** @type {import('@docusaurus/types').DocusaurusConfig} */ -module.exports = { +// @ts-check +// Note: type annotations allow type checking and IDEs autocompletion + +/** @type {import('@docusaurus/types').Config} */ +const config = { title: 'My Site', tagline: 'The tagline of my site', url: 'https://your-docusaurus-test-site.com', @@ -86,3 +89,5 @@ module.exports = { ], ], }; + +module.exports = config; diff --git a/packages/docusaurus-init/templates/classic/docusaurus.config.js b/packages/docusaurus-init/templates/classic/docusaurus.config.js index c9b59e12f4..ba5f8adcb7 100644 --- a/packages/docusaurus-init/templates/classic/docusaurus.config.js +++ b/packages/docusaurus-init/templates/classic/docusaurus.config.js @@ -1,9 +1,11 @@ +// @ts-check +// Note: type annotations allow type checking and IDEs autocompletion + const lightCodeTheme = require('prism-react-renderer/themes/github'); const darkCodeTheme = require('prism-react-renderer/themes/dracula'); -// With JSDoc @type annotations, IDEs can provide config autocompletion -/** @type {import('@docusaurus/types').DocusaurusConfig} */ -(module.exports = { +/** @type {import('@docusaurus/types').Config} */ +const config = { title: 'My Site', tagline: 'Dinosaurs are cool', url: 'https://your-docusaurus-test-site.com', @@ -111,4 +113,6 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula'); darkTheme: darkCodeTheme, }, }), -}); +}; + +module.exports = config; diff --git a/packages/docusaurus-init/templates/facebook/docusaurus.config.js b/packages/docusaurus-init/templates/facebook/docusaurus.config.js index 2167667116..10ec80355f 100644 --- a/packages/docusaurus-init/templates/facebook/docusaurus.config.js +++ b/packages/docusaurus-init/templates/facebook/docusaurus.config.js @@ -6,10 +6,11 @@ * * @format */ +// @ts-check +// Note: type annotations allow type checking and IDEs autocompletion -// With JSDoc @type annotations, IDEs can provide config autocompletion -/** @type {import('@docusaurus/types').DocusaurusConfig} */ -(module.exports = { +/** @type {import('@docusaurus/types').Config} */ +const config = { title: 'My Site', tagline: 'The tagline of my site', url: 'https://your-docusaurus-test-site.com', @@ -146,4 +147,6 @@ copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc. Built with Docusaurus.`, }, }), -}); +}; + +module.exports = config; diff --git a/packages/docusaurus-mdx-loader/src/types.d.ts b/packages/docusaurus-mdx-loader/src/types.d.ts index 0f2b0dae9d..01a23fa3be 100644 --- a/packages/docusaurus-mdx-loader/src/types.d.ts +++ b/packages/docusaurus-mdx-loader/src/types.d.ts @@ -11,7 +11,7 @@ declare module '@docusaurus/mdx-loader' { [Function, Record] | Function; export type RemarkAndRehypePluginOptions = { remarkPlugins: RemarkOrRehypePlugin[]; - rehypePlugins: string[]; + rehypePlugins: RemarkOrRehypePlugin[]; beforeDefaultRemarkPlugins: RemarkOrRehypePlugin[]; beforeDefaultRehypePlugins: RemarkOrRehypePlugin[]; }; diff --git a/packages/docusaurus-plugin-content-blog/package.json b/packages/docusaurus-plugin-content-blog/package.json index 24b99b0aa5..b6b5e1d031 100644 --- a/packages/docusaurus-plugin-content-blog/package.json +++ b/packages/docusaurus-plugin-content-blog/package.json @@ -34,6 +34,7 @@ "reading-time": "^1.5.0", "remark-admonitions": "^1.2.1", "tslib": "^2.3.1", + "utility-types": "^3.10.0", "webpack": "^5.40.0" }, "peerDependencies": { diff --git a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts index 56c08645fb..2c1e8767e0 100644 --- a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts +++ b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts @@ -6,7 +6,7 @@ */ declare module '@docusaurus/plugin-content-blog' { - export type Options = import('./types').PluginOptions; + export type Options = Partial; } declare module '@theme/BlogSidebar' { diff --git a/packages/docusaurus-plugin-content-blog/src/types.ts b/packages/docusaurus-plugin-content-blog/src/types.ts index 1f362b52b7..755858c781 100644 --- a/packages/docusaurus-plugin-content-blog/src/types.ts +++ b/packages/docusaurus-plugin-content-blog/src/types.ts @@ -11,6 +11,7 @@ import type { BrokenMarkdownLink, ContentPaths, } from '@docusaurus/utils/lib/markdownLinks'; +import {Overwrite} from 'utility-types'; export type BlogContentPaths = ContentPaths; @@ -24,6 +25,20 @@ export interface BlogContent { export type FeedType = 'rss' | 'atom'; +export type FeedOptions = { + type?: FeedType[] | null; + title?: string; + description?: string; + copyright: string; + language?: string; +}; + +// Feed options, as provided by user config +export type UserFeedOptions = Overwrite< + Partial, + {type?: FeedOptions['type'] | 'all'} // Handle the type: "all" shortcut +>; + export type EditUrlFunction = (editUrlParams: { blogDirPath: string; blogPath: string; @@ -63,6 +78,12 @@ export type PluginOptions = RemarkAndRehypePluginOptions & { authorsMapPath: string; }; +// Options, as provided in the user config (before normalization) +export type UserPluginOptions = Overwrite< + Partial, + {feedOptions?: UserFeedOptions} +>; + export interface BlogTags { [key: string]: BlogTag; } diff --git a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts index a54bcf9bdd..5385721a34 100644 --- a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts +++ b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts @@ -6,7 +6,7 @@ */ declare module '@docusaurus/plugin-content-docs' { - export type Options = import('./types').PluginOptions; + export type Options = Partial; } // TODO public api surface types should rather be exposed as "@docusaurus/plugin-content-docs" diff --git a/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts b/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts index 1e406dec9b..ff347f9fc0 100644 --- a/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts +++ b/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts @@ -6,7 +6,7 @@ */ declare module '@docusaurus/plugin-content-pages' { - export type Options = import('./types').PluginOptions; + export type Options = Partial; } declare module '@theme/MDXPage' { diff --git a/packages/docusaurus-plugin-sitemap/src/plugin-sitemap.d.ts b/packages/docusaurus-plugin-sitemap/src/plugin-sitemap.d.ts index c31b8d34c5..c7c9cc93b5 100644 --- a/packages/docusaurus-plugin-sitemap/src/plugin-sitemap.d.ts +++ b/packages/docusaurus-plugin-sitemap/src/plugin-sitemap.d.ts @@ -5,4 +5,4 @@ * LICENSE file in the root directory of this source tree. */ -export type Options = import('./types').PluginOptions; +export type Options = Partial; diff --git a/packages/docusaurus-preset-classic/src/preset-classic.d.ts b/packages/docusaurus-preset-classic/src/preset-classic.d.ts index 96ab487358..a007881dd5 100644 --- a/packages/docusaurus-preset-classic/src/preset-classic.d.ts +++ b/packages/docusaurus-preset-classic/src/preset-classic.d.ts @@ -14,10 +14,11 @@ export type Options = { theme?: import('@docusaurus/theme-classic').Options; }; -export type ThemeConfig = import('@docusaurus/theme-common').ThemeConfig & +export type ThemeConfig = import('@docusaurus/types').ThemeConfig & + import('@docusaurus/theme-common').UserThemeConfig & + // Those plugins themeConfigs should rather be moved to preset/plugin options + // Plugin data can be made available to browser thank to the globalData api import('@docusaurus/plugin-google-analytics').ThemeConfig & import('@docusaurus/plugin-google-gtag').ThemeConfig & { - // Those themeConfigs should rather be moved to preset/plugin options - // Plugin data can be made available to browser thank to the globalData api algolia?: unknown; // TODO type plugin }; diff --git a/packages/docusaurus-theme-classic/src/index.ts b/packages/docusaurus-theme-classic/src/index.ts index bf4f66f7d2..33fd4fd8d3 100644 --- a/packages/docusaurus-theme-classic/src/index.ts +++ b/packages/docusaurus-theme-classic/src/index.ts @@ -92,7 +92,7 @@ function getInfimaCSSFile(direction: string) { } export type PluginOptions = { - customCss?: string; + customCss?: string | string[]; }; export default function docusaurusThemeClassic( diff --git a/packages/docusaurus-theme-classic/src/types.d.ts b/packages/docusaurus-theme-classic/src/types.d.ts index 714def2a66..a22b412822 100644 --- a/packages/docusaurus-theme-classic/src/types.d.ts +++ b/packages/docusaurus-theme-classic/src/types.d.ts @@ -11,7 +11,7 @@ /// declare module '@docusaurus/theme-classic' { - export type Options = import('./index').PluginOptions; + export type Options = Partial; } declare module '@theme/AnnouncementBar' { diff --git a/packages/docusaurus-theme-common/package.json b/packages/docusaurus-theme-common/package.json index 62802cc78e..d71faffd30 100644 --- a/packages/docusaurus-theme-common/package.json +++ b/packages/docusaurus-theme-common/package.json @@ -25,7 +25,8 @@ "@docusaurus/types": "2.0.0-beta.6", "clsx": "^1.1.1", "fs-extra": "^10.0.0", - "tslib": "^2.3.1" + "tslib": "^2.3.1", + "utility-types": "^3.10.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "2.0.0-beta.6", diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts index fe50023fc8..7ce4453dd5 100644 --- a/packages/docusaurus-theme-common/src/index.ts +++ b/packages/docusaurus-theme-common/src/index.ts @@ -9,6 +9,7 @@ export {useThemeConfig} from './utils/useThemeConfig'; export type { ThemeConfig, + UserThemeConfig, Navbar, NavbarItem, NavbarLogo, diff --git a/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts b/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts index 922a2db46d..5159f23766 100644 --- a/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts +++ b/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts @@ -7,6 +7,7 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import {PrismTheme} from 'prism-react-renderer'; import {CSSProperties} from 'react'; +import {DeepPartial} from 'utility-types'; export type DocsVersionPersistence = 'localStorage' | 'none'; @@ -16,7 +17,7 @@ export type NavbarItem = { items?: NavbarItem[]; label?: string; position?: 'left' | 'right'; -}; +} & Record; export type NavbarLogo = { src: string; @@ -90,6 +91,7 @@ export type TableOfContents = { maxHeadingLevel: number; }; +// Theme config after validation/normalization export type ThemeConfig = { docs: { versionPersistence: DocsVersionPersistence; @@ -112,6 +114,9 @@ export type ThemeConfig = { tableOfContents: TableOfContents; }; +// User-provided theme config, unnormalized +export type UserThemeConfig = DeepPartial; + export function useThemeConfig(): ThemeConfig { return useDocusaurusContext().siteConfig.themeConfig as ThemeConfig; } diff --git a/packages/docusaurus-types/package.json b/packages/docusaurus-types/package.json index 47c901e39d..7126d2a7a8 100644 --- a/packages/docusaurus-types/package.json +++ b/packages/docusaurus-types/package.json @@ -19,6 +19,7 @@ "commander": "^5.1.0", "joi": "^17.4.2", "querystring": "0.2.0", + "utility-types": "^3.10.0", "webpack": "^5.40.0", "webpack-merge": "^5.8.0" } diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index 8aa011b877..2699c0db18 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -9,6 +9,7 @@ import type {RuleSetRule, Configuration} from 'webpack'; import type {Command} from 'commander'; import type {ParsedUrlQueryInput} from 'querystring'; import type Joi from 'joi'; +import type {Overwrite, DeepPartial} from 'utility-types'; // Convert webpack-merge webpack-merge enum to union type // For type retro-compatible webpack-merge upgrade: we used string literals before) @@ -21,6 +22,7 @@ export type ThemeConfig = { [key: string]: unknown; }; +// Docusaurus config, after validation/normalization export interface DocusaurusConfig { baseUrl: string; baseUrlIssueBanner: boolean; @@ -68,6 +70,19 @@ export interface DocusaurusConfig { }; } +// Docusaurus config, as provided by the user (partial/unnormalized) +// This type is used to provide type-safety / IDE auto-complete on the config file +// See https://docusaurus.io/docs/typescript-support +export type Config = Overwrite< + Partial, + { + title: Required; + url: Required; + baseUrl: Required; + i18n?: DeepPartial; + } +>; + /** * - `type: 'package'`, plugin is in a different package. * - `type: 'project'`, plugin is in the same docusaurus project. diff --git a/website/_dogfooding/dogfooding.config.js b/website/_dogfooding/dogfooding.config.js index d53be223b5..f3ce28b477 100644 --- a/website/_dogfooding/dogfooding.config.js +++ b/website/_dogfooding/dogfooding.config.js @@ -1,7 +1,7 @@ const fs = require('fs'); -const path = require('path'); -exports.dogfoodingPluginInstances = [ +/** @type {import('@docusaurus/types').PluginConfig[]} */ +const dogfoodingPluginInstances = [ [ '@docusaurus/plugin-content-docs', /** @type {import('@docusaurus/plugin-content-docs').Options} */ @@ -43,3 +43,5 @@ exports.dogfoodingPluginInstances = [ }), ], ]; + +exports.dogfoodingPluginInstances = dogfoodingPluginInstances; diff --git a/website/docs/typescript-support.md b/website/docs/typescript-support.md index 1a409e1a6b..407ac48aa1 100644 --- a/website/docs/typescript-support.md +++ b/website/docs/typescript-support.md @@ -42,8 +42,11 @@ It is **not possible** to use a TypeScript config file in Docusaurus, unless you We recommend using [JSDoc type annotations](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html): - ```js title="docusaurus.config.js" +// highlight-start +// @ts-check +// highlight-end + // highlight-start /** @type {import('@docusaurus/types').Plugin} */ // highlight-end @@ -54,9 +57,9 @@ function MyPlugin(context, options) { } // highlight-start -/** @type {import('@docusaurus/types').DocusaurusConfig} */ +/** @type {import('@docusaurus/types').Config} */ // highlight-end -(module.exports = { +const config = { title: 'Docusaurus', tagline: 'Build optimized websites quickly, focus on your content', organizationName: 'facebook', @@ -98,9 +101,10 @@ function MyPlugin(context, options) { }, }, }), -}); +}; + +module.exports = config; ``` - :::tip @@ -110,6 +114,18 @@ The best IDEs (VSCode, WebStorm, Intellij...) will provide a nice auto-completio ::: +:::info + +By default, the Docusaurus TypeScript config does not type-check JavaScript files. + +The `// @ts-check` comment ensures the config file is properly type-checked when running: + +```bash npm2yarn +npm run tsc +``` + +::: + ## Swizzling TypeScript theme components {#swizzling-typescript-theme-components} For themes that supports TypeScript theme components, you can add the `--typescript` flag to the end of swizzling command to get TypeScript source code. For example, the following command will generate `index.tsx` and `styles.module.css` into `src/theme/Footer`. diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 02cd8f8e81..3edb71d780 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -4,6 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +// @ts-check const path = require('path'); const versions = require('./versions.json'); @@ -49,8 +50,8 @@ const isVersioningDisabled = !!process.env.DISABLE_VERSIONING || isI18nStaging; const TwitterSvg = ''; -/** @type {import('@docusaurus/types').DocusaurusConfig} */ -(module.exports = { +/** @type {import('@docusaurus/types').Config} */ +const config = { title: 'Docusaurus', tagline: 'Build optimized websites quickly, focus on your content', organizationName: 'facebook', @@ -486,4 +487,6 @@ const TwitterSvg = copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc. Built with Docusaurus.`, }, }), -}); +}; + +module.exports = config; diff --git a/website/package.json b/website/package.json index 2df6957e42..a5577aeb55 100644 --- a/website/package.json +++ b/website/package.json @@ -24,7 +24,8 @@ "netlify:crowdin:downloadTranslationsFailSafe": "yarn netlify:crowdin:wait && (yarn --cwd .. crowdin:download:website || echo 'Crowdin translation download failure (only internal PRs have access to the Crowdin env token)')", "netlify:crowdin:uploadSources": "yarn --cwd .. crowdin:upload:website", "netlify:test": "yarn netlify:build:deployPreview && yarn netlify dev --debug", - "typecheck": "tsc" + "typecheck": "tsc", + "watch": "tsc --watch" }, "dependencies": { "@crowdin/cli": "^3.5.2", diff --git a/website/src/featureRequests/FeatureRequestsPlugin.js b/website/src/featureRequests/FeatureRequestsPlugin.js index e3fa404b34..8afaa6a941 100644 --- a/website/src/featureRequests/FeatureRequestsPlugin.js +++ b/website/src/featureRequests/FeatureRequestsPlugin.js @@ -9,7 +9,7 @@ function FeatureRequestsPlugin() { return { name: 'feature-requests-plugin', - contentLoaded({actions}) { + async contentLoaded({actions}) { actions.addRoute({ path: '/feature-requests', exact: false,