Skip to content

Commit df6f53a

Browse files
authored
feat(svgr): create new Docusaurus SVGR plugin (#10677)
1 parent 750edc7 commit df6f53a

File tree

31 files changed

+1247
-149
lines changed

31 files changed

+1247
-149
lines changed

.eslintrc.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,14 @@ module.exports = {
380380
// We don't provide any escape hatches for this rule. Rest siblings and
381381
// function placeholder params are always ignored, and any other unused
382382
// locals must be justified with a disable comment.
383-
'@typescript-eslint/no-unused-vars': [ERROR, {ignoreRestSiblings: true}],
383+
'@typescript-eslint/no-unused-vars': [
384+
ERROR,
385+
{
386+
ignoreRestSiblings: true,
387+
argsIgnorePattern: '^_',
388+
varsIgnorePattern: '^_',
389+
},
390+
],
384391
'@typescript-eslint/prefer-optional-chain': ERROR,
385392
'@docusaurus/no-html-links': ERROR,
386393
'@docusaurus/prefer-docusaurus-heading': ERROR,

packages/docusaurus-module-type-aliases/src/index.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,9 @@ declare module '@docusaurus/useGlobalData' {
369369
export default function useGlobalData(): GlobalData;
370370
}
371371

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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.tsbuildinfo*
2+
tsconfig*
3+
__tests__
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# `@docusaurus/plugin-svgr`
2+
3+
[SVGR](https://react-svgr.com/) plugin for Docusaurus.
4+
5+
## Usage
6+
7+
See [plugin-svgr documentation](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-svgr).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "@docusaurus/plugin-svgr",
3+
"version": "3.6.3",
4+
"description": "SVGR plugin for Docusaurus.",
5+
"main": "lib/index.js",
6+
"types": "lib/index.d.ts",
7+
"scripts": {
8+
"build": "tsc --build",
9+
"watch": "tsc --build --watch"
10+
},
11+
"publishConfig": {
12+
"access": "public"
13+
},
14+
"repository": {
15+
"type": "git",
16+
"url": "https://github.com/facebook/docusaurus.git",
17+
"directory": "packages/docusaurus-plugin-svgr"
18+
},
19+
"license": "MIT",
20+
"dependencies": {
21+
"@docusaurus/core": "3.6.3",
22+
"@docusaurus/types": "3.6.3",
23+
"@docusaurus/utils": "3.6.3",
24+
"@docusaurus/utils-validation": "3.6.3",
25+
"@svgr/core": "8.1.0",
26+
"@svgr/webpack": "^8.1.0",
27+
"tslib": "^2.6.0",
28+
"webpack": "^5.88.1"
29+
},
30+
"peerDependencies": {
31+
"react": "^18.0.0",
32+
"react-dom": "^18.0.0"
33+
},
34+
"engines": {
35+
"node": ">=18.0"
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {normalizePluginOptions} from '@docusaurus/utils-validation';
9+
import {
10+
validateOptions,
11+
type PluginOptions,
12+
type Options,
13+
DEFAULT_OPTIONS,
14+
} from '../options';
15+
import type {Validate} from '@docusaurus/types';
16+
17+
function validate(options?: Options) {
18+
return validateOptions({
19+
validate: normalizePluginOptions as Validate<
20+
Options | undefined,
21+
PluginOptions
22+
>,
23+
options,
24+
});
25+
}
26+
27+
function result(options?: Options) {
28+
return {
29+
id: 'default',
30+
...DEFAULT_OPTIONS,
31+
...options,
32+
};
33+
}
34+
35+
describe('validateOptions', () => {
36+
it('accepts undefined', () => {
37+
expect(validate(undefined)).toEqual(result(DEFAULT_OPTIONS));
38+
});
39+
40+
it('accepts empty object', () => {
41+
expect(validate({})).toEqual(result(DEFAULT_OPTIONS));
42+
});
43+
44+
it('accepts defaults', () => {
45+
expect(validate(DEFAULT_OPTIONS)).toEqual(result(DEFAULT_OPTIONS));
46+
});
47+
48+
it('rejects null', () => {
49+
expect(
50+
// @ts-expect-error: TS should error
51+
() => validate(null),
52+
).toThrowErrorMatchingInlineSnapshot(`""value" must be of type object"`);
53+
});
54+
55+
it('rejects number', () => {
56+
expect(
57+
// @ts-expect-error: TS should error
58+
() => validate(42),
59+
).toThrowErrorMatchingInlineSnapshot(`""value" must be of type object"`);
60+
});
61+
62+
describe('svgrConfig', () => {
63+
it('accepts undefined', () => {
64+
expect(validate({svgrConfig: undefined})).toEqual(
65+
result(DEFAULT_OPTIONS),
66+
);
67+
});
68+
69+
it('accepts empty', () => {
70+
expect(validate({svgrConfig: {}})).toEqual(result(DEFAULT_OPTIONS));
71+
});
72+
73+
it('accepts any record', () => {
74+
expect(validate({svgrConfig: {any: 'value', evenNumbers: 42}})).toEqual(
75+
result({
76+
...DEFAULT_OPTIONS,
77+
svgrConfig: {
78+
any: 'value',
79+
evenNumbers: 42,
80+
},
81+
}),
82+
);
83+
});
84+
85+
it('accepts default', () => {
86+
expect(validate({svgrConfig: DEFAULT_OPTIONS.svgrConfig})).toEqual(
87+
result(DEFAULT_OPTIONS),
88+
);
89+
});
90+
91+
it('rejects number values', () => {
92+
expect(() =>
93+
// @ts-expect-error: invalid type
94+
validate({svgrConfig: 42}),
95+
).toThrowErrorMatchingInlineSnapshot(
96+
`""svgrConfig" must be of type object"`,
97+
);
98+
});
99+
});
100+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {createLoader} from './svgrLoader';
9+
import type {LoadContext, Plugin} from '@docusaurus/types';
10+
import type {PluginOptions, Options} from './options';
11+
12+
export default function pluginSVGR(
13+
_context: LoadContext,
14+
options: PluginOptions,
15+
): Plugin {
16+
return {
17+
name: 'docusaurus-plugin-svgr',
18+
configureWebpack: (config, isServer) => {
19+
return {
20+
module: {
21+
rules: [createLoader({isServer, svgrConfig: options.svgrConfig})],
22+
},
23+
};
24+
},
25+
};
26+
}
27+
28+
export {validateOptions} from './options';
29+
30+
export type {PluginOptions, Options};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
import {Joi} from '@docusaurus/utils-validation';
8+
import type {OptionValidationContext} from '@docusaurus/types';
9+
10+
// TODO unfortunately there's a SVGR TS error when skipLibCheck=false
11+
// related to prettier, see https://github.com/gregberge/svgr/issues/904
12+
// import type {Config as SVGRConfig} from '@svgr/core';
13+
// export type {SVGRConfig};
14+
export type SVGRConfig = any;
15+
export type SVGOConfig = NonNullable<SVGRConfig['svgoConfig']>;
16+
17+
export type PluginOptions = {
18+
svgrConfig: SVGRConfig;
19+
};
20+
21+
export type Options = {
22+
svgrConfig?: Partial<SVGRConfig>;
23+
};
24+
25+
export const DEFAULT_OPTIONS: Partial<PluginOptions> = {
26+
svgrConfig: {},
27+
};
28+
29+
const pluginOptionsSchema = Joi.object<PluginOptions>({
30+
svgrConfig: Joi.object()
31+
.pattern(Joi.string(), Joi.any())
32+
.optional()
33+
.default(DEFAULT_OPTIONS.svgrConfig),
34+
}).default(DEFAULT_OPTIONS);
35+
36+
export function validateOptions({
37+
validate,
38+
options,
39+
}: OptionValidationContext<Options | undefined, PluginOptions>): PluginOptions {
40+
return validate(pluginOptionsSchema, options);
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {getFileLoaderUtils} from '@docusaurus/utils';
9+
10+
import type {SVGRConfig, SVGOConfig} from './options';
11+
import type {RuleSetRule} from 'webpack';
12+
13+
// TODO Docusaurus v4: change these defaults?
14+
// see https://github.com/facebook/docusaurus/issues/8297
15+
// see https://github.com/facebook/docusaurus/pull/10205
16+
// see https://github.com/facebook/docusaurus/pull/10211
17+
const DefaultSVGOConfig: SVGOConfig = {
18+
plugins: [
19+
{
20+
name: 'preset-default',
21+
params: {
22+
overrides: {
23+
removeTitle: false,
24+
removeViewBox: false,
25+
},
26+
},
27+
},
28+
],
29+
};
30+
31+
const DefaultSVGRConfig: SVGRConfig = {
32+
prettier: false,
33+
svgo: true,
34+
svgoConfig: DefaultSVGOConfig,
35+
titleProp: true,
36+
};
37+
38+
type Params = {isServer: boolean; svgrConfig: SVGRConfig};
39+
40+
function createSVGRLoader(params: Params): RuleSetRule {
41+
const options: SVGRConfig = {
42+
...DefaultSVGRConfig,
43+
...params.svgrConfig,
44+
};
45+
return {
46+
loader: require.resolve('@svgr/webpack'),
47+
options,
48+
};
49+
}
50+
51+
export function createLoader(params: Params): RuleSetRule {
52+
const utils = getFileLoaderUtils(params.isServer);
53+
return {
54+
test: /\.svg$/i,
55+
oneOf: [
56+
{
57+
use: [createSVGRLoader(params)],
58+
// We don't want to use SVGR loader for non-React source code
59+
// ie we don't want to use SVGR for CSS files...
60+
issuer: {
61+
and: [/\.(?:tsx?|jsx?|mdx?)$/i],
62+
},
63+
},
64+
{
65+
use: [utils.loaders.url({folder: 'images'})],
66+
},
67+
],
68+
};
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
/// <reference types="@docusaurus/module-type-aliases" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"noEmit": false
5+
},
6+
"include": ["src"],
7+
"exclude": ["**/__tests__/**"]
8+
}

packages/docusaurus-preset-classic/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@docusaurus/plugin-google-gtag": "3.6.3",
2828
"@docusaurus/plugin-google-tag-manager": "3.6.3",
2929
"@docusaurus/plugin-sitemap": "3.6.3",
30+
"@docusaurus/plugin-svgr": "3.6.3",
3031
"@docusaurus/theme-classic": "3.6.3",
3132
"@docusaurus/theme-common": "3.6.3",
3233
"@docusaurus/theme-search-algolia": "3.6.3",

packages/docusaurus-preset-classic/src/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export default function preset(
3737
blog,
3838
pages,
3939
sitemap,
40+
svgr,
4041
theme,
4142
googleAnalytics,
4243
gtag,
@@ -92,6 +93,9 @@ export default function preset(
9293
if (sitemap !== false && (isProd || debug)) {
9394
plugins.push(makePluginConfig('@docusaurus/plugin-sitemap', sitemap));
9495
}
96+
if (svgr !== false) {
97+
plugins.push(makePluginConfig('@docusaurus/plugin-svgr', svgr));
98+
}
9599
if (Object.keys(rest).length > 0) {
96100
throw new Error(
97101
`Unrecognized keys ${Object.keys(rest).join(

packages/docusaurus-preset-classic/src/options.ts

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {Options as DocsPluginOptions} from '@docusaurus/plugin-content-docs
99
import type {Options as BlogPluginOptions} from '@docusaurus/plugin-content-blog';
1010
import type {Options as PagesPluginOptions} from '@docusaurus/plugin-content-pages';
1111
import type {Options as SitemapPluginOptions} from '@docusaurus/plugin-sitemap';
12+
import type {Options as SVGRPluginOptions} from '@docusaurus/plugin-svgr';
1213
import type {Options as GAPluginOptions} from '@docusaurus/plugin-google-analytics';
1314
import type {Options as GtagPluginOptions} from '@docusaurus/plugin-google-gtag';
1415
import type {Options as GTMPluginOptions} from '@docusaurus/plugin-google-tag-manager';
@@ -31,6 +32,8 @@ export type Options = {
3132
pages?: false | PagesPluginOptions;
3233
/** Options for `@docusaurus/plugin-sitemap`. Use `false` to disable. */
3334
sitemap?: false | SitemapPluginOptions;
35+
/** Options for `@docusaurus/plugin-svgr`. Use `false` to disable. */
36+
svgr?: false | SVGRPluginOptions;
3437
/** Options for `@docusaurus/theme-classic`. */
3538
theme?: ThemeOptions;
3639
/**

0 commit comments

Comments
 (0)