Photo by Heidi Sandstrom. on Unsplash

Optimizing your Node.js lambdas with Webpack and Tree shaking

Learn how to apply tree shaking to remove dead code from your lambdas

TLDR: Using Webpack to bundle your Node.js lambdas can help reduce your package size significantly and ship only the code you actually use.

Introduction To Webpack

If you’re not familiar with Webpack and Tree Shaking you can read about here.
Basically it is a technique used for dead code elimination when bundling JavaScript assets to improve loading time of web sites (smaller files, faster loading times).

The same technique can be applied to Node.js lambdas using the wonderful Serverless Webpack plugin.

I recommend reading Jeremy Daly’s post about the Serverless Optimizer Plugin as well, as it inspired me to write this one.

Setting Up The Environment

For the rest of this post, I assume you already have the Serverless Framework installed and configured (along with the AWS CLI), and that you have at least Nodejs version 8 installed.

This is a “hands on” post so expect to play with some code :)

I’ve prepared a git repository with the different steps, so you should start by cloning it and switching to the no_webpack_optimization branch:

git clone git@github.com:erezrokah/serverless-webpack-example.git
cd serverless-webpack-example
git checkout no_webpack_optimization

Make sure to install dependencies by running yarn install (or npm install).

The repository contains two functions, both using the lodash library to simulate an external dependency. In addition each function imports a different utility method from a utils.js file:

Project structure
handler1 code
handler2 code

Let’s deploy the code and see what happens. Run yarn deploy (or npm run deploy) and check out the result in you aws console. You should see something similar to this:

Code size without optimization

If you inspect the functions’ code you’ll see:

handler1 deployed code
handler2 deployed code

740.6kB seems a lot for a simple function. You’ll notice we have many redundant files in our package and also the entire lodash library. 
This is because the serverless framework packages everything but dev dependencies. 
We could exclude items in our serverless.yml but doing so manually seems time consuming, especially when we can use the Serverless Webpack plugin to do it for us.


Adding Webpack To The Mix

Let’s switch to the next branch to add Webpack support:

git checkout basic_webpack_no_tree_shaking

Run yarn install (or npm run install) to update dependencies.
Notice the changes to serverless.yml and the new webpack.config.js file:

serverless.yml configured with Webpack plugin
Webpack basic configuration

If you use npm as your packager make sure to update serverless.yml accordingly. 
Also, notice the externals Webpack configuration. Anything under externals won’t be part of the bundle.

Let’s deploy again. Run yarn deploy (or npm run deploy) and examine the result in you aws console. You should see something like this:

Code size with basic optimization
handler1 minified deployed code
handler2 minified deployed code

Nothing to get excited about. Most of the redundant files are removed, our function code is minified, but we still package the entire lodash library despite the fact we only use a single function, and if you look at the minified code carefully you’ll notice each handler contains both the square and cube utility functions.


Let’s Shake Some Trees

In order to support tree shaking we need to change our code to use ES2015 modules syntax. Let’s switch to the webpack_with_tree_shaking branch:

git checkout webpack_with_tree_shaking

Run yarn install (or npm run install) again.
Check out the modified handlers’ code and notice we’ve switched to the lodash-es library that exports ES modules:

handler1 code with ES2015 syntax
handler2 code with ES2015 syntax
Usage of lodash-es in our package.json file

Let’s deploy again. Run yarn deploy (or npm run deploy) and look at the result in your aws console:

Code size with tree shaking
handler1 optimized deployed code
handler2 optimized deployed code

Now this is something. Each handler contains just a single optimized file. Only the necessary code from lodash was included, and you can notice only the relevant utility (square or cube) function was added to each handler.

The downside here is that the code is mangled as Webpack runs in production mode and it is difficult to debug. There is another branch called add_source_maps you can use to add source map support for better debugging and also play with various Webpack settings to maintain function names and beautify the code.

Summary

We’ve gone from 740.6kB to 6.6kB in just a few one time steps, but I think the issue here is not just optimizing package size.

Dealing with dependencies in the JavaScript world can be hell. One should not do it manually when writing lambdas, especially when we can leverage the amazing work that had already been done in other parts of the JavaScript ecosystem (might we see lambdas loading code dynamically? Not sure that this is such a good idea…).