Home > Enterprise >  Webpack - Update React 18 — Module not found 'ReactDOM/client'
Webpack - Update React 18 — Module not found 'ReactDOM/client'

Time:12-06

I've proposed upgrading —lucky of me— the React version of our project from v17.0.2 to v18.x.

While the npm i is done fine, I have the problem that when I want to import the createRoot method from react-dom/client I get the error:

Module not found: Error: Can't resolve 'react-dom/client' in 'My-local-path'

If I don't import this module, the app correctly compiles, however, if I do, even if I don't use createRoot, I get the error.

appLoader.js (index.js)

import React from 'react';
import ReactDOM from 'react-dom';
import { MemoryRouter, Route } from 'react-router-dom';

// import { createRoot } from 'react-dom/client'; // this breaks the app

// this works fine
ReactDOM.render(
  <React.StrictMode>
    <MemoryRouter>
      <Route component={() => 'hello world'} />
    </MemoryRouter>
  </React.StrictMode>,
  document.getElementById('root')
);

// const container = document.getElementById('root');
// if (container) {
//   createRoot(container).render(
//     <React.StrictMode>
//       <MemoryRouter>
//         <Route component={() => 'hello world'} />
//       </MemoryRouter>
//     </React.StrictMode>
//   );
// }

I suspect that this has to do with Webpack. I saw here that it might be related of not including the module in Webpack.

We don't use Jest, therefore this solution won't apply.

  1. Also tried adding externals to webpack like commented here
  2. I've tried deleting package-log.json then removing node_modules and reinstalling.
  3. Played with several options in babel.config.js
  4. Wrapping all the components with React.StrictMode

My webpack.config.js

require('env2')('./.env');

const _ = require('lodash');
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MomentLocalesPlugin = require('moment-locales-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const WebpackAssetsManifest = require('webpack-assets-manifest');

const isDev = process.env.NODE_ENV !== 'production';
const ifDev = (then) => (isDev ? then : null);
const ifProd = (then) => (!isDev ? then : null);

module.exports = {
  target: isDev ? 'web' : ['web', 'es5'],
  profile: true,
  mode: isDev ? 'development' : 'production',
  entry: {
    sso: [
      './sso/publicPath',
      ifDev('webpack-hot-middleware/client?dynamicPublicPath=true'),
      ifDev('react-hot-loader/patch'),
      './sso/appLoader',
    ].filter(_.identity),
  },
  optimization: {
    runtimeChunk: isDev,
    minimize: !isDev,
    minimizer: [
      new TerserPlugin({
        // fix bug "cannot declare const twice..." in Safari 10
        minify: TerserPlugin.uglifyJsMinify,
        terserOptions: {
          mangle: { safari10: true },
          toplevel: true,
          output: { comments: false },
        },
        extractComments: false,
      }),
      new CssMinimizerPlugin(),
    ],
  },
  performance: { hints: false },
  context: path.resolve(__dirname, './src'),
  devtool: false,
  output: {
    publicPath: '/',
    path: path.resolve(__dirname, './dist'),
    filename: isDev ? '[name].bundle.js' : '[name].bundle.[contenthash].js',
  },
  // tried this
  // externals: {
  //   'react': { commonjs: 'react', commonjs2: 'react', amd: 'react', root: 'React' },
  //   'react-dom': { commonjs: 'react-dom', commonjs2: 'react-dom', amd: 'react-dom', root: 'ReactDOM' },
  // },
  resolve: {
    fallback: {
      crypto: require.resolve('crypto-browserify'),
      path: require.resolve('path-browserify'),
      stream: require.resolve('stream-browserify'),
    },
    modules: [
      path.resolve(__dirname, './src'),
      path.resolve(__dirname, './assets'),
      'node_modules',
    ],
    alias: {
      '@': path.resolve(__dirname, './src'), // include your file like this in less files: ~@/yourFile.less
      '../../theme.config$': path.join(
        __dirname,
        './src/theme/semantic/theme.config.less'
      ), // semantic requirement
      'react-dom': isDev ? '@hot-loader/react-dom' : 'react-dom',
    },
  },
  plugins: [
    new webpack.ProvidePlugin({ process: 'process/browser' }),
    ifDev(
      new webpack.SourceMapDevToolPlugin({
        filename: '[file].map',
        exclude: /node_modules/,
      })
    ),
    ifProd(
      new CleanWebpackPlugin({
        cleanOnceBeforeBuildPatterns: [
          '**/*',
          path.join(process.cwd(), 'logs/**/*'),
        ],
        verbose: true,
      })
    ),
    ifProd(new webpack.LoaderOptionsPlugin({ minimize: true, debug: false })),
    new MomentLocalesPlugin(),
    ifDev(new webpack.HotModuleReplacementPlugin()),
    new WebpackAssetsManifest({
      publicPath: true,
      writeToDisk: true,
      entrypoints: true,
      output: '../rendering-manifest.json',
    }),
    new MiniCssExtractPlugin({
      filename: isDev ? '[name].css' : '[name].bundle.[contenthash].css',
    }),
    ifProd(
      new CopyWebpackPlugin({
        patterns: [{ from: path.resolve(__dirname, './assets/static') }],
      })
    ),
  ].filter(_.identity),
  module: {
    rules: [
      {
        test: /\.js$/,
        include: [
          path.resolve(__dirname, './src'),
          ifProd(path.resolve(__dirname, './node_modules/joi')),
        ].filter(_.identity),
        use: 'babel-loader',
      },
      {
        test: /\.(css|less)$/,
        use: [
          { loader: MiniCssExtractPlugin.loader },
          {
            loader: 'css-loader',
            options: { importLoaders: 1, sourceMap: isDev },
          },
          { loader: 'less-loader', options: { sourceMap: isDev } },
        ],
      },
      {
        test: /\.jpe?g$|\.gif$|\.png$|\.ico$|\.ttf$|\.eot$|\.svg$|\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              esModule: false,
              name: isDev ? '[name].[ext]' : '[name].[contentHash].[ext]',
            },
          },
        ],
      },
    ],
  },
};

