Ari Palo

AWS CDK IaC with EcmaScript Modules

Publish date
Reading time
4 min read
Tag aws
Tag cdk
Tag esm
Tag typescript

Solving the annoying ERR_REQUIRE_ESM error in TypeScript AWS CDK apps.

While developing infrastructure code with AWS CDK and TypeScript, you may have bumped into ERR_REQUIRE_ESM error when using 3rd-party libraries from npm. This happens because CDK uses ts-node to execute TypeScript code, which doesn't support EcmaScript Modules (ESM) out of the box.

You can see this use of ts-node by initializing a new CDK TypeScript application and looking into cdk.json configuration file:

json
cdk.json
{  "app": "npx ts-node --prefer-ts-exts bin/my-app.ts",  // ...}

Searching for a solution

Danger

The solutions discussed below don't do type checking. You need to handle that separately.

One could of course approach the problem by introducing some kind of transpilation step themselves, but I want to avoid having to define extra steps (that are easy to forget) and continue using CDK by just “executing TypeScript”.

A while back, I stumbled upon article about how to “Speed up AWS CDK deploys up to 80%”. The article itself is worth a read, but the gist of it is that by configuring ts-node to use Rust-based SWC for transpilation, you can speed up the deployment process significantly.

There is ESM support in swc-node:

node --import @swc-node/register/esm-register script.ts # for esm project with node>=20.6

Unfortunately, though I didn't figure out how to get it working with CDK ☹️ … doesn't mean it can't be done, just that I didn't figure it out.

But that got me thinking about other ways to execute the CDK TypeScript app:

Enter TSX

Tsx or TypeScript Execute (not to be confused with JSX in TypeScript) is a tool to execute TypeScript files. It's kind of like ts-node but much better:

  1. It's faster, much faster, than ts-node by utilizing esbuild for the transpilation
  2. It runs TypeScript code with modern and sensible defaults, so no need for configuration
  3. It supports “Seamless CJS ↔ ESM imports”, i.e. no more ERR_REQUIRE_ESM errors

Configuring it into your CDK TypeScript project only requires few steps:

  1. Install:

    npm i -D tsx
  2. Modify cdk.json:

    json
    cdk.json
    "app": "npx ts-node --prefer-ts-exts bin/my-app.ts""app": "npx tsx bin/my-app.ts",

Now, you can install any ESM package and use it in your CDK TypeScript project without any issues, for example change-case, which is a “pure ESM package” and “cannot be require'd or used with CommonJS module resolution in TypeScript”:

typescript
lib/my-stack.ts
import * as cdk from "aws-cdk-lib";import { Construct } from "constructs";import { kebabCase } from "change-case";export class MyStack extends cdk.Stack {  constructor(scope: Construct, id: string, props?: cdk.StackProps) {    super(scope, id, props);    new cdk.CfnOutput(this, "OutputViaEsm", {      value: kebabCase("Hello from ESM!"),    });  }}

… which of course once synthesized will result to output like this:

Outputs:  OutputViaEsm:    Value: hello-from-esm

Performance

In addition to enabling ESM support, you get a big performance boost by using tsx as it is powered by esbuild that is written in Go.

I ran few unscientific tests on my M1 MacBook Pro, by initializing a new CDK TypeScript application, then running cdk synth with both the default setup (ts-node) and finally with tsx:

Time (in seconds)CDK app
3.451npx ts-node --prefer-ts-exts bin/my-app.ts
1.286npx tsx bin/my-app.ts

… and that is just with a freshly initialized small CDK app, I'd imagine the difference would be even more noticeable with larger projects.

Type Checking

Besides the performance boost of using Go under the hood of tsx (or Rust in case of swc-node), one major factor is the omission of type checking. Tools like tsx (or swc-node) don't do type checking! This means that you can't rely on them to catch type errors in your code.

During development, you can often just rely on your editor's functionality (such as VS Code IntelliSense), but you still most likely want to setup a separate type checking step, for example in your CI/CD pipeline at least.

To improve the performance of your type checking process, you can enable incremental compilation in your tsconfig.json:

json
tsconfig.json
{  "$schema": "https://json.schemastore.org/tsconfig.json",  "compilerOptions": {    // ...    "noEmit": true,    "incremental": true  }}

Used in Production

We've utilized this tsx setup in Alma Media for few of our CDK TypeScript projects without a hitch! Additionally, after originally publishing this blog post, I've heard from couple fellow AWS Community members that they've also adopted this setup and are happy with it!

Notes