admin管理员组

文章数量:1344297

Currently working

Using Webpack 2 and React Router v4 I have been able to set up working code splitting. There is an intermediate <AsyncComponent> that resolves the promise and returns the ponent (a pattern found on github issues).

An example set of routes below:

<Switch>
    <Route 
        path="me" 
        render={(props) => 
            <AsyncComponent promise={ require.ensure([], (require) => require('./modules/Profile'), 'profile') } props={props} />
        } 
    />
    <Route 
        path="credit-card" 
        render={(props) => 
            <AsyncComponent promise={ require.ensure([], (require) => require('./modules/CreditCard'), 'credit-card') } props={props} />
        } 
    />
</Switch>

Objective

I would like to extend that further, and for certain routes only, load in additional libraries. In the example above, I would like to fetch the StripeJS (/) library only when on the credit card route.

I want to emphasise that I can load Stripe directly into the footer and all works normally. There are multiple libraries, I'm using Stripe as an easy to digest example.

Attempted

The following has been attempted to little success:

  • Marking the lib external within the webpack config. This (correctly) flags the library as being external and won't attempt to bundle it during the resolve sequence of a require. However the expectation is that the library is manually brought in.
  • I have played with the idea of using a pseudo script loader (when hitting that route, manually create a <script> with a src attribute, wait for it to load, then let the ponent do its thing. This works ok, but is really horrible from a maintainability (if two or more libraries are needed, I then need to replicate the clumsy manual script load) point of view and seems to work against the webpack "way".

Relevant parts of the config

const core = [
    'lodash',
    'react',
    'react-dom',
    'axios',
    'react-router-dom',
];

const config = {
    context: path.resolve(__dirname, './ts_build'),
    node: {
        fs: "empty"
    },
    entry: {
        app: './app.js',
        core: core,
    },
    output: {
        filename: '[name].js',
        chunkFilename: '[name].[id].chunk.js',
        path: path.resolve(__dirname, './../../public'),
        publicPath: '/',
    },
    resolve: {
        modules: [
            path.resolve('./src'),
        ],
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            names: ['core'], 
            minChunks: Infinity,
        }),
        new webpack.NamedModulesPlugin(),
    ],
};

Currently working

Using Webpack 2 and React Router v4 I have been able to set up working code splitting. There is an intermediate <AsyncComponent> that resolves the promise and returns the ponent (a pattern found on github issues).

An example set of routes below:

<Switch>
    <Route 
        path="me" 
        render={(props) => 
            <AsyncComponent promise={ require.ensure([], (require) => require('./modules/Profile'), 'profile') } props={props} />
        } 
    />
    <Route 
        path="credit-card" 
        render={(props) => 
            <AsyncComponent promise={ require.ensure([], (require) => require('./modules/CreditCard'), 'credit-card') } props={props} />
        } 
    />
</Switch>

Objective

I would like to extend that further, and for certain routes only, load in additional libraries. In the example above, I would like to fetch the StripeJS (https://js.stripe./v2/) library only when on the credit card route.

I want to emphasise that I can load Stripe directly into the footer and all works normally. There are multiple libraries, I'm using Stripe as an easy to digest example.

Attempted

The following has been attempted to little success:

  • Marking the lib external within the webpack config. This (correctly) flags the library as being external and won't attempt to bundle it during the resolve sequence of a require. However the expectation is that the library is manually brought in.
  • I have played with the idea of using a pseudo script loader (when hitting that route, manually create a <script> with a src attribute, wait for it to load, then let the ponent do its thing. This works ok, but is really horrible from a maintainability (if two or more libraries are needed, I then need to replicate the clumsy manual script load) point of view and seems to work against the webpack "way".

Relevant parts of the config

const core = [
    'lodash',
    'react',
    'react-dom',
    'axios',
    'react-router-dom',
];

const config = {
    context: path.resolve(__dirname, './ts_build'),
    node: {
        fs: "empty"
    },
    entry: {
        app: './app.js',
        core: core,
    },
    output: {
        filename: '[name].js',
        chunkFilename: '[name].[id].chunk.js',
        path: path.resolve(__dirname, './../../public'),
        publicPath: 'http://example/',
    },
    resolve: {
        modules: [
            path.resolve('./src'),
        ],
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            names: ['core'], 
            minChunks: Infinity,
        }),
        new webpack.NamedModulesPlugin(),
    ],
};
Share Improve this question edited Feb 23, 2017 at 10:43 Ben Everard 13.8k14 gold badges69 silver badges96 bronze badges asked Feb 13, 2017 at 15:03 ChrisChris 58.4k33 gold badges157 silver badges196 bronze badges 1
  • Thanks @Ben Everard for the edit - damn my spelling (ie my oven mitts that I call hands) – Chris Commented Feb 23, 2017 at 10:47
Add a ment  | 

4 Answers 4

Reset to default 3

https://github./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

  • by route config
  • by ponent

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

  • https://webpack.js/guides/lazy-load-react/
  • https://gist.github./acdlite/a68433004f9d6b4cbc83b5cc3990c194

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 mit 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./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:

  • https://github./webpack/webpack/issues/150
  • https://github./webpack/webpack/issues/118
  • https://github./webpack/webpack/issues/462
  • https://github./webpack/webpack/issues/240
  • https://github./webpack/webpack/issues/274
  • https://github./webpack-contrib/script-loader/issues/7

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

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

I highly remend checking it out.

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

export default {
 ponent: 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 ponent to pass in modules you want specifically for that route.

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 ponents to PlainRoute object, this will give you more flexibility when it es to doing the code splitting, and also skips the creation of <AsyncComponent> ponent
  • I'm pretty sure you are going to have more nested ponents within your route, so what if a nested ponent within credit-card route needs a library?

Suggestions

  • Use your route ponent as an entry point, so it doesn't bee a huge implementation in a single ponent
  • Given the above suggestion, you can add there an index with your library dependencies and use them within that route (in you nested ponents)
  • 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 ponents 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 'ponents/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 {
  ponent: App,
  childRoutes: [
    {
      path: '/(index.html)',
      name: 'home',
      getComponent(location, cb) {
        const importModules = Promise.all([
          import('ponents/HomePage/'),
          import('ponents/HomePage/libraries'),
        ]);

        const renderRoute = loadRoute(cb);

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

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

        const renderRoute = loadRoute(cb);

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

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

Your libraries file should look like this:

import stripe from 'stripe';

export default {
  stripe 
};

You may want to look into:

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

https://github./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./faceyspacey/webpack-flush-chunks

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

本文标签: