pmmmwh / react-refresh-webpack-plugin

A Webpack plugin to enable "Fast Refresh" (also previously known as Hot Reloading) for React components.
MIT License
3.13k stars 193 forks source link

ReferenceError: $RefreshSig$ is not defined while running with webpack-dev-server #92

Closed enzy closed 4 years ago

enzy commented 4 years ago

Our webpack configuration matches the readme. No other options passed to the plugin. The main difference is devServer writeToDisk: true option as we have a PHP backend which servers builded files. Single file entry.

"scripts": {
  "dev-server": "webpack-dev-server --watch --mode development --hot",
  "dev": "webpack --watch --mode development"
}

When we run npm run dev-server, the compilation step finishes without a problem:

[1] multi (webpack)-dev-server/client?http://localhost:5000 (webpack)/hot/dev-server.js ./node_modules/@pmmmwh/react-refresh-webpack-plugin/src/runtime/ReactRefreshEntry.js ./node_modules/@pmmmwh/react-refresh-webpack-plugin/src/runtime/ErrorOverlayEntry.js ./app/index 76 bytes {main} [built]

But we face the fatal error in a browser afterwards:

sockjs.js:4533 Uncaught ReferenceError: $RefreshSig$ is not defined
    at setup (sockjs.js:4533)
    at Object.<anonymous> (sockjs.js:4506)
    at Object.55../common (sockjs.js:4519)
    at o (sockjs.js:41)
    at sockjs.js:43
    at Object.<anonymous> (sockjs.js:4094)
    at Object.52.debug (sockjs.js:4133)
    at o (sockjs.js:41)
    at sockjs.js:43
    at Object.<anonymous> (sockjs.js:3276)

And it looks like ReactRefreshEntry.js was never called.

If we run npm run dev which calls webpack directly, the compilation step finished successfully:

[0] multi ./node_modules/@pmmmwh/react-refresh-webpack-plugin/src/runtime/ReactRefreshEntry.js ./node_modules/@pmmmwh/react-refresh-webpack-plugin/src/runtime/ErrorOverlayEntry.js ./app/index 52 bytes {main} [built]

And the browser console is clean and our app works fine. But the hot reload functionality is gone as there is no running dev server to get changes from.

RefreshReg, $RefreshSig$ and $RefreshSetup$ are now present under window object.

I have tried disabling everything, even the whole app/index.js file but without a success. My humble debugging suggests that ReactRefreshEntry is not called early enough. The chain goes like this:

webpack-dev-server/client/index.js → webpack-dev-server/client/socket.js → webpack-dev-server/client/clients/SockJSClient.js → sockjs-client/dist/sockjs.js → and finally setup(env) failing to call var _s = $RefreshSig$();

That's where my abilities ends and wonders begin :)

Can you point me in some direction what to check and how can I provide better feedback?

Thanks!


Versions:

michaelgmiller1 commented 4 years ago

