Every Friday, I will share a small tip with you on something Lambda/FaaS-related. Because Fridays are fun, and so are functions. 🥳

Today we’ll cover why and how to package your Node.js Lambda functions for deployment using Webpack and the Serverless Framework. This is an approach I take for all my Lambda function development.

What problem does this solve?

The primary goal of using Webpack is to reduce the amount of code contained in the zip artifact that is uploaded when your Lambda function is being deployed. This has the benefit of reducing cold start times whenever your function code is loaded into memory.

It also has some secondary benefits:

  • Lower security risk as only the required parts of third party modules are deployed rather than the entire contents of the node_modules folder.
  • Transpilers such as TypeScript and Babel can be easily hooked into the build process via Webpack loaders.

How does it work?

If you’ve used Webpack in the past for front-end development, you might already know that (amongst other things) it’s used to bundle multiple client-side JavaScript modules into a single file. You may not know that it can be used to do the same thing with server-side Node.js modules (it’s just JavaScript after all).

It works by first configuring an entrypoint function, which in our case will be the Lambda handler function. Starting with this function, it proceeds with a static analysis checking for require and import statements, following each path to other files as needed. It bundles up each file into its own module within a single file. Webpack uses a technique called treeshaking to eliminate dead code and only import the specific functions from a module that are referenced by your application code.

You might also know that Webpack can be pretty complex to configure! Don’t worry though, our configuration will be simple and we’ll be using the serverless-webpack plugin to help us.

This plugin allows us to create optimised individual bundles for each Lambda function in our service.

Setting it up

You can follow the detailed instructions on the serverless-webpack plugin README, but here’s a quick run-through of my standard setup. I’m assuming you already have the Serverless Framework installed and an existing serverless.yml file in place.

Install the plugin:

npm install serverless-webpack --save-dev

Add the following sections to your serverless.yml file:

# serverless.yml

custom:
  webpack:
    includeModules: false

package:
    individually: true

plugins:
  - serverless-webpack

This specifies that separate zip files for each individual function should be created rather than one for the whole service. It also tells the plugin to not package the node_modules folder in the zip but instead to trust Webpack to discover all the required modules itself and bundle them into a single .js file.

Now create a file called webpack.config.js in the same folder as your serverless.yml file. Here’s what mine typically looks like for plain JavaScript projects (Typescript requires a bit more config):

// webpack.config.js
const path = require('path');
const slsw = require('serverless-webpack');

module.exports = {
  entry: slsw.lib.entries,
  target: 'node',
  mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
  stats: 'minimal',
  devtool: 'nosources-source-map',
  performance: {
    hints: false,
  },
  resolve: {
    extensions: ['.js', '.jsx', '.json'],
  },
  output: {
    libraryTarget: 'commonjs2',
    path: path.join(__dirname, '.webpack'),
    filename: '[name].js',
    sourceMapFilename: '[file].map',
  },
};

That’s all the config done.

To view the results, you can package your service without deploying by running serverless package in your terminal. Then open the ./.serverless folder and look at the zip files that have been created.

Handling edge cases

You may need to stray from the above configuration if your Lambda function references a module that is required at runtime but Webpack cannot discover it during its analysis. The most common cause of this is when the module contains dynamic requires, whereby the path string passed into the require statement is composed at runtime. If this is the case, you can configure serverless-webpack to use forced inclusion to always include specific modules in its bundle.

Have a great weekend!

— Paul.


Comments