react-refresh-webpack-plugin: $RefreshReg$ is not defined with ChildCompiler usage

This is a follow up to https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/36#issuecomment-665036157.

I created a demo where you can reproduce the error: https://github.com/donaldpipowitch/react-refresh-webpack-plugin-RefreshSig-bug


It looks to happen because of a child compiler. I use html-webpack-plugin to prerender a React component into an index.html file. (This will render some placeholder graphics in our real app until the app has loaded completely.) Normally the template you use for html-webpack-plugin can use all the loaders you have configured for the rest of your source code which is quite convenient. But I guess we’d need to disable ReactRefreshWebpackPlugin for this somehow 🤔

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 47 (19 by maintainers)

Commits related to this issue

Most upvoted comments

Solution for storybook

Create file .storybook/preview-head.html:

<script>
  // https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/176#issuecomment-683150213
  window.$RefreshReg$ = () => {};
  window.$RefreshSig$ = () => () => {};
</script>

Storybook support fast refresh since 6.1 version 😉 Add this to .storybook/main.js

reactOptions: {
    fastRefresh: true,
  },

I resolved the problem in such a way:

  • move fast-refresh plugin from babelrc to babel-loader
  • include it to loader and to plugins via additional argument

image

Yes, I do understand the frustration. It is an unfortunate constraint.

Though, I think you might be able to workaround this:

window.$RefreshReg$ = () => {};
window.$RefreshSig$ = () => () => {};

It is quite a hack, but for your purposes (i.e. code paths where HMR does not make sense share files with code paths where HMR is useful) it would suffice.

I had a somewhat relevant run-in with this “$RefreshReg$ is not defined” error, though my cause is not the same as the one OP mentions. I’m not sure where I should post this, but let me know if I should post this somewhere else. Just wanted to post in case someone else runs into this issue. For my project, developing with this plugin was great. It was only when I went to deploy this project to GCP that this “$RefreshReg$ is not defined” error occurred and prevented my app from loading at all – the screen was just blank. I’m using this plugin with the Typescript loader. The issue was resolved after I changed the conditional to only load the ReactRefreshTypescript plugin on my local environment. I made the following change:

options: {
  getCustomTransformers: () => ({
    before: [(process.env.environment !== "prod") && ReactRefreshTypeScript()].filter(Boolean),
  }),
  transpileOnly: isDevelopment,
},

to this:

options: {
  getCustomTransformers: () => ({
    before: [(process.env.environment === "local") && ReactRefreshTypeScript()].filter(Boolean),
  }),
  transpileOnly: isDevelopment,
},

Since react refresh is only really useful for local development anyway, I think it’s better to only include the plugin when on your local environment. I know the README checks to see if it is not the production env, but I feel like if you have multiple environments that you deploy to such as dev vs prod, then this plugin will be included when you try to build dev. In the end, this worked for me, so try this if you run into the “$RefreshReg$ is not defined” error and it isnt because of the OP’s reason.

I’ve started a create react app and immediately ejected to get access to the configuration. They had included your plugin in 4.0 without me knowing anything about it.

The source of the problem is that webpack configs aren’t used just for one thing. I’ve e.g. a project where I’m using the webpack config to build a) the frontend b) the frontend for server side rendering c) a library for 3rd parties to include plugins of the frontend into their pages d) storybook to preview the components.

Separating the webpack config into 4 configs would be an absolute no-go for me. When I’m developing a component in storybook it must work in the other environments a,b,c, too!

So the webpack config is built in one place only. And referenced with minimal changes only (like setting libraryTarget or exchanging HtmlWebpackPlugin for Storybook) for the 4 cases.

Storybook e.g. is recommending this way also in their docs: https://storybook.js.org/docs/react/configure/webpack#using-your-existing-config

It’s not only me. A google search for this error delivered many hits. That’s a bigger problem for people out there.

For now I’m filtering the plugin out at case b, c and d. (The other plugins etc don’t crash your app, they just take a little bit longer than needed. But that’s worth it for having a consistent build configuration)

