5 min read Nov 8, 2020 Serverless - Bundle Lambda Functions with Webpack Tree Shaking
Prashanth HN

5 min read

2020-11-08

Serverless framework is the popular way of deploying nodejs applications into Lambda functions. In this article, we will look into how the Serverless framework bundles the code & why it is not the best option for large applications. And then, we will look into how we can override bundling done by Serverless framework & implement our way of bundling the application more optimally. We will be using Webpack to achieve the same!

How Serverless does it?

Let’s create a sample nodejs project & understand how the Serverless framework bundles the code by default.

sls create --template aws-nodejs --path tree-shake-example

cd tree-shake-example

The first command generates a sample Serverless project using the nodejs template. You will see a hello world function in handler.js and that being referenced in serverless.yml

functions:
  hello:
    handler: handler.hello

Let’s make a copy of handler.js, call it hi.js & add it in serverless.yml

functions:
  hello:
    handler: handler.hello
  hi:
    handler: hi.hello

What we have here is two identical functions. Let’s deploy them and see what happens:

sls deploy

We can open the AWS console and see the size of functions:

first-deploy.png

As you can see, both Lambda functions have the same size, which was expected since they were identical. But, there’s more to look when you get inside the function:

function-directory.png

As you see above, both handler.js & hi.js is bundled into the Lambda function. Ideally, we should have had only handler.js in the handler function and hi.js in the hi function. While this is ok for small applications, as your application grows, you will be sacrificing performance and also start approaching Lambda limits.

Assume that you added a library which you use in only one lambda function, but still, it is bundled in all the lambda function unnecessarily. This will increase the bundle size and can impact cold-start times of the Lambda function. To demonstrate this, I installed axios to the project & imported it into the “hi” function.

npm i --save axios

Add import statement into hi.js

import axios from "axios"

Our function size has gone up from 746bytes to 135.5kb & it has increased for both functions even though we are using axios in only one. No changes were made in the handler.js.

Function size after adding axios

Bundling using Webpack tree-shaking

Tree-shaking is a dead code elimination technique; you can read more about that on Wikipedia. We can achieve this in our project using Webpack. Let’s add webpack, webpack-cli & zip-webpack-plugin (needed to zip the bundle) as dev dependencies.

npm i --save-dev webpack webpack-cli zip-webpack-plugin

We need to tell webpack how we want to bundle our application. To do that, we will have to create webpack.config.js

const ZipPlugin = require("zip-webpack-plugin")
const path = require("path")

const config = {
  //what are the entry points to our functions
  entry: {
    handler: "./handler.js",
    hi: "./hi.js",
  },
  //how we want the output
  output: {
    filename: "[name]/index.js",
    path: path.resolve(__dirname, "dist/"),
    libraryTarget: "umd",
  },
  target: "node",
  mode: "production",
  optimization: { minimize: false },
}
//finally zip the output directory, ready to deploy
const pluginConfig = {
  plugins: Object.keys(config.entry).map(entryName => {
    return new ZipPlugin({
      path: path.resolve(__dirname, "dist/"),
      filename: entryName,
      extension: "zip",
      include: [entryName],
    })
  }),
}

const webpackConfig = Object.assign(config, pluginConfig)
module.exports = webpackConfig
  • We instruct webpack the entry point for each of our Lambda functions; webpack can start here and build a dependency tree based on import statements. This is why we should always do named imports.
  • For each entry point, webpack will generate a single index.js file containing all the dependencies that function needs. Webpack places this file in functionName/index.js under dist directory as specified in config. So, we will have dist/handler/index.js & dist/hi/index.js
  • Then, using zip-webpack-plugin, we zip these individual directories into zip files. The result would be dist/handler.zip & dist/hi.zip

Now that we have done all the configs, we can give it a shot by running webpack. To make it easier, we can add that as a script inside our package.json

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "bundle": "./node_modules/.bin/webpack"
  }

With that in place, we can run:

npm run bundle

The above script should create a new directory dist and create our bundles & create zip files as expected. I went ahead and checked the file size of both the index.js files:

Files after tree-shaking

The bundle of the handler function is at 1kb & hi function, which had axios import, is at 35kb. That confirms our tree-shaking technique is working!

Overriding Serverless framework’s Bundling

Now that we took the job of bundling into our own hands, we need to tell Serverless not to bundle & use the bundle we generated by passing artifact attribute

functions:
  handler:
    handler: handler/index.hello
    package:
      artifact: "./dist/handler.zip"
  hi:
    handler: hi/index.hello
    package:
      artifact: "./dist/hi.zip"

We have done everything needed & good to go with deployment!

sls deploy

Time to load AWS console and see what sizes we get there

Files after tree-shaking

And it looks like we have achieved what we wanted! But if you recollect, the handler function in the first deployment was 746bytes & we haven’t changed it at all! But now it is at 1.5kb! Indeed we are missing something here! Change the minimize flag under optimization in webpack config to true

optimization: {
  minimize: true
}

This enables the minification of our bundle and reduces the size drastically.

Files after tree-shaking

With that, we are done! Happy Tree Shaking!!

Topics

Share this blog

Serverless - Bundle Lambda Functions with Webpack Tree Shaking

Nov 8, 2020

By default, Serverless framework bundles the entire source code into each lambda, but why should you bundle dependencies which may not be required by a Lambda?

5 min read

5 min read

Nov 8, 2020

Serverless Starters - A Community For All Serverless Enthusiasts

Jul 14, 2020

Serverless Starters is a community for all serverless enthusiasts, beginners to experts

1 min read

1 min read

Jul 14, 2020

Implementing OTP based Phone auth in Amazon Cognito using Custom Auth Flow & Amplify

Dec 18, 2019

OTP based logins are gaining popularity in India since there is no need to remember passwords and huge mobile user base

5 min read

5 min read

Dec 18, 2019

Introducing AntStack

Oct 5, 2019

Story of how AntStack came into existence

6 min read

6 min read

Oct 5, 2019

This website stores cookies on your computer.

These cookies are used to collect information about how you interact with this website and allow us to remember you. We use this information in order to improve and customize your browsing experience and for analytics and metrics about our visitors on this website.

If you decline, your information won’t be tracked when you visit this website. A single cookie will be used in your browser to remember your preference not to be tracked.