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 192 forks source link

Component modifications are not applied in module federation #394

Open kabel2 opened 3 years ago

kabel2 commented 3 years ago

I'm having an issue getting react refresh working on a project with module fedration. The ws connection is working, also in the console it seems its working, but the modifications are not applied to the component.

[webpack-dev-server] App updated. Recompiling...
index.js:3210 [webpack-dev-server] App hot update...
log.js:24 [HMR] Checking for updates on the server...
log.js:24 [HMR] Updated modules:
log.js:24 [HMR]  - ./src/Button.js
log.js:24 [HMR] App is up to date.

I have created an basic example: https://github.com/kabel2/module-federation-examples/tree/feature/example-react-refresh/basic-host-remote

Changing the innertext of the button in the "Button.js" component in "app2" does not apply the changes live, only after reload.

With the old react-hot-loader it seems to work.

Ridermansb commented 3 years ago

Same with me

pmmmwh commented 3 years ago

Are you externalising React?

kabel2 commented 3 years ago

React is shared from App1 via the ModuleFederation plugin. I tried remove react from the list of shared components, but the problem remains.

Ridermansb commented 3 years ago

Any news on this?

pmmmwh commented 3 years ago

No. This is likely due to externalising and injection order of stuff. I currently do not have much time to check this, but I can revisit this later.

Muritavo commented 3 years ago

There are 2 key events for it to work correctly.

Put this piece of code at the start of your app2 entry and it should work right now. A PR would be welcome to fix it, maybe I'll do it :).

//A reference to the react refresh injector
const { injectIntoGlobalHook } = require("react-refresh/cjs/react-refresh-runtime.development")

//Injects the react refresh replacing the one from the app1
injectIntoGlobalHook(window);

