reactjswebpackserver-side-renderingreact-loadable

A/B testing for React SSR app with chunking


Versions:

webpack: 4.30.0,
react: 16.8.6,
react-loadable: 5.5.0,

I'm having one entry JS in webpack. Other chunks are currently created using react-loadable and vendor JS is created using splitChunks.

Idea of my application: SSR and chunking is working with react-loadable for route-level components. And dynamic-components chunk can be made using either webpack's import promise or react Loadable.


Now, I want to introduce A/B testing to these dynamic components.

I've 2 approaches:

  1. First approach: Wrap dynamic-import statement inside if-else to import A/B variation of that dynamic-component. And at server level, I would know that for a given user, I would set a flag in Redux store, in order to select A/B variation. (This approach can be followed for whether app is SSR and SPA.)

    Notes for this approach:

    a. Seems doable :P

    b. Can't scale for A/B/C very easily. In this case, I would have to introduce a 3rd else condition to dynamically import C variation. It'll bust the browser caching for the route-level component's chunk(containing these variations) because hash in file name is changed. And same will happen to all the route-level components which takes effect for adding additional C testing.

    c. Have to unnecessarily pollute code with if-conditions. If A/B testing is completed, then code again will have to modified, which busts the browser cache again.

  2. Second Approach: I'll have only 1 dynamic import in code but I want 3 different chunks for the same component A, B and C, like A/some_chunk[hash].js, B/some_chunk[hash].js and C/some_chunk[hash].js. And at server level, I would use same logic for segmenting the user for A/B/C testing as in approach 1, but instead of setting flag in redux store, **I'll serve some_chunk[hash].js from A, B or C folder as per user's segment.

    Notes for this approach:

    a. I need to ask how we can create chunks for dynamic-component without they being actually imported in a file.

    b. Can very easily scale for A/B/C testing, since now A/B testing is now dependent upon which variation of file server is serving.

    c. No polluting of code with if-else conditions in client code. No issues of browser cache busting.

    d. Will need separate wrapper component for server code for ReactDOMServer.renderToString to know which variation to pick for server side rendering.

Questions:

  1. So, I need to know how we can create chunk as per 2nd approach? Because webpack would ignore creating chunk for the file if not reachable (i.e not imported by any other file)

  2. Would you recommend this approach. What's the right micro FE app approach of doing AB testing?

PS:

If this is doable, then my roadmap would be to chunk for base layouts as well. Where I can avoid if-else conditions in code and entire layout can be changed as per which A/B variation server sends to browser.


Solution

  • This can be done using webpack's weak resolve

    Example from webpack docs:

    const page = 'Foo'; // Trick: Can be taken from props
    __webpack_modules__[require.resolveWeak(`./page/${page}`)];
    

    My use case:

    Suppose, we're doing A/B testing on component D which has variations D1, D2 and D3.

    We can make folder D/ with D1.js D2.js and D3.js variations inside this folder. Now, require.resolveWeak('./D/${variation}') will pack chunks for D1, D2 and D3 in the build folder. Now, on runtime, passing the props to pick the particular variation will dynamically load that JS.

    Note: For eg: to pick D2 variation, experiment name also must be D2 (or else you must store mapping of experiment name to component name) to be passed as props. Generally, people do A/B testing by just having multiple if-elses. So, in the loadVariationOfD.js, instead of having weak resolve import statement, if-else is used with dynamic imports(I'm using loadable-components for this).