Skip to content

Commit

Permalink
feat(react): Add SvgOptions for NxReactWebpackPlugin and WithNx (#23283)
Browse files Browse the repository at this point in the history
This PR adds the ability to now override our svg options by providing
them either using `NxReactWebpackPlugin` for react apps or `withNx` for
Next.js apps

```
new NxReactWebpackPlugin({
  svgr: {
    svgo: true,
    titleProp: true,
    ref: true,
  }
}),
  ```

This now gives you control on customizing how the svg is handled. Should you need to enable svgo you can provide the config using `svgr.config.js`

https://react-svgr.com/docs/options/#svgo

closes: #9487
  • Loading branch information
ndcunningham authored May 13, 2024
1 parent 8cda56e commit 9cd0b42
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 15 deletions.
63 changes: 63 additions & 0 deletions e2e/next/src/next-svgr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,67 @@ describe('NextJs SVGR support', () => {
);
expect(svgFile).toBeTruthy();
});

it('should allow both SVG asset and SVGR component to be used (using SvgrOptions)', () => {
const appName = uniq('app');
runCLI(
`generate @nx/next:app ${appName} --no-interactive --appDir=true --src=true`
);
createFile(
`apps/${appName}/src/app/nx.svg`,
`
<svg version="1.1" width="300" height="200" xmlns="http://www.w3.org/2000/svg">
<text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG for app</text>
</svg>
`
);
updateFile(
`apps/${appName}/src/app/page.tsx`,
`
import Image from 'next/image';
import svgImg, { ReactComponent as Logo } from './nx.svg';
export default async function Index() {
return (
<>
<Image src={svgImg} alt="Alt for SVG img tag" />
<Logo />
</>
);
}
`
);
updateFile(
`apps/${appName}/next.config.js`,
`
const { composePlugins, withNx } = require('@nx/next');
const nextConfig = {
nx: {
svgr: {
svgo: false,
titleProp: true,
ref: true,
},
},
};
const plugins = [
withNx,
];
module.exports = composePlugins(...plugins)(nextConfig);
`
);

runCLI(`build ${appName}`);

const pageFile = readFile(`apps/${appName}/.next/server/app/page.js`);
const svgFile = listFiles(`apps/${appName}/.next/static/media`).find((f) =>
/nx\.[a-z0-9]+\.svg$/.test(f)
);
expect(`apps/${appName}/.next/static/chunks/app/${pageFile}`).toMatch(
/SVG for app/
);
expect(`apps/${appName}/.next/static/chunks/app/${pageFile}`).toMatch(
/Alt for SVG img tag/
);
expect(svgFile).toBeTruthy();
});
});
83 changes: 83 additions & 0 deletions e2e/react/src/react-webpack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,87 @@ describe('Build React applications and libraries with Vite', () => {
expect(mainContent).toMatch(/Alt for SVG img tag/);
expect(svgFile).toBeTruthy();
}, 300_000);

