Optimising package size for Typescript AWS Lambda functions using serverless-esbuild

Here is how I reduced the package size of my lambda functions 35MB per function to ~1MB and cold-start init times from 606ms to 291ms.

Introduction

We have recently been using the serverless-esbuild plugin instead of serverless-plugin-types…


This content originally appeared on DEV Community and was authored by Hugh

Here is how I reduced the package size of my lambda functions 35MB per function to ~1MB and cold-start init times from 606ms to 291ms.

Introduction

We have recently been using the serverless-esbuild plugin instead of serverless-plugin-typescript package when making new Typescript serverless projects because it transpiles much faster (saving development time) and can create more optimized package sizes.

However, without proper configuration, your transpiled code can end up surprisingly large. This can impact the speed of deployment pipelines and cold start times of your lambda functions.

Related article: Size is (almost) all that matters for optimizing AWS Lambda cold starts | by Adrian Tanasa | Medium.

Furthermore, if the package size of a single function gets too large (50MB zipped), it will actually fail to deploy due to AWS limits on lambda package size (this happened to me and was the motivation for me to optimise file size and make this guide).

So, here are the steps to optimise serverless-esbuild settings:

Analyzing your bundle size

To package your serverless project locally, run:

serverless package -s offline

Note: the stage option (-s offline) is optional, depends on if you use different stages or not.

This generates a .serverless folder in the root of your project, which contains a zip file of your package. Open the zip file to check the index.js and index.js.map file sizes.

By setting the metafile option in serverless.yml, you can analyze the size of dependencies.

# serverless.yml
custom:
  esbuild:
    metafile: true # for local analysis only!

Don’t forget to comment out this option or revert to false before deploying, as the metafile is unnecessary in production and will increase the package size.

Once again packaging your project, you should see the meta files in the zipped package (index-meta.json).

You can use the esbuild - Bundle Size Analyzer tool to easily analyze the size of your dependencies:

Image description

In this example, the moment-timezone , sequelize, and iconv-lite shouldn’t have been in some of the functions, as those dependencies were not required by those functions, and accidentally imported into the source code through a shared common module.

This resulted in every lambda function having an extra unnecessary ~2.5MB of scripts to load.

Optimize esbuild settings

#serverless.yml
plugins:
  - serverless-esbuild

package:
  individually: true

custom:
  # Here you can define your esbuild options
  esbuild:
    bundle: true
    sourcemap: true
    minify: true
    exclude: ['@aws-sdk']
    # custom plugin to remove vendor sourcemaps
    plugins: esbuild-plugins.js

Explanation of settings:

package:
  individually: true

The option will create a separate zip file for each lambda function, speeding up deployment and lowering size per function. If you don’t set this to true, each of your lambda functions will contain the entire source code of the whole project and all dependencies, rather than only the files it needs.
This is one of the most important features for reducing filesize.

sourcemap: true

Sourcemap option should be true because we want to be able to locate stack traces thrown in production environments easily. However, there is a major downside: sourcemaps will include all node_modules packages resulting in an increase in package size. I will explain how to remedy this in the next section.

minify: true

Minifies code, resulting in further file size reduction

exclude: ['@aws-sdk']

Exclude aws-sdk from your package (if you use it), since Node.js lambda functions automatically have access to the AWS libraries when deployed!

Removing vendor source maps

The source map files generated by esbuild can be extremely large if you have many heavy dependencies (larger than the source code). This is because esbuild generates sourcemaps not just for your code, but for all imported node_modules too.

While it can sometimes be useful to have vendor sourcemaps when debugging code, the use case for debugging 3rd party package’s source code from production stack traces is quite uncommon.

To exclude vendor scripts from your source files, we need to write a custom plugin for esbuild.

First add this option to your serverless.yml:

# serverless.yml
custom:
  esbuild:
    plugins: esbuild-plugins.js

Then create a esbuild-plugins.js file in the same directory as the serverless.yml file:

// esbuild-plugins.js

const { readFileSync } = require('fs');

const excludeVendorFromSourceMapPlugin = {
  name: 'excludeVendorFromSourceMap',
  setup(build) {
    // eslint-disable-next-line consistent-return
    build.onLoad({ filter: /node_modules/ }, (args) => {
      if (args.path.endsWith('.js') || args.path.endsWith('.mjs') || args.path.endsWith('.cjs')) {
        return {
          contents:
            // eslint-disable-next-line prefer-template
            readFileSync(args.path, 'utf8') +
            '\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJtYXBwaW5ncyI6IkEifQ==',
          loader: 'default',
        };
      }
    });
  },
};

