Table of Contents
So far we created one bundle that loads the code for all components, no matter if we are going to render them or not. if for example we just open one page with only one component we don’t need to load the code for all other components that are not on the page. It makes the load process slower, especially with a big site with many components.
Let’s fix this and split the bundle into chunks that contain the code only for the components that are on the page that the user opened, and load dynamically other components when the user navigates to a different sections in the site.
Install react-loadable
This is as simple as adding ‘react-loadable’ component.
yarn add react-loadable
Implementing react-loadabe
Before modifying PageLayout to use react-loadable, let’s create helper component called Loading that will show a spinning bar while the components are loading.
Create ‘Loading’ helper component
create a loading component folder mkdir ./src/components/Loading
with index file with these contents:
./src/components/Loading/index.js
import React from 'react'; const styles = require('./styles.scss'); /** * HELPER COMPONENT TO DISPLAY LOADING ... AND HANDLE ERRORS WHEN COMPONENTS LOADS DYNAMICALLY * @param {} props */ const Loading = (props) => { if (props.error) { return (<div className={styles.wrapper}> <h3>Error loading component!</h3> <p><b>{props.error.message}</b></p> <p>{props.error.stack}</p> <div><button onClick={ props.retry }>Retry</button></div> </div>); } else { return <div>Loading...</div>; } } export default Loading;
and the styling file:
./src/components/Loading/styles.scss
.wrapper { background:rgb(141, 141, 172); padding-bottom: 10px; h3 { background: red; text-align: center; } div{ text-align: center; } }
and replace <div>Loading…</div> with the new component.
./src/containers/PageLayout/index.js
import React, { Component } from 'react'; import ComponentList from './ComponentList'; import Loading from '../../components/Loading'; import query from './query'; import { graphql } from 'react-apollo'; const styles = require('./styles.scss'); class PageLayout extends Component { constructor(props) { super(props); } render() { if(!this.props.data.getPageByUrl) { return (<Loading />); } ...
Adding react-loadable.
`react-loadable’ works the same way like ‘import’ but will load required components on demand and won’t pre-load the components until it’s time to render them.
./src/containers/PageLayout/ComponentList/index.js
import Loadable from 'react-loadable'; import Loading from '../../../components/Loading'; /* Components */ const Header = Loadable({ loader: () => import ('../../../components/Header'), loading: Loading }); const Home = Loadable({ loader: () => import ('../../../components/Home'), loading: Loading }); const About = Loadable({ loader: () => import ('../../../components/About'), loading: Loading }); const Greetings = Loadable({ loader: () => import ('../../../components/Greetings'), loading: Loading }); const DogsCatalog = Loadable({ loader: () => import ('../../../containers/DogsCatalog'), loading: Loading }); export default { Home: Home, About: About, Greetings: Greetings, DogsCatalogWithRedux: Gallery, Header: Header }
if you run the code you will notice that it will fail with this error message
`… Support for the experimental syntax ‘dynamicImport’ isn’t currently enabled (7:17)`
This is happening since Babel doesn’t have the appropriate plug-in to transpile dynamic imports. Let’s fix this by installing ‘plugin-syntax-dynamic-import’.
yarn add @babel/plugin-syntax-dynamic-import --dev
then add the plug-in into .babelrc file (line 6)
{ "presets": [ "@babel/preset-env", "@babel/preset-react" ], "plugins": ["@babel/plugin-syntax-dynamic-import"] }
Now open the dev console, switch to the net tab and filter by .js
and navigate to http://localhost:8080/home You will notice that there will be three bundles loaded: main-bundle.js, dist0-bundle.js and dist1-bundle.js the numbers could very but don’t worry about this as long as you see 3 bundles loaded.These bundles have the code for each component: Header component and the Home compoent. Navigate to the “about” section and you will see another bundle loading: dist2-bundle.js.
Now you have bundle split and loaded whenever the component is about to render. If you navigate back, you will see that the bundle won’t be reloaded.
Now the bundle splitting is done.
Fixing source mapping.
Let’s put debugger
breakpoint into DogsComponent and take a look at the file source:
./src/containers/DogsCatalog/index.js
/* eslint-disable no-debugger */ import React, { Component } from 'react'; import { graphql } from 'react-apollo'; import DogDetails from './DogDetails'; import query from './query'; const styles = require('./styles.scss'); class DogsCatalog extends Component { constructor(props) { super(props); this.state = { breed: "labrador" } } handleClick(breedType) { debugger; this.setState({ breed: breedType }); } ...
Give it a try and check the location and you will see something like this:http://localhost:8080/dist0-bundle.js
which is not quite understandable.
Let’s fix this. Open base Webpack file, and replace `devtool:'source-map'
with devtool: 'cheap-module-eval-source-map'
in order to have source mapping working after bundle splitting.
If we want even better source mapping with preserved column # as well we could use devtool: eval-source-map
which will give the most detailed information for debugging but will make the building process slower.
There are a good amount of Dev tool config parameters, some are suitable only for development since they make bundle rebuild faster some for production only.
Dev tool option | build | rebuild | production | debug quality |
---|---|---|---|---|
eval | +++ | +++ | no | This is pretty fast. The main disadvantage is that it doesn't display line numbers correctly since it gets mapped to transpiled code instead of the original code |
cheap-eval-source-map | + | ++ | no | Each module is executed with eval() and a SourceMap is added as a DataUrl to the eval(). t is "cheap" because it doesn't have column mappings, it only maps line numbers. It ignores SourceMaps from Loaders and only display transpiled code similar to the eval devtool. |
cheap-module-eval-source-map | 0 | ++ | no | Similar to cheap-eval-source-map, however, in this case Source Maps from Loaders are processed for better results. However Loader Source Maps are simplified to a single mapping per line. |
eval-source-map | -- | + | no | Each module is executed with eval() and a SourceMap is added as a DataUrl to the eval(). Initially it is slow, but it provides fast rebuild speed and yields real files. Line numbers are correctly mapped since it gets mapped to the original code. It yields the best quality SourceMaps for development. |
cheap-source-map | + | o | yes | A SourceMap without column-mappings ignoring loader Source Maps. |
cheap-module-source-map | 0 | - | yes | A SourceMap without column-mappings that simplifies loader Source Maps to a single mapping per line. |
inline-cheap-source-map | + | 0 | no | Similar to cheap-source-map but SourceMap is added as a DataUrl to the bundle. |
inline-cheap-module-source-map | 0 | - | no | Similar to cheap-module-source-map but SourceMap is added as a DataUrl to the bundle. |
source-map | -- | -- | yes | A full SourceMap is emitted as a separate file. It adds a reference comment to the bundle so development tools know where to find it. |
inline-source-map | -- | -- | no | A SourceMap is added as a DataUrl to the bundle. |
hidden-source-map | -- | -- | yes | Same as source-map, but doesn't add a reference comment to the bundle. Useful if you only want SourceMaps to map error stack traces from error reports, but don't want to expose your SourceMap for the browser development tools. |
nosources-source-map | -- | -- | yes | A SourceMap is created without the sourcesContent in it. It can be used to map stack traces on the client without exposing all of the source code. You can deploy the Source Map file to the webserver. |
For detailed information about all options visit WebPack dev tool page.
Make sure that the debugger
statement is removed before we continue to the next chapter.