react-refresh-webpack-plugin: Multiple entry points not working correctly

Our team has a config with the form:

{
  ...
  entry: {
    entry1: ['file1', 'file2'],
    entry2: ['file3', 'file4'],
  }
}

We were very excited to try out react-refresh-webpack-plugin, but unfortunately, could not get it working!

Through a process of trial and error, I removed entry2, and it started working. I think this is due to the logic in injectRefreshEntry, which would double-append react refresh into both entries. Is this intentional behavior?

Thanks! Mike

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 20 (8 by maintainers)

Most upvoted comments

You can use multiple entry points on a single page, but make sure to use optimization.runtimeChunk: "single". This makes sure you only have a single runtime (with module cache) and modules are not instantiated twice.

Webpack will also add the ability to create entry points that are children of other entries with dependOn.

Anyway: Use a single webpack runtime per html page.

In case anyone is looking for a workaround for multiple entry points this worked for me:

    const BaseReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
    class ReactRefreshWebpackPlugin extends BaseReactRefreshWebpackPlugin {
        apply(compiler) {
            super.apply(compiler);
            if (compiler.options.entry.djangoStyles) {
                // Remove all the hot module related packages from djangoStyles entry point.
                // Without this HMR breaks with error `Uncaught Error: Iframe has not been created yet.`
                // This came from react-error-overlay but this issue is likely related:
                // https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/88
                // There's no option to disable it directly
                const excludeEntries = [
                    '@pmmmwh/react-refresh-webpack-plugin/client/ReactRefreshEntry.js',
                    '@alliance-software/webpack-dev-utils/client/hot-client-errors.js',
                    'webpack/hot/dev-server.js',
                ];
                compiler.options.entry.djangoStyles = compiler.options.entry.djangoStyles.filter(
                    path => excludeEntries.filter(p => path.endsWith(p)).length === 0
                );
            }
        }
    }

I think it would be the best to avoid using globals and instead plugin into the parser to rewrite $RefreshSig$ etc to be local to the bundle/runtime. Otherwise you run into problems when using multiple webpack builds on a page, e.g. with microfrontends and module federation. Globals are bad anyway.

The webpack ApiPlugin has examples how to rewrite in the parser. It need to be rewritten e.g. to something like __webpack_require__.$RefreshSig$.

I’m also having trouble with this. In my case, this plugin inserts itself into the polyfill bundle, breaking IE 11 because it’s using URLSearchParams internally (which is not yet defined).

Hmm … The call to URLSearchParams should be called only after your code loads. Is it possible to create a minimal reproduction of what’s happening?

Would it be possible to expose something like @pmmmwh/react-refresh-webpack-plugin/runtime so that it could be manually added where ever?

The difficult part is that there’s both code to run BEFORE any user code, and AFTER all user code. The runtime, as of it’s current state, is also quite coupled with the window global and some other injected dependencies (via the plugin). I will revisit this idea after I’m done with some core logic rewrite.

@sokra Is there a proper way to inject an external dependency to Webpack’s require function (i.e. __webpack_require__)? The main issue blocking me using parser rewrites instead of globals is that $RefreshSig$ is an external function (createSignatureFunctionForTransform from react-refresh/runtime) but I need that in global scope (during initialization in MainTemplate). I think even the implementation in ProvidePlugin wouldn’t work because the main template won’t get processed by the parser.

I was thinking to do something along the lines of:

// Somehow get this injected
__webpack_require__.$RefreshRuntime$ = require('react-refresh/runtime');

Do I need to somehow get the source of the module and evaluate it to string?

When I started writing the plugin I recall myself going down this path too, but was unable to properly make this work (without multiple evaluation passes/busting cache all the time).

Why does including the runtime multiple times cause a problem? Can we simply make its second execution a no-op? i.e. it would not override any globals and would not do any side effects if $RefreshReg$ is already set up.