Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

$RefreshReg$ is not defined with ChildCompiler usage #176

Closed
donaldpipowitch opened this issue Aug 7, 2020 · 47 comments
Closed

$RefreshReg$ is not defined with ChildCompiler usage #176

donaldpipowitch opened this issue Aug 7, 2020 · 47 comments

Comments

@donaldpipowitch
Copy link

This is a follow up to #36 (comment).

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 🤔

@pmmmwh
Copy link
Owner

pmmmwh commented Aug 7, 2020

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?

@gaearon
Copy link

gaearon commented Aug 7, 2020

Shouldn’t prerendering be done in production mode?

@pmmmwh
Copy link
Owner

pmmmwh commented Aug 7, 2020

Shouldn’t prerendering be done in production mode?

From my understanding this is more of an "edge" case - this is using React as a "templating language" and injecting the created DOM tree into a template (kinda like how SSG works) during compilation to set default content of the index.html.

I see a general form of this problem: there's some code within a repo yields a separate "branch" of execution (e.g. WebWorkers, Electron Prerender) controlled by some other plugin and that code is stored in close proximity with the other source code (since most of the time Babel configuration can be shared and will be shared). This idea fails when we apply react-refresh/babel, because implicitly it requires the Webpack plugin to transform it's generated code to work, but more often than not these code "branches" does not allow plugin execution (they might inherit the loader chain though).

@donaldpipowitch
Copy link
Author

Shouldn’t prerendering be done in production mode?

It's partially prerendering. I basically just use html-webpack-plugin for "Output Management". But I don't want to duplicate styles and want render something like placeholder layout. For that I require and render some components in my index.html template.

Not sure if this needs to be fixed on the side of html-webpack-plugin. There doesn't seem to be a way to override loaders, so they aren't inherited.

@pmmmwh
Copy link
Owner

pmmmwh commented Aug 27, 2020

Not sure if this needs to be fixed on the side of html-webpack-plugin. There doesn't seem to be a way to override loaders, so they aren't inherited.

Yea I would agree that this is a limitation of html-webpack-plugin. Not sure this is something that they should support because opening a new hole would introduce quite a bit of complexity to both the maintainers and the users.

I think the workaround I've highlighted above (to use separate Babel configs) is the best way for you to move forward.

I'll add a section to the troubleshooting docs in #189 to talk about how to handle cases like this that introduces indirection.

@donaldpipowitch
Copy link
Author

to use separate Babel configs

I'm not sure I know, how to do that. As html-webpack-plugin inherits all loaders also the Babel configs will be inherited, I guess. I can't use include/exclude patterns as well, because my HTML file and my application code potentially import the same files. ☹️ I fear I need to create some meta.json which contains all my generated chunk names and I'll need to generate the HTML file in a completely separate process/step. :/

@pmmmwh
Copy link
Owner

pmmmwh commented Aug 28, 2020

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.

@sshmyg
Copy link

sshmyg commented Aug 28, 2020

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

@pmmmwh
Copy link
Owner

pmmmwh commented Sep 3, 2020

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

This actually seems quite a smart way of handling it! I don't have time to test it out, but env.FAST_REFRESH would be undefined when using child compilers?

@sshmyg
Copy link

sshmyg commented Sep 3, 2020

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

This actually seems quite a smart way of handling it! I don't have time to test it out, but env.FAST_REFRESH would be undefined when using child compilers?

Hi, yes it's undefined for any others scripts, and webpack doesn't include it to babel config. In other case it extend babel.config.js with options from webpack config.

@pmmmwh
Copy link
Owner

pmmmwh commented Sep 3, 2020

Hi, yes it's undefined for any others scripts, and webpack doesn't include it to babel config. In other case it extend babel.config.js with options from webpack config.

I'll test it later today/tomorrow and add this to the new troubleshooting guide if I don't hit any issues!

@donaldpipowitch
Copy link
Author

@sshmyg I tried it in my example here, but it doesn't seem to work. Any idea why?

@akphi
Copy link

akphi commented Sep 3, 2020

@pmmmwh do you think this workaround (if it does work) applicable to #24 ?

@sshmyg
Copy link

sshmyg commented Sep 3, 2020

@sshmyg I tried it in my example here, but it doesn't seem to work. Any idea why?

I don't import babel config inside webpack. Babel loader should extend it by itself.

@donaldpipowitch
Copy link
Author

I don't import babel config inside webpack. Babel loader should extend it by itself.

