Skip to content

Commit

Permalink
fix(plugin-svgr): dedupe SVGO plugins config (#2984)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenjiahan authored Jul 22, 2024
1 parent 16bc758 commit 36297f2
Show file tree
Hide file tree
Showing 15 changed files with 243 additions and 24 deletions.
2 changes: 1 addition & 1 deletion e2e/cases/svg/svgr-default-export-component/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { build, gotoPage } from '@e2e/helper';
import { expect, test } from '@playwright/test';

test('Use SVGR and default export React component', async ({ page }) => {
test('use SVGR and default export React component', async ({ page }) => {
const rsbuild = await build({
cwd: __dirname,
runServer: true,
Expand Down
2 changes: 1 addition & 1 deletion e2e/cases/svg/svgr-exclude-importer/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { build, gotoPage } from '@e2e/helper';
import { expect, test } from '@playwright/test';

test('Use SVGR and exclude some files', async ({ page }) => {
test('use SVGR and exclude some files', async ({ page }) => {
const rsbuild = await build({
cwd: __dirname,
runServer: true,
Expand Down
2 changes: 1 addition & 1 deletion e2e/cases/svg/svgr-exclude/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { build, gotoPage } from '@e2e/helper';
import { expect, test } from '@playwright/test';

test('Use SVGR and exclude some files', async ({ page }) => {
test('use SVGR and exclude some files', async ({ page }) => {
const rsbuild = await build({
cwd: __dirname,
runServer: true,
Expand Down
2 changes: 1 addition & 1 deletion e2e/cases/svg/svgr-external-react/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { build, gotoPage } from '@e2e/helper';
import { expect, test } from '@playwright/test';

// It's an old bug when use svgr in css and external react.
test('Use SVGR and externals react', async ({ page }) => {
test('use SVGR and externals react', async ({ page }) => {
const rsbuild = await build({
cwd: __dirname,
runServer: true,
Expand Down
2 changes: 1 addition & 1 deletion e2e/cases/svg/svgr-named-export-component/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { build, gotoPage } from '@e2e/helper';
import { expect, test } from '@playwright/test';

test('Use SVGR and default export React component', async ({ page }) => {
test('use SVGR and default export React component', async ({ page }) => {
const rsbuild = await build({
cwd: __dirname,
runServer: true,
Expand Down
17 changes: 17 additions & 0 deletions e2e/cases/svg/svgr-override-svgo-options/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { build, gotoPage } from '@e2e/helper';
import { expect, test } from '@playwright/test';

test('use SVGR and override svgo plugin options', async ({ page }) => {
const rsbuild = await build({
cwd: __dirname,
runServer: true,
});

await gotoPage(page, rsbuild);

await expect(
page.evaluate(`document.getElementById('test-svg').tagName === 'svg'`),
).resolves.toBeTruthy();

await rsbuild.close();
});
25 changes: 25 additions & 0 deletions e2e/cases/svg/svgr-override-svgo-options/rsbuild.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginSvgr } from '@rsbuild/plugin-svgr';

export default {
plugins: [
pluginReact(),
pluginSvgr({
svgrOptions: {
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false,
cleanupIds: false,
},
},
},
],
},
},
}),
],
};
12 changes: 12 additions & 0 deletions e2e/cases/svg/svgr-override-svgo-options/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Logo from './with-id.svg?react';

function App() {
return (
<div>
<div id="test">Hello Rsbuild!</div>
<Logo id="test-svg" />
</div>
);
}

export default App;
9 changes: 9 additions & 0 deletions e2e/cases/svg/svgr-override-svgo-options/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

const container = document.getElementById('root');
if (container) {
const root = createRoot(container);
root.render(React.createElement(App));
}
2 changes: 2 additions & 0 deletions e2e/cases/svg/svgr-override-svgo-options/src/with-id.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/plugin-svgr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@types/node": "18.x",
"file-loader": "6.2.0",
"prebundle": "1.2.2",
"svgo": "^3.3.2",
"typescript": "^5.5.2",
"url-loader": "4.1.1"
},
Expand Down
108 changes: 89 additions & 19 deletions packages/plugin-svgr/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import path from 'node:path';
import type { RsbuildPlugin, Rspack } from '@rsbuild/core';
import { PLUGIN_REACT_NAME } from '@rsbuild/plugin-react';
import type { Config } from '@svgr/core';
import type { Config as SvgrOptions } from '@svgr/core';
import deepmerge from 'deepmerge';
import type { Config as SvgoConfig } from 'svgo';

type SvgoPluginConfig = NonNullable<SvgoConfig['plugins']>[0];

export type SvgDefaultExport = 'component' | 'url';

Expand All @@ -13,7 +16,7 @@ export type PluginSvgrOptions = {
* Configure SVGR options.
* @see https://react-svgr.com/docs/options/
*/
svgrOptions?: Config;
svgrOptions?: SvgrOptions;

/**
* Whether to allow the use of default import and named import at the same time.
Expand All @@ -38,23 +41,88 @@ export type PluginSvgrOptions = {
excludeImporter?: Rspack.RuleSetCondition;
};

function getSvgoDefaultConfig() {
return {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
// viewBox is required to resize SVGs with CSS.
// @see https://github.com/svg/svgo/issues/1128
removeViewBox: false,
},
const getSvgoDefaultConfig = (): SvgoConfig => ({
plugins: [
{
name: 'preset-default',
params: {
overrides: {
// viewBox is required to resize SVGs with CSS.
// @see https://github.com/svg/svgo/issues/1128
removeViewBox: false,
},
},
'prefixIds',
],
};
}
},
'prefixIds',
],
});

/**
* Dedupe SVGO plugins config.
*
* @example
* Input:
* {
* plugins: [
* { name: 'preset-default', params: { foo: true }],
* { name: 'preset-default', params: { bar: true }],
* ]
* }
* Output:
* {
* plugins: [
* { name: 'preset-default', params: { foo: true, bar: true }],
* ]
* }
*/
const dedupeSvgoPlugins = (config: SvgoConfig): SvgoConfig => {
if (!config.plugins) {
return config;
}

let mergedPlugins: SvgoPluginConfig[] = [];

for (const plugin of config.plugins) {
if (typeof plugin === 'string') {
const exist = mergedPlugins.find(
(item) =>
item === plugin || (typeof item === 'object' && item.name === plugin),
);

if (!exist) {
mergedPlugins.push(plugin);
}

continue;
}

const strIndex = mergedPlugins.findIndex(
(item) => typeof item === 'string' && item === plugin.name,
);
if (strIndex !== -1) {
mergedPlugins[strIndex] = plugin;
continue;
}

let isMerged = false;

mergedPlugins = mergedPlugins.map((item) => {
if (typeof item === 'object' && item.name === plugin.name) {
isMerged = true;
return deepmerge<SvgoPluginConfig>(item, plugin);
}
return item;
});

if (!isMerged) {
mergedPlugins.push(plugin);
}
}

config.plugins = mergedPlugins;

return config;
};

export const PLUGIN_SVGR_NAME = 'rsbuild:svgr';

Expand Down Expand Up @@ -85,14 +153,16 @@ export const pluginSvgr = (options: PluginSvgrOptions = {}): RsbuildPlugin => ({

const rule = chain.module.rule(CHAIN_ID.RULE.SVG).test(SVG_REGEX);

const svgrOptions = deepmerge(
const svgrOptions = deepmerge<SvgrOptions>(
{
svgo: true,
svgoConfig: getSvgoDefaultConfig(),
},
options.svgrOptions || {},
);

svgrOptions.svgoConfig = dedupeSvgoPlugins(svgrOptions.svgoConfig);

// force to url: "foo.svg?url",
rule
.oneOf(CHAIN_ID.ONE_OF.SVG_URL)
Expand All @@ -116,7 +186,7 @@ export const pluginSvgr = (options: PluginSvgrOptions = {}): RsbuildPlugin => ({
.options({
...svgrOptions,
exportType: 'default',
} satisfies Config)
} satisfies SvgrOptions)
.end();

// SVG in JS files
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions website/docs/en/plugins/list/plugin-svgr.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,46 @@ pluginSvgr({
});
```

When you set `svgoConfig.plugins`, the configuration for plugins with the same name is automatically merged. For example, the following configuration will be merged with the built-in `preset-default`:

```ts
pluginSvgr({
svgrOptions: {
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
cleanupIds: false,
},
},
},
],
},
},
});
```

The merged `svgoConfig` will be:

```ts
const mergedSvgoConfig = {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: true,
cleanupIds: false,
},
},
},
'prefixIds',
],
};
```

### svgrOptions.exportType

Set the export type of SVG React components.
Expand Down
40 changes: 40 additions & 0 deletions website/docs/zh/plugins/list/plugin-svgr.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,46 @@ pluginSvgr({
});
```

当你设置 `svgoConfig.plugins` 时,同名 plugin 的配置会被自动合并,比如下面的配置会与内置的 `preset-default` 进行合并:

```ts
pluginSvgr({
svgrOptions: {
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
cleanupIds: false,
},
},
},
],
},
},
});
```

合并后的 `svgoConfig` `如下:

```ts
const mergedSvgoConfig = {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: true,
cleanupIds: false,
},
},
},
'prefixIds',
],
};
```

### svgrOptions.exportType

设置 SVG React 组件的导出方式。
Expand Down

0 comments on commit 36297f2

Please sign in to comment.