Skip to content

Commit

Permalink
feat(svgr): create new Docusaurus SVGR plugin (facebook#10677)
Browse files Browse the repository at this point in the history
  • Loading branch information
slorber authored Nov 29, 2024
1 parent 750edc7 commit df6f53a
Show file tree
Hide file tree
Showing 31 changed files with 1,247 additions and 149 deletions.
9 changes: 8 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,14 @@ module.exports = {
// We don't provide any escape hatches for this rule. Rest siblings and
// function placeholder params are always ignored, and any other unused
// locals must be justified with a disable comment.
'@typescript-eslint/no-unused-vars': [ERROR, {ignoreRestSiblings: true}],
'@typescript-eslint/no-unused-vars': [
ERROR,
{
ignoreRestSiblings: true,
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'@typescript-eslint/prefer-optional-chain': ERROR,
'@docusaurus/no-html-links': ERROR,
'@docusaurus/prefer-docusaurus-heading': ERROR,
Expand Down
3 changes: 3 additions & 0 deletions packages/docusaurus-module-type-aliases/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,9 @@ declare module '@docusaurus/useGlobalData' {
export default function useGlobalData(): GlobalData;
}

// TODO find a way to move this ambient type to the SVGR plugin?
// unfortunately looks complicated in practice
// see https://x.com/sebastienlorber/status/1859543512661832053
declare module '*.svg' {
import type {ComponentType, SVGProps} from 'react';

Expand Down
3 changes: 3 additions & 0 deletions packages/docusaurus-plugin-svgr/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.tsbuildinfo*
tsconfig*
__tests__
7 changes: 7 additions & 0 deletions packages/docusaurus-plugin-svgr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# `@docusaurus/plugin-svgr`

[SVGR](https://react-svgr.com/) plugin for Docusaurus.

## Usage

See [plugin-svgr documentation](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-svgr).
37 changes: 37 additions & 0 deletions packages/docusaurus-plugin-svgr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@docusaurus/plugin-svgr",
"version": "3.6.3",
"description": "SVGR 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-svgr"
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.6.3",
"@docusaurus/types": "3.6.3",
"@docusaurus/utils": "3.6.3",
"@docusaurus/utils-validation": "3.6.3",
"@svgr/core": "8.1.0",
"@svgr/webpack": "^8.1.0",
"tslib": "^2.6.0",
"webpack": "^5.88.1"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"engines": {
"node": ">=18.0"
}
}
100 changes: 100 additions & 0 deletions packages/docusaurus-plugin-svgr/src/__tests__/options.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* 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('svgrConfig', () => {
it('accepts undefined', () => {
expect(validate({svgrConfig: undefined})).toEqual(
result(DEFAULT_OPTIONS),
);
});

it('accepts empty', () => {
expect(validate({svgrConfig: {}})).toEqual(result(DEFAULT_OPTIONS));
});

it('accepts any record', () => {
expect(validate({svgrConfig: {any: 'value', evenNumbers: 42}})).toEqual(
result({
...DEFAULT_OPTIONS,
svgrConfig: {
any: 'value',
evenNumbers: 42,
},
}),
);
});

it('accepts default', () => {
expect(validate({svgrConfig: DEFAULT_OPTIONS.svgrConfig})).toEqual(
result(DEFAULT_OPTIONS),
);
});

it('rejects number values', () => {
expect(() =>
// @ts-expect-error: invalid type
validate({svgrConfig: 42}),
).toThrowErrorMatchingInlineSnapshot(
`""svgrConfig" must be of type object"`,
);
});
});
});
30 changes: 30 additions & 0 deletions packages/docusaurus-plugin-svgr/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {createLoader} from './svgrLoader';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {PluginOptions, Options} from './options';

export default function pluginSVGR(
_context: LoadContext,
options: PluginOptions,
): Plugin {
return {
name: 'docusaurus-plugin-svgr',
configureWebpack: (config, isServer) => {
return {
module: {
rules: [createLoader({isServer, svgrConfig: options.svgrConfig})],
},
};
},
};
}

export {validateOptions} from './options';

export type {PluginOptions, Options};
41 changes: 41 additions & 0 deletions packages/docusaurus-plugin-svgr/src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* 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';

// TODO unfortunately there's a SVGR TS error when skipLibCheck=false
// related to prettier, see https://github.com/gregberge/svgr/issues/904
// import type {Config as SVGRConfig} from '@svgr/core';
// export type {SVGRConfig};
export type SVGRConfig = any;
export type SVGOConfig = NonNullable<SVGRConfig['svgoConfig']>;

export type PluginOptions = {
svgrConfig: SVGRConfig;
};

export type Options = {
svgrConfig?: Partial<SVGRConfig>;
};

export const DEFAULT_OPTIONS: Partial<PluginOptions> = {
svgrConfig: {},
};

const pluginOptionsSchema = Joi.object<PluginOptions>({
svgrConfig: Joi.object()
.pattern(Joi.string(), Joi.any())
.optional()
.default(DEFAULT_OPTIONS.svgrConfig),
}).default(DEFAULT_OPTIONS);

export function validateOptions({
validate,
options,
}: OptionValidationContext<Options | undefined, PluginOptions>): PluginOptions {
return validate(pluginOptionsSchema, options);
}
69 changes: 69 additions & 0 deletions packages/docusaurus-plugin-svgr/src/svgrLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* 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 {getFileLoaderUtils} from '@docusaurus/utils';

import type {SVGRConfig, SVGOConfig} from './options';
import type {RuleSetRule} from 'webpack';

// TODO Docusaurus v4: change these defaults?
// see https://github.com/facebook/docusaurus/issues/8297
// see https://github.com/facebook/docusaurus/pull/10205
// see https://github.com/facebook/docusaurus/pull/10211
const DefaultSVGOConfig: SVGOConfig = {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeTitle: false,
removeViewBox: false,
},
},
},
],
};

