From 24716787d3667a531017e3bb93ef454d11cce871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Thu, 17 Oct 2024 17:20:06 +0200 Subject: [PATCH] feat: new Rsdoctor official plugin (#10588) --- .../docusaurus-plugin-rsdoctor/.npmignore | 3 + packages/docusaurus-plugin-rsdoctor/README.md | 7 ++ .../docusaurus-plugin-rsdoctor/package.json | 35 ++++++ .../src/__tests__/options.test.ts | 102 ++++++++++++++++++ .../docusaurus-plugin-rsdoctor/src/index.ts | 55 ++++++++++ .../docusaurus-plugin-rsdoctor/src/options.ts | 34 ++++++ .../docusaurus-plugin-rsdoctor/src/types.d.ts | 8 ++ .../docusaurus-plugin-rsdoctor/tsconfig.json | 8 ++ website/docs/api/plugins/plugin-rsdoctor.mdx | 57 ++++++++++ website/docusaurus.config.ts | 23 +++- website/package.json | 3 +- .../src/plugins/rsdoctor/RsdoctorPlugin.ts | 50 --------- 12 files changed, 331 insertions(+), 54 deletions(-) create mode 100644 packages/docusaurus-plugin-rsdoctor/.npmignore create mode 100644 packages/docusaurus-plugin-rsdoctor/README.md create mode 100644 packages/docusaurus-plugin-rsdoctor/package.json create mode 100644 packages/docusaurus-plugin-rsdoctor/src/__tests__/options.test.ts create mode 100644 packages/docusaurus-plugin-rsdoctor/src/index.ts create mode 100644 packages/docusaurus-plugin-rsdoctor/src/options.ts create mode 100644 packages/docusaurus-plugin-rsdoctor/src/types.d.ts create mode 100644 packages/docusaurus-plugin-rsdoctor/tsconfig.json create mode 100644 website/docs/api/plugins/plugin-rsdoctor.mdx delete mode 100644 website/src/plugins/rsdoctor/RsdoctorPlugin.ts diff --git a/packages/docusaurus-plugin-rsdoctor/.npmignore b/packages/docusaurus-plugin-rsdoctor/.npmignore new file mode 100644 index 000000000000..03c9ae1e1b54 --- /dev/null +++ b/packages/docusaurus-plugin-rsdoctor/.npmignore @@ -0,0 +1,3 @@ +.tsbuildinfo* +tsconfig* +__tests__ diff --git a/packages/docusaurus-plugin-rsdoctor/README.md b/packages/docusaurus-plugin-rsdoctor/README.md new file mode 100644 index 000000000000..88336d6c4f1e --- /dev/null +++ b/packages/docusaurus-plugin-rsdoctor/README.md @@ -0,0 +1,7 @@ +# `@docusaurus/plugin-rsdoctor` + +[Rsdoctor](https://rsdoctor.dev/) plugin for Docusaurus. + +## Usage + +See [plugin-rsdoctor documentation](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-rsdoctor). diff --git a/packages/docusaurus-plugin-rsdoctor/package.json b/packages/docusaurus-plugin-rsdoctor/package.json new file mode 100644 index 000000000000..17a11a918cdd --- /dev/null +++ b/packages/docusaurus-plugin-rsdoctor/package.json @@ -0,0 +1,35 @@ +{ + "name": "@docusaurus/plugin-rsdoctor", + "version": "3.5.2", + "description": "Rsdoctor plugin for Docusaurus.", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "tsc --build", + "watch": "tsc --build --watch" + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/facebook/docusaurus.git", + "directory": "packages/docusaurus-plugin-rsdoctor" + }, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", + "@rsdoctor/webpack-plugin": "^0.4.6", + "@rsdoctor/rspack-plugin": "^0.4.6", + "tslib": "^2.6.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "engines": { + "node": ">=18.0" + } +} diff --git a/packages/docusaurus-plugin-rsdoctor/src/__tests__/options.test.ts b/packages/docusaurus-plugin-rsdoctor/src/__tests__/options.test.ts new file mode 100644 index 000000000000..f61daad45187 --- /dev/null +++ b/packages/docusaurus-plugin-rsdoctor/src/__tests__/options.test.ts @@ -0,0 +1,102 @@ +/** + * 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 {normalizePluginOptions} from '@docusaurus/utils-validation'; +import { + validateOptions, + type PluginOptions, + type Options, + DEFAULT_OPTIONS, +} from '../options'; +import type {Validate} from '@docusaurus/types'; + +function validate(options?: Options) { + return validateOptions({ + validate: normalizePluginOptions as Validate< + Options | undefined, + PluginOptions + >, + options, + }); +} + +function result(options?: Options) { + return { + id: 'default', + ...DEFAULT_OPTIONS, + ...options, + }; +} + +describe('validateOptions', () => { + it('accepts undefined', () => { + expect(validate(undefined)).toEqual(result(DEFAULT_OPTIONS)); + }); + + it('accepts empty object', () => { + expect(validate({})).toEqual(result(DEFAULT_OPTIONS)); + }); + + it('accepts defaults', () => { + expect(validate(DEFAULT_OPTIONS)).toEqual(result(DEFAULT_OPTIONS)); + }); + + it('rejects null', () => { + expect( + // @ts-expect-error: TS should error + () => validate(null), + ).toThrowErrorMatchingInlineSnapshot(`""value" must be of type object"`); + }); + + it('rejects number', () => { + expect( + // @ts-expect-error: TS should error + () => validate(42), + ).toThrowErrorMatchingInlineSnapshot(`""value" must be of type object"`); + }); + + describe('rsdoctorOptions', () => { + it('accepts undefined', () => { + expect(validate({rsdoctorOptions: undefined})).toEqual( + result(DEFAULT_OPTIONS), + ); + }); + + it('accepts empty', () => { + expect(validate({rsdoctorOptions: {}})).toEqual(result(DEFAULT_OPTIONS)); + }); + + it('accepts any record', () => { + expect( + validate({rsdoctorOptions: {any: 'value', evenNumbers: 42}}), + ).toEqual( + result({ + ...DEFAULT_OPTIONS, + rsdoctorOptions: { + any: 'value', + evenNumbers: 42, + }, + }), + ); + }); + + it('accepts default', () => { + expect( + validate({rsdoctorOptions: DEFAULT_OPTIONS.rsdoctorOptions}), + ).toEqual(result(DEFAULT_OPTIONS)); + }); + + it('rejects number values', () => { + expect(() => + // @ts-expect-error: invalid type + validate({rsdoctorOptions: 42}), + ).toThrowErrorMatchingInlineSnapshot( + `""rsdoctorOptions" must be of type object"`, + ); + }); + }); +}); diff --git a/packages/docusaurus-plugin-rsdoctor/src/index.ts b/packages/docusaurus-plugin-rsdoctor/src/index.ts new file mode 100644 index 000000000000..d43412823bcd --- /dev/null +++ b/packages/docusaurus-plugin-rsdoctor/src/index.ts @@ -0,0 +1,55 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {RsdoctorRspackMultiplePlugin} from '@rsdoctor/rspack-plugin'; +import {RsdoctorWebpackMultiplePlugin} from '@rsdoctor/webpack-plugin'; +import type {CurrentBundler, LoadContext, Plugin} from '@docusaurus/types'; +import type {PluginOptions, Options} from './options'; + +function createRsdoctorBundlerPlugin({ + isServer, + currentBundler, + options, +}: { + isServer: boolean; + currentBundler: CurrentBundler; + options: PluginOptions; +}) { + const RsdoctorPlugin = + currentBundler.name === 'rspack' + ? RsdoctorRspackMultiplePlugin + : RsdoctorWebpackMultiplePlugin; + + return new RsdoctorPlugin({ + name: isServer ? 'server' : 'client', + ...options.rsdoctorOptions, + }); +} + +export default (async function pluginRsdoctor( + context: LoadContext, + options: PluginOptions, +): Promise { + return { + name: 'docusaurus-plugin-rsdoctor', + configureWebpack: (__config, isServer) => { + return { + plugins: [ + createRsdoctorBundlerPlugin({ + isServer, + currentBundler: context.currentBundler, + options, + }), + ], + }; + }, + }; +}); + +export {validateOptions} from './options'; + +export type {PluginOptions, Options}; diff --git a/packages/docusaurus-plugin-rsdoctor/src/options.ts b/packages/docusaurus-plugin-rsdoctor/src/options.ts new file mode 100644 index 000000000000..9dcbda2aa50c --- /dev/null +++ b/packages/docusaurus-plugin-rsdoctor/src/options.ts @@ -0,0 +1,34 @@ +/** + * 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 {Joi} from '@docusaurus/utils-validation'; +import type {OptionValidationContext} from '@docusaurus/types'; + +export type PluginOptions = { + rsdoctorOptions: Record; +}; + +export type Options = { + rsdoctorOptions?: Record; +}; + +export const DEFAULT_OPTIONS: Partial = { + rsdoctorOptions: {}, +}; + +const pluginOptionsSchema = Joi.object({ + rsdoctorOptions: Joi.object() + .pattern(Joi.string(), Joi.any()) + .optional() + .default(DEFAULT_OPTIONS.rsdoctorOptions), +}).default(DEFAULT_OPTIONS); + +export function validateOptions({ + validate, + options, +}: OptionValidationContext): PluginOptions { + return validate(pluginOptionsSchema, options); +} diff --git a/packages/docusaurus-plugin-rsdoctor/src/types.d.ts b/packages/docusaurus-plugin-rsdoctor/src/types.d.ts new file mode 100644 index 000000000000..6f6f99f12793 --- /dev/null +++ b/packages/docusaurus-plugin-rsdoctor/src/types.d.ts @@ -0,0 +1,8 @@ +/** + * 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. + */ + +/// diff --git a/packages/docusaurus-plugin-rsdoctor/tsconfig.json b/packages/docusaurus-plugin-rsdoctor/tsconfig.json new file mode 100644 index 000000000000..343f87c70bdc --- /dev/null +++ b/packages/docusaurus-plugin-rsdoctor/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": false + }, + "include": ["src"], + "exclude": ["**/__tests__/**"] +} diff --git a/website/docs/api/plugins/plugin-rsdoctor.mdx b/website/docs/api/plugins/plugin-rsdoctor.mdx new file mode 100644 index 000000000000..100d714893d4 --- /dev/null +++ b/website/docs/api/plugins/plugin-rsdoctor.mdx @@ -0,0 +1,57 @@ +--- +sidebar_position: 7 +slug: /api/plugins/@docusaurus/plugin-rsdoctor +--- + +# 📦 plugin-rsdoctor + +import APITable from '@site/src/components/APITable'; + +A [Rsdoctor](https://rsdoctor.dev/) plugin can help you troubleshoot the bundling phase of your Docusaurus site, supporting both Webpack and Rspack. + +:::tip + +Use it to figure out which plugin or loader is slowing down the bundler, and focus your efforts on optimizing the bottleneck. + +::: + +## Installation {#installation} + +```bash npm2yarn +npm install --save @docusaurus/plugin-rsdoctor +``` + +## Configuration {#configuration} + +Accepted fields: + +```mdx-code-block + +``` + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| `rsdoctorOptions` | `object` | `{}` | The [Rsdoctor bundler plugin options](https://rsdoctor.dev/config/options/options), forwarded as is | + +```mdx-code-block + +``` + +### Example configuration {#ex-config} + +You can configure this plugin through plugin options. + +```js title="docusaurus.config.js" +export default { + plugins: [ + [ + 'rsdoctor', + { + rsdoctorOptions: { + mode: 'lite', + }, + }, + ], + ], +}; +``` diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 228aafcbc813..7ae2a4c9b7a2 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -9,7 +9,6 @@ import npm2yarn from '@docusaurus/remark-plugin-npm2yarn'; import remarkMath from 'remark-math'; import rehypeKatex from 'rehype-katex'; import configTabs from './src/remark/configTabs'; -import RsdoctorPlugin from './src/plugins/rsdoctor/RsdoctorPlugin'; import versions from './versions.json'; import VersionsArchived from './versionsArchived.json'; @@ -129,6 +128,8 @@ const isI18nStaging = process.env.I18N_STAGING === 'true'; const isVersioningDisabled = !!process.env.DISABLE_VERSIONING || isI18nStaging; +const isRsdoctor = process.env.RSDOCTOR === 'true'; + /* const TwitterSvg = ''; @@ -259,7 +260,25 @@ export default async function createConfigAsync() { ], themes: ['live-codeblock', ...dogfoodingThemeInstances], plugins: [ - RsdoctorPlugin, + isRsdoctor && [ + 'rsdoctor', + { + rsdoctorOptions: { + disableTOSUpload: true, + supports: { + // https://rsdoctor.dev/config/options/options#generatetilegraph + generateTileGraph: true, + }, + linter: { + // See https://rsdoctor.dev/guide/usage/rule-config + rules: { + 'ecma-version-check': 'off', + 'duplicate-package': 'off', + }, + }, + }, + }, + ], [ './src/plugins/changelog/index.js', { diff --git a/website/package.json b/website/package.json index a631805ef5d3..770d02245290 100644 --- a/website/package.json +++ b/website/package.json @@ -43,6 +43,7 @@ "@docusaurus/plugin-client-redirects": "3.5.2", "@docusaurus/plugin-ideal-image": "3.5.2", "@docusaurus/plugin-pwa": "3.5.2", + "@docusaurus/plugin-rsdoctor": "3.5.2", "@docusaurus/preset-classic": "3.5.2", "@docusaurus/remark-plugin-npm2yarn": "3.5.2", "@docusaurus/theme-classic": "3.5.2", @@ -84,8 +85,6 @@ "devDependencies": { "@docusaurus/eslint-plugin": "3.5.2", "@docusaurus/tsconfig": "3.5.2", - "@rsdoctor/webpack-plugin": "^0.4.6", - "@rsdoctor/rspack-plugin": "^0.4.6", "@types/color": "^3.0.4", "@types/jest": "^29.5.3", "cross-env": "^7.0.3", diff --git a/website/src/plugins/rsdoctor/RsdoctorPlugin.ts b/website/src/plugins/rsdoctor/RsdoctorPlugin.ts deleted file mode 100644 index edad0cc00942..000000000000 --- a/website/src/plugins/rsdoctor/RsdoctorPlugin.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * 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 {PluginConfig} from '@docusaurus/types'; - -function createRsdoctorBundlerPlugin({isServer}: {isServer: boolean}) { - // TODO Shitty workaround to bypass lib typechecking - // package does not work will with skipLibCheck false - // // eslint-disable-next-line - // const {RsdoctorWebpackMultiplePlugin} = require('@rsdoctor/webpack-plugin'); - // eslint-disable-next-line - const {RsdoctorRspackMultiplePlugin} = require('@rsdoctor/rspack-plugin'); - - // return new RsdoctorWebpackMultiplePlugin({ - return new RsdoctorRspackMultiplePlugin({ - name: isServer ? 'server' : 'client', - disableTOSUpload: true, - supports: { - // https://rsdoctor.dev/config/options/options#generatetilegraph - generateTileGraph: true, - }, - linter: { - rules: { - 'ecma-version-check': 'off', - }, - }, - }); -} - -export default (async function RsdoctorPlugin() { - if (!process.env.RSDOCTOR) { - return null; - } - const pluginClient = await createRsdoctorBundlerPlugin({isServer: false}); - const pluginServer = await createRsdoctorBundlerPlugin({isServer: true}); - console.log('Rsdoctor plugin enabled'); - return { - name: 'rsdoctor-plugin', - configureWebpack: (__config, isServer) => { - const plugin = isServer ? pluginServer : pluginClient; - return { - plugins: [plugin], - }; - }, - }; -} satisfies PluginConfig);