## Current Behavior Currently when a user runs into a compile/runtime error in react application 2 error overlays will show. One from react-refresh-plugin and the second one from webpack-dev-server (which is enabled by default)   ### Steps to reproduce This is reproducible using both webpack config types when the react refresh plugin is applied to the configuration. 1. create fresh nx react app with webpack 2. add `hot: true`in webpack dev server configuration, so react-refresh-plugin gets applied 3. anywhere in the app code throw an error i.e. ``` useEffect(() => { setTimeout(() => { throw new Error('test'); }, 1000); }, []); ``` 4. observe 2 error overlays shown Or clone the repo https://github.com/zoran995/nx-react-error-overlay, branch main is using nx enhanced config, branch default-config is using plain webpack config. Here is also a codesandbox showcasing an issue https://codesandbox.io/p/github/zoran995/nx-react-error-overlay/main?file=%2Fapps%2Forg%2Fsrc%2Fapp%2Fapp.tsx&import=true ## Expected Behavior Only one error should be shown to the user, and this is the actual configuration of the plugin that is used by react scripts. I went with not exposing another config option for nx react webpack plugin as there is already an option to configure webpack dev server error overlay and most of the react community is used to have the react-refresh one disabled.
109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
import { Configuration, WebpackOptionsNormalized } from 'webpack';
|
|
import { SvgrOptions } from '../../with-react';
|
|
|
|
export function applyReactConfig(
|
|
options: { svgr?: boolean | SvgrOptions },
|
|
config: Partial<WebpackOptionsNormalized | Configuration> = {}
|
|
): void {
|
|
if (!process.env['NX_TASK_TARGET_PROJECT']) return;
|
|
|
|
addHotReload(config);
|
|
|
|
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: [{
|
|
// test: /\.svg$/i,
|
|
// type: 'asset',
|
|
// resourceQuery: /react/, // *.svg?react
|
|
// },
|
|
// {
|
|
// test: /\.svg$/i,
|
|
// issuer: /\.[jt]sx?$/,
|
|
// resourceQuery: { not: [/react/] }, // exclude react component if *.svg?react
|
|
// use: ['@svgr/webpack'],
|
|
// }],
|
|
// See:
|
|
// - SVGR: https://react-svgr.com/docs/webpack/#use-svgr-and-asset-svg-in-the-same-project
|
|
// - Vite: https://www.npmjs.com/package/vite-plugin-svgr
|
|
// - Rsbuild: https://github.com/web-infra-dev/rsbuild/pull/1783
|
|
// Note: We also need a migration for any projects that are using SVGR to convert
|
|
// `import { ReactComponent as X } from './x.svg` to
|
|
// `import X from './x.svg?react';
|
|
config.module.rules.push({
|
|
test: /\.svg$/,
|
|
issuer: /\.(js|ts|md)x?$/,
|
|
use: [
|
|
{
|
|
loader: require.resolve('@svgr/webpack'),
|
|
options: svgrOptions,
|
|
},
|
|
{
|
|
loader: require.resolve('file-loader'),
|
|
options: {
|
|
name: '[name].[hash].[ext]',
|
|
},
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
// enable webpack node api
|
|
config.node = {
|
|
__dirname: true,
|
|
__filename: true,
|
|
};
|
|
}
|
|
|
|
function addHotReload(
|
|
config: Partial<WebpackOptionsNormalized | Configuration>
|
|
) {
|
|
if (config.mode === 'development' && config['devServer']?.hot) {
|
|
// add `react-refresh/babel` to babel loader plugin
|
|
const babelLoader = config.module.rules.find(
|
|
(rule) =>
|
|
rule &&
|
|
typeof rule !== 'string' &&
|
|
rule.loader?.toString().includes('babel-loader')
|
|
);
|
|
|
|
if (babelLoader && typeof babelLoader !== 'string') {
|
|
babelLoader.options['plugins'] = [
|
|
...(babelLoader.options['plugins'] || []),
|
|
[
|
|
require.resolve('react-refresh/babel'),
|
|
{
|
|
skipEnvCheck: true,
|
|
},
|
|
],
|
|
];
|
|
}
|
|
|
|
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
|
config.plugins.push(new ReactRefreshPlugin({ overlay: false }));
|
|
}
|
|
}
|
|
|
|
// We remove potentially conflicting rules that target SVGs because we use @svgr/webpack loader
|
|
// See https://github.com/nrwl/nx/issues/14383
|
|
function removeSvgLoaderIfPresent(
|
|
config: Partial<WebpackOptionsNormalized | Configuration>
|
|
) {
|
|
const svgLoaderIdx = config.module.rules.findIndex(
|
|
(rule) => typeof rule === 'object' && rule.test.toString().includes('svg')
|
|
);
|
|
if (svgLoaderIdx === -1) return;
|
|
config.module.rules.splice(svgLoaderIdx, 1);
|
|
}
|