Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugin-svgr): support for react query #1783

Merged
merged 1 commit into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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