Zoran Kokeza 14eb8de800
fix(react): disable react refresh overlay (#22013)
## 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)

![image](https://github.com/nrwl/nx/assets/19208948/c6be47bc-3ffd-4148-bdc6-18b59affae7f)

![image](https://github.com/nrwl/nx/assets/19208948/65ebe2a4-ed47-4feb-b289-645f3e2303cc)

### 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.
2024-05-29 09:20:37 -06:00

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);
}