Here at Innovid, a leading video marketing platform where we serve 1.3 million hours of video per day, we love Webpack and use it in many of our web-based projects.

Recently we migrated one of our projects to the new Webpack 4, which provides some great features out of the box including a drastic decrease in build time.

As part of the migration we decided to also code-split a major part of our app by using one of the most compelling features Webpack has to offer:

Lazy Loading.

Code-splitting your app can help you “lazy-load” just the things that are currently needed by the user, which can dramatically improve the performance of your app. While you haven’t reduced the overall amount of code in your app, you’ve avoided loading code that the user may never need, and reduced the amount of code needed during the initial load. — React documentation

Webpack builds a dependency graph from your application. Starting from your entry points, it traverses your files and their dependencies, does all sorts of magic like running your files through multiple loaders and plugins, and finally outputs your generated bundles which are then served to the clients.

We currently split our generated bundles into two files: app.js (our app code) and vendors.js (our 3rd party libraries).

We used webpack-bundle-analyzer to get this nice view of our two generated bundles:

Initial bundles split: vendors.js 399 KiB, app.js 116 KiB

Webpack Configurations

The app is bundled automatically as it’s the entrypoint of our application.

The 3rd party vendors are bundled using the new optimization config and by specifying that the vendors bundle should include all of the files that are imported from our node_modules:

Webpack 4 loading vendors into vendor.js and generating a manifest file which includes the Webpack runtime

Note: in Webpack 4 we no longer use the CommonChunkPlugin. It was removed in favor of some new API named splitChunks and runtimeChunk .

Lazy Loading React Components

Currently the vendors and the app bundles are the ones our clients will initially download when first loading the page. We realized we can improve this by lazy loading a pretty “heavy” component and reducing our initially served bundle sizes.

For example: redux-form (a library for managing forms in react redux apps), which is only used in a large component named GenerateTags , is a good candidate for lazy loading, as it’s quite large and used only in specific scenarios. The redux-form dependency and our GenerateTags component could be extracted to a different chunk, resulting in a smaller bundle size served on the first page render.

We looked into a popular library called react-loadable for utilizing dynamic imports, which is basically a wrapper around the future javascript import() syntax (it’s part of stage 3 of the TC39 process).

Using React Loadable for lazy loading our GenerateTags component

After this change our bundles looked like this:

GenerateTags is separated to a different chunk, redux-form is still in the vendor.js bundle

The result is almost what we wanted but redux-form is still included in the vendors bundle, and we wanted redux-form and GenerateTags to be served in different chunks and be lazy loaded on demand.

This was happening because redux-form is also being imported in another file, where we add the redux reducer into our combineReducers redux function like this:

adding redux form reducer to our app reducers

The static import statement at the top causes the redux-form library to be a part of our vendors bundle. This makes sense: Webpack realized it cannot be lazy loaded as it’s statically imported as part of our app entry point tree of dependencies.

To improve this we decided to dynamically inject the redux-form reducer. First we removed the import of the redux-form reducer at the top and added some code to allow injecting redux reducers dynamically:

Configuring the redux store and Injecting Async Redux Reducers

Lastly we call our injectAsyncReducer from the componentDidMount of GenerateTags component:

Injecting the redux form reducer on the GenerateTags component load

Note that getting a direct reference to the store from the component is not so recommended and can be quite problematic when you do server side rendering.

You can read more about injecting async reducers and also about a cleaner way to inject the reducer using a HOC (High-order component) here.

Typescript Configurations

As we use Typescript in our project, we had to update the module property to esnext and set the removeComments property to false in our tsconfig.json file (Typescript version must be ≥ 2.4 to support dynamic imports). This will allow the dynamic import behavior. by “telling” the typescript compiler to leave our import statements alone and not transpile them along with their inline comments, allowing Webpack to take control and do its magic.

tsconfig.json for dynamic imports

The final result looks like this:

Final split: vendors.js 314 KiB, app.js 96.6 KiB, generateTags.js 23.2 KiB, vendors~generateTags.js 90.2 KiB

Finally we achieved what we wanted, the GenerateTags along with its dependency redux-form are now separated from our vendors bundle and lazy loaded on demand.

Summary

We recommend reading more on optimization techniques using Webpack in this link.

By using dynamic imports in your app you can reduce the final bundle sizes served on first page load and provide faster load time by deferring the load of certain parts of your app.

Typescript supports dynamic imports since version 2.4. You only need to remember setting a few properties in tsconfig.json to support this feature.

Migrating to Webpack 4 is pretty straightforward. Currently not much documentation exists on the new configurations and API, but it will definitely improve soon.

Dynamically injecting redux reducers is a good trick to keep up your sleeve. It enabled us to lazy load a library which used a redux reducer in our app.

References:

* RIP CommonsChunkPlugin

* https://reactjs.org/docs/code-splitting.html

* https://medium.com/front-end-hacking/lazy-loading-with-react-and-webpack-2-8e9e586cf442

* https://blogs.msdn.microsoft.com/typescript/2017/06/27/announcing-typescript-2-4/

* https://survivejs.com/webpack/building/code-splitting/

* https://medium.com/front-end-hacking/lazy-loading-with-react-and-webpack-2-8e9e586cf442

* https://tylergaw.com/articles/dynamic-redux-reducers/

* https://stackoverflow.com/questions/32968016/how-to-dynamically-load-reducers-for-code-splitting-in-a-redux-application

* https://developers.google.com/web/updates/2017/11/dynamic-import

* https://reacttraining.com/react-router/web/guides/code-splitting