const DefaultSVGRConfig: SVGRConfig = {
prettier: false,
svgo: true,
svgoConfig: DefaultSVGOConfig,
titleProp: true,
};

type Params = {isServer: boolean; svgrConfig: SVGRConfig};

function createSVGRLoader(params: Params): RuleSetRule {
const options: SVGRConfig = {
...DefaultSVGRConfig,
...params.svgrConfig,
};
return {
loader: require.resolve('@svgr/webpack'),
options,
};
}

export function createLoader(params: Params): RuleSetRule {
const utils = getFileLoaderUtils(params.isServer);
return {
test: /\.svg$/i,
oneOf: [
{
use: [createSVGRLoader(params)],
// We don't want to use SVGR loader for non-React source code
// ie we don't want to use SVGR for CSS files...
issuer: {
and: [/\.(?:tsx?|jsx?|mdx?)$/i],
},
},
{
use: [utils.loaders.url({folder: 'images'})],
},
],
};
}
8 changes: 8 additions & 0 deletions packages/docusaurus-plugin-svgr/src/types.d.ts
Original file line number Diff line number Diff line change
@@ -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.
*/

/// <reference types="@docusaurus/module-type-aliases" />
8 changes: 8 additions & 0 deletions packages/docusaurus-plugin-svgr/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"noEmit": false
},
"include": ["src"],
"exclude": ["**/__tests__/**"]
}
1 change: 1 addition & 0 deletions packages/docusaurus-preset-classic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@docusaurus/plugin-google-gtag": "3.6.3",
"@docusaurus/plugin-google-tag-manager": "3.6.3",
"@docusaurus/plugin-sitemap": "3.6.3",
"@docusaurus/plugin-svgr": "3.6.3",
"@docusaurus/theme-classic": "3.6.3",
"@docusaurus/theme-common": "3.6.3",
"@docusaurus/theme-search-algolia": "3.6.3",
Expand Down
4 changes: 4 additions & 0 deletions packages/docusaurus-preset-classic/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default function preset(
blog,
pages,
sitemap,
svgr,
theme,
googleAnalytics,
gtag,
Expand Down Expand Up @@ -92,6 +93,9 @@ export default function preset(
if (sitemap !== false && (isProd || debug)) {
plugins.push(makePluginConfig('@docusaurus/plugin-sitemap', sitemap));
}
if (svgr !== false) {
plugins.push(makePluginConfig('@docusaurus/plugin-svgr', svgr));
}
if (Object.keys(rest).length > 0) {
throw new Error(
`Unrecognized keys ${Object.keys(rest).join(
Expand Down
3 changes: 3 additions & 0 deletions packages/docusaurus-preset-classic/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {Options as DocsPluginOptions} from '@docusaurus/plugin-content-docs
import type {Options as BlogPluginOptions} from '@docusaurus/plugin-content-blog';
import type {Options as PagesPluginOptions} from '@docusaurus/plugin-content-pages';
import type {Options as SitemapPluginOptions} from '@docusaurus/plugin-sitemap';
import type {Options as SVGRPluginOptions} from '@docusaurus/plugin-svgr';
import type {Options as GAPluginOptions} from '@docusaurus/plugin-google-analytics';
import type {Options as GtagPluginOptions} from '@docusaurus/plugin-google-gtag';
import type {Options as GTMPluginOptions} from '@docusaurus/plugin-google-tag-manager';
Expand All @@ -31,6 +32,8 @@ export type Options = {
pages?: false | PagesPluginOptions;
/** Options for `@docusaurus/plugin-sitemap`. Use `false` to disable. */
sitemap?: false | SitemapPluginOptions;
/** Options for `@docusaurus/plugin-svgr`. Use `false` to disable. */
svgr?: false | SVGRPluginOptions;
/** Options for `@docusaurus/theme-classic`. */
theme?: ThemeOptions;
/**
Expand Down
Loading

0 comments on commit df6f53a

Please sign in to comment.