Skip to content

Commit

Permalink
feat(plugin-svgr): support for react query (#1783)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenjiahan authored Mar 11, 2024
1 parent 4d8b102 commit 6683ffb
Show file tree
Hide file tree
Showing 10 changed files with 376 additions and 89 deletions.
24 changes: 24 additions & 0 deletions e2e/cases/svg/svgr-query-react/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { expect, test } from '@playwright/test';
import { build, gotoPage } from '@e2e/helper';

test('should import default from SVG with react query correctly', async ({
page,
}) => {
const rsbuild = await build({
cwd: __dirname,
runServer: true,
});

await gotoPage(page, rsbuild);

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

// test svg asset
await expect(
page.evaluate(`document.getElementById('url').src`),
).resolves.toMatch(/http:/);

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

export default defineConfig({
plugins: [pluginReact(), pluginSvgr()],
});
13 changes: 13 additions & 0 deletions e2e/cases/svg/svgr-query-react/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import url from './small.svg?url';
import Component from './small.svg?react';

function App() {
return (
<div>
<Component id="component" />
<img id="url" src={url} alt="url" />
</div>
);
}

export default App;
9 changes: 9 additions & 0 deletions e2e/cases/svg/svgr-query-react/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));
}
3 changes: 3 additions & 0 deletions e2e/cases/svg/svgr-query-react/src/small.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 41 additions & 1 deletion packages/core/src/plugins/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,50 @@ import {
IMAGE_EXTENSIONS,
VIDEO_EXTENSIONS,
AUDIO_EXTENSIONS,
chainStaticAssetRule,
type BundlerChainRule,
} from '@rsbuild/shared';
import type { RsbuildPlugin } from '../types';

const chainStaticAssetRule = ({
rule,
maxSize,
filename,
assetType,
}: {
rule: BundlerChainRule;
maxSize: number;
filename: string;
assetType: string;
}) => {
// force to url: "foo.png?url" or "foo.png?__inline=false"
rule
.oneOf(`${assetType}-asset-url`)
.type('asset/resource')
.resourceQuery(/(__inline=false|url)/)
.set('generator', {
filename,
});

// force to inline: "foo.png?inline"
rule
.oneOf(`${assetType}-asset-inline`)
.type('asset/inline')
.resourceQuery(/inline/);

// default: when size < dataUrlCondition.maxSize will inline
rule
.oneOf(`${assetType}-asset`)
.type('asset')
.parser({
dataUrlCondition: {
maxSize,
},
})
.set('generator', {
filename,
});
};