it('should support SVGR and SVG asset in the same project (using SvgrOptions)', async () => {
const appName = uniq('app');

runCLI(
`generate @nx/react:app ${appName} --bundler=webpack --compiler=babel --unitTestRunner=none --no-interactive`
);
createFile(
`apps/${appName}/src/app/nx.svg`,
`
<svg version="1.1" width="300" height="200" xmlns="http://www.w3.org/2000/svg">
<text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG for app</text>
</svg>
`
);
updateFile(
`apps/${appName}/src/app/app.tsx`,
`
import svgImg, { ReactComponent as Logo } from './nx.svg';
export function App() {
return (
<>
<img src={svgImg} alt="Alt for SVG img tag" />
<Logo />
</>
);
}
export default App;
`
);

updateFile(
`apps/${appName}/webpack.config.js`,
`
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
const { NxReactWebpackPlugin } = require('@nx/react/webpack-plugin');
const { join } = require('path');
module.exports = {
output: {
path: join(__dirname, '../../dist/apps/${appName}'),
},
devServer: {
port: 4201,
},
plugins: [
new NxAppWebpackPlugin({
tsConfig: './tsconfig.app.json',
compiler: 'babel',
main: './src/main.tsx',
index: './src/index.html',
baseHref: '/',
assets: ['./src/favicon.ico', './src/assets'],
styles: ['./src/styles.css'],
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
optimization: process.env['NODE_ENV'] === 'production',
}),
new NxReactWebpackPlugin({
// Uncomment this line if you don't want to use SVGR
// See: https://react-svgr.com/
svgr: {
svgo: false,
titleProp: true,
ref: true,
},
}),
],
};
`
);

await runCLIAsync(`build ${appName}`);

const outFiles = listFiles(`dist/apps/${appName}`);
const mainFile = outFiles.find((f) => f.startsWith('main.'));
const mainContent = readFile(`dist/apps/${appName}/${mainFile}`);
const svgFile = outFiles.find((f) => f.endsWith('.svg'));
expect(mainContent).toMatch(/SVG for app/);
expect(mainContent).toMatch(/Alt for SVG img tag/);
expect(svgFile).toBeTruthy();
}, 300_000);
});
24 changes: 17 additions & 7 deletions packages/next/plugins/with-nx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ import {
type Target,
} from '@nx/devkit';

export interface SvgrOptions {
svgo?: boolean;
titleProp?: boolean;
ref?: boolean;
}

export interface WithNxOptions extends NextConfig {
nx?: {
svgr?: boolean;
svgr?: boolean | SvgrOptions;
babelUpwardRootMode?: boolean;
};
}
Expand Down Expand Up @@ -331,7 +337,15 @@ export function getNextConfig(
*/

// Default SVGR support to be on for projects.
if (nx?.svgr !== false) {
if (nx?.svgr !== false || typeof nx?.svgr === 'object') {
const defaultSvgrOptions = {
svgo: false,
titleProp: true,
ref: true,
};

const svgrOptions =
typeof nx?.svgr === 'object' ? nx.svgr : defaultSvgrOptions;
// TODO(v20): Remove file-loader and use `?react` querystring to differentiate between asset and SVGR.
// It should be:
// use: [{
Expand Down Expand Up @@ -365,11 +379,7 @@ export function getNextConfig(
use: [
{
loader: require.resolve('@svgr/webpack'),
options: {
svgo: false,
titleProp: true,
ref: true,
},
options: svgrOptions,
},
{
loader: require.resolve('file-loader'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import { Configuration, WebpackOptionsNormalized } from 'webpack';
import { SvgrOptions } from '../../with-react';

export function applyReactConfig(
options: { svgr?: boolean },
options: { svgr?: boolean | SvgrOptions },
config: Partial<WebpackOptionsNormalized | Configuration> = {}
): void {
if (!process.env['NX_TASK_TARGET_PROJECT']) return;

addHotReload(config);

if (options.svgr !== false) {
if (options.svgr !== false || typeof options.svgr === 'object') {
removeSvgLoaderIfPresent(config);

const defaultSvgrOptions = {
svgo: false,
titleProp: true,
ref: true,
};

const svgrOptions =
typeof options.svgr === 'object' ? options.svgr : defaultSvgrOptions;

// TODO(v20): Remove file-loader and use `?react` querystring to differentiate between asset and SVGR.
// It should be:
// use: [{
Expand All @@ -37,11 +47,7 @@ export function applyReactConfig(
use: [
{
loader: require.resolve('@svgr/webpack'),
options: {
svgo: false,
titleProp: true,
ref: true,
},
options: svgrOptions,
},
{
loader: require.resolve('file-loader'),
Expand Down
7 changes: 6 additions & 1 deletion packages/react/plugins/with-react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import { applyReactConfig } from './nx-react-webpack-plugin/lib/apply-react-conf

const processed = new Set();

export interface SvgrOptions {
svgo?: boolean;
titleProp?: boolean;
ref?: boolean;
}
export interface WithReactOptions extends WithWebOptions {
svgr?: false;
svgr?: boolean | SvgrOptions;
}

/**
Expand Down

0 comments on commit 9cd0b42

Please sign in to comment.