I'm Matt Bidewell
A software engineer in London 👨💻
Hidden Sam CLI features
⚠️ Everything in this post is subject to change. I'm also not employed by AWS and don't work on the Sam CLI source code.
Recently I've been in a position of using the AWS Sam CLI a lot. As a result of digging around the source code and the Lambda builder, I've found some undocumented features. By the phrase undocumented, I mean it doesn't appear on the documentation as of 2023/01/26.
A short bit of context, I'm currently working on NodeJS functions written in Typescript deployed on AWS Lambda using the AWS Sam CLI which features the bundler esbuild.
Feature 1: Format
If you want to use something like Top level await in Lambda you'll need to output your code in the esModule (esm) format. You might want to use this to utilise the init stage of the Lambda function to execute a task.
According to AWS, top-level await is available for NodeJS Lambdas as of 2022. However, if you use Typescript, esbuild and Sam CLI, as suggested by AWS and the default target field of
es2020, you will come across issues in the build stage. The error will be something like
Top-level await is not available in the configured target environment
To get around these build issues you can set your target to be
Node18. This is documented.
You can also set a new property -
Format: esm. This will ensure the output of esbuild is in the esm format as needed top-level await as there is no current support for CommonJS.
Solution? After digging through the source code on GitHub I found that you can set the
format output for esbuild. Here's an example CloudFormation template for a Lambda function: full example here
myFunction: Type: AWS::Serverless::Function Properties: Handler: handler.lambdaHandler CodeUri: ./ Runtime: nodejs18.x FunctionName: "MyTestFunction" Metadata: # Manage esbuild properties BuildMethod: esbuild BuildProperties: Target: "node18" Format: esm # Undocumented EntryPoints: - src/handler.ts
Feature 2: OutExtension
Setting the format type for esbuild to use is only 50% of the problem. The code compiles now. Deploy it and you may see it still doesn't work. The error might look something like this:
await is only valid in async functions and the top level bodies of modules
- Does the
type: moduleJSON property?
- Is the file extension a
We could change the
package.json but alas, doesn't fix our issue. This is because the Sam CLI omits the
package.json from the deployment. You don't need it to run a Lambda. This is where you can add an
OutExtension property to your CloudFormation template.
Take the example before and add the
Resources: myFunction: Type: AWS::Serverless::Function Properties: Handler: handler.lambdaHandler CodeUri: ./ Runtime: nodejs18.x FunctionName: "MyTestFunction" Metadata: # Manage esbuild properties BuildMethod: esbuild BuildProperties: Target: "node18" Format: esm # Undocumented OutExtension: # Undocumented - .js=.mjs Sourcemap: true EntryPoints: - src/handler.ts
Esbuild will now output the
.mjs files instead of
Feature 3: npm ci
npm ci is a way to do a clean install of your dependencies.
To get this to run you need to add the following to your
BuildProperties like the following
BuildProperties: Target: "node18" Format: esm # Undocumented UseNpmCi: true # Undocumented OutExtension: # Undocumented - .js=.mjs Sourcemap: true EntryPoints: - src/handler.ts