The fix for https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/88 might address this (not 100% sure, but it's possible)

pmmmwh commented 4 years ago

My guess is that you've included the react-refresh/babel plugin to process node_modules. This will break because some code (as used by Webpack and WDS) will inevitably run before the plugin.

enzy commented 4 years ago

Indeed. What a great guess! The cause was missing exclude: /node_modules/ in js/ts loaders with babel-loader.

Thanks a lot, I was stuck on this for so long 🙏

davidtranjs commented 3 years ago

My guess is that you've included the react-refresh/babel plugin to process node_modules. This will break because some code (as used by Webpack and WDS) will inevitably run before the plugin.

You save my life <3

keita-makino commented 3 years ago

I have recently introduced the plugin and have got the error only in production. The exclude setting is applied to my webpack.common.js and the plugin has to do nothing with my production config, so what'd be wrong here?

webpack.common.js ```js const path = require('path'); const TerserPlugin = require('terser-webpack-plugin'); const Dotenv = require('dotenv-webpack'); module.exports = { entry: './src/index.tsx', module: { rules: [ { test: /\.tsx?$/, exclude: /node_modules/, loader: 'babel-loader', }, ], }, resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'], fallback: { fs: false, net: false, tls: false, assert: require.resolve('assert/'), https: require.resolve('https-browserify'), crypto: require.resolve('crypto-browserify'), buffer: require.resolve('buffer/'), http: require.resolve('stream-http'), stream: require.resolve('stream-browserify'), os: require.resolve('os-browserify/browser'), zlib: require.resolve('browserify-zlib'), path: require.resolve('path-browserify'), }, }, output: { path: path.resolve(__dirname, 'dist/'), filename: 'bundle.js', }, optimization: { minimize: true, minimizer: [ new TerserPlugin({ extractComments: false, }), ], }, plugins: [new Dotenv()], }; ```
webpack.dev.js ```js const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); const CompressionPlugin = require('compression-webpack-plugin'); const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); module.exports = merge(common, { mode: 'development', devServer: { static: path.join(__dirname, 'dist'), compress: true, host: '127.0.0.1', hot: true, }, plugins: [ new HtmlWebpackPlugin({ template: 'public/index.html', }), new CompressionPlugin(), new ReactRefreshWebpackPlugin(), ], }); ```
webpack.prod.js ```js const { ProvidePlugin } = require('webpack'); const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlInlineScriptPlugin = require('html-inline-script-webpack-plugin'); module.exports = merge(common, { mode: 'production', plugins: [ new ProvidePlugin({ process: 'process/browser', Buffer: ['buffer', 'Buffer'], }), new HtmlWebpackPlugin({ template: 'public/index.html', }), new HtmlInlineScriptPlugin(), ], }); ```
package.json ```json { "..." "scripts": { "start": "webpack serve -c webpack.dev.js --open", "build": "webpack -c webpack.prod.js", "test": "echo \"Error: no test specified\" && exit 1" }, "..." "devDependencies": { "@apollo/client": "^3.3.13", "@googlemaps/js-api-loader": "^1.11.3", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.60", "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3", "assert": "^2.0.0", "browserify-zlib": "^0.2.0", "buffer": "^6.0.3", "crypto-browserify": "^3.12.0", "dotenv": "^8.2.0", "graphql": "^15.5.0", "https-browserify": "^1.0.0", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "react-refresh": "^0.10.0", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", "webpack-merge": "^5.7.3", "@babel/cli": "7.13.10", "@babel/core": "7.13.10", "@babel/preset-env": "7.13.12", "@babel/preset-react": "7.12.13", "@babel/preset-typescript": "7.13.0", "@material-ui/core": "4.11.3", "@react-google-maps/api": "2.1.1", "@types/dotenv": "^8.2.0", "@types/google.maps": "^3.44.2", "@types/lodash": "^4.14.168", "@types/node": "^14.14.36", "@types/react": "17.0.3", "@types/react-dom": "17.0.3", "@typescript-eslint/eslint-plugin": "4.19.0", "@typescript-eslint/parser": "4.19.0", "babel-eslint": "10.1.0", "babel-loader": "8.2.2", "compression-webpack-plugin": "7.1.2", "core-js": "3.9.1", "css-loader": "5.2.0", "dotenv-webpack": "^7.0.2", "eslint": "7.22.0", "eslint-config-prettier": "8.1.0", "eslint-config-react-app": "6.0.0", "eslint-plugin-flowtype": "5.4.0", "eslint-plugin-import": "2.22.1", "eslint-plugin-jsx-a11y": "6.4.1", "eslint-plugin-prettier": "3.3.1", "eslint-plugin-react": "7.23.1", "eslint-plugin-react-hooks": "4.2.0", "framer-motion": "4.0.3", "html-inline-script-webpack-plugin": "^2.0.0", "html-webpack-inline-source-plugin": "1.0.0-beta.2", "html-webpack-plugin": "5.3.1", "husky": "5.2.0", "license-checker": "25.0.1", "lint-staged": "10.5.4", "prettier": "2.2.1", "process": "^0.11.10", "react": "17.0.2", "react-dom": "17.0.2", "react-hook-geolocation": "1.0.7", "react-use": "17.2.1", "style-loader": "2.0.0", "terser-webpack-plugin": "^5.1.1", "ts-loader": "^8.0.18", "typescript": "4.2.3", "webpack": "5.28.0", "webpack-bundle-analyzer": "4.4.0", "webpack-cli": "4.5.0", "webpack-dev-server": "4.0.0-beta.0" }, "..." } ```
pmmmwh commented 3 years ago

I have recently introduced the plugin and have got the error only in production. The exclude setting is applied to my webpack.common.js and the plugin has to do nothing with my production config, so what'd be wrong here?

You probably need to exclude the react-refresh/babel plugin for your production build, or properly set NODE_ENV for the build process as well.

keita-makino commented 3 years ago

I had a separate babel.config.js and there was a development-only setting for the plugin. However it seemed not working so that I merged the settings into webpack configs and now it seems fine. Would this be a workaround for everyone or just for me?

pmmmwh commented 3 years ago

I had a separate babel.config.js and there was a development-only setting for the plugin. However it seemed not working so that I merged the settings into webpack configs and now it seems fine. Would this be a workaround for everyone or just for me?

Can you share your babel.config.js? Maybe the development only setting was not set up correctly?

keita-makino commented 3 years ago

Well I have already deleted it but here is the one.

module.exports = function babel(api) {
  const presets = [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage',
        corejs: {
          version: 3,
          proposals: true,
        },
      },
    ],
    '@babel/preset-react',
    '@babel/preset-typescript',
  ];
  const plugins = [];
  const ENV = api.env();
  if (ENV === 'development') {
    plugins.push('react-refresh/babel');
  }

  return {
    presets,
    plugins,
  };
};
pmmmwh commented 3 years ago

