We've recently been looking at implementing code splitting into our complex web application to reduce the main bundle size. The main part that we're focussing on is dynamic imports using webpack & react-loadable.

I've come across something I deem to be quite a big problem, take this example:

const FirstChild = Loadable({
    loader: () => import('some/file/path/FirstChild.jsx') 
});
const SecondChild = Loadable({
    loader: () => import('some/file/path/SecondChild.jsx') 
});
const ParentComponent = () => (
    <div>
        <FirstChild />
        <SecondChild />
    </div>
);

where FirstChild.jsx & SecondChild.jsx both import the same service:

import MyService from 'some/file/path/my.service.js';

When bundling this with webpack, we end up with 3 files:

  • Main bundle (which includes the parent component)
  • FirstChild bundle (which includes MyService)
  • SecondChild bundle (which also includes MyService)

At this point I see a problem - we have duplicates of MyService between both files. For some small apps this might not be a problem (or if the service was specifically just helper methods) but if we're using our service to store some data across the lifetime of the app, we would end up with two object references of this service, therefore defeating it's point entirely.

I understand that the service here could be moved 'top-level' to the ParentComponent and possibly passed as a prop to each component but it seems like it destroys the architecture that webpack has in place in the first place - to be able to import whatever we need wherever we need and it creating just one reference. It also could be problematic if you have lots of nested components which all need to import various services and other components.

Obviously this example is simple but implementing this into a massive app which has a very complex architecture could immediately run us into problems.

Any thoughts on this? Thanks!

1 Answers

0
LT86 On

Going to answer my own question on this one.

Discovered that:

  • It doesn't matter that MyService.js ends up in the output of each chunk. Webpack is clever enough to only load the first instance of MyService that it finds and uses just one reference for that. Meaning that if FirstChild loads MyService first, then SecondChild will also use the same MyService that was loaded by FirstChild and it's copy of MyService will just be ignored.
  • Files imports inside dynamic imports can be split manually by adding small Webpack config & regex filename matching (thanks @AndriiGolubenko for pointing me in the right direction), we just end up with strange folder structures & file names. For eg the code for MyService.js in this case might end up in a chunk like services~FirstChild/SecondChild/SecondChild.js - which doesn't make very nice for debugging but I assume it's the way Webpack references it's imports.

    optimization: {
        splitChunks: {
           cacheGroups: {
                services: {
                    test: /\.service.js$/,
                    enforce: true
                }
            }
        }
    },

Please let me know if any of that is wrong or you find other interesting points about code splitting + webpack.