Thanks to webpack, code splitting has become easy with dynamic import. However, for code splitting to work together with server-side rendering, things become more tricky and not that straight forward because at the server side, the modules have to be loaded synchronously in order to render the page correctly. There are several different available loaders that can help get the job done, with the correct configuration and implementation. Or using the simpler and easier method I would like to share in the Part 2 of this post.
This post will be break down into 2 parts:
Part 1: Enabling code splitting
Part 2: Support code splitting with server side rendering
Enable react code splitting
Goal: To enable code splitting (with redux-first-router)
Prerequisite: Some experience with React, Redux and redux-first-router. Check out my Get started series if needed.
Base project code: Example code from From redux-little-router to redux-first-router for React/Redux SPA will be used as starting point.
Dynamic Import
syntax: var promise: Promise<any> = import('./AnyReactComponent')
Calling import within code will dynamically load the module and returns a promise. Once the module finishes loading, the promise resolves and return the module for use. When bundling using webpack, webpack will code split this module as a separate file and only load the module when import is invoked.
Important: path to the component file should use literal string. (variable shall not be used or webpack will not be able to do the code splitting correctly)
Asynchronous React Component
To enable dynamic loading of the module, we use a React component (loader) as a wrapper. This wrapper component will only import the actual module when the corresponding page is shown. Let’s create the a new file AsyncComponent.tsx under Scripts folder with following codes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import * as React from 'react'; interface Props { loader: () => Promise<{ default: any }>; } export class AsyncComponent extends React.PureComponent<Props, { Component: any }> { state = { Component: null }; componentDidMount() { this.props.loader() .then(module => { this.setState({ Component: module.default }); }); } render() { const { Component } = this.state; return Component && <Component />; } } |
This is a basic version (no error handling) of an asynchronous component to load the module on demand. A loader (function) is passed as a property to AsyncComponent. When the component is mounted, it will invoke the loader and start downloading the actual module for display. Once the module is loaded, the default export will be used as React component for rendering. Below shows an example of the loader:
() => import('./Contact')
Note: there are several react packages that offer more sophisticated asynchronous loader including react-loadable, react-async-component, react-universal-component and etc. For the purpose of this tutorial, we will simply use the basic one.
Modules for code splitting
Next we create separate files for About page and Contact page:
About.tsx:
1 2 3 4 5 6 7 |
import * as React from 'react'; export default class Comp extends React.PureComponent<any, any> { render() { return <div>About page</div>; } } |
Contact.tsx:
1 2 3 4 5 6 7 |
import * as React from 'react'; export default class Comp extends React.PureComponent<any, any> { render() { return <div>Contact page</div>; } } |
Finally, we will update routes.tsx to use AsyncComponent for code splitting.
Add following codes:
1 2 3 4 5 6 |
import { AsyncComponent } from './AsyncComponent'; const loaders = { CONTACT: () => import('./Contact'), ABOUT: () => import('./About') } |
Then replace the Components variable in routes.tsx as follow:
1 2 3 4 5 |
const Components = { HOME: <HelloWorld />, ABOUT: <AsyncComponent key='ABOUT' loader={loaders['ABOUT']} />, CONTACT: <AsyncComponent key='CONTACT' loader={loaders['CONTACT']} /> }; |
Use of key property is for React to identify that those are different AsyncComponent instances.
Typescript configuration
Webpack code-splitting would not work with default module target (CommonJS). The module target should be set to esnext. So let’s add the following lines to tsconfig.json
1 2 |
"module": "esnext", "moduleResolution": "node", |
That’s it. Build and run and you have About and Contact pages loaded on demand!
Important: So far we have taken care of code splitting at client side. But it’s not working correctly at server side rendering yet. Guess what the problem is. We will discuss and resolve the problem in part 2. Stay tuned!
Example code is available at Github.
2 Comments
Pascal
Thanks ! A simple project that compiles…
I tried several projects but I didn’t find a good Aspnet Core + Typescipt + Redux solution for code splitting.
When do you plan to publish part 2 SSR (or at least the source code) ?
Alan Chan
Hopefully I can have part 2 ready in a couple days. Glad that it’s helpful to you. Thanks for leaving comments.