DEV Community

Cover image for Enabling Decorators for Lambda Functions with CDK in an Nx Monorepo
Jochen
Jochen

Posted on

Enabling Decorators for Lambda Functions with CDK in an Nx Monorepo

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)"
  ]
}
Enter fullscreen mode Exit fullscreen mode

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

The relevant section in tsconfig.base.json might look like this:

{
  "compilerOptions": {
    "paths": {
       "@MyComp/MyLibrary": [
                "libs/shared/MyLibrary/src/index.ts"
            ],
    },
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
  });

Enter fullscreen mode Exit fullscreen mode

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

  1. Performance Impact: Custom build scripts can slow down the bundling process.
  2. 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)