Accelerating AWS deployment when using Typescript
Update: AWS announced SAM Accelerate for Typescript, which replaces the makefile with a native esbuild, enabling faster build time and cloud synchronization.
Also check out How We Sped Up Our TypeScript Serverless Builds Ten Times (70 Lambdas Under 1 Minute)!
At the end of October, AWS announced the beta release of AWS SAM Accelerate, with the aim of shortening the distance between cloud and developers. It consists in a set of tools to instantaneously sync local code to cloud and reading output logs directly on our terminals, reducing the needs to locally emulate the application stack.
You can check a full vide demonstration by AWS here.
The good news? It works great on Python!
The bad news? It doesn’t work very well (yet) with Typescript. But there are workarounds.
Premises and problems
Premise: we based our Typescript project on this great article by Evil Martians. Things were going pretty well, until the number of our lambdas increased and the build time skyrocketed. And by increased I mean that sam build took 7 minutes on our fastest PC and 25 on our slowest, plus ~2 minutes of sam deploy.
This is a completely different topic and it’s mainly caused by how sam build handles builds via makefile, which is by coping the entire folder first and executing the makefile second. And since it can’t ignore folders like node_modules, even if it’s not included in the final zip, it won’t be a short process. We can only hope for AWS to add a flag to ignore folders in the future.
The long build time rapidly became unbearable when writing E2E tests and integrations tests with the frontend. We began searching for a way to speed up our feedback time, but:
sam build <function name>
just zip the entire project folder, then update all lambdas with this giant zip, and finally breaks everything because now every lambdas has a non-working zip.sam build --cached
does not work with makefiles.sam local start-api
was a no-go, because it doesn't support authorizers and we rely on them to provide the lambda some extra user data.
The solution
What to do then? Well, there’s an extra command from the base aws-cli which allow to directly push a zip to the cloud, without passing via CloudFormation: aws lambda update-function-code
.
So I tried writing a simple script based on this, and this is the result:
#!/bin/bashreadonly ENV=${1}
readonly FUNCTION_NAME=${2}
readonly FULL_LAMBDA_NAME="$ENV-backend-$FUNCTION_NAME"echo "Syncing Lambda Function $FULL_LAMBDA_NAME..."zip -ur0q lambda_to_upload.zip dist/ -x '*__tests__*'
aws lambda update-function-code --function-name $FULL_LAMBDA_NAME --zip-file fileb://lambda_to_upload.zip > /dev/null
rm lambda_to_upload.zipecho "Finished syncing Lambda Function $FULL_LAMBDA_NAME"
Our /dist directory contains the compiled Javascript code, including tests, which are excluded from the zip. Given that a npm run build was already been executed, you can push your latest code to the cloud in seconds!
But there’s more! What if we don’t really want to be bothered by running a build and deploy every time? npm
already provide us with an automatic incremental build, which is npm run watch
(or node_modules/typescript/bin/tsc -w --preserveWatchOutput
), so what if we execute this deploy every time a build is successful?
tsc-watch
does exactly what we need! We can add a new script to our scripts part in our package.json made like this
"sync": "tsc-watch --onSuccess \"../scripts/deploy_single_lambda.sh $ENV $FUNCTION\""
and running it via shell by setting the local variables like this
$ENV=test $FUNCTION=NameOfTheFunction npm run sync
and that’s it!