Skip to main content

esbuild and @types/aws-lambda nightmare

· 8 min read
Ivan Barlog
AWS Solutions Architect @ BeeSolve

I have spent 2 days postponing very weird bug with @types/aws-lambda in my serverless project. Finally I decided to fight the fight and try to understand what's going on.

TL;DR

When you use DefinitelyTyped packages and esbuild you need to use type import not type annotation so esbuild can strip the whole import. Also you need to mark the package as external.

// type annotation
import { type SQSEvent } from "aws-lambda";
// type import
import type { SQSEvent } from "aws-lambda";

The full story

The problem was easy - I was bundling my AWS Lambda handler written in TypeScript via esbuild which is the default option within CDK like this:

import { NodejsFunction, SourceMapMode } from "aws-cdk-lib/aws-lambda-nodejs";

new NodejsFunction(this, "SqsHandler", {
entry: resolve(__dirname, "./src/handler.ts"),
handler: "handler",
bundling: {
minify: true,
sourceMap: true,
sourceMapMode: SourceMapMode.INLINE,
sourcesContent: false,
target: "es2022",
},
// ... redacted for brevity
});

Unfortunately I got this error:

Bundling asset stg/Api/Emails/SqsHandler/Code/Stage...
✘ [ERROR] Could not resolve "aws-lambda"

packages/email/src/handler.ts:6:30:
6 │ import { type SQSEvent } from "aws-lambda";
╵ ~~~~~~~~~~~~

You can mark the path "aws-lambda" as external to exclude it from the bundle, which will remove
this error and leave the unresolved path in the bundle.

1 error
error: "esbuild" exited with code 1

This is strange I thought! The @types/aws-lambda is installed correctly howcome esbuild cannot resolve it?

I have deployed this exact service yesterday. What happened? Ok, I've been doing some mambo-jumbo with packages like updating them and deleting some of them 1 so I thought that I might've installed some broken version of @types/aws-lambda. I went down the rabbit hole where I was trying to remove node_modules reinstall them several times and also reverting the lock file. Still no luck so I took a hint from esbuild itself.

I marked aws-lambda as external module which basically means that esbuild can ignore it within its build step and since this is TypeScript package in runtime it should be OK - as TypeScript is not going to be executed in the runtime, my assumption was that this will be OK.

// ...
bundling: {
minify: true,
sourceMap: true,
sourceMapMode: SourceMapMode.INLINE,
sourcesContent: false,
target: "es2022",
externalModules: ["aws-lambda"], // <-- add this line
},
// ...

The build succeeded! Tap ourself on back (and esbuild for helping us) and let's deploy.

Well, it seems that it won't be that easy as the runtime failed with following error:

2025-10-14T21:33:55.818Z	undefined	ERROR	Uncaught Exception
{
"errorType": "Runtime.ImportModuleError",
"errorMessage": "Error: Cannot find module 'aws-lambda'\nRequire stack:\n- /var/task/index.js\n- /var/runtime/index.mjs",
"stack": [
"Runtime.ImportModuleError: Error: Cannot find module 'aws-lambda'",
"Require stack:",
"- /var/task/index.js",
"- /var/runtime/index.mjs",
" at _loadUserApp (file:///var/runtime/index.mjs:1192:17)",
" at async UserFunction.js.module.exports.load (file:///var/runtime/index.mjs:1235:21)",
" at async start (file:///var/runtime/index.mjs:1454:23)",
" at async file:///var/runtime/index.mjs:1464:1"
]
}

Wait what? I thought that types are stripped when esbuild builds the JavaScript. Out of TypeScript. Isn't that the whole point?

Well I had no idea what to do next. So as you can imagine, I tried everything:

I tried to remove node_modules and reinstall packages - several times.

I tried to upgrade/downgrade @types/aws-lambda package - several times.

I tried to run the esbuild command manually and run this with --log-level=debug - here I found out that esbuild is trying all the paths and everything but since @types/aws-lambda does not contain JavaScript code (just .d.ts) it couldn't find anything. Also I have found out that esbuild does not trying to find anything within node_modules/@types/aws-lambda but instead all the paths were trying to find something within node_modules/aws-lambda. No surprise poor bastard didn't find anything.

I had all the information but I still had no clue what was going on.

So I've tried to rule out the posibility that there is something wrong with @types/aws-lambda. So I went to npmjs.org and I searched for @types. I have installed first package there was and I tried to build everything the same way as with @types/aws-lambda but now with @types/prop-types like this:

import { type Validator } from 'prop-types';