export function getRegExpForExts(exts: string[]): RegExp {
const matcher = exts
.map((ext) => ext.trim())
Expand Down
125 changes: 84 additions & 41 deletions packages/plugin-svgr/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
getDistPath,
getFilename,
SCRIPT_REGEX,
chainStaticAssetRule,
} from '@rsbuild/shared';
import { PLUGIN_REACT_NAME } from '@rsbuild/plugin-react';
import type { RsbuildPlugin } from '@rsbuild/core';
Expand Down Expand Up @@ -53,52 +52,19 @@ export const pluginSvgr = (options: PluginSvgrOptions = {}): RsbuildPlugin => ({
setup(api) {
api.modifyBundlerChain(async (chain, { isProd, CHAIN_ID }) => {
const config = api.getNormalizedConfig();

const { svgDefaultExport = 'url' } = options;
const assetType = 'svg';
const distDir = getDistPath(config, assetType);
const filename = getFilename(config, assetType, isProd);
const distDir = getDistPath(config, 'svg');
const filename = getFilename(config, 'svg', isProd);
const outputName = path.posix.join(distDir, filename);
const { dataUriLimit } = config.output;
const maxSize =
typeof dataUriLimit === 'number'
? dataUriLimit
: dataUriLimit[assetType];
typeof dataUriLimit === 'number' ? dataUriLimit : dataUriLimit.svg;

// delete origin rules
// delete Rsbuild builtin SVG rules
chain.module.rules.delete(CHAIN_ID.RULE.SVG);

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

// If we import SVG from a CSS file, it will be processed as assets.
chainStaticAssetRule({
rule,
maxSize,
filename: path.posix.join(distDir, filename),
assetType,
issuer: {
// The issuer option ensures that SVGR will only apply if the SVG is imported from a JS file.
not: [SCRIPT_REGEX],
},
});

const jsRule = chain.module.rules.get(CHAIN_ID.RULE.JS);
const svgrRule = rule.oneOf(CHAIN_ID.ONE_OF.SVG).type('javascript/auto');

[CHAIN_ID.USE.SWC, CHAIN_ID.USE.BABEL].some((id) => {
const use = jsRule.uses.get(id);

if (use) {
svgrRule
.use(id)
.loader(use.get('loader'))
.options(use.get('options'));
return true;
}

return false;
});

const svgrOptions = deepmerge(
{
svgo: true,
Expand All @@ -107,12 +73,65 @@ export const pluginSvgr = (options: PluginSvgrOptions = {}): RsbuildPlugin => ({
options.svgrOptions || {},
);

svgrRule
// force to url: "foo.svg?url",
rule
.oneOf(CHAIN_ID.ONE_OF.SVG_URL)
.type('asset/resource')
.resourceQuery(/(__inline=false|url)/)
.set('generator', {
filename: outputName,
});

// force to inline: "foo.svg?inline"
rule
.oneOf(CHAIN_ID.ONE_OF.SVG_INLINE)
.type('asset/inline')
.resourceQuery(/inline/);

// force to react component: "foo.svg?react"
rule
.oneOf(CHAIN_ID.ONE_OF.SVG_REACT)
.type('javascript/auto')
.resourceQuery(/react/)
.use(CHAIN_ID.USE.SVGR)
.loader(path.resolve(__dirname, './loader'))
.options(svgrOptions)
.options({
...svgrOptions,
exportType: 'default',
} satisfies Config)
.end();

// SVG in non-JS files
// default: when size < dataUrlCondition.maxSize will inline
rule
.oneOf(CHAIN_ID.ONE_OF.SVG_ASSET)
.type('asset')
.parser({
dataUrlCondition: {
maxSize,
},
})
.set('generator', {
filename: outputName,
})
.set('issuer', {
// The issuer option ensures that SVGR will only apply if the SVG is imported from a JS file.
not: [SCRIPT_REGEX],
});

// SVG in JS files
const exportType = svgDefaultExport === 'url' ? 'named' : 'default';
rule
.oneOf(CHAIN_ID.ONE_OF.SVG)
.type('javascript/auto')
.use(CHAIN_ID.USE.SVGR)
.loader(path.resolve(__dirname, './loader'))
.options({
...svgrOptions,
exportType,
})
.end()
.when(svgDefaultExport === 'url', (c) =>
.when(exportType === 'named', (c) =>
c
.use(CHAIN_ID.USE.URL)
.loader(path.join(__dirname, '../compiled', 'url-loader'))
Expand All @@ -121,6 +140,30 @@ export const pluginSvgr = (options: PluginSvgrOptions = {}): RsbuildPlugin => ({
name: outputName,
}),
);

// apply current JS transform rule to SVGR rules
const jsRule = chain.module.rules.get(CHAIN_ID.RULE.JS);

[CHAIN_ID.USE.SWC, CHAIN_ID.USE.BABEL].some((jsUseId) => {
const use = jsRule.uses.get(jsUseId);

if (use) {
[CHAIN_ID.ONE_OF.SVG, CHAIN_ID.ONE_OF.SVG_REACT].forEach(
(oneOfId) => {
rule
.oneOf(oneOfId)
.use(jsUseId)
.before(CHAIN_ID.USE.SVGR)
.loader(use.get('loader'))
.options(use.get('options'));
},
);

return true;
}

return false;
});
});
},
});
Loading

0 comments on commit 6683ffb

Please sign in to comment.