Well I have already deleted it but here is the one.

I think you didn't set NODE_ENV to production for the production build? That's why the api.env()is always development?

keita-makino commented 3 years ago

That was true indeed, I let the npm script call the production settings of the webpack. Because of that my production settings in the .env file is loaded into the .ts files so I thought the same environment is set to production for babel as well.

eugene-g13 commented 1 year ago

Need help, please. I'm using @pmmmwh/react-refresh-webpack-pluginand @react-refresh-typescript. Using rule:

{
    test: /\.tsx?$/,
    exclude: /node_modules/,
    use: [
        {
            loader: 'ts-loader',
            options: {
                getCustomTransformers: () => ({
                    before: [require('react-refresh-typescript').default()],
                }),
            },
        },
    ],
}

Build succeded. I started dev-server with "webpack serve --config webpack.dev.js". But in browser I see: "Uncaught ReferenceError: $RefreshSig$ is not defined at ..."

optimization: {
    runtimeChunk: 'single',
},

doesn't help.

When I use this:

// @ts-expect-error
global.$RefreshReg$ = () => {};
// @ts-expect-error
global.$RefreshSig$ = () => () => {};

I have errors in console like this (when open MuiDialog): _Cannot read properties of undefined (reading 'displayName') TypeError: Cannot read properties of undefined (reading 'displayName') at getDisplayName (chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtoolsbackend.js:261:19)

What I did wrong?

"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"react-refresh-typescript": "^2.0.8",
"ts-loader": "^9.4.2",
"tsconfig-paths-webpack-plugin": "^4.0.0",
"typescript": "^4.9.4",
"webpack": "^5.76.3",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.13.1",
"webpack-merge": "^5.8.0"
pmmmwh commented 1 year ago

Need help, please. I'm using @pmmmwh/react-refresh-webpack-pluginand @react-refresh-typescript. Using rule:

{
  test: /\.tsx?$/,
  exclude: /node_modules/,
  use: [
      {
          loader: 'ts-loader',
          options: {
              getCustomTransformers: () => ({
                  before: [require('react-refresh-typescript').default()],
              }),
          },
      },
  ],
}

Build succeded. I started dev-server with "webpack serve --config webpack.dev.js". But in browser I see: "Uncaught ReferenceError: RefreshSig is not defined at ..."

optimization: {
  runtimeChunk: 'single',
},

doesn't help.

When I use this:

// @ts-expect-error
global.$RefreshReg$ = () => {};
// @ts-expect-error
global.$RefreshSig$ = () => () => {};

I have errors in console like this (when open MuiDialog): _Cannot read properties of undefined (reading 'displayName') TypeError: Cannot read properties of undefined (reading 'displayName') at getDisplayName (chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtoolsbackend.js:261:19)

What I did wrong?

"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"react-refresh-typescript": "^2.0.8",
"ts-loader": "^9.4.2",
"tsconfig-paths-webpack-plugin": "^4.0.0",
"typescript": "^4.9.4",
"webpack": "^5.76.3",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.13.1",
"webpack-merge": "^5.8.0"

Can you provide your plugin configuration portions?

eugene-g13 commented 1 year ago

Here it is: plugins: [ new ReactRefreshWebpackPlugin(), new HtmlWebpackPlugin({..}), new Dotenv(), ], ... module: { rules: [ { test: /.tsx?$/, use: 'ts-loader', exclude: /node_modules/, options: { getCustomTransformers: () => ({ before: [require('react-refresh/typescript')()], }), }, }, { test: /.svg$/, type: 'asset', use: 'svgo-loader', }, { test: /.(png|jpg|jpeg|gif|ico|mp3)$/i, type: 'asset/resource', }, { test: /.(woff|woff2|eot|ttf|otf)$/i, type: 'asset/resource', generator: { filename: 'fonts/[name][ext]', }, }, ], },

Nantris commented 6 months ago

I just hit this. We have a requirement to allow refreshing of a single node module. I wonder if it's possible?

It works for our Electron app, but when I try to load the file standalone in a browser this regex seems not to be satisfactory: exclude: /node_modules\/(?!@our\/package)/

But exclude: /node_modules/ works fine (except that we need to hot reload @our/package.

Can anyone advise if/how this is possible?

pmmmwh commented 4 months ago

I just hit this. We have a requirement to allow refreshing of a single node module. I wonder if it's possible?

It works for our Electron app, but when I try to load the file standalone in a browser this regex seems not to be satisfactory: exclude: /node_modules\/(?!@our\/package)/

But exclude: /node_modules/ works fine (except that we need to hot reload @our/package.

Can anyone advise if/how this is possible?

You probably also need to pass your package through Babel with the react-refresh/babel plugin.