And then run the build - no error! So it must be the @types/aws-lambda somehow.

I was really desperate so I did what I don't do usually - I have consulted the LLM. I explained the situation and asked politely what might be the problem. The answer was 100% halucination as almost always.

Here is the reply:

> ## Why the import fails
>
> `esbuild` bundles **runtime** JavaScript files.
> When you write
>
> ```ts
> import { type SQSEvent } from "aws-lambda";
> ```
>
> the **module name** it tries to resolve is **`aws-lambda`**.
> `@types/aws-lambda` only provides *type declarations* for the compiler; it does **not** create a real package that can be loaded at runtime. Because there is no `node_modules/aws-lambda` folder, esbuild cannot find the module > and aborts.
>
> The same thing does **not** happen with `prop-types` because the `prop-types` package **does** exist at runtime (it ships a JavaScript file), so esbuild can resolve it even though you only import a type.
>
> ## Fixes
>
> | Approach | What to do | When to use |
> |----------|------------|-------------|
> | **Add a stub runtime package** | `bun add -D aws-lambda` (or `npm i -D aws-lambda`). The package is essentially empty (it only contains a `package.json`), but its presence satisfies esbuild’s resolver. | Most straightforward; > works for any type‑only import. |
> | **Mark the import as external** | Add `--external:aws-lambda` to the esbuild command (or configure it in a build script). | If you never need the module at runtime (e.g., the code runs in AWS Lambda where the environment > already provides the types). |
> | **Use a path alias to a local declaration file** | Create `src/types/aws-lambda.d.ts` with `declare module "aws-lambda";` and add a `paths` entry in `tsconfig.json` pointing `"aws-lambda": ["./src/types/aws-lambda"]`. | When > you want to keep the import but avoid adding a dummy package. |
> | **Switch to a bundler that understands type‑only imports** | Tools like `tsc` (emit only) or `vite` with the `esbuild` plugin can strip type‑only imports before bundling. | If you already use such a tool and prefer not to add > a stub. |
>
> ### Recommended minimal change
>
> ```bash
> bun add -D aws-lambda # installs an empty runtime package
> bun add -D @types/aws-lambda # (already installed)
> ```
>
> Now the same `esbuild` command succeeds:
>
> ```bash
> bun run esbuild --bundle reporting/services/packages/email/src/handler.ts \
> --target=es2022 --platform=node \
> --outfile=reporting/cdk.out/bundling-temp-.../index.js \
> --sources-content=false --external:@aws-sdk/*
> ```
>
> The generated bundle will contain **no code** from `aws-lambda` because the import is type‑only, but the resolver is satisfied.
>
> ## Quick checklist
>
> 1. **Install the stub package**: `bun add -D aws-lambda`.
> 2. **Keep the type package**: `bun add -D @types/aws-lambda`.
> 3. Re‑run the esbuild command – it should finish without the “Could not resolve `aws-lambda`” error.
>
> If you prefer the external‑flag route, just add `--external:aws-lambda` to the command instead of installing the stub. Both approaches achieve the same result.

First thougths:

  1. no prop-types does not exists and it does not contain any JavaScript - I have checked the @types/prop-types only contains index.d.ts
  2. no I am not going to install aws-lambda package which I don't need just to satisfy esbuild - this is horrible solution
  3. no I have already tried to mark the package as external but then I get the error in runtime

But then I started thinking - am I right or is the LLM? This time, the LLM was right because indeed the package prop-types has existed because it was direct dependency of Material UI.

But why do I need to install package which I don't need eg. aws-lambda? Isn't there any better way? And also, why the type is not ignored/stripped when it is imported ad type SqsEvent?

Since I was on a roll here I asked these questions to LLM.

It seems that there is big difference between these two imports:

// type annotation
import { type SQSEvent } from "aws-lambda";
// type import
import type { SQSEvent } from "aws-lambda";

The first one - type annotation - seems not to be stripped properly, eg. the annotation is stripped but final JavaScript bundle still contains import {} from "aws-lambda"; which results in the runtime error.

When you use the second one - type import - the whole import is being stripped.

So it seems that for packages DefinitelyTyped you should be using type imports not type annotations - otherwise you might get the same error like I did.

I hope next time I have this issue I won't be trying to solve it until 2am but I will remember about this article instead.

Footnotes

  1. Yes I have had installed aws-lambda by mistake which is how I found out that this package is for AWS Lambda CLI deployments. I haven't realized that this was the whole reason why it was working the day before until I had chat with ChatGPT.