//Injects the react-dom instance again, that will now be referenced by the refresher from app2
(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__.inject(ReactDOM);
pmmmwh commented 3 years ago

Maybe you could use the library option to ensure react refresh is injected for both apps?

ScriptedAlchemy commented 3 years ago

HMR cannot work with federated modules, you can use @module-federation/fmr as an alternative solution.

We would have to rewrite HMR graphs in webpack in order to achieve this. Webpack needs to be aware of all module and parent modules in the graph which means we would have to build a way for federated webpack graphs to be interconnected as well. Changing a module in a remote doesn't mean that the container knows where its being used inside a host.

ije commented 2 years ago

i figured out how resovle to this in #516, maybe not a greate idea, but it works

raine commented 2 years ago

Does anyone have an example how react-refresh etc. should be loaded on the page in order for hot reload to work?

In my module federation setup, hot reload works occasionally, which suggests there's some indeterminism going on in the order that scripts are loading on the page.

The suggestion of bundling react-refresh separately, placing it in window and then making it external in MF libs does not seem to work.

ccambournac commented 2 years ago

Maybe you could use the library option to ensure react refresh is injected for both apps?

Indeed. Seems like using Webpack 5's output.uniqueName helped me solve this same issue with 2 separate builds used within the same page.

pmmmwh commented 2 years ago

I would be happy to curate examples where React Refresh can be used properly with MF if anyone can provide a small sample repo.

ccambournac commented 2 years ago

I would be happy to curate examples where React Refresh can be used properly with MF if anyone can provide a small sample repo.

I'll definitely try to find time to do so @pmmmwh.

ruanyl commented 2 years ago

For anyone who runs into this issue, the solution from @ije and @danieloprado in https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/516 works perfectly for me.

I end up with pnpm patch to patch the package, you can also do it with yarn patch if you're using yarn.

@pmmmwh would you reconsider applying the changes suggested by @ije and @danieloprado?

ESoch commented 1 year ago

As someone else who is experiencing issues here with module federation, I am hoping to re-up this conversation.

@pmmmwh, would you be open to applying the changes described here: https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/516#issuecomment-1159038683

As module federation becomes more popular I can imagine this will become a more common issue.

ESoch commented 1 year ago

Although, to further complicate things, I am not able to get it working even with the suggested fixes. I continue to get this error:

   [HMR] Update failed: ChunkLoadError: Loading hot update chunk main failed.

@ruanyl, does your patch continue to work as expected for you?

danieloprado commented 1 year ago

@ESoch try change runtimeChunk to single during development

//...
optimization: {
    runtimeChunk: isDevelopment ? 'single' : false
 }
ESoch commented 1 year ago

@danieloprado, thanks for the suggestion. I've tried that and it does indeed fix this issue for the host application. The problem, however, is that we're using bi-directional module federation and when we set runtimeChunk: single it breaks any applications that are consuming a component from the host.

zhangHongEn commented 1 year ago

If you want to configure react-refresh to shared, you need react-refresh-webpack-plugin to change the import method of source code from react-refresh/runtime to react-refresh, and you also need to configure "react-refresh-webpack-plugin" include and exclude to avoid shared being referenced in the entry chunk, even so there will be other problems, so I also tend to force react-refresh to become a singleton to solve it quickly

zhangHongEn commented 1 year ago

https://github.com/zhangHongEn/universal-module-federation-plugin/tree/main/packages/single-react-refresh-plugin

pmmmwh commented 1 year ago

As someone else who is experiencing issues here with module federation, I am hoping to re-up this conversation.

@pmmmwh, would you be open to applying the changes described here: #516 (comment)

As module federation becomes more popular I can imagine this will become a more common issue.

I cannot really offer much help without any example to look into. For the patch, my stance still stands that polluting global and binding this package to browser only is a bad thing to do. I personally do not use module federation and is not knoledgeable with it by any means, so help would be appreciated.

zhangHongEn commented 1 year ago

As someone else who is experiencing issues here with module federation, I am hoping to re-up this conversation. @pmmmwh, would you be open to applying the changes described here: #516 (comment) As module federation becomes more popular I can imagine this will become a more common issue.

I cannot really offer much help without any example to look into. For the patch, my stance still stands that polluting global and binding this package to browser only is a bad thing to do. I personally do not use module federation and is not knoledgeable with it by any means, so help would be appreciated.

@pmmmwh You can refer to this example, app2 achieves hmr by mounting react-refresh/runtime to the global maintain singleton, app3's react-refresh/runtime is not a singleton so it cannot hmr https://github.com/wpmjs/examples/tree/main/module-federation-react-hmr

felixmokross commented 1 year ago

We also have this issue.

Workaround for us is to opt-into Module Federation when starting the dev server (via a command line arg), so that most developers are not impacted by this when developing the standalone application. When consuming a component via Module Federation, Fast Refresh doesn't work anyway as stated by @ScriptedAlchemy, so we are not losing anything.

    if (shouldEnableModuleFederation()) {
        config.plugins.push(getModuleFederationPlugin());
    }

…

    function shouldEnableModuleFederation() {
        return isEnvProduction || process.argv.includes('--enable-module-federation');
    }
alexander-akait commented 1 year ago

@felixmokross Maybe you can provide small reproducible example? It helps, thank you

dtyyy commented 1 month ago

Maybe you could use the library option to ensure react refresh is injected for both apps?

It seems like RefreshRuntime.injectIntoGlobalHook is not executed in MF, because the flag RefreshInjected has Injected by host app,but MF also require a refresh-runtime.So when refresh happend,MF can't make a connection with React.InjectIntoGlobalHook injects some hooks to make sure react refresh ready to perform.

const safeThis = require('core-js-pure/features/global-this');
const RefreshRuntime = require('react-refresh/runtime');

if (process.env.NODE_ENV !== 'production') {
  if (typeof safeThis !== 'undefined') {
    var $RefreshInjected$ = '__reactRefreshInjected';
    // Namespace the injected flag (if necessary) for monorepo compatibility
    if (typeof __react_refresh_library__ !== 'undefined' && __react_refresh_library__) {
      $RefreshInjected$ += '_' + __react_refresh_library__;
    }

    // Only inject the runtime if it hasn't been injected
    if (!safeThis[$RefreshInjected$]) {
      // Inject refresh runtime into global scope
      RefreshRuntime.injectIntoGlobalHook(safeThis);

      // Mark the runtime as injected to prevent double-injection
      safeThis[$RefreshInjected$] = true;
    }
  }
}
   // Library mode
      __react_refresh_library__: JSON.stringify(
        Template.toIdentifier(
          this.options.library ||
            compiler.options.output.uniqueName ||
            compiler.options.output.library
        )
      ),

as the code show, we can use library or other options to config the __react_refresh_library__.