My babel.config.js

module.exports = {
  sourceType: process.env.NODE_ENV === 'production' ? 'unambiguous' : 'module',
  presets: [
    ['@babel/preset-react', { 'runtime': 'automatic' }],
    ['@babel/preset-env', {
      loose: true,
      useBuiltIns: 'usage',
      corejs: { version: 3, proposals: true },
      modules: false,
      targets: { browsers: process.env.NODE_ENV === 'production' ? 'chrome 53, safari 10, >0.1%' : 'chrome 86, safari 10' },
    }],
  ],
  plugins: [
    ['@babel/plugin-proposal-class-properties', { loose: true }],
    'react-hot-loader/babel',
  ],
};

My partial package.json

  "devDependencies": {
    "@babel/core": "^7.12.9",
    "@babel/plugin-proposal-class-properties": "^7.12.1",
    "@babel/preset-env": "^7.12.7",
    "@babel/preset-react": "^7.12.7",
    "@hot-loader/react-dom": "^17.0.1",
    "babel-eslint": "^10.1.0",
    "babel-loader": "^8.2.2",
    "classnames": "^2.2.6",
    "clean-webpack-plugin": "^3.0.0",
    "copy-webpack-plugin": "^6.3.2",
    "core-js": "^3.8.0",
    "cross-env": "^7.0.3",
    "crypto-browserify": "^3.12.0",
    "css-loader": "^5.0.1",
    "css-minimizer-webpack-plugin": "^1.1.5",
    "downscale": "^1.0.6",
    "eslint": "^7.14.0",
    "eslint-import-resolver-node": "^0.3.4",
    "eslint-import-resolver-webpack": "^0.13.0",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-react": "^7.21.5",
    "eslint-plugin-react-hooks": "^4.2.0",
    "file-dialog": "0.0.8",
    "file-loader": "^6.2.0",
    "jwt-decode": "^3.1.2",
    "less": "^3.12.2",
    "less-loader": "^7.1.0",
    "mini-css-extract-plugin": "^1.3.1",
    "moment-duration-format": "^2.3.2",
    "moment-locales-webpack-plugin": "^1.2.0",
    "patch-package": "^6.2.2",
    "path-browserify": "^1.0.1",
    "process": "^0.11.10",
    "prop-types": "^15.7.2",
    "react-day-picker": "^7.4.8",
    "react-helmet": "^6.1.0",
    "react-hot-loader": "^4.13.0",
    "react-rangeslider": "^2.2.0",
    "react-redux": "^7.2.2",
    "react-router-dom": "^5.2.0",
    "react-virtualized": "^9.22.3",
    "redux": "^4.0.5",
    "redux-thunk": "^2.3.0",
    "reselect": "^4.0.0",
    "search-parser": "^0.1.19",
    "semantic-ui-less": "^2.4.1",
    "semantic-ui-react": "^2.0.1",
    "stream-browserify": "^3.0.0",
    "sweetalert": "^2.1.2",
    "tachyons": "^4.12.0",
    "terser-webpack-plugin": "^5.0.3",
    "webpack": "^5.9.0",
    "webpack-assets-manifest": "^3.1.1",
    "webpack-cli": "^4.2.0",
    "webpack-dev-middleware": "^4.0.2",
    "webpack-hot-middleware": "^2.25.0",
    "xlsx": "^0.16.9"
  },
  "dependencies": {
    "@azure/storage-blob": "^12.3.0",
    "@supercharge/promise-pool": "^1.6.0",
    "applicationinsights": "^1.8.10",
    "compression": "^1.7.4",
    "cors": "^2.8.5",
    "env2": "^2.2.2",
    "express": "^4.17.1",
    "fast-xml-parser": "^3.17.5",
    "install": "^0.13.0",
    "joi": "^17.3.0",
    "jsonwebtoken": "^8.5.1",
    "jwk-to-pem": "^2.0.4",
    "knex": "^0.21.12",
    "locutus": "^2.0.14",
    "lodash": "^4.17.20",
    "mjml": "^4.7.1",
    "moment": "^2.29.1",
    "moment-timezone": "^0.5.32",
    "react-dom": "^18.2.0",
    "react": "^18.2.0",
    "mssql": "^6.2.3",
    "nanoid": "^3.1.20",
    "node-cache": "^5.1.2",
    "node-fetch": "^2.6.1",
    "nodemailer": "^6.4.16",
    "npm": "^8.13.2",
    "parse": "^2.17.0",
    "parse-dashboard": "^2.1.0",
    "parse-server": "^4.4.0",
    "query-string": "^6.13.7",
    "react-sortable-hoc": "^1.11.0",
    "redis": "^4.2.0",
    "saslprep": "^1.0.3",
    "sharp": "^0.26.3",
    "uuid": "^8.3.1"
  }

CodePudding user response:

The issue is that this project is use the dependency @hot-loader/react-dom, and it was overriding react-dom.

module.exports = {
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'), // include your file like this in less files: ~@/yourFile.less
      '../../theme.config$': path.join(
        __dirname,
        './src/theme/semantic/theme.config.less'
      ), // semantic requirement
      // 'react-dom': isDev ? '@hot-loader/react-dom' : 'react-dom', // this was overriding the react-dom version in node_modules
    },
  },
};

By the way, the aforementhioned dependency has been deprecated, so if you want to upgrade React you'll have to find an alternative like react-refresh-webpack-plugin

  • Related