dherault / dherault/serverless-offline
Windows ESM handler loading fails with Serverless v4 native esbuild
- 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
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