Working with CDK's Node.js Lambda Function Construct
The AWS CDK provides a convenient construct for Node.js Lambda functions.
What is the NodejsFunction
Construct?
According to the documentation:
The
NodejsFunction
construct creates a Lambda function with automatic transpiling and bundling of TypeScript or JavaScript code. This results in smaller Lambda packages that contain only the code and dependencies needed to run the function.
To perform this transpiling and bundling, it relies on esbuild.
Issue: Esbuild Lacks Support for Decorators
Unfortunately, esbuild
does not support the TypeScript compiler options experimentalDecorators
and emitDecoratorMetadata
. This causes it to ignore decorators, leading to errors when the Lambda function is invoked. A typical error looks like this:
{
"errorType": "MissingReflectMetadata",
"errorMessage": "Could not reflect metadata of type design:type, did you forget to enable 'emitDecoratorMetadata' on compiler options?",
"stack": [
"MissingReflectMetadata: Could not reflect metadata of type design:type, did you forget to enable 'emitDecoratorMetadata' on compiler options?",
" at /var/task/index.js:85333:13",
" at __decorateClass (/var/task/index.js:39:24)",
" at Object.<anonymous> (/var/task/index.js:85734:1)"
]
}
Workaround: Using Pre-Compilation with TypeScript
Thankfully, the CDK provides a workaround for this issue by allowing a pre-compilation step using TypeScript (tsc). To enable this, simply set the preCompilation flag in the bundling options:
new nodejs.NodejsFunction(this, 'my-handler', {
bundling: {
preCompilation: true,
},
});
Challenges in an Nx Monorepo
In an Nx monorepo, projects are organized into apps and libraries that share a common tsconfig.base.json
file at the root. This file defines path aliases for libraries, enabling easy imports like this:
import doingX from '@MyComp/MyLibrary'
The relevant section in tsconfig.base.json might look like this:
{
"compilerOptions": {
"paths": {
"@MyComp/MyLibrary": [
"libs/shared/MyLibrary/src/index.ts"
],
},
}
Unfortunately, the tsc step used in CDK's pre-compilation process does not respect the paths configuration, resulting in errors like:
Cannot find module '@MyComp/MyLibrary' or its corresponding type declarations
Since we don't have control over how the CDK runs the tsc command it's difficult to modify this behavior.
Solution: Custom Build Script
The CDK provides an escape hatch by allowing developers to run a custom-build script as part of the CDK synthesis process.
Here’s an example of such a script using esbuild and the esbuild-plugin-tsc plugin:
import path from 'path';
import esbuild from 'esbuild';
import esbuildPluginTsc from 'esbuild-plugin-tsc';
await esbuild
.build({
entryPoints: [path.join(__dirname, 'handler', 'index.ts')],
outfile: path.join(__dirname, 'build-output', 'index.js'),
external: ['@aws-sdk/*', 'aws-sdk'],
format: 'cjs',
platform: 'node',
target: 'node18',
bundle: true,
minify: true,
plugins: [esbuildPluginTsc()]],
})
.catch((error) => {
console.log(error);
process.exit(1)
});
Why Use esbuild-plugin-tsc?
The esbuild-plugin-tsc plugin integrates with TypeScript’s compiler, ensuring that all TypeScript options -including paths- are respected. This enables the seamless compiling of files with decorators and path aliases while retaining the benefits of esbuild for other files.
Caveats
- Performance Impact: Custom build scripts can slow down the bundling process.
- Maintenance: By adopting a custom script, you assume responsibility for maintaining it.
When to Use This Solution
Reserve this approach for cases where the default CDK configuration is insufficient. For simpler setups, the built-in preCompilation flag should suffice.
Top comments (0)