AWS CDK IaC with EcmaScript Modules
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:
{ "app": "npx ts-node --prefer-ts-exts bin/my-app.ts", // ...}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.6Unfortunately, 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:
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:
- It's faster, much faster, than
ts-nodeby utilizing esbuild for the transpilation - It runs TypeScript code with modern and sensible defaults, so no need for configuration
- It supports “Seamless CJS ↔ ESM imports”, i.e. no more
ERR_REQUIRE_ESMerrors
Configuring it into your CDK TypeScript project only requires few steps:
Install:
npm i -D tsxModify
cdk.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”:
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-esmIn 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.451 | npx ts-node --prefer-ts-exts bin/my-app.ts |
| 1.286 | npx 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.
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:
{ "$schema": "https://json.schemastore.org/tsconfig.json", "compilerOptions": { // ... "noEmit": true, "incremental": true }}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!
- Use only in CDK apps: I wouldn't use EcmaScript Modules (ESM) in any shared constructs that others might consume (until AWS CDK itself adopts true ESM support), as you can't be sure that they have the same setup as you do.
- Adjust
tsconfig.json: You probably want to adjust your project'stsconfig.jsonto match your needs, so that VS Code IntelliSense and the separatetsctype checking process work as expected.