module.exports = [excludeVendorFromSourceMapPlugin];

What this script does is replace sourcemaps of files from the node_modules folder with an empty sourcemap template.

Applying this plugin reduced sourcemap file in each lambda function of my project from 10MB to about 2.5MB.

Avoid importing dependencies that aren’t needed

ESBuild has tree-shaking built in, meaning that only modules your lambda function imports (from the module containing lambda handler's entry point) will be included in the bundle. However, it is easy to unintentionally import modules that your lambda function doesn’t need indirectly through commonly shared modules.

Use the bundle analyzer and find out if there are any large dependencies that look like they shouldn’t be there for thespecific lambda function you’re analyzing.

In one project, we used the sequelize package to connect to a database. However, only some of these functions actually required a database connection.

The code was unintentionally importing the ‘sequelize’ package in every lambda function indirectly through a commonly imported module, adding around 1MB of script size per lambda function.

A simple refactor to remove the sequelize import when a DB connection wasn’t needed reduced the file size significantly.

Conclusion

Before optimising esbuild, the project package was 60MB, and this was higher than the limit allowed by AWS.

Furthermore, as I hadn’t packaged functions individually, every single lambda function would have been 60MB, making the total storage space used by 18 functions over 1GB.

These optimization changes allowed me to reduce package size to around 0.5MB to 1.5MB per function, totalling only 16MB uploaded to AWS, a dramatic reduction.

Furthermore, the deployment pipeline became quicker and the init duration of a simple ‘Hello World’ function, added to cold starts, from 606ms to 291ms.

Summary

  • Use esbuild - Bundle Size Analyzer (after setting metafile esbuild option to true) to observe dependencies size
  • Add aws-sdk to the exclude array in esbuild options (reducing script slightly) - the aws library should exist in the deployed environment.
  • Set the esbuild minify option to true in serverless.yml (another slight reduction)
  • Package functions individually using serverless.yml setting (large reduction of total size uploaded to AWS for multiple functions)
  • Added a plugin to exclude vendor sourcemaps from the scripts (big reduction) exclude node_modules from source map Issue #1685 · evanw/esbuild · GitHub
  • Use the serverless package command locally to check the file size output in .serverless folder.


This content originally appeared on DEV Community and was authored by Hugh


Print Share Comment Cite Upload Translate Updates
APA

Hugh | Sciencx (2024-07-18T11:05:37+00:00) Optimising package size for Typescript AWS Lambda functions using serverless-esbuild. Retrieved from https://www.scien.cx/2024/07/18/optimising-package-size-for-typescript-aws-lambda-functions-using-serverless-esbuild/

MLA
" » Optimising package size for Typescript AWS Lambda functions using serverless-esbuild." Hugh | Sciencx - Thursday July 18, 2024, https://www.scien.cx/2024/07/18/optimising-package-size-for-typescript-aws-lambda-functions-using-serverless-esbuild/
HARVARD
Hugh | Sciencx Thursday July 18, 2024 » Optimising package size for Typescript AWS Lambda functions using serverless-esbuild., viewed ,<https://www.scien.cx/2024/07/18/optimising-package-size-for-typescript-aws-lambda-functions-using-serverless-esbuild/>
VANCOUVER
Hugh | Sciencx - » Optimising package size for Typescript AWS Lambda functions using serverless-esbuild. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/07/18/optimising-package-size-for-typescript-aws-lambda-functions-using-serverless-esbuild/
CHICAGO
" » Optimising package size for Typescript AWS Lambda functions using serverless-esbuild." Hugh | Sciencx - Accessed . https://www.scien.cx/2024/07/18/optimising-package-size-for-typescript-aws-lambda-functions-using-serverless-esbuild/
IEEE
" » Optimising package size for Typescript AWS Lambda functions using serverless-esbuild." Hugh | Sciencx [Online]. Available: https://www.scien.cx/2024/07/18/optimising-package-size-for-typescript-aws-lambda-functions-using-serverless-esbuild/. [Accessed: ]
rf:citation
» Optimising package size for Typescript AWS Lambda functions using serverless-esbuild | Hugh | Sciencx | https://www.scien.cx/2024/07/18/optimising-package-size-for-typescript-aws-lambda-functions-using-serverless-esbuild/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.