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 component (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.com/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 asrc
attribute, wait for it to load, then let the component 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 manualscript
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.org/', }, resolve: { modules: [ path.resolve('./src'), ], }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ names: ['core'], minChunks: Infinity, }), new webpack.NamedModulesPlugin(), ], };
2 Answers
Answers 1
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.
Answers 2
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 toPlainRoute
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 };
0 comments:
Post a Comment