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

Lines changed: 8 additions & 1 deletion
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.tsbuildinfo*
2+
tsconfig*
3+
__tests__
Lines changed: 7 additions & 0 deletions
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).
Lines changed: 37 additions & 0 deletions
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+
}
Lines changed: 100 additions & 0 deletions
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+
});
Lines changed: 30 additions & 0 deletions
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};
Lines changed: 41 additions & 0 deletions
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+
}
Lines changed: 69 additions & 0 deletions
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+
}
Lines changed: 8 additions & 0 deletions
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" />

0 commit comments

Comments
 (0)