Seems to have the same result for me :(

@sshmyg
Copy link

sshmyg commented Sep 3, 2020

I don't import babel config inside webpack. Babel loader should extend it by itself.

Seems to have the same result for me :(

Probably try to add some value to variable? This is mine env var --env.FAST_REFRESH=true. It's always empty in your variant.

@pmmmwh
Copy link
Owner

pmmmwh commented Sep 3, 2020

@pmmmwh do you think this workaround (if it does work) applicable to #24 ?

Yes.

@donaldpipowitch
Copy link
Author

donaldpipowitch commented Sep 3, 2020

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."

@sshmyg
Copy link

sshmyg commented Sep 3, 2020

@donaldpipowitch I use fast refresh with webpack-devserver only. But as I see you want to build bundle with fast refresh, and use this bundle in some other server?

@donaldpipowitch
Copy link
Author

some other server

Hmm, well I use webpack-plugin-serve as my dev server, but I think it should behave like webpack-dev-server here.

@donaldpipowitch
Copy link
Author

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

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

By the way I'll try that now. In my demo it looks good: https://github.com/donaldpipowitch/react-refresh-webpack-plugin-RefreshSig-bug/pull/3/files
But I have to verify this with our real project.

@pmmmwh
Copy link
Owner

pmmmwh commented Sep 3, 2020

I've preliminarily tested the env.FAST_REFRESH way of doing things - and I don't think it will work for this case. Particularly, the function Webpack config are only evaluated during CLI startup, and will passed to child compilers as a static object afterwards.

@sshmyg
Copy link

sshmyg commented Sep 8, 2020

@pmmmwh Latest storybook show nothing because of refresh babel plugin :(

storybookjs/storybook#12396

@pmmmwh
Copy link
Owner

pmmmwh commented Sep 8, 2020

@pmmmwh Latest storybook show nothing because of refresh babel plugin :(

storybookjs/storybook#12396

Hi, i it possible to provide a reproduction? Did you have the webpack plugin setup? What HMR solution does storybook use?

@sshmyg
Copy link

sshmyg commented Sep 8, 2020

@pmmmwh https://github.com/sshmyg/storybook-test this is a link to repo where you can reproduce this bug with storybook.
What HMR solution is in latest storybook - I don't know :(

@pmmmwh
Copy link
Owner

pmmmwh commented Sep 8, 2020

@pmmmwh https://github.com/sshmyg/storybook-test this is a link to repo where you can reproduce this bug with storybook.
What HMR solution is in latest storybook - I don't know :(

Thanks
I'll take a look tomorrow.

@restuwahyu13
Copy link

restuwahyu13 commented Sep 14, 2020

hey this worked for me to handle this issue, follow this step by step

1.install cross-env
2.add cross-env NODE_ENV=production and cross-env NODE_ENV=development in package.json
3. follow this config

const isDevPlugin = ['react-refresh/babel']

const isProdPlugin = [
  '@babel/plugin-transform-block-scoping',
  'transform-remove-console',
  'babel-plugin-transform-remove-undefined',
  ['transform-react-remove-prop-types', { mode: 'wrap', ignoreFilenames: ['node_modules'] }]
]

const isProdDevPlugin = [
  '@babel/plugin-transform-async-to-generator',
  '@babel/plugin-syntax-dynamic-import',
  ['@babel/plugin-proposal-class-properties', { loose: true }],
  ['@babel/plugin-transform-runtime', { corejs: 3, useESModules: true }],
  ['styled-jsx/babel', { optimizeForSpeed: true }]
]

const isPlugins = process.env.NODE_ENV !== 'production' ? isDevPlugin : isProdPlugin

 {
    test: /\.(js|jsx)$/,
    use: [
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
              presets: [
                [
                  '@babel/preset-env',
                  {
                    useBuiltIns: 'usage',
                    corejs: 3,
                    loose: true,
                    bugfixes: true,
                    modules: false
                  }
                ],
                ['@babel/preset-react', { useBuiltIns: true }]
              ],
              plugins: [...isProdDevPlugin, ...isPlugins]
            }
          }
        ],
        include: resolve(process.cwd(), 'src'),
        exclude: /node_modules/
  }
  1. enjoy working

@pmmmwh pmmmwh changed the title $RefreshReg$ is not defined $RefreshReg$ is not defined with ChildCompiler usage Sep 20, 2020
@pmmmwh
Copy link
Owner

pmmmwh commented Sep 20, 2020

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.

@pmmmwh pmmmwh closed this as completed Sep 20, 2020
@pmmmwh
Copy link
Owner

pmmmwh commented Sep 20, 2020

@pmmmwh Latest storybook show nothing because of refresh babel plugin :(

storybookjs/storybook#12396

We (me and Dan) had a thorough discussion with the Storybook maintainers themselves, and it seems that at this point in time it is quite difficult for them to support fast-refresh within the scope of storybook because their core story loader is framework agnostic and have custom HMR integration wired up. It also explicitly requires the default export (the storybook config) to be present to consume at runtime, which conflicts with how FR set up boundaries (cause the default will be an unnamed object). They plan to revisit this in the future, but for now you will have to disable the react-refresh/babel plugin in your storybook Babel configuration.

@donaldpipowitch
Copy link
Author

FYI: It looks like Storybook enabled fast refresh in a recent alpha. https://twitter.com/mshilman/status/1307722781774151681

Thank you @pmmmwh for your work on this topic.

@donaldpipowitch
Copy link
Author

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: styleguidist/mini-html-webpack-plugin#39)

@RobIsHere
Copy link

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$
}

@pmmmwh
Copy link
Owner

pmmmwh commented Nov 10, 2020

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.

@RobIsHere
Copy link

OK. I understand. At least parts of it. Would it be possible to add a config switch like "callOverridableStubs" that defaults to off?
So the normalo-user doesn't get an error. And if someone enables this feature, he should know about the consequences.

@pmmmwh
Copy link
Owner

pmmmwh commented Nov 11, 2020

OK. I understand. At least parts of it. Would it be possible to add a config switch like "callOverridableStubs" that defaults to off?
So the normalo-user doesn't get an error. And if someone enables this feature, he should know about the consequences.

What issue are you hitting specifically? For files that are configured to be parsed by the Babel plugin and this plugin, there shouldn't be any issues AFAIK?

@RobIsHere
Copy link

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)

@pmmmwh
Copy link
Owner

pmmmwh commented Nov 15, 2020

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.

@Jack-Works
Copy link
Contributor

I'm having this problem with worker-plugin/loader and I decided to use the workaround

@pmmmwh
Copy link
Owner

pmmmwh commented Nov 18, 2020

I'm having this problem with worker-plugin/loader and I decided to use the workaround

If you have more input feel free to PR them into https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md

@Jack-Works
Copy link
Contributor

I'm using

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

@sshmyg
Copy link

sshmyg commented Jan 27, 2021

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,
  },

