react-refresh-webpack-plugin: Getting error in web workers

I’m getting error when using with web workers: Uncaught ReferenceError: $RefreshReg$ is not defined

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 12
  • Comments: 30 (15 by maintainers)

Most upvoted comments

@blacksteed232 @GuskiS

I have some workarounds.

Sloppy

  • In the entry of your Worker, add the following two lines:

    self.$RefreshReg$ = () => {};
    self.$RefreshSig$$ = () => () => {};
    
  • This is basically a “polyfill” for helpers expected by react-refresh/babel

Simple

  • You don’t have to mess with configurations
  • Ensure all exports in the whole code path does not contain anything in PascalCase, which will make the Babel plugin do nothing when it hits your files
  • In general, the PascalCase naming scheme should be reserved for React components only, and I assume there wouldn’t exist any React components within a Web Worker
  • Slight issue is when you have dependencies with that naming scheme, but most setups bypass node_modules from Babel anyways

Robust

  • Similar to what @blacksteed232 attempted, but with Webpack’s oneOf rule

  • Alter the Webpack module rules (the section for JS-related files) as follows:

    {
      rules: [
        // JS-related rules only
        {
          oneOf: [
            {
              test: /\.[jt]s$/,
              include: '<Your worker files here>',
              exclude: /node_modules/,
              use: {
                loader: 'babel-loader',
                options: {
                  // Your Babel config here
                },
              },
            },
            {
              test: /\.[jt]sx?$/,
              include: '<Your files here>',
              exclude: ['<Your worker files here>', /node_modules/],
              use: {
                loader: 'babel-loader',
                options: {
                  // Your Babel config here
                },
              },
            },
          ],
        },
        // Any other rules, such as CSS
        {
          test: /\.css$/,
          use: ['style-loader', 'css-loader'],
        },
      ],
    }
    

Edit:

I also would suggest to not run ReactRefreshPlugin through WorkerPlugin - messing with module templates in child compilers for a different realm does not end well from my testing while figuring out the two solutions above. It COULD be made to work, but I currently don’t see a good path towards that goal.

I do understand that it is a bit of work to add this configuration, but I also hope that people can understand that react-refresh and this plugin weren’t designed to be used “standalone” or in non-React related code paths. For the case of Web Workers, in my honest opinion, I think they should be compiled from the start separately (in terms of TypeScript/Babel transpilation) - import/exports are not available natively, and the global object is different.

Hopefully this provided some insight and hopefully the workarounds above have solved your problems!

Happy that the resolutions worked for both of you!

But that got me thinking, could it be possible that I’m somehow importing that code as worker and directly? For example, that file contains class, in my code it is only used as worker(new Worker("./myworker.worker.ts")), but in few typing files I use it as type definition to see what methods are being exposed.

Could something in bundling process mess it up? I haven’t analyzed bundle output in a while, will give it a try later.

If you are on TypeScript 3.8+, I highly suggest checking out the import type syntax, this option for TypeScript and this option for Babel’s TypeScript transpilation. Type imports ensures that no side effects will be ran from the imported module, and they will be erased completely after bundling.

This might be useful to add to documentation, maybe worker section? As it requires additional changes for workers 🤔

I’ll probably add a TROUBLESHOOTING.md soon and move all the FAQs there from the readme. I’ve been hoping to rewrite documentation so stuff will be clearer.

@pmmmwh - I haven’t had much time to looking into this yet.

But I believe the issue is caused because workers don’t have window. And at least with https://github.com/developit/workerize-loader and https://github.com/GoogleChromeLabs/worker-plugin it suggests changing your webpack config to use this to allow for Hot Module Replacement

  output: {
    globalObject: "this"
  },

When you do this Webpack is throwing on

bootstrap:22 Uncaught ReferenceError: window is not defined
    at __webpack_require__ (bootstrap:22)
    at bootstrap:97
    at bootstrap:97

Which is lines (https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/615157e5daadc7d1979e717e3cfb45c7c101a154/src/helpers/createRefreshTemplate.js#L13-L15)

It appears if you reference window at all here webpack throws even if its to check if window exists.

Changing these lines to use this seems to work for me for both when I’ve changed the globalObject to this and to when I have left it as window.

I wanted to post this here in case someone else is encountering the same issue as I was.

We are using worker-loader instead of worker-plugin.

After trying the suggested Robust solution, I still kept getting the error Cannot read properties of undefined (reading 'signature') from the worker bundle (__webpack_require__.$Refresh$ being undefined), despite verifying that worker files were excluded from babel-loader. This could still be some misconfiguration on my part.

Fix inspired by

Usage in webpack configuration is to replace ReactRefreshWebpackPlugin with an extended WorkerSafeReactRefreshWebpackPlugin.

import { Template } from 'webpack';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';

// This ensures we don't get runtime errors by ensuring workers have stubbed out expected functions.
export default class WorkerSafeReactRefreshWebpackPlugin extends ReactRefreshWebpackPlugin {
  apply(compiler) {
    super.apply(compiler);

    compiler.hooks.compilation.tap('ReactRefreshWebpackPlugin', (compilation) => {
      const hookVars = compilation.mainTemplate.hooks.localVars;

      hookVars.tap('ReactFreshWebpackPlugin', (source) =>
        Template.asString([
          source,
          '',
          '// noop fns to prevent runtime errors during initialization',
          '__webpack_require__.$Refresh$ = { register: function () {}, signature: function() { return function(type) { return type; } } };',
        ]),
      );
    });
  }
}

Thanks, when I added:

(global as any).$RefreshReg$ = () => {};
(global as any).$RefreshSig$$ = () => () => {};

it didn’t show error. (CRA eslint rule didn’t allow me to use self)

But that got me thinking, could it be possible that I’m somehow importing that code as worker and directly? For example, that file contains class, in my code it is only used as worker(new Worker("./myworker.worker.ts")), but in few typing files I use it as type definition to see what methods are being exposed.

Could something in bundling process mess it up? I haven’t analyzed bundle output in a while, will give it a try later.