score:3

Accepted answer

https://github.com/hinok/webpack2-react-router-code-splitting

Repository contains webpack2 + react-router v4 + implemented code splitting using dynamic import() and loading external libraries on demand only once by using loadjs. As example it loads Stripe.js for specific route.

In repository you can find two ways to do code splitting

It's based on official Webpack documentation and gist by Andrew Clark

While preparing that repository I found that @Chris would like to load Stripe.js only for certain routes which is hosted on external CDN. Since then I've been thinking about the best way to use also AMD modules and avoid leaking global variables but this is a bit tricky because each AMD module could have different wrapper, by saying wrapper I mean something like:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['b'], function (b) {
            return (root.amdWebGlobal = factory(b));
        });
    } else {
        root.amdWebGlobal = factory(root.b);
    }
}(this, function (b) {
    return {};
}));

I could say that UMD wrapper is kind of standard but sometimes people prefer less opinionated wrappers or they just don't care about other environments. I'm mentioning it because in the git history, you can find commit called Amdify which was more proof of concept how AMD env could be simulated. Finally I decided to remove it because it's far from ideal and it doesn't cover all usages and edge cases.

I couldn't find any information about integrating external AMD modules or using AMD modules in some way with webpack. On the contrary I found that it will just not work

@mc-zone you should be able to load AMD modules with script.js. But this has nothing to do with webpack, it just works because AMD was designed for this. Thus you won't be able to use webpack features inside these AMD modules. webpack requires static analysis on build time.

by @jhns see on github

webpack doesn't handle script loading. Use an separate library for this. i. e. https://github.com/ded/script.js/

by @sokra see on github

If it's not possible you should not require() it but load it via a script-loader like script.js.

by @jhns see on github

Related issues on Github:


Today I found an article by James Kale on medium about his project react-loadable which do almost the same thing as LazilyLoad component but with promise that

  • it supports server-side rendering via a dynamic require()
  • it eagerly preloads components when needed

I highly recommend checking it out.

score:0

If you are using Webpack 2 you use import() in your React Router config file

export default {
 component: App,
 childRoutes: [
   {
     path: '/',
     getComponent(location, cb) {
       import('external-library-here')
       .then(function(){
         return System.import('pages/Home');
       })
      .then(loadRoute(cb)).catch(errorLoading);
     }
   },
   {
     path: 'blog',
     getComponent(location, cb) {
       import('pages/Blog').then(loadRoute(cb)).catch(errorLoading);
     }
   },
   {
     path: 'about',
     getComponent(location, cb) {
       import('pages/About').then(loadRoute(cb)).catch(errorLoading);
     }
   },
 ]
};

You can also use the getComponent or getComponents props in the Router component to pass in modules you want specifically for that route.

score:0

There are some cases in this question that should be taken in detail.

Let's start.

Observations

  • You should switch the use of react-router from components to PlainRoute object, this will give you more flexibility when it comes to doing the code splitting, and also skips the creation of <AsyncComponent> component
  • I'm pretty sure you are going to have more nested components within your route, so what if a nested component within credit-card route needs a library?

Suggestions

  • Use your route component as an entry point, so it doesn't become a huge implementation in a single component
  • Given the above suggestion, you can add there an index with your library dependencies and use them within that route (in you nested components)
  • You should not import these libraries in any part of the code otherwise this functionality breaks
  • Once you have loaded these libraries inject them in the window object. and your nested components will able to access globally to your libraries

This is a suggestion that has not been implemented, but I'm trying to take you in the right direction regarding your question

In the end, this is the approach that I have suggested:

import App from 'components/App';

function errorLoading(err) {
  console.error('Dynamic page loading failed', err);
}

function loadRoute(cb) {
  return (module) => cb(null, module.default);
}

function injectLibraries(libraries) {
  Object.keys(libraries).forEach(key => {
    window[key] = libraries[key];
  });
}

export default {
  component: App,
  childRoutes: [
    {
      path: '/(index.html)',
      name: 'home',
      getComponent(location, cb) {
        const importModules = Promise.all([
          import('components/HomePage/'),
          import('components/HomePage/libraries'),
        ]);

        const renderRoute = loadRoute(cb);

        importModules.then(([component, libraries]) => {
          injectLibraries(libraries);
          renderRoute(component);
        });

        importModules.catch(errorLoading);
      },
    },
    {
      path: '/credit-card',
      name: 'credit-card',
      getComponent(nextState, cb) {
        const importModules = Promise.all([
          import('components/CreditCardPage/'),
          import('components/CreditCardPage/libraries'),
        ]);

        const renderRoute = loadRoute(cb);

        importModules.then(([component, libraries]) => {
          injectLibraries(libraries);
          renderRoute(component);
        });

        importModules.catch(errorLoading);
      },
    },
  ],
};

Your libraries file should look like this:

import stripe from 'stripe';

export default {
  stripe 
};

score:0

You may want to look into:

https://github.com/faceyspacey/webpack-flush-chunks

https://github.com/faceyspacey/require-universal-module

The latter is a general purpose package geared toward creating modules that have the need of BOTH synchronously and asynchronously requiring other modules.

This package is what you use to flush the Webpack chunks that were determined to be needed by the request:

https://github.com/faceyspacey/webpack-flush-chunks

It's not React Router specific, which likely means it's more straightforward and requires less workarounds.


Related Query

More Query from same tag