0

I have been trying to find a good pattern to split up my code as I am currently bundling N versions of every React component, and only ever using one at a time.

The current pattern is

import {
    Component1 as Component1_Lib1,
    Component2 as Component2_Lib1,
} from 'lib1';

import {
    Component1 as Component1_Lib2,
    Component2 as Component2_Lib2,
} from 'lib2';

export const SiteSpecificComponent1 = (site) => {
    return {
            site1: Component1_Lib1,
            site2: Component1_Lib2
        }[site]
    };

etc.

These are then used in various components shared across the sites. The bundle will always include all versions of every component.

I have been trying to design a pattern to cleanly split this up. I have looked at lazy loading and conditional importing but I haven't come up with anything yet so I come to you. Are there any build tricks or architecture patterns you are familiar with that could help me split the unused components from my bundle?

Thanks for any help.

hjonasson
  • 140
  • 8

1 Answers1

1

Ideally, you should rely on Tree shaking to do its job such that only required components are ever bundled. However successful tree shaking depends on two factors:

  • Publishing your library as ESM Module. (Using type: "module" in your package.json file or using *.mjs files)
  • Using sideEffects to declare package side effect free (Your module should actually have not side effects like singleton, using global window object at the top of the module, etc.)

However, this is not enough. The presence of following code will not allow tree shaking to work properly as Webpack/Rollup cannot figure out what's the value of site would be at compile time. This means it ends up picking everything in the bundled code.

export const SiteSpecificComponent1 = (site) => {
  return ({
      site1: Component1_Lib1,
      site2: Component1_Lib2
  })[site];
};

To solve this problem, we need to make an assumption about your sites. If these different versions of the components are meant to be used in different sites/applications, then it probably means that application would have a separate build script and also separate repository. Now if you are using tech stack is latest - Node 16, Webpack 5, TypeScritp 4.5, Jest 28, then you can make use of new package.json exports fields that supports submodules or subpath exports. Your package.json will look like:

// package.json file
{
  "name": "your-lib-name",
  "type": "module",
  "exports": {
    "site1": "./dist/site1.js",
    "site2": "./dist/site2.js"
  }
}

In each file, you would only export components that belong to a particular site or application. For example,

// site1.js
export { Component1 as Component1_Lib1 } from 'lib1';
export { Component1 as Component1_Lib2 } from 'lib2';

// site2.js
export { Component2 as Component2_Lib1 } from 'lib1';
export { Component2 as Component2_Lib2 } from 'lib2';


// Usage
import { Component1_Lib1 } from 'you-lib-name/site1.js';
import { Component2_Lib1 } from 'you-lib-name/site2.js';

Of course, you loose the capability to dynamically get the component name using a method invocation, but this is more bundle friendly as it readily tree-shakeable.

Harshal Patil
  • 17,838
  • 14
  • 60
  • 126
  • This would be optimal but would require a lot of re-writes and re-configuration. Maybe I didn't explain this very well. There are a lot of components that just import `SiteSpecificComponent1` (or 2 or 3 or 4...). As you point out tree shaking doesn't work on this. – hjonasson May 31 '22 at 21:13
  • @hjonasson, I cannot seem to think of any other clean way. You can try importing bundle with dynamic imports inside the `SiteSpecificComponent1` function but that would make your function `SiteSpecificComponent1` async and will return promise. The code will still bundle everything but users will only get the bundle they really need. However, that's not really clean. It feels more like a code-smell. – Harshal Patil Jun 01 '22 at 05:13