@catamphetamine
Copy link

catamphetamine commented Feb 21, 2021

I also had this bug.
My workaround is:

In babel.config.js:

module.exports = (api) => {
  const reactPlugins = []
  if (api.env('development')) {
    reactPlugins.push('react-refresh/babel')
  }
  return {
    presets: [
      "@babel/preset-env",
      "@babel/preset-react"
    ],
    plugins: reactPlugins
  }
}

In webpack.config.js:

export default function ({ development }) {
  return {
    ...,
    module: {
      rules: [{
        test: /\.js$/,
        exclude: /node_modules/,
        use: [{
          loader: 'babel-loader',
          options: {
            envName: development ? 'development' : 'production'
          }
        }]
      }, 
      ...
}

If it throws Error: Cannot change caching after evaluation has completed then change the api.env() part to:

  if (process.env.BABEL_ENV === 'development') {
    reactPlugins.push('react-refresh/babel');
  }

And then in scripts in package.json:

cross-env BABEL_ENV=development webpack --hot ...

@myou11
Copy link

myou11 commented Sep 28, 2021

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.

@pmmmwh
Copy link
Owner

pmmmwh commented Sep 28, 2021

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.

This is completely unrelated to this issue ...

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.

The problem isn't really running this in dev or prod or whatever, the README aims to demonstrate that you really should only run this when you INTENDED to. The problem is basically you enabling the Babel/TS transform while skipping this plugin - they have to be BOTH used or not used, or else you will hit this issue, which is intentional given that the transform is not intended to be used standalone.

@myou11
Copy link

myou11 commented Sep 29, 2021

This is completely unrelated to this issue ...

Yes, I should have made it more clear that I meant it was somewhat relevant only because of the "$RefreshReg$ is not defined" error and not at all related to the original child compiler issue originally posted. That is why I was unsure of where to post this, but thought here might be good since this is the only link that shows up when googling for "$RefreshReg$ is not defined". I realize now that there is a separate issue in this repo that goes over my issue exactly.

Thank you for the explanation though.

acederberg added a commit to acederberg/typescript-webpack5-react-template that referenced this issue Jan 6, 2022
…ts are working properly. There was of course a monster bug since webpack is hell :). Read [this](pmmmwh/react-refresh-webpack-plugin#176).
@mfix-stripe
Copy link

@pmmmwh would it be possible to automatically inject self. = () => {} during child compilation? I had to introduce this hack (https://github.com/markdoc/next.js/blob/main/src/loader.js#L74-L76) to my Next.js plugin because I need to call importModule at build time. Would be great if we could include this idiomatic fix in this package, instead of in userland.

@jakubstankowski
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests