How can I get webpack-dev-server to stop downloading incorrect chunks on content change with React lazy/Suspense code splitting?
Asked Answered
C

2

7

Here is my set up:

const DesktopApp = lazy(() => import(/* webpackChunkName: "DesktopApp" */'./DesktopApp'));
const MobileApp = lazy(() => import(/* webpackChunkName: "MobileApp" */'./MobileApp'));

type Props = { shouldServeMobile: boolean };

export const App = ({ shouldServeMobile }: Props): JSX.Element => (
  shouldServeMobile
    ? (
      <Suspense fallback={<AppLoading />}>
        <MobileApp />
      </Suspense>
    ) : (
      <Suspense fallback={<AppLoading />}>
        {/* GlobalDesktopStyle is injected in multiple places due to a bug where the
          theme gets reset when lazy loading via React.Lazy + webpack */}
        <GlobalDesktopStyle />
        <DesktopApp />
      </Suspense>
    )
);

This is being loaded by a webpack-dev-server with the following configuration:

  devServer: {
    contentBase: paths.output.path,
    // this host value allows devices on a LAN to connect to the dev server
    host: '0.0.0.0',
    https: true,
    port: 9001,
    hotOnly: true,
    // lets any URL work
    historyApiFallback: true,
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]

Now, imagine that we are rendering

import { hot } from 'react-hot-loader/root';
// some imports omitted

const HotApp = hot(App);

ReactDOM.render(
  <HotApp shouldServeMobile={true} />,
  document.getElementById('root')
);

On initial load, this works properly. The MobileApp chunk downloads and the DesktopApp does not. However, as soon as I change any data within my components and the HMR kicks in - the reload downloads the DesktopApp chunk.

This clearly defeats the purpose of code splitting. Does anybody have any idea how to stop this from happening?

To be clear: I have outputted console.log(shouldServeMobile) and it is always true. Also, I tried the suggestion here: Webpack-dev-server emits all chunks after every change and it didn't help at all.

Castera answered 6/11, 2019 at 2:50 Comment(2)
Why do you need react-hot-loader/root instead just use webpack hot reloader. ?Plurality
@SakhiMansoor I'm fairly sure it's so that react hooks can hot reload too. It's been a while since I set it up, so I may be mis-remembering. The codesplitting is new - using this hot-reloading setup is not. But yes - if memory serves correctly, you can't hot reload hooks without that.Castera
C
1

I think it has to do with react-hot-loader

Per https://github.com/gaearon/react-hot-loader

our internal processes of re-rendering React Tree, which is required to reconcile an updated application before React will try to rerender it

So I think the hot loader you're using, by design, will try to render the entire tree, regardless of the laziness or current state so it can reconcile the changes.

An option to look into might be instead of hot reloading the entire app like you have, hot reload the <MobileApp /> and <DesktopApp /> separately w/in those components. That way you can keep the laziness of the app for bundle splitting, which works on load, but hot reload changes depending on the bundle being used.

Coprolalia answered 11/11, 2019 at 18:14 Comment(3)
Considering react-hot-loader was made by one of the main people behind Suspense/lazy, and considering that the react-hot-loader github README has a section dedicated to code splitting that makes it clear that it supports react.lazy - I doubt that's the issue. (though it could be) The suggestion around hot reloading the separate components is a good one - but I have shared logic that runs above that switch that would then no longer be captured in hot reloads.Castera
There's a few people with issues in that repo regarding code splitting. Some can't get it to work at all, doubt they are caring too much about bundling if the hot reloading is generally working. And if you're using hooks it looks like it's a crapshoot. Hopefully you can nail it down, sorry couldn't be more help.Coprolalia
This answer doesn't meet my needs, but it is likely on the right track and is informative (and not obviously completely uninformed and incorrect like the other one). I'd rather somebody get the bounty than for it to expire, so I've given it to you. Not gonna check this as the right answer though as all you've given me is conjecture.Castera
S
-1

Should work:

    const DesktopApp = lazy(() => import(/* webpackChunkName: "DesktopApp" */'./DesktopApp'));
    const MobileApp = lazy(() => import(/* webpackChunkName: "MobileApp" */'./MobileApp'));

    type Props = { shouldServeMobile: boolean };

    export const App = ({ shouldServeMobile }: Props): JSX.Element => (
        <Suspense fallback={<AppLoading />}>
            shouldServeMobile
            ?
              <MobileApp />
            : <GlobalDesktopStyle />
              <DesktopApp />
         </Suspense>

    );
Sibyl answered 18/11, 2019 at 3:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.