dherault / dherault/serverless-offline

Windows ESM handler loading fails with Serverless v4 native esbuild

Open
#1,911 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
5.3k
Forks
811
Avg merge
2d 4h
Merged PRs (30d)
3

Description

## Summary

A minimal TypeScript Lambda handler fails on Windows when all of these are used together:

- Serverless Framework v4 native esbuild
- `serverless-offline`
- a package with `"type": "module"`
- a TypeScript handler

`serverless package` succeeds and creates `.serverless/build/src/handler.js`, but the first `serverless offline` request returns `502` and the Offline loader falls through to `tsx` with a raw Windows absolute path.

## Environment

- OS: Windows 11
- Node.js: 24.13.0
- Serverless Framework: 4.41.0
- `serverless-offline`: 14.8.0
- TypeScript: 7.0.2

## Reproduction

```text
npm install
npm run package
npm run offline
```

In another terminal:

```powershell
Invoke-WebRequest -UseBasicParsing `
-Uri http://localhost:4000/offline/repro `
-Method Post `
-Headers @{'x-api-key' = 'local-repro-key'}
```

Minimal `package.json`:

```json
{
"private": true,
"type": "module",
"scripts": {
"offline": "serverless offline start --stage offline",
"package": "serverless package --stage offline"
},
"devDependencies": {
"serverless": "4.41.0",
"serverless-offline": "14.8.0",
"typescript": "7.0.2"
}
}
```

Minimal `serverless.yml`:

```yaml
service: serverless-offline-windows-esm-repro
frameworkVersion: '4'

provider:
name: aws
runtime: nodejs24.x
region: eu-west-2
stage: offline
enableLegacyDeploymentBucket: true
apiGateway:
apiKeys:
- name: local-repro
value: local-repro-key

plugins:
- serverless-offline

build:
esbuild:
bundle: true
minify: false
sourcemap: true

functions:
repro:
handler: src/handler.handler
events:
- http:
method: post
path: repro
private: true
```

`src/handler.ts`:

```ts
export async function handler(): Promise<{ statusCode: number; body: string }> {
return { statusCode: 200, body: 'ok' };
}
```

## Expected behavior

The request returns HTTP `200` with body `ok`.

## Actual behavior

The request returns HTTP `502`. The Offline process reports:

```text
Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'd:'
```

The stack passes through `serverless-offline`'s `tsx` loader. The package step creates the native bundle under `.serverless/build`, but the Offline request still attempts to load the TypeScript handler path directly.

## Control

Changing `"type": "module"` to `"type": "commonjs"` and setting `verbatimModuleSyntax: false` makes the identical repro return HTTP `200` on the same machine.

Is native Serverless v4 esbuild expected to redirect `serverless-offline` to the generated `.serverless/build` handler for ESM packages? If so, is there an additional supported configuration for Windows ESM handlers, or should `serverless-offline` convert the handler path with `pathToFileURL()` before using the ESM loader?

Contributor guide

Open the contributing guide

Research direction

Run the minimal reproduction with npm install, npm run package, and npm run offline, then inspect the Offline request path through its tsx loader. Compare the attempted Windows TypeScript handler path with .serverless/build/src/handler.js and verify that the POST request returns HTTP 200 with body ok without the ESM URL-scheme error.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js, typescript
Domain
api, backend, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.