diff --git a/packages/create-docusaurus/src/index.ts b/packages/create-docusaurus/src/index.ts index f41121d686f7..f216e3651c47 100755 --- a/packages/create-docusaurus/src/index.ts +++ b/packages/create-docusaurus/src/index.ts @@ -157,17 +157,6 @@ async function copyTemplate( ): Promise { await fs.copy(path.join(templatesDir, 'shared'), dest); - // TypeScript variants will copy duplicate resources like CSS & config from - // base template - if (typescript) { - await fs.copy(template.path, dest, { - filter: async (filePath) => - (await fs.stat(filePath)).isDirectory() || - path.extname(filePath) === '.css' || - path.basename(filePath) === 'docusaurus.config.js', - }); - } - await fs.copy(typescript ? template.tsVariantPath! : template.path, dest, { // Symlinks don't exist in published npm packages anymore, so this is only // to prevent errors during local testing diff --git a/packages/create-docusaurus/templates/classic-typescript/docusaurus.config.ts b/packages/create-docusaurus/templates/classic-typescript/docusaurus.config.ts new file mode 100644 index 000000000000..4058eac59e23 --- /dev/null +++ b/packages/create-docusaurus/templates/classic-typescript/docusaurus.config.ts @@ -0,0 +1,133 @@ +import {themes as prismThemes} from 'prism-react-renderer'; +import type {Config} from '@docusaurus/types'; +import type * as Preset from '@docusaurus/preset-classic'; + +const config: Config = { + title: 'My Site', + tagline: 'Dinosaurs are cool', + favicon: 'img/favicon.ico', + + // Set the production url of your site here + url: 'https://your-docusaurus-site.example.com', + // Set the // pathname under which your site is served + // For GitHub pages deployment, it is often '//' + baseUrl: '/', + + // GitHub pages deployment config. + // If you aren't using GitHub pages, you don't need these. + organizationName: 'facebook', // Usually your GitHub org/user name. + projectName: 'docusaurus', // Usually your repo name. + + onBrokenLinks: 'throw', + onBrokenMarkdownLinks: 'warn', + + // Even if you don't use internationalization, you can use this field to set + // useful metadata like html lang. For example, if your site is Chinese, you + // may want to replace "en" with "zh-Hans". + i18n: { + defaultLocale: 'en', + locales: ['en'], + }, + + presets: [ + [ + 'classic', + { + docs: { + sidebarPath: './sidebars.ts', + // Please change this to your repo. + // Remove this to remove the "edit this page" links. + editUrl: + 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', + }, + blog: { + showReadingTime: true, + // Please change this to your repo. + // Remove this to remove the "edit this page" links. + editUrl: + 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', + }, + theme: { + customCss: './src/css/custom.css', + }, + } satisfies Preset.Options, + ], + ], + + themeConfig: { + // Replace with your project's social card + image: 'img/docusaurus-social-card.jpg', + navbar: { + title: 'My Site', + logo: { + alt: 'My Site Logo', + src: 'img/logo.svg', + }, + items: [ + { + type: 'docSidebar', + sidebarId: 'tutorialSidebar', + position: 'left', + label: 'Tutorial', + }, + {to: '/blog', label: 'Blog', position: 'left'}, + { + href: 'https://github.com/facebook/docusaurus', + label: 'GitHub', + position: 'right', + }, + ], + }, + footer: { + style: 'dark', + links: [ + { + title: 'Docs', + items: [ + { + label: 'Tutorial', + to: '/docs/intro', + }, + ], + }, + { + title: 'Community', + items: [ + { + label: 'Stack Overflow', + href: 'https://stackoverflow.com/questions/tagged/docusaurus', + }, + { + label: 'Discord', + href: 'https://discordapp.com/invite/docusaurus', + }, + { + label: 'Twitter', + href: 'https://twitter.com/docusaurus', + }, + ], + }, + { + title: 'More', + items: [ + { + label: 'Blog', + to: '/blog', + }, + { + label: 'GitHub', + href: 'https://github.com/facebook/docusaurus', + }, + ], + }, + ], + copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`, + }, + prism: { + theme: prismThemes.github, + darkTheme: prismThemes.dracula, + }, + } satisfies Preset.ThemeConfig, +}; + +export default config; diff --git a/packages/create-docusaurus/templates/classic-typescript/package.json b/packages/create-docusaurus/templates/classic-typescript/package.json index e3191cf647ea..db11de07e28a 100644 --- a/packages/create-docusaurus/templates/classic-typescript/package.json +++ b/packages/create-docusaurus/templates/classic-typescript/package.json @@ -26,6 +26,7 @@ "devDependencies": { "@docusaurus/module-type-aliases": "3.0.0-beta.0", "@docusaurus/tsconfig": "3.0.0-beta.0", + "@docusaurus/types": "3.0.0-beta.0", "typescript": "~5.2.2" }, "browserslist": { diff --git a/packages/create-docusaurus/templates/classic-typescript/sidebars.ts b/packages/create-docusaurus/templates/classic-typescript/sidebars.ts new file mode 100644 index 000000000000..acc7685acd59 --- /dev/null +++ b/packages/create-docusaurus/templates/classic-typescript/sidebars.ts @@ -0,0 +1,31 @@ +import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; + +/** + * Creating a sidebar enables you to: + - create an ordered group of docs + - render a sidebar for each doc of that group + - provide next/previous navigation + + The sidebars can be generated from the filesystem, or explicitly defined here. + + Create as many sidebars as you want. + */ +const sidebars: SidebarsConfig = { + // By default, Docusaurus generates a sidebar from the docs folder structure + tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], + + // But you can create a sidebar manually + /* + tutorialSidebar: [ + 'intro', + 'hello', + { + type: 'category', + label: 'Tutorial', + items: ['tutorial-basics/create-a-document'], + }, + ], + */ +}; + +export default sidebars; diff --git a/packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures/styles.module.css b/packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures/styles.module.css deleted file mode 120000 index 26d4838f9dc9..000000000000 --- a/packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures/styles.module.css +++ /dev/null @@ -1 +0,0 @@ -../../../../classic/src/components/HomepageFeatures/styles.module.css \ No newline at end of file diff --git a/packages/create-docusaurus/templates/classic-typescript/src/pages/index.module.css b/packages/create-docusaurus/templates/classic-typescript/src/pages/index.module.css deleted file mode 120000 index 38af7a6ff1b4..000000000000 --- a/packages/create-docusaurus/templates/classic-typescript/src/pages/index.module.css +++ /dev/null @@ -1 +0,0 @@ -../../../classic/src/pages/index.module.css \ No newline at end of file diff --git a/packages/create-docusaurus/templates/classic/docusaurus.config.js b/packages/create-docusaurus/templates/classic/docusaurus.config.js index 12d5019f3505..7b8d5b1c7f11 100644 --- a/packages/create-docusaurus/templates/classic/docusaurus.config.js +++ b/packages/create-docusaurus/templates/classic/docusaurus.config.js @@ -4,10 +4,7 @@ // There are various equivalent ways to declare your Docusaurus config. // See: https://docusaurus.io/docs/api/docusaurus-config -const {themes} = require('prism-react-renderer'); - -const lightCodeTheme = themes.github; -const darkCodeTheme = themes.dracula; +import {themes as prismThemes} from 'prism-react-renderer'; /** @type {import('@docusaurus/types').Config} */ const config = { @@ -43,7 +40,7 @@ const config = { /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { - sidebarPath: require.resolve('./sidebars.js'), + sidebarPath: './sidebars.js', // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: @@ -57,7 +54,7 @@ const config = { 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', }, theme: { - customCss: require.resolve('./src/css/custom.css'), + customCss: './src/css/custom.css', }, }), ], @@ -135,10 +132,10 @@ const config = { copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`, }, prism: { - theme: lightCodeTheme, - darkTheme: darkCodeTheme, + theme: prismThemes.github, + darkTheme: prismThemes.dracula, }, }), }; -module.exports = config; +export default config; diff --git a/packages/create-docusaurus/templates/classic/package.json b/packages/create-docusaurus/templates/classic/package.json index 67bc1513b7c5..2d85197e2c96 100644 --- a/packages/create-docusaurus/templates/classic/package.json +++ b/packages/create-docusaurus/templates/classic/package.json @@ -23,7 +23,8 @@ "react-dom": "^18.0.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.0.0-beta.0" + "@docusaurus/module-type-aliases": "3.0.0-beta.0", + "@docusaurus/types": "3.0.0-beta.0" }, "browserslist": { "production": [ diff --git a/packages/create-docusaurus/templates/shared/sidebars.js b/packages/create-docusaurus/templates/classic/sidebars.js similarity index 96% rename from packages/create-docusaurus/templates/shared/sidebars.js rename to packages/create-docusaurus/templates/classic/sidebars.js index 9ab54c2459c4..332758032214 100644 --- a/packages/create-docusaurus/templates/shared/sidebars.js +++ b/packages/create-docusaurus/templates/classic/sidebars.js @@ -30,4 +30,4 @@ const sidebars = { */ }; -module.exports = sidebars; +export default sidebars; diff --git a/packages/create-docusaurus/templates/shared/docs/tutorial-basics/create-a-document.md b/packages/create-docusaurus/templates/shared/docs/tutorial-basics/create-a-document.md index ffddfa8eb8ac..c22fe2944687 100644 --- a/packages/create-docusaurus/templates/shared/docs/tutorial-basics/create-a-document.md +++ b/packages/create-docusaurus/templates/shared/docs/tutorial-basics/create-a-document.md @@ -42,7 +42,7 @@ This is my **first Docusaurus document**! It is also possible to create your sidebar explicitly in `sidebars.js`: ```js title="sidebars.js" -module.exports = { +export default { tutorialSidebar: [ 'intro', // highlight-next-line diff --git a/packages/create-docusaurus/templates/shared/docs/tutorial-extras/manage-docs-versions.md b/packages/create-docusaurus/templates/shared/docs/tutorial-extras/manage-docs-versions.md index e12c3f3444fa..ccda0b9076bf 100644 --- a/packages/create-docusaurus/templates/shared/docs/tutorial-extras/manage-docs-versions.md +++ b/packages/create-docusaurus/templates/shared/docs/tutorial-extras/manage-docs-versions.md @@ -28,7 +28,7 @@ To navigate seamlessly across versions, add a version dropdown. Modify the `docusaurus.config.js` file: ```js title="docusaurus.config.js" -module.exports = { +export default { themeConfig: { navbar: { items: [ diff --git a/packages/create-docusaurus/templates/shared/docs/tutorial-extras/translate-your-site.md b/packages/create-docusaurus/templates/shared/docs/tutorial-extras/translate-your-site.md index 4294a27a5cf6..b5a644abdf96 100644 --- a/packages/create-docusaurus/templates/shared/docs/tutorial-extras/translate-your-site.md +++ b/packages/create-docusaurus/templates/shared/docs/tutorial-extras/translate-your-site.md @@ -11,7 +11,7 @@ Let's translate `docs/intro.md` to French. Modify `docusaurus.config.js` to add support for the `fr` locale: ```js title="docusaurus.config.js" -module.exports = { +export default { i18n: { defaultLocale: 'en', locales: ['en', 'fr'], @@ -54,7 +54,7 @@ To navigate seamlessly across languages, add a locale dropdown. Modify the `docusaurus.config.js` file: ```js title="docusaurus.config.js" -module.exports = { +export default { themeConfig: { navbar: { items: [ diff --git a/packages/create-docusaurus/templates/classic/src/components/HomepageFeatures/styles.module.css b/packages/create-docusaurus/templates/shared/src/components/HomepageFeatures/styles.module.css similarity index 100% rename from packages/create-docusaurus/templates/classic/src/components/HomepageFeatures/styles.module.css rename to packages/create-docusaurus/templates/shared/src/components/HomepageFeatures/styles.module.css diff --git a/packages/create-docusaurus/templates/classic/src/css/custom.css b/packages/create-docusaurus/templates/shared/src/css/custom.css similarity index 100% rename from packages/create-docusaurus/templates/classic/src/css/custom.css rename to packages/create-docusaurus/templates/shared/src/css/custom.css diff --git a/packages/create-docusaurus/templates/classic/src/pages/index.module.css b/packages/create-docusaurus/templates/shared/src/pages/index.module.css similarity index 100% rename from packages/create-docusaurus/templates/classic/src/pages/index.module.css rename to packages/create-docusaurus/templates/shared/src/pages/index.module.css diff --git a/packages/docusaurus-plugin-content-docs/package.json b/packages/docusaurus-plugin-content-docs/package.json index aa1bf658d931..623e90419939 100644 --- a/packages/docusaurus-plugin-content-docs/package.json +++ b/packages/docusaurus-plugin-content-docs/package.json @@ -45,7 +45,6 @@ "@types/react-router-config": "^5.0.7", "combine-promises": "^1.1.0", "fs-extra": "^11.1.1", - "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "lodash": "^4.17.21", "tslib": "^2.6.0", diff --git a/packages/docusaurus-plugin-content-docs/src/cli.ts b/packages/docusaurus-plugin-content-docs/src/cli.ts index 23cd26e7e461..5cc52f2a5f9b 100644 --- a/packages/docusaurus-plugin-content-docs/src/cli.ts +++ b/packages/docusaurus-plugin-content-docs/src/cli.ts @@ -17,7 +17,7 @@ import { readVersionsFile, } from './versions/files'; import {validateVersionName} from './versions/validation'; -import {loadSidebarsFileUnsafe} from './sidebars'; +import {loadSidebarsFile} from './sidebars'; import {CURRENT_VERSION_NAME} from './constants'; import type {PluginOptions} from '@docusaurus/plugin-content-docs'; import type {LoadContext} from '@docusaurus/types'; @@ -37,7 +37,7 @@ async function createVersionedSidebarFile({ // Note: we don't need the sidebars file to be normalized: it's ok to let // plugin option changes to impact older, versioned sidebars // We don't validate here, assuming the user has already built the version - const sidebars = await loadSidebarsFileUnsafe(sidebarPath); + const sidebars = await loadSidebarsFile(sidebarPath); // Do not create a useless versioned sidebars file if sidebars file is empty // or sidebars are disabled/false) diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts index 36bd576f7f8a..1566ec82175a 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts @@ -9,10 +9,9 @@ import fs from 'fs-extra'; import path from 'path'; import _ from 'lodash'; import logger from '@docusaurus/logger'; -import {Globby} from '@docusaurus/utils'; +import {loadFreshModule, Globby} from '@docusaurus/utils'; import Yaml from 'js-yaml'; import combinePromises from 'combine-promises'; -import importFresh from 'import-fresh'; import {validateSidebars, validateCategoryMetadataFile} from './validation'; import {normalizeSidebars} from './normalization'; import {processSidebars} from './processor'; @@ -68,7 +67,7 @@ async function readCategoriesMetadata(contentPath: string) { ); } -export async function loadSidebarsFileUnsafe( +async function loadSidebarsFileUnsafe( sidebarFilePath: string | false | undefined, ): Promise { // false => no sidebars @@ -89,7 +88,19 @@ export async function loadSidebarsFileUnsafe( } // We don't want sidebars to be cached because of hot reloading. - return importFresh(sidebarFilePath); + const module = await loadFreshModule(sidebarFilePath); + + // TODO unsafe, need to refactor and improve validation + return module as SidebarsConfig; +} + +export async function loadSidebarsFile( + sidebarFilePath: string | false | undefined, +): Promise { + const sidebars = await loadSidebarsFileUnsafe(sidebarFilePath); + + // TODO unsafe, need to refactor and improve validation + return sidebars as SidebarsConfig; } export async function loadSidebars( diff --git a/packages/docusaurus-plugin-ideal-image/src/plugin-ideal-image.d.ts b/packages/docusaurus-plugin-ideal-image/src/plugin-ideal-image.d.ts index 0c151f88cbf9..561cbfb18ae7 100644 --- a/packages/docusaurus-plugin-ideal-image/src/plugin-ideal-image.d.ts +++ b/packages/docusaurus-plugin-ideal-image/src/plugin-ideal-image.d.ts @@ -47,6 +47,8 @@ declare module '@docusaurus/plugin-ideal-image' { */ disableInDev?: boolean; }; + + export type Options = Partial; } declare module '@theme/IdealImage' { diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index afb737e96b48..53e83ce96345 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -52,6 +52,7 @@ export { PluginVersionInformation, Preset, PresetConfig, + PresetConfigDefined, PresetModule, OptionValidationContext, ThemeConfigValidationContext, diff --git a/packages/docusaurus-types/src/plugin.d.ts b/packages/docusaurus-types/src/plugin.d.ts index 482699623d5a..646562df8d9d 100644 --- a/packages/docusaurus-types/src/plugin.d.ts +++ b/packages/docusaurus-types/src/plugin.d.ts @@ -26,11 +26,9 @@ export type PluginConfig = | false | null; -export type PresetConfig = - | string - | [string, {[key: string]: unknown}] - | false - | null; +export type PresetConfigDefined = string | [string, {[key: string]: unknown}]; + +export type PresetConfig = PresetConfigDefined | false | null; /** * - `type: 'package'`, plugin is in a different package. diff --git a/packages/docusaurus-utils/package.json b/packages/docusaurus-utils/package.json index b9ad09ba9873..f5f757e5673c 100644 --- a/packages/docusaurus-utils/package.json +++ b/packages/docusaurus-utils/package.json @@ -26,6 +26,7 @@ "github-slugger": "^1.5.0", "globby": "^11.1.0", "gray-matter": "^4.0.3", + "jiti": "^1.20.0", "js-yaml": "^4.1.0", "lodash": "^4.17.21", "micromatch": "^4.0.5", @@ -44,7 +45,8 @@ "@types/github-slugger": "^1.3.0", "@types/micromatch": "^4.0.2", "@types/react-dom": "^18.2.7", - "dedent": "^0.7.0" + "dedent": "^0.7.0", + "tmp-promise": "^3.0.3" }, "peerDependencies": { "@docusaurus/types": "*" diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.cjs b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.cjs new file mode 100644 index 000000000000..394d1bb4ce56 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.cjs @@ -0,0 +1,7 @@ +exports.someNamedExport = 42; + +module.exports = { + firstName: 'Sebastien', + lastName: 'Lorber', + birthYear: 1986, +}; diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.cjs.js b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.cjs.js new file mode 100644 index 000000000000..394d1bb4ce56 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.cjs.js @@ -0,0 +1,7 @@ +exports.someNamedExport = 42; + +module.exports = { + firstName: 'Sebastien', + lastName: 'Lorber', + birthYear: 1986, +}; diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.cjs.ts b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.cjs.ts new file mode 100644 index 000000000000..7b4798aa9093 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.cjs.ts @@ -0,0 +1,11 @@ +exports.someNamedExport = 42 as number; + +module.exports = { + firstName: 'Sebastien', + lastName: 'Lorber', + birthYear: 1986, +} satisfies { + firstName: string; + lastName: string; + birthYear: number; +}; diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.esm.js b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.esm.js new file mode 100644 index 000000000000..82912ee03082 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.esm.js @@ -0,0 +1,7 @@ +export const someNamedExport = 42; + +export default { + firstName: 'Sebastien', + lastName: 'Lorber', + birthYear: 1986, +}; diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.esm.ts b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.esm.ts new file mode 100644 index 000000000000..af7241e542b2 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.esm.ts @@ -0,0 +1,11 @@ +export const someNamedExport: number = 42; + +export default { + firstName: 'Sebastien', + lastName: 'Lorber', + birthYear: 1986, +} satisfies { + firstName: string; + lastName: string; + birthYear: number; +}; diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.mjs b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.mjs new file mode 100644 index 000000000000..82912ee03082 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.mjs @@ -0,0 +1,7 @@ +export const someNamedExport = 42; + +export default { + firstName: 'Sebastien', + lastName: 'Lorber', + birthYear: 1986, +}; diff --git a/packages/docusaurus-utils/src/__tests__/moduleUtils.test.ts b/packages/docusaurus-utils/src/__tests__/moduleUtils.test.ts new file mode 100644 index 000000000000..653667292a9a --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/moduleUtils.test.ts @@ -0,0 +1,289 @@ +/** + * 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 fs from 'fs-extra'; +import path from 'path'; +import tmp from 'tmp-promise'; +import dedent from 'dedent'; +import {loadFreshModule} from '../moduleUtils'; + +async function createTmpDir() { + return ( + await tmp.dir({ + prefix: 'jest-tmp-moduleUtils-tests', + }) + ).path; +} + +async function moduleGraphHelpers() { + const dir = await createTmpDir(); + + async function fileHelper(name: string, initialContent?: string) { + const filePath = path.resolve(dir, name); + if (initialContent) { + await fs.outputFile(filePath, initialContent); + } + return { + filePath, + write: (content: string) => fs.outputFile(filePath, content), + load: () => loadFreshModule(filePath), + }; + } + + return {fileHelper}; +} + +async function loadFixtureModule(fixtureName: string) { + return loadFreshModule( + path.resolve(__dirname, '__fixtures__/moduleUtils', fixtureName), + ); +} + +describe('loadFreshModule', () => { + describe('can load CJS user module', () => { + async function testUserFixture(fixtureName: string) { + const userFixturePath = `user/${fixtureName}`; + const userModule = await loadFixtureModule(userFixturePath); + expect(userModule).toEqual({ + birthYear: 1986, + firstName: 'Sebastien', + lastName: 'Lorber', + }); + } + + it('for .cjs.js', async () => { + await testUserFixture('user.cjs.js'); + }); + + it('for .cjs.ts', async () => { + await testUserFixture('user.cjs.ts'); + }); + + it('for .cjs', async () => { + await testUserFixture('user.cjs'); + }); + }); + + describe('can load ESM user module', () => { + async function testUserFixture(fixtureName: string) { + const userFixturePath = `user/${fixtureName}`; + const userModule = await loadFixtureModule(userFixturePath); + expect(userModule).toEqual({ + birthYear: 1986, + firstName: 'Sebastien', + lastName: 'Lorber', + someNamedExport: 42, + }); + } + + it('for .esm.js', async () => { + await testUserFixture('user.esm.js'); + }); + + it('for .esm.ts', async () => { + await testUserFixture('user.esm.ts'); + }); + + it('for .mjs', async () => { + await testUserFixture('user.mjs'); + }); + }); + + describe('module graph', () => { + it('can load and reload fresh module graph', async () => { + const {fileHelper} = await moduleGraphHelpers(); + + const dependency1 = await fileHelper( + 'dependency1.js', + /* language=js */ + dedent` + export const dep1Export = "dep1 val1"; + + export default {dep1Val: "dep1 val2"} + `, + ); + + const dependency2 = await fileHelper( + 'dependency2.ts', + /* language=ts */ + dedent` + export default {dep2Val: "dep2 val"} satisfies {dep2Val: string} + `, + ); + + const entryFile = await fileHelper( + 'entry.js', + /* language=js */ + dedent` + import dependency1 from "./dependency1"; + import dependency2 from "./dependency2"; + + export default { + someEntryValue: "entryVal", + dependency1, + dependency2 + }; + `, + ); + + // Should be able to read the initial module graph + await expect(entryFile.load()).resolves.toEqual({ + someEntryValue: 'entryVal', + dependency1: { + dep1Export: 'dep1 val1', + dep1Val: 'dep1 val2', + }, + dependency2: { + dep2Val: 'dep2 val', + }, + }); + await expect(dependency1.load()).resolves.toEqual({ + dep1Export: 'dep1 val1', + dep1Val: 'dep1 val2', + }); + await expect(dependency2.load()).resolves.toEqual({ + dep2Val: 'dep2 val', + }); + + // Should be able to read the module graph again after updates + await dependency1.write( + /* language=js */ + dedent` + export const dep1Export = "dep1 val1 updated"; + + export default {dep1Val: "dep1 val2 updated"} + `, + ); + await expect(entryFile.load()).resolves.toEqual({ + someEntryValue: 'entryVal', + dependency1: { + dep1Export: 'dep1 val1 updated', + dep1Val: 'dep1 val2 updated', + }, + dependency2: { + dep2Val: 'dep2 val', + }, + }); + await expect(dependency1.load()).resolves.toEqual({ + dep1Export: 'dep1 val1 updated', + dep1Val: 'dep1 val2 updated', + }); + await expect(dependency2.load()).resolves.toEqual({ + dep2Val: 'dep2 val', + }); + + // Should be able to read the module graph again after updates + await dependency2.write( + /* language=ts */ + dedent` + export default {dep2Val: "dep2 val updated"} satisfies {dep2Val: string} + `, + ); + await expect(entryFile.load()).resolves.toEqual({ + someEntryValue: 'entryVal', + dependency1: { + dep1Export: 'dep1 val1 updated', + dep1Val: 'dep1 val2 updated', + }, + dependency2: { + dep2Val: 'dep2 val updated', + }, + }); + await expect(dependency1.load()).resolves.toEqual({ + dep1Export: 'dep1 val1 updated', + dep1Val: 'dep1 val2 updated', + }); + await expect(dependency2.load()).resolves.toEqual({ + dep2Val: 'dep2 val updated', + }); + + // Should be able to read the module graph again after updates + await entryFile.write( + /* language=js */ + dedent` + import dependency1 from "./dependency1"; + import dependency2 from "./dependency2"; + + export default { + someEntryValue: "entryVal updated", + dependency1, + dependency2, + newAttribute: "is there" + } + `, + ); + await expect(entryFile.load()).resolves.toEqual({ + someEntryValue: 'entryVal updated', + newAttribute: 'is there', + dependency1: { + dep1Export: 'dep1 val1 updated', + dep1Val: 'dep1 val2 updated', + }, + dependency2: { + dep2Val: 'dep2 val updated', + }, + }); + await expect(dependency1.load()).resolves.toEqual({ + dep1Export: 'dep1 val1 updated', + dep1Val: 'dep1 val2 updated', + }); + await expect(dependency2.load()).resolves.toEqual({ + dep2Val: 'dep2 val updated', + }); + }); + }); + + describe('invalid module path param', () => { + it('throws if module path does not exist', async () => { + await expect(() => loadFreshModule('/some/unknown/module/path.js')) + .rejects.toThrowErrorMatchingInlineSnapshot(` + "Docusaurus could not load module at path "/some/unknown/module/path.js" + Cause: Cannot find module '/some/unknown/module/path.js' from 'packages/docusaurus-utils/src/moduleUtils.ts'" + `); + }); + + it('throws if module path is undefined', async () => { + await expect(() => + // @ts-expect-error: undefined is invalid + loadFreshModule(undefined), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + "Docusaurus could not load module at path "undefined" + Cause: Invalid module path of type undefined" + `); + }); + + it('throws if module path is null', async () => { + await expect(() => + // @ts-expect-error: null is invalid + loadFreshModule(null), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + "Docusaurus could not load module at path "null" + Cause: Invalid module path of type null" + `); + }); + + it('throws if module path is number', async () => { + await expect(() => + // @ts-expect-error: number is invalid + loadFreshModule(42), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + "Docusaurus could not load module at path "42" + Cause: Invalid module path of type 42" + `); + }); + + it('throws if module path is object', async () => { + await expect(() => + // @ts-expect-error: object is invalid + loadFreshModule({}), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + "Docusaurus could not load module at path "[object Object]" + Cause: Invalid module path of type [object Object]" + `); + }); + }); +}); diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index fbfe062952d8..d5cabc65301a 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -100,6 +100,7 @@ export { } from './globUtils'; export {getFileLoaderUtils} from './webpackUtils'; export {escapeShellArg} from './shellUtils'; +export {loadFreshModule} from './moduleUtils'; export { getDataFilePath, getDataFileData, diff --git a/packages/docusaurus-utils/src/moduleUtils.ts b/packages/docusaurus-utils/src/moduleUtils.ts new file mode 100644 index 000000000000..e7adaf9880e5 --- /dev/null +++ b/packages/docusaurus-utils/src/moduleUtils.ts @@ -0,0 +1,43 @@ +/** + * 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 jiti from 'jiti'; +import logger from '@docusaurus/logger'; + +/* +jiti is able to load ESM, CJS, JSON, TS modules + */ +export async function loadFreshModule(modulePath: string): Promise { + try { + if (typeof modulePath !== 'string') { + throw new Error( + logger.interpolate`Invalid module path of type name=${modulePath}`, + ); + } + const load = jiti(__filename, { + // Transpilation cache, can be safely enabled + cache: true, + // Bypass Node.js runtime require cache + // Same as "import-fresh" package we used previously + requireCache: false, + // Only take into consideration the default export + // For now we don't need named exports + // This also helps normalize return value for both CJS/ESM/TS modules + interopDefault: true, + // debug: true, + }); + + return load(modulePath); + } catch (error) { + throw new Error( + logger.interpolate`Docusaurus could not load module at path path=${modulePath}\nCause: ${ + (error as Error).message + }`, + {cause: error}, + ); + } +} diff --git a/packages/docusaurus/package.json b/packages/docusaurus/package.json index 76112754805c..61158590d8ff 100644 --- a/packages/docusaurus/package.json +++ b/packages/docusaurus/package.json @@ -76,7 +76,6 @@ "html-minifier-terser": "^7.2.0", "html-tags": "^3.3.1", "html-webpack-plugin": "^5.5.3", - "import-fresh": "^3.3.0", "leven": "^3.1.0", "lodash": "^4.17.21", "mini-css-extract-plugin": "^2.7.6", diff --git a/packages/docusaurus/src/server/__tests__/__fixtures__/config/configCJS.js b/packages/docusaurus/src/server/__tests__/__fixtures__/config/configCJS.js new file mode 100644 index 000000000000..0a57bd7fade6 --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/__fixtures__/config/configCJS.js @@ -0,0 +1,12 @@ +/** + * 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. + */ + +module.exports = { + title: 'title', + url: 'https://example.com', + baseUrl: '/', +} diff --git a/packages/docusaurus/src/server/__tests__/__fixtures__/config/configCJS.ts b/packages/docusaurus/src/server/__tests__/__fixtures__/config/configCJS.ts new file mode 100644 index 000000000000..2a63438819ed --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/__fixtures__/config/configCJS.ts @@ -0,0 +1,14 @@ +/** + * 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 type {Config} from '@docusaurus/types'; + +module.exports = { + title: 'title', + url: 'https://example.com', + baseUrl: '/', +} satisfies Config; diff --git a/packages/docusaurus/src/server/__tests__/__fixtures__/config/configESM.js b/packages/docusaurus/src/server/__tests__/__fixtures__/config/configESM.js new file mode 100644 index 000000000000..307aff266bc2 --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/__fixtures__/config/configESM.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +export default { + title: 'title', + url: 'https://example.com', + baseUrl: '/', +} diff --git a/packages/docusaurus/src/server/__tests__/__fixtures__/config/configESM.ts b/packages/docusaurus/src/server/__tests__/__fixtures__/config/configESM.ts new file mode 100644 index 000000000000..8530617239be --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/__fixtures__/config/configESM.ts @@ -0,0 +1,14 @@ +/** + * 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 type {Config} from '@docusaurus/types'; + +export default { + title: 'title', + url: 'https://example.com', + baseUrl: '/', +} satisfies Config; diff --git a/packages/docusaurus/src/server/__tests__/__fixtures__/config/sites/ts-and-js-site/docusaurus.config.js b/packages/docusaurus/src/server/__tests__/__fixtures__/config/sites/ts-and-js-site/docusaurus.config.js new file mode 100644 index 000000000000..83ccfca38b04 --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/__fixtures__/config/sites/ts-and-js-site/docusaurus.config.js @@ -0,0 +1,13 @@ + +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +export default { + title: 'JS title', + url: 'https://example.com', + baseUrl: '/', +} diff --git a/packages/docusaurus/src/server/__tests__/__fixtures__/config/sites/ts-and-js-site/docusaurus.config.ts b/packages/docusaurus/src/server/__tests__/__fixtures__/config/sites/ts-and-js-site/docusaurus.config.ts new file mode 100644 index 000000000000..f29a98d160de --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/__fixtures__/config/sites/ts-and-js-site/docusaurus.config.ts @@ -0,0 +1,14 @@ +/** + * 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 type {Config} from '@docusaurus/types'; + +export default { + title: 'TS title', + url: 'https://example.com', + baseUrl: '/', +} satisfies Config; diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap index d83f688d657d..2ed2b796fd01 100644 --- a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap +++ b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap @@ -48,6 +48,246 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = ` } `; +exports[`loadSiteConfig website with ts + js config 1`] = ` +{ + "siteConfig": { + "baseUrl": "/", + "baseUrlIssueBanner": true, + "clientModules": [], + "customFields": {}, + "headTags": [], + "i18n": { + "defaultLocale": "en", + "localeConfigs": {}, + "locales": [ + "en", + ], + "path": "i18n", + }, + "markdown": { + "format": "mdx", + "mdx1Compat": { + "admonitions": true, + "comments": true, + "headingIds": true, + }, + "mermaid": false, + "preprocessor": undefined, + }, + "noIndex": false, + "onBrokenLinks": "throw", + "onBrokenMarkdownLinks": "warn", + "onDuplicateRoutes": "warn", + "plugins": [], + "presets": [], + "scripts": [], + "staticDirectories": [ + "static", + ], + "stylesheets": [], + "tagline": "", + "themeConfig": {}, + "themes": [], + "title": "TS title", + "titleDelimiter": "|", + "url": "https://example.com", + }, + "siteConfigPath": "/packages/docusaurus/src/server/__tests__/__fixtures__/config/sites/ts-and-js-site/docusaurus.config.ts", +} +`; + +exports[`loadSiteConfig website with valid JS CJS config 1`] = ` +{ + "siteConfig": { + "baseUrl": "/", + "baseUrlIssueBanner": true, + "clientModules": [], + "customFields": {}, + "headTags": [], + "i18n": { + "defaultLocale": "en", + "localeConfigs": {}, + "locales": [ + "en", + ], + "path": "i18n", + }, + "markdown": { + "format": "mdx", + "mdx1Compat": { + "admonitions": true, + "comments": true, + "headingIds": true, + }, + "mermaid": false, + "preprocessor": undefined, + }, + "noIndex": false, + "onBrokenLinks": "throw", + "onBrokenMarkdownLinks": "warn", + "onDuplicateRoutes": "warn", + "plugins": [], + "presets": [], + "scripts": [], + "staticDirectories": [ + "static", + ], + "stylesheets": [], + "tagline": "", + "themeConfig": {}, + "themes": [], + "title": "title", + "titleDelimiter": "|", + "url": "https://example.com", + }, + "siteConfigPath": "/packages/docusaurus/src/server/__tests__/__fixtures__/config/configCJS.js", +} +`; + +exports[`loadSiteConfig website with valid JS ESM config 1`] = ` +{ + "siteConfig": { + "baseUrl": "/", + "baseUrlIssueBanner": true, + "clientModules": [], + "customFields": {}, + "headTags": [], + "i18n": { + "defaultLocale": "en", + "localeConfigs": {}, + "locales": [ + "en", + ], + "path": "i18n", + }, + "markdown": { + "format": "mdx", + "mdx1Compat": { + "admonitions": true, + "comments": true, + "headingIds": true, + }, + "mermaid": false, + "preprocessor": undefined, + }, + "noIndex": false, + "onBrokenLinks": "throw", + "onBrokenMarkdownLinks": "warn", + "onDuplicateRoutes": "warn", + "plugins": [], + "presets": [], + "scripts": [], + "staticDirectories": [ + "static", + ], + "stylesheets": [], + "tagline": "", + "themeConfig": {}, + "themes": [], + "title": "title", + "titleDelimiter": "|", + "url": "https://example.com", + }, + "siteConfigPath": "/packages/docusaurus/src/server/__tests__/__fixtures__/config/configESM.js", +} +`; + +exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = ` +{ + "siteConfig": { + "baseUrl": "/", + "baseUrlIssueBanner": true, + "clientModules": [], + "customFields": {}, + "headTags": [], + "i18n": { + "defaultLocale": "en", + "localeConfigs": {}, + "locales": [ + "en", + ], + "path": "i18n", + }, + "markdown": { + "format": "mdx", + "mdx1Compat": { + "admonitions": true, + "comments": true, + "headingIds": true, + }, + "mermaid": false, + "preprocessor": undefined, + }, + "noIndex": false, + "onBrokenLinks": "throw", + "onBrokenMarkdownLinks": "warn", + "onDuplicateRoutes": "warn", + "plugins": [], + "presets": [], + "scripts": [], + "staticDirectories": [ + "static", + ], + "stylesheets": [], + "tagline": "", + "themeConfig": {}, + "themes": [], + "title": "title", + "titleDelimiter": "|", + "url": "https://example.com", + }, + "siteConfigPath": "/packages/docusaurus/src/server/__tests__/__fixtures__/config/configCJS.ts", +} +`; + +exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = ` +{ + "siteConfig": { + "baseUrl": "/", + "baseUrlIssueBanner": true, + "clientModules": [], + "customFields": {}, + "headTags": [], + "i18n": { + "defaultLocale": "en", + "localeConfigs": {}, + "locales": [ + "en", + ], + "path": "i18n", + }, + "markdown": { + "format": "mdx", + "mdx1Compat": { + "admonitions": true, + "comments": true, + "headingIds": true, + }, + "mermaid": false, + "preprocessor": undefined, + }, + "noIndex": false, + "onBrokenLinks": "throw", + "onBrokenMarkdownLinks": "warn", + "onDuplicateRoutes": "warn", + "plugins": [], + "presets": [], + "scripts": [], + "staticDirectories": [ + "static", + ], + "stylesheets": [], + "tagline": "", + "themeConfig": {}, + "themes": [], + "title": "title", + "titleDelimiter": "|", + "url": "https://example.com", + }, + "siteConfigPath": "/packages/docusaurus/src/server/__tests__/__fixtures__/config/configESM.ts", +} +`; + exports[`loadSiteConfig website with valid async config 1`] = ` { "siteConfig": { diff --git a/packages/docusaurus/src/server/__tests__/config.test.ts b/packages/docusaurus/src/server/__tests__/config.test.ts index d66a56c4f325..283d0696e1cb 100644 --- a/packages/docusaurus/src/server/__tests__/config.test.ts +++ b/packages/docusaurus/src/server/__tests__/config.test.ts @@ -19,6 +19,20 @@ describe('loadSiteConfig', () => { expect(config).not.toEqual({}); }); + it('website with ts + js config', async () => { + const config = await loadSiteConfig({ + siteDir: path.join( + __dirname, + '__fixtures__', + 'config/sites/ts-and-js-site', + ), + }); + expect(config).toMatchSnapshot(); + // Docusaurus uses in priority a TS config + expect(config.siteConfig.title).toBe('TS title'); + expect(config).not.toEqual({}); + }); + it('website with .cjs siteConfig', async () => { const config = await loadSiteConfig({siteDir}); expect(config).toMatchSnapshot(); @@ -52,6 +66,42 @@ describe('loadSiteConfig', () => { expect(config).not.toEqual({}); }); + it('website with valid JS CJS config', async () => { + const config = await loadSiteConfig({ + siteDir, + customConfigFilePath: 'configCJS.js', + }); + expect(config).toMatchSnapshot(); + expect(config).not.toEqual({}); + }); + + it('website with valid JS ESM config', async () => { + const config = await loadSiteConfig({ + siteDir, + customConfigFilePath: 'configESM.js', + }); + expect(config).toMatchSnapshot(); + expect(config).not.toEqual({}); + }); + + it('website with valid TypeScript CJS config', async () => { + const config = await loadSiteConfig({ + siteDir, + customConfigFilePath: 'configCJS.ts', + }); + expect(config).toMatchSnapshot(); + expect(config).not.toEqual({}); + }); + + it('website with valid TypeScript ESM config', async () => { + const config = await loadSiteConfig({ + siteDir, + customConfigFilePath: 'configESM.ts', + }); + expect(config).toMatchSnapshot(); + expect(config).not.toEqual({}); + }); + it('website with incomplete siteConfig', async () => { await expect( loadSiteConfig({ diff --git a/packages/docusaurus/src/server/config.ts b/packages/docusaurus/src/server/config.ts index 8f2fa9c9129e..59b30b133e24 100644 --- a/packages/docusaurus/src/server/config.ts +++ b/packages/docusaurus/src/server/config.ts @@ -7,15 +7,18 @@ import path from 'path'; import fs from 'fs-extra'; -import importFresh from 'import-fresh'; import logger from '@docusaurus/logger'; -import {DEFAULT_CONFIG_FILE_NAME, findAsyncSequential} from '@docusaurus/utils'; +import { + DEFAULT_CONFIG_FILE_NAME, + findAsyncSequential, + loadFreshModule, +} from '@docusaurus/utils'; import {validateConfig} from './configValidation'; import type {LoadContext} from '@docusaurus/types'; async function findConfig(siteDir: string) { // We could support .mjs, .ts, etc. in the future - const candidates = ['.js', '.cjs'].map( + const candidates = ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs'].map( (ext) => DEFAULT_CONFIG_FILE_NAME + ext, ); const configPath = await findAsyncSequential( @@ -46,7 +49,7 @@ export async function loadSiteConfig({ throw new Error(`Config file at "${siteConfigPath}" not found.`); } - const importedConfig = importFresh(siteConfigPath); + const importedConfig = await loadFreshModule(siteConfigPath); const loadedConfig: unknown = typeof importedConfig === 'function' diff --git a/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-plugins.js b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-plugins.cjs.js similarity index 100% rename from packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-plugins.js rename to packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-plugins.cjs.js diff --git a/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-plugins.esm.js b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-plugins.esm.js new file mode 100644 index 000000000000..d6d88565d75f --- /dev/null +++ b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-plugins.esm.js @@ -0,0 +1,15 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +export default function preset(context, opts = {}) { + return { + plugins: [ + ['@docusaurus/plugin-content-docs', opts.docs], + ['@docusaurus/plugin-content-blog', opts.blog], + ], + }; +} diff --git a/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-plugins.ts b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-plugins.ts new file mode 100644 index 000000000000..e8f9a6e73b49 --- /dev/null +++ b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-plugins.ts @@ -0,0 +1,18 @@ +/** + * 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 {LoadContext, Preset} from '@docusaurus/types'; + +export default function preset( + context: LoadContext, + opts: {theme: {}; test: {}}, +) { + return { + themes: [['@docusaurus/theme-classic', opts.theme], null], + plugins: [['@docusaurus/plugin-test', opts.theme], false], + } satisfies Preset; +} diff --git a/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/docusaurus.config.js b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/docusaurus.config.js index e62c4bee0546..d869bc6b51ba 100644 --- a/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/docusaurus.config.js +++ b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/docusaurus.config.js @@ -23,8 +23,8 @@ module.exports = { ], './plugin3.js', ['./plugin4.js', {}], + './pluginEsm', + './pluginTypeScript', ], - presets: [ - './preset.js', - ], + presets: ['./preset.js'], }; diff --git a/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/pluginEsm.js b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/pluginEsm.js new file mode 100644 index 000000000000..992e09998877 --- /dev/null +++ b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/pluginEsm.js @@ -0,0 +1,11 @@ +import {Joi} from '@docusaurus/utils-validation'; + +export default function (context, options) { + return { + name: 'plugin-esm', + }; +} + +export function validateThemeConfig({validate, themeConfig}) { + return {esmPlugin: {joi: !!Joi}}; +} diff --git a/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/pluginTypeScript.ts b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/pluginTypeScript.ts new file mode 100644 index 000000000000..7f1c241cace8 --- /dev/null +++ b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/pluginTypeScript.ts @@ -0,0 +1,14 @@ +import {Joi} from '@docusaurus/utils-validation'; +import {LoadContext, ThemeConfigValidationContext} from '@docusaurus/types'; +export default function (context: LoadContext, options: unknown) { + return { + name: 'plugin-ts', + }; +} + +export function validateThemeConfig({ + validate, + themeConfig, +}: ThemeConfigValidationContext) { + return {tsPlugin: {joi: !!Joi}}; +} diff --git a/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/presets.test.ts.snap b/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/presets.test.ts.snap index d5da620ec554..4588a0fb0200 100644 --- a/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/presets.test.ts.snap +++ b/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/presets.test.ts.snap @@ -63,6 +63,38 @@ exports[`loadPresets array form with options 1`] = ` } `; +exports[`loadPresets cjs form 1`] = ` +{ + "plugins": [ + [ + "@docusaurus/plugin-content-docs", + undefined, + ], + [ + "@docusaurus/plugin-content-blog", + undefined, + ], + ], + "themes": [], +} +`; + +exports[`loadPresets esm form 1`] = ` +{ + "plugins": [ + [ + "@docusaurus/plugin-content-docs", + undefined, + ], + [ + "@docusaurus/plugin-content-blog", + undefined, + ], + ], + "themes": [], +} +`; + exports[`loadPresets mixed form 1`] = ` { "plugins": [ @@ -127,7 +159,7 @@ exports[`loadPresets mixed form with themes 1`] = ` } `; -exports[`loadPresets string form 1`] = ` +exports[`loadPresets string form composite 1`] = ` { "plugins": [ [ @@ -139,31 +171,34 @@ exports[`loadPresets string form 1`] = ` undefined, ], ], - "themes": [], -} -`; - -exports[`loadPresets string form composite 1`] = ` -{ - "plugins": [ + "themes": [ [ - "@docusaurus/plugin-content-docs", + "@docusaurus/theme-live-codeblock", undefined, ], [ - "@docusaurus/plugin-content-blog", + "@docusaurus/theme-algolia", undefined, ], ], - "themes": [ +} +`; + +exports[`loadPresets ts form 1`] = ` +{ + "plugins": [ [ - "@docusaurus/theme-live-codeblock", + "@docusaurus/plugin-test", undefined, ], + false, + ], + "themes": [ [ - "@docusaurus/theme-algolia", + "@docusaurus/theme-classic", undefined, ], + null, ], } `; diff --git a/packages/docusaurus/src/server/plugins/__tests__/init.test.ts b/packages/docusaurus/src/server/plugins/__tests__/init.test.ts index 249b5af2bf54..4e07fb686def 100644 --- a/packages/docusaurus/src/server/plugins/__tests__/init.test.ts +++ b/packages/docusaurus/src/server/plugins/__tests__/init.test.ts @@ -21,8 +21,8 @@ describe('initPlugins', () => { it('parses plugins correctly and loads them in correct order', async () => { const {context, plugins} = await loadSite(); - expect(context.siteConfig.plugins).toHaveLength(4); - expect(plugins).toHaveLength(8); + expect(context.siteConfig.plugins).toHaveLength(6); + expect(plugins).toHaveLength(10); expect(plugins[0]!.name).toBe('preset-plugin1'); expect(plugins[1]!.name).toBe('preset-plugin2'); @@ -32,7 +32,15 @@ describe('initPlugins', () => { expect(plugins[5]!.name).toBe('second-plugin'); expect(plugins[6]!.name).toBe('third-plugin'); expect(plugins[7]!.name).toBe('fourth-plugin'); - expect(context.siteConfig.themeConfig).toEqual({a: 1}); + expect(context.siteConfig.themeConfig).toEqual({ + a: 1, + esmPlugin: { + joi: true, + }, + tsPlugin: { + joi: true, + }, + }); }); it('throws user-friendly error message for plugins with bad values', async () => { diff --git a/packages/docusaurus/src/server/plugins/__tests__/presets.test.ts b/packages/docusaurus/src/server/plugins/__tests__/presets.test.ts index 6cff740b08b0..cc4ab92a00da 100644 --- a/packages/docusaurus/src/server/plugins/__tests__/presets.test.ts +++ b/packages/docusaurus/src/server/plugins/__tests__/presets.test.ts @@ -26,12 +26,38 @@ describe('loadPresets', () => { `); }); - it('string form', async () => { + it('cjs form', async () => { const context = { siteConfigPath: __dirname, siteConfig: { presets: [ - path.join(__dirname, '__fixtures__/presets/preset-plugins.js'), + path.join(__dirname, '__fixtures__/presets/preset-plugins.cjs.js'), + ], + }, + } as LoadContext; + const presets = await loadPresets(context); + expect(presets).toMatchSnapshot(); + }); + + it('esm form', async () => { + const context = { + siteConfigPath: __dirname, + siteConfig: { + presets: [ + path.join(__dirname, '__fixtures__/presets/preset-plugins.esm.js'), + ], + }, + } as LoadContext; + const presets = await loadPresets(context); + expect(presets).toMatchSnapshot(); + }); + + it('ts form', async () => { + const context = { + siteConfigPath: __dirname, + siteConfig: { + presets: [ + path.join(__dirname, '__fixtures__/presets/preset-plugins.ts'), ], }, } as LoadContext; @@ -44,7 +70,7 @@ describe('loadPresets', () => { siteConfigPath: __dirname, siteConfig: { presets: [ - path.join(__dirname, '__fixtures__/presets/preset-plugins.js'), + path.join(__dirname, '__fixtures__/presets/preset-plugins.cjs.js'), path.join(__dirname, '__fixtures__/presets/preset-themes.js'), ], }, @@ -58,7 +84,7 @@ describe('loadPresets', () => { siteConfigPath: __dirname, siteConfig: { presets: [ - [path.join(__dirname, '__fixtures__/presets/preset-plugins.js')], + [path.join(__dirname, '__fixtures__/presets/preset-plugins.cjs.js')], ], }, } as unknown as LoadContext; @@ -72,7 +98,7 @@ describe('loadPresets', () => { siteConfig: { presets: [ [ - path.join(__dirname, '__fixtures__/presets/preset-plugins.js'), + path.join(__dirname, '__fixtures__/presets/preset-plugins.cjs.js'), {docs: {path: '../'}}, ], ], @@ -88,7 +114,7 @@ describe('loadPresets', () => { siteConfig: { presets: [ [ - path.join(__dirname, '__fixtures__/presets/preset-plugins.js'), + path.join(__dirname, '__fixtures__/presets/preset-plugins.cjs.js'), {docs: {path: '../'}}, ], [ @@ -108,7 +134,7 @@ describe('loadPresets', () => { siteConfig: { presets: [ [ - path.join(__dirname, '__fixtures__/presets/preset-plugins.js'), + path.join(__dirname, '__fixtures__/presets/preset-plugins.cjs.js'), {docs: {path: '../'}}, ], path.join(__dirname, '__fixtures__/presets/preset-themes.js'), @@ -125,7 +151,7 @@ describe('loadPresets', () => { siteConfig: { presets: [ [ - path.join(__dirname, '__fixtures__/presets/preset-plugins.js'), + path.join(__dirname, '__fixtures__/presets/preset-plugins.cjs.js'), {docs: {path: '../'}}, ], false, diff --git a/packages/docusaurus/src/server/plugins/configs.ts b/packages/docusaurus/src/server/plugins/configs.ts index eb2898e5d312..2100dd9be1b7 100644 --- a/packages/docusaurus/src/server/plugins/configs.ts +++ b/packages/docusaurus/src/server/plugins/configs.ts @@ -6,7 +6,7 @@ */ import {createRequire} from 'module'; -import importFresh from 'import-fresh'; +import {loadFreshModule} from '@docusaurus/utils'; import {loadPresets} from './presets'; import {resolveModuleName} from './moduleShorthand'; import type { @@ -61,7 +61,9 @@ async function normalizePluginConfig( if (typeof pluginConfig === 'string') { const pluginModuleImport = pluginConfig; const pluginPath = pluginRequire.resolve(pluginModuleImport); - const pluginModule = importFresh(pluginPath); + const pluginModule = (await loadFreshModule( + pluginPath, + )) as ImportedPluginModule; return { plugin: pluginModule.default ?? pluginModule, options: {}, @@ -88,7 +90,9 @@ async function normalizePluginConfig( if (typeof pluginConfig[0] === 'string') { const pluginModuleImport = pluginConfig[0]; const pluginPath = pluginRequire.resolve(pluginModuleImport); - const pluginModule = importFresh(pluginPath); + const pluginModule = (await loadFreshModule( + pluginPath, + )) as ImportedPluginModule; return { plugin: pluginModule.default ?? pluginModule, options: pluginConfig[1], diff --git a/packages/docusaurus/src/server/plugins/presets.ts b/packages/docusaurus/src/server/plugins/presets.ts index 04b73357f38f..f8318ee9a1e1 100644 --- a/packages/docusaurus/src/server/plugins/presets.ts +++ b/packages/docusaurus/src/server/plugins/presets.ts @@ -6,12 +6,13 @@ */ import {createRequire} from 'module'; -import importFresh from 'import-fresh'; +import {loadFreshModule} from '@docusaurus/utils'; import {resolveModuleName} from './moduleShorthand'; import type { LoadContext, - PluginConfig, + PresetConfigDefined, PresetModule, + Preset, DocusaurusConfig, } from '@docusaurus/types'; @@ -28,16 +29,13 @@ export async function loadPresets( // we are using `require.resolve` on those module names. const presetRequire = createRequire(context.siteConfigPath); - const {presets} = context.siteConfig; - const plugins: PluginConfig[] = []; - const themes: PluginConfig[] = []; + const presets = context.siteConfig.presets.filter( + (p): p is PresetConfigDefined => !!p, + ); - presets.forEach((presetItem) => { + async function loadPreset(presetItem: PresetConfigDefined): Promise { let presetModuleImport: string; let presetOptions = {}; - if (!presetItem) { - return; - } if (typeof presetItem === 'string') { presetModuleImport = presetItem; } else { @@ -49,21 +47,20 @@ export async function loadPresets( 'preset', ); - const presetModule = importFresh( - presetRequire.resolve(presetName), - ); - const preset = (presetModule.default ?? presetModule)( - context, - presetOptions, - ); + const presetPath = presetRequire.resolve(presetName); + const presetModule = (await loadFreshModule( + presetPath, + )) as ImportedPresetModule; - if (preset.plugins) { - plugins.push(...preset.plugins); - } - if (preset.themes) { - themes.push(...preset.themes); - } - }); + const presetFunction = presetModule.default ?? presetModule; + + return presetFunction(context, presetOptions); + } + + const loadedPresets = await Promise.all(presets.map(loadPreset)); + + const plugins = loadedPresets.flatMap((preset) => preset.plugins ?? []); + const themes = loadedPresets.flatMap((preset) => preset.themes ?? []); return {plugins, themes}; } diff --git a/project-words.txt b/project-words.txt index 4e8076b252c2..aec839742361 100644 --- a/project-words.txt +++ b/project-words.txt @@ -60,7 +60,6 @@ cssnano csvg customizability dabit -dabit daishi datagit datas @@ -98,20 +97,22 @@ eslintcache estree evaluable execa -execa externalwaiting failfast +Fargate fbid février fienny flac +flightcontrol formik fouc froms funboxteam -gantt gabrielcsapo +gantt getopts +gifs gitgraph gitpod globbing @@ -123,6 +124,7 @@ gtag hahaha hamel hardcoding +hastscript hasura heavener héctor @@ -136,6 +138,7 @@ hoverable husain ianad idempotency +Iframes immer infima inlines @@ -148,6 +151,7 @@ jakepartusch jamstack janvier javadoc +jiti jmarcey jodyheavener joshcena @@ -183,6 +187,7 @@ mathjax maxlynch maxresdefault mdast +mdwn mdxa mdxast mdxhast @@ -198,9 +203,8 @@ minifier mkcert mkdir mkdirs -mkdocs mkdn -mdwn +mkdocs mkdown moesif msapplication @@ -273,6 +277,7 @@ prerendered prerendering println prismjs +producthunt profilo protobuf protobuffet @@ -319,7 +324,6 @@ serializers setaf setext shiki -shiki showinfo sida simen @@ -328,7 +332,6 @@ sluggified sluggifies sluggify solana -solana spâce stackblitz stackblitzrc @@ -373,6 +376,7 @@ typecheck typechecks typedoc typesense +unavatar unflat unist unlinkable @@ -415,11 +419,3 @@ yangshunz zhou zoomable zpao -hastscript -Flightcontrol -Fargate -Flightcontrol's -producthunt -Gifs -Iframes -Unavatar diff --git a/website/_dogfooding/dogfooding.config.js b/website/_dogfooding/dogfooding.config.ts similarity index 78% rename from website/_dogfooding/dogfooding.config.js rename to website/_dogfooding/dogfooding.config.ts index a6e2bcde9592..10f70d71fb95 100644 --- a/website/_dogfooding/dogfooding.config.js +++ b/website/_dogfooding/dogfooding.config.ts @@ -5,24 +5,24 @@ * LICENSE file in the root directory of this source tree. */ -/** @type {import('@docusaurus/types').PluginConfig[]} */ -const dogfoodingThemeInstances = [ - /** @type {import('@docusaurus/types').PluginModule} */ - function swizzleThemeTests() { +import type {PluginConfig, Plugin} from '@docusaurus/types'; +import type {Options as DocsOptions} from '@docusaurus/plugin-content-docs'; +import type {Options as BlogOptions} from '@docusaurus/plugin-content-blog'; +import type {Options as PageOptions} from '@docusaurus/plugin-content-pages'; + +export const dogfoodingThemeInstances: PluginConfig[] = [ + function swizzleThemeTests(): Plugin { return { name: 'swizzle-theme-tests', getThemePath: () => './_swizzle_theme_tests/src/theme', }; }, ]; -exports.dogfoodingThemeInstances = dogfoodingThemeInstances; -/** @type {import('@docusaurus/types').PluginConfig[]} */ -const dogfoodingPluginInstances = [ +export const dogfoodingPluginInstances: PluginConfig[] = [ [ 'content-docs', // Shorthand - /** @type {import('@docusaurus/plugin-content-docs').Options} */ - ({ + { id: 'docs-tests', routeBasePath: '/tests/docs', sidebarPath: '_dogfooding/docs-tests-sidebars.js', @@ -43,20 +43,20 @@ const dogfoodingPluginInstances = [ const eligibleDocIndexNames = [ 'index', 'readme', - directories[0].toLowerCase(), + directories[0]!.toLowerCase(), 'intro', ]; return eligibleDocIndexNames.includes(fileName.toLowerCase()); }, }); }, - }), + } satisfies DocsOptions, ], [ '@docusaurus/plugin-content-blog', // Longhand /** @type {import('@docusaurus/plugin-content-blog').Options} */ - ({ + { id: 'blog-tests', path: '_dogfooding/_blog tests', routeBasePath: '/tests/blog', @@ -72,21 +72,19 @@ const dogfoodingPluginInstances = [ frontMatter.hide_reading_time ? undefined : defaultReadingTime({content, options: {wordsPerMinute: 5}}), - }), + } satisfies BlogOptions, ], [ require.resolve('@docusaurus/plugin-content-pages'), // Full path - /** @type {import('@docusaurus/plugin-content-pages').Options} */ - ({ + { id: 'pages-tests', path: '_dogfooding/_pages tests', routeBasePath: '/tests/pages', - }), + } satisfies PageOptions, ], - /** @type {import('@docusaurus/types').Plugin} */ - function clientModuleTestPlugin() { + function clientModuleTestPlugin(): Plugin { return { name: 'client-module-test-plugin', getClientModules() { @@ -99,9 +97,7 @@ const dogfoodingPluginInstances = [ }, ]; -exports.dogfoodingPluginInstances = dogfoodingPluginInstances; - -exports.dogfoodingRedirects = [ +export const dogfoodingRedirects: {from: string[]; to: string}[] = [ { from: ['/home/'], to: '/', diff --git a/website/blog/2023-09-29-preparing-your-site-for-docusaurus-v3/index.mdx b/website/blog/2023-09-29-preparing-your-site-for-docusaurus-v3/index.mdx index 7a5c288beb52..678dea498970 100644 --- a/website/blog/2023-09-29-preparing-your-site-for-docusaurus-v3/index.mdx +++ b/website/blog/2023-09-29-preparing-your-site-for-docusaurus-v3/index.mdx @@ -362,13 +362,22 @@ console.log('hello'); All the official packages (Unified, Remark, Rehype...) in the MDX ecosystem are now [**ES Modules only**](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) and do not support [CommonJS](https://nodejs.org/api/modules.html#modules-commonjs-modules) anymore. -In practice this means that you can't do `require("remark-plugin")` anymore. Unfortunately the Docusaurus config file only supports CommonJS at the moment, and you can't use `import MyRemarkPlugin from "remark-plugin"`. +In practice this means that you can't do `require("remark-plugin")` anymore. :::tip How to prepare -Using dynamic imports is a good workaround that enables you to import ES modules inside a CommonJS module. Fortunately, the [Docusaurus config supports the usage of an async function](/docs/configuration#syntax-to-declare-docusaurus-config) to let you do so. +Docusaurus v3 now supports [**ES Modules**](https://flaviocopes.com/es-modules/) configuration files. We recommend that you migrate your config file to ES module, that enables you to import the Remark plugins easily: -You can start refactoring your config to use **async dynamic imports** today. Refer to the [MDX plugin installation documentation](/docs/3.0.0-beta.0/markdown-features/plugins#installing-plugins) for details. +```js title="docusaurus.config.js" +import remarkPlugin from 'remark-plugin'; + +export default { + title: 'Docusaurus', + /* site config using remark plugins here */ +}; +``` + +If you want to keep using CommonJS modules, you can use dynamic imports as a workaround that enables you to import ES modules inside a CommonJS module. Fortunately, the [Docusaurus config supports the usage of an async function](/docs/configuration#syntax-to-declare-docusaurus-config) to let you do so. ```js title="docusaurus.config.js" module.exports = async function () { diff --git a/website/docs/advanced/architecture.mdx b/website/docs/advanced/architecture.mdx index 91dfecf3675c..8d1f8bdb2da6 100644 --- a/website/docs/advanced/architecture.mdx +++ b/website/docs/advanced/architecture.mdx @@ -20,7 +20,7 @@ This diagram shows how Docusaurus works to build your app. Plugins each collect Although you (either plugin authors or site creators) are writing JavaScript all the time, bear in mind that the JS is actually run in different environments: -- All plugin lifecycle methods are run in Node. Therefore, until we support ES Modules in our codebase, plugin source code must be provided as CommonJS that can be `require`'d. +- All plugin lifecycle methods are run in Node. Therefore, until we support ES Modules in our codebase, plugin source code must be provided as ES modules that can be imported, or CommonJS that can be `require`'d. - The theme code is built with Webpack. They can be provided as ESM—following React conventions. Plugin code and theme code never directly import each other: they only communicate through protocols (in our case, through JSON temp files and calls to `addRoute`). A useful mental model is to imagine that the plugins are not written in JavaScript, but in another language like Rust. The only way to interact with plugins for the user is through `docusaurus.config.js`, which itself is run in Node (hence you can use `require` and pass callbacks as plugin options). diff --git a/website/docs/advanced/plugins.mdx b/website/docs/advanced/plugins.mdx index 01b66d0c305b..e724c6214a18 100644 --- a/website/docs/advanced/plugins.mdx +++ b/website/docs/advanced/plugins.mdx @@ -11,7 +11,7 @@ A plugin is a function that takes two parameters: `context` and `options`. It re You can use a plugin as a function directly included in the Docusaurus config file: ```js title="docusaurus.config.js" -module.exports = { +export default { // ... plugins: [ // highlight-start @@ -38,7 +38,7 @@ module.exports = { You can use a plugin as a module path referencing a separate file or npm package: ```js title="docusaurus.config.js" -module.exports = { +export default { // ... plugins: [ // without options: @@ -52,7 +52,7 @@ module.exports = { Then in the folder `my-plugin`, you can create an `index.js` such as this: ```js title="my-plugin/index.js" -module.exports = async function myPlugin(context, options) { +export default async function myPlugin(context, options) { // ... return { name: 'my-plugin', @@ -64,7 +64,7 @@ module.exports = async function myPlugin(context, options) { }, /* other lifecycle API */ }; -}; +} ``` --- @@ -99,7 +99,7 @@ This is a contrived example: in practice, `@docusaurus/theme-classic` provides t ::: ```js title="docusaurus.config.js" -module.exports = { +export default { // highlight-next-line themes: ['theme-blog'], plugins: ['plugin-content-blog'], @@ -109,7 +109,7 @@ module.exports = { And if you want to use Bootstrap styling, you can swap out the theme with `theme-blog-bootstrap` (another fictitious non-existing theme): ```js title="docusaurus.config.js" -module.exports = { +export default { // highlight-next-line themes: ['theme-blog-bootstrap'], plugins: ['plugin-content-blog'], diff --git a/website/docs/api/docusaurus.config.js.mdx b/website/docs/api/docusaurus.config.js.mdx index 35ab29326a61..3ef712b23067 100644 --- a/website/docs/api/docusaurus.config.js.mdx +++ b/website/docs/api/docusaurus.config.js.mdx @@ -18,12 +18,20 @@ Refer to the Getting Started [**Configuration**](../configuration.mdx) for examp `docusaurus.config.js` contains configurations for your site and is placed in the root directory of your site. -This file is run in Node.js using the [**CommonJS**](https://flaviocopes.com/commonjs/) module system, and should export a site configuration object, or a function that creates it. +This file is run in Node.js and should export a site configuration object, or a function that creates it. + +The `docusaurus.config.js` file supports: + +- [**ES Modules**](https://flaviocopes.com/es-modules/) +- [**CommonJS**](https://flaviocopes.com/commonjs/) +- [**TypeScript**](../typescript-support.mdx#typing-config) + +hey Examples: ```js title="docusaurus.config.js" -module.exports = { +export default { title: 'Docusaurus', url: 'https://docusaurus.io', // your site config ... @@ -31,13 +39,13 @@ module.exports = { ``` ```js title="docusaurus.config.js" -module.exports = async function createConfigAsync() { +export default async function createConfigAsync() { return { title: 'Docusaurus', url: 'https://docusaurus.io', // your site config ... }; -}; +} ``` :::tip @@ -55,7 +63,7 @@ Refer to [**Syntax to declare `docusaurus.config.js`**](../configuration.mdx#syn Title for your website. Will be used in metadata and as browser tab title. ```js title="docusaurus.config.js" -module.exports = { +export default { title: 'Docusaurus', }; ``` @@ -67,7 +75,7 @@ module.exports = { URL for your website. This can also be considered the top-level hostname. For example, `https://facebook.github.io` is the URL of https://facebook.github.io/metro/, and `https://docusaurus.io` is the URL for https://docusaurus.io. This field is related to the [`baseUrl`](#baseUrl) field. ```js title="docusaurus.config.js" -module.exports = { +export default { url: 'https://docusaurus.io', }; ``` @@ -79,7 +87,7 @@ module.exports = { Base URL for your site. Can be considered as the path after the host. For example, `/metro/` is the base URL of https://facebook.github.io/metro/. For URLs that have no path, the baseUrl should be set to `/`. This field is related to the [`url`](#url) field. Always has both leading and trailing slash. ```js title="docusaurus.config.js" -module.exports = { +export default { baseUrl: '/', }; ``` @@ -93,7 +101,7 @@ module.exports = { Path to your site favicon; must be a URL that can be used in link's href. For example, if your favicon is in `static/img/favicon.ico`: ```js title="docusaurus.config.js" -module.exports = { +export default { favicon: '/img/favicon.ico', }; ``` @@ -127,7 +135,7 @@ Example: {/* cSpell:ignore فارسی */} ```js title="docusaurus.config.js" -module.exports = { +export default { i18n: { defaultLocale: 'en', locales: ['en', 'fa'], @@ -171,7 +179,7 @@ This option adds `` to every pag Example: ```js title="docusaurus.config.js" -module.exports = { +export default { noIndex: true, // Defaults to `false` }; ``` @@ -213,7 +221,7 @@ By default, it displays a warning after you run `yarn start` or `yarn build`. The tagline for your website. ```js title="docusaurus.config.js" -module.exports = { +export default { tagline: 'Docusaurus makes it easy to maintain Open Source documentation websites.', }; @@ -226,7 +234,7 @@ module.exports = { The GitHub user or organization that owns the repository. You don't need this if you are not using the `docusaurus deploy` command. ```js title="docusaurus.config.js" -module.exports = { +export default { // Docusaurus' organization is facebook organizationName: 'facebook', }; @@ -239,7 +247,7 @@ module.exports = { The name of the GitHub repository. You don't need this if you are not using the `docusaurus deploy` command. ```js title="docusaurus.config.js" -module.exports = { +export default { projectName: 'docusaurus', }; ``` @@ -251,7 +259,7 @@ module.exports = { The name of the branch to deploy the static files to. You don't need this if you are not using the `docusaurus deploy` command. ```js title="docusaurus.config.js" -module.exports = { +export default { deploymentBranch: 'gh-pages', }; ``` @@ -263,7 +271,7 @@ module.exports = { The hostname of your server. Useful if you are using GitHub Enterprise. You don't need this if you are not using the `docusaurus deploy` command. ```js title="docusaurus.config.js" -module.exports = { +export default { githubHost: 'github.com', }; ``` @@ -275,7 +283,7 @@ module.exports = { The port of your server. Useful if you are using GitHub Enterprise. You don't need this if you are not using the `docusaurus deploy` command. ```js title="docusaurus.config.js" -module.exports = { +export default { githubPort: '22', }; ``` @@ -289,7 +297,7 @@ The [theme configuration](./themes/theme-configuration.mdx) object to customize Example: ```js title="docusaurus.config.js" -module.exports = { +export default { themeConfig: { docs: { sidebar: { @@ -358,7 +366,7 @@ type PluginConfig = string | [string, any] | PluginModule | [PluginModule, any]; See [plugin method references](./plugin-methods/README.mdx) for the shape of a `PluginModule`. ```js title="docusaurus.config.js" -module.exports = { +export default { plugins: [ 'docusaurus-plugin-awesome', ['docusuarus-plugin-confetti', {fancy: false}], @@ -376,7 +384,7 @@ module.exports = { - Type: `PluginConfig[]` ```js title="docusaurus.config.js" -module.exports = { +export default { themes: ['@docusaurus/theme-classic'], }; ``` @@ -390,7 +398,7 @@ type PresetConfig = string | [string, any]; ``` ```js title="docusaurus.config.js" -module.exports = { +export default { presets: [], }; ``` @@ -426,7 +434,7 @@ type MarkdownConfig = { Example: ```js title="docusaurus.config.js" -module.exports = { +export default { markdown: { format: 'mdx', mermaid: true, @@ -464,7 +472,7 @@ Docusaurus guards `docusaurus.config.js` from unknown fields. To add a custom fi - Type: `Object` ```js title="docusaurus.config.js" -module.exports = { +export default { customFields: { admin: 'endi', superman: 'lol', @@ -487,7 +495,7 @@ An array of paths, relative to the site's directory or absolute. Files under the Example: ```js title="docusaurus.config.js" -module.exports = { +export default { staticDirectories: ['static'], }; ``` @@ -501,7 +509,7 @@ An array of tags that will be inserted in the HTML ``. The values must be Example: ```js title="docusaurus.config.js" -module.exports = { +export default { headTags: [ { tagName: 'link', @@ -527,7 +535,7 @@ Note that `