Step 2 - Bundle node_modules (be extra careful!)Okay, we now understand that node modules obviously account for most of the space of our package. And we intentionally did it using `webpack-node-externals`. But do we really need it? As the documentation says: "When bundling with Webpack for the backend, - you usually don't want to bundle its node_modules dependencies" and it refers to an article Backend Apps with Webpack that provides a detailed explanation: "Webpack will load modules from the node_modules folder and bundle them in. This is fine for frontend code, but backend modules typically aren't prepared for this (i.e. using require in weird ways) or even worse are binary dependencies. We simply don't want to bundle in anything from node_modules." As an example, the author provides express.js framework that has some binary dependencies that can lead to an error if run with bundling. But in our case we most probably don't have any binary dependencies. So let's try to bundle our project without `webpack-node-externals`. After removing 'externals' from webpack.config.js and running the `sls package` command the size of our result zip file is 1.2 MB. Here is the image produced by webpack-bundle-analyzer plugin: [caption id="attachment_8341" align="alignnone" width="800"] webpack-bundle-analyze result with bundled packages[/caption] And we can see something interesting. Yes, we have all our npm dependencies bundled, but among them we can see `aws-sdk` which is provided by the AWS Lambda environment and because of that, was purposely moved to the devDependencies. But with the current configuration, webpack doesn't know that it should ignore devDependencies. Let's add `aws-sdk` to the array of externals in the webpack.config.js and package our functions one more time (again, 'externals' prevent bundling of certain imported packages and instead retrieve these external dependencies at runtime). Now `aws-sdk` has disappeared from the bundle: [caption id="attachment_8339" align="alignnone" width="800"] webpack-bundle-analyze result without aws-sdk[/caption] And the size of our functions is: - handler package size: 445 KB - authorizer package size: 445 KB This step is marked as be extra careful. And the reason is that you should double check that your bundled dependencies don't rely on any binaries, otherwise you will have troubles in production. One option to check that you're safe is to implement good end-to-end tests of your deployed Lambda functions. Pay attention that unit tests won't help you here because all node_modules will be in scope without webpack processing. If you happen to know that a specific npm package has binary dependencies, you can add it to the 'externals' block in the webpack config and still bundle all other packages. * bundling the dependencies in our sample project will cause two warnings: "Module not found: Error: Can't resolve 'bufferutil'" and "Module not found: Error: Can't resolve 'utf-8-validate'". It's not a fault of our solution and definitely not a flaw in webpack. The reason is that one of our dependencies is trying to import these modules but they are not listed in any of the package.json files. If you want to understand the reason and find the ways to get rid of the warning, you can read this discussion on GitHub.
Step 3 - Package: individuallyYou already noticed that we always show the package size for two functions: handler and authorizer. But so far we always had one package deployed for both of them and the numbers were the same. But it doesn't make sense especially because the authorizer function is of hundreds times smaller than the handler. You can see it in the last picture of the bundle analyzer. The small violet rectangle displays the relative size of the authorizer bundle very well. To produce separate packages for the separate lambda functions, we can simply add the following option to our serverless.yml file: And here we get our final numbers that are ~10 times smaller than the initial one for the handler and ~7000 times smaller for the authorizer package: - handler package size: 445 KB - authorizer package size: 744 B This step was very trivial and probably could be the first one, but `serverless-plugin-typescript` ignores the `individually: true` option so we delayed it until the webpack config was in place.
ConclusionTo summarize, when you are writing AWS Lambda functions in TypeScript, you can start with the convenient `serverless-plugin-typescript`. But once you need to optimize the size of the deployment packages you most probably need to tune your packaging process with webpack. You can start with individual packaging and continue with not only your source code bundling but also with the npm dependencies bundling. But make sure that these dependencies don't use any binaries that can be dropped by webpack during the bundling process because this can lead to errors in production. This article provided the basic configurations that served only one goal - showing how to minimize the size of AWS Lambda functions in TypeScript. Webpack is a very powerful tool with many different configuration options that can help you to tune the bundle according to your needs, for instance, to add source maps or improve build process speed using the caching mechanism.
Get in Touch.
Let’s discuss how we can help with your cloud journey. Our experts are standing by to talk about your migration, modernisation, development and skills challenges.