I don’t think filtering out stuff is a problem when you share a Webpack config across multiple slightly varying setups? It is expected that some stuff don’t work for some particular setups. The error thrown is unfortunate but it also indicates that some part of the integration is broken. In an ideal world it would be up to us the plugin to inject the transform for you, but since Babel does not provide such an API at the moment we couldn’t to it.

Also to reiterate, react-refresh/babel is intentionally low level as its aim is to be used with integrations. When you eject from CR, it means that you will be maintaining the config to adopt to your various use cases. In that sense I think the docs are pretty clear that either you use this Webpack plugin together with the Babel plugin, or none - if you only use the Babel plugin not this Webpack plugin your app will likely crash.

Side note - you would probably also want to enable this plugin and the Babel plugin when you SSR to gain the most benefit.

Is it impossible that react-refresh/babel checks if the expected methods are defined, like every programmer does something similar at least once a day? If not log a warning to inform the user about sth missing, but don’t cause an error?

if (typeof window.$RefreshReg$ === ‘undefined’){ console.warn(‘Hint!’); } else { // … proceed using window.$RefreshReg$ }

The problem is - the expected methods are stubs.

It is in place to allow overridable behaviour in any environment. It might be in a alternative JS environment where the window variable is undefined, or maybe a web extension, electron or even a CLI. They could also - in this plugin’s case - be replaced by the compiler. If you check the actual produced code from this plugin for v0.4.x onwards, there should be no call to $RefreshReg$ and $RefreshSig$, but rather calls to __webpack_require__.$Refresh$.register and __webpack_require__.$Refresh$.signature.

It is meant to be low level and crude so frameworks and compilers can do whatever optimisation they want.

A possible solution to my original problem: use a separated webpack compilation inside a compilation. (https://github.com/donaldpipowitch/react-refresh-webpack-plugin-RefreshSig-bug/pull/4/files, original idea from bebraw: https://github.com/styleguidist/mini-html-webpack-plugin/issues/39)

@pmmmwh Latest storybook show nothing because of refresh babel plugin 😦

https://github.com/storybookjs/storybook/issues/12396

This is mine env var --env.FAST_REFRESH=true. It’s always empty in your variant.

It should be the same as Webpack automatically sets this to true. “Setting up your env variable without assignment, --env.production sets --env.production to true by default.”

This is not really related to enabling/disabling this plugin - it’s more about the Babel plugin.

For example, if you intentionally exclude react-refresh/babel from being able to process the file you’d import from the html template your setup would be working fine. This is more of a “limitation” of what you can do with templating - html-webpack-plugin inherits all the loaders but would not clone the plugins used, and does not allow you to add plugins to the pipeline without using their custom hooks (I’m not sure what is possible with them).

The solution to this would be to exclude your entire code path from being processed by react-refresh/babel (for example you can use another instance of babel-loader). Yes it is more config and it might be potentially expensive (depending on the project) but for stuff like that I think it warrants a separate Babel config: it is outside of your main bundle, served and consumed differently, and you don’t really need HMR for it?

hello, i check it out every solution, but for this problem in my case, this is the best:

If you use react, just add this code in public/index.html page:

 <script>
      window.$RefreshReg$ = () => {};
      window.$RefreshSig$ = () => () => {};
  </script>

and that’s all, it’s very simple and effective

I think a lot of the solutions posted in this thread are actually not related to the problem the OP is facing - they are just how you want to setup the Babel plugin and hooking it into your specific build pipeline, rather than disabling stuff or patching stuff in a constrained Webpack Child Compiler.

I’ll be closing this cause since solutions I can think of have been listed in the Troubleshooting Guide, and the unrelated solutions on this matter will really confuse people coming later.

If you still want to contribute to this issue/have a new way of dealing with it, feel free to open a PR against the guide and I will review it.

Shouldn’t prerendering be done in production mode?