Azure / Azure/azure-functions-nodejs-worker
Plan ESM migration for Azure Functions Node.js Worker
- Dominant language
- TypeScript
- Stars
- 110
- Forks
- 51
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 2
Description
## Summary
The Node.js ecosystem is steadily moving toward ES Modules (ESM) as the default module system. Several key dependencies of the Azure Functions Node.js Worker have already shipped ESM-only major versions, and Node.js itself is on a path to make ESM the default. The Worker currently uses CommonJS throughout, which will become increasingly difficult to maintain.
## Key Insight: Worker ESM migration does NOT break customer code
The Worker loads customer code via dynamic `import()` or `require()` based on the customer's own `package.json` `"type"` field and file extension (`.mjs`/`.cjs`). See [`loadScriptFile.ts`](https://github.com/Azure/azure-functions-nodejs-worker/blob/v3.x/src/loadScriptFile.ts#L51-L67):
```typescript
if (isESModule(filePath, packageJson)) {
script = await eval('import(fileUrl.href)'); // ESM customer code
} else {
script = require(filePath); // CJS customer code
}
```
**The Worker's internal module system and the customer's module system are independent.** Node.js supports CJS and ESM coexisting in the same process. Therefore:
| Layer | Current | After Worker ESM migration |
|---|---|---|
| **Worker** (internal) | CJS | ESM |
| **@azure/functions library** | Dual (CJS + ESM) | Maintain dual support |
| **Customer code** | CJS or ESM (user choice) | **No change** |
This means:
- **Worker can safely migrate to ESM-only internally** — it is not exposed to customers
- **`@azure/functions` library should maintain dual CJS/ESM support** in the current major version (4.x)
- **Library 5.x can go ESM-only** with sufficient migration period (keep 4.x CJS support for 1+ year)
## Ecosystem Facts
### Node.js: ESM as default is in progress
- Node.js TSC discussed making `--default-type=module` the default (nodejs/TSC#1445)
- Node.js 22.7.0+ can auto-detect module format (`require()` of ESM modules)
- Node.js 26.0.0 (released May 2026) removed `--experimental-default-type` and `--experimental-transform-types`, continuing the trend toward ESM-first
- PR to mark CommonJS as legacy in Node.js docs: nodejs/node#57460
- npm CLI issue to default `npm init --type=module`: npm/cli#8159
- Node.js continues to support CommonJS but is shifting it to opt-in rather than opt-out
### Key npm packages going ESM-only
Several packages in the Worker's dependency chain have shipped ESM-only versions:
- **long@5** (ESM-only): Required by `@grpc/proto-loader >= 0.7.9` and `protobufjs >= 8.0`
- **chalk@5**, **ora@6**, **globby@13**, **got@13**, **node-fetch@3**, **execa@6**: Popular utilities that dropped CommonJS in major versions
- **sindresorhus ecosystem**: One of the largest package authors, ESM-only since 2021
### protobufjs 8.x: ESM migration pressure on gRPC stack
protobufjs 8.0 depends on `long@5` (ESM-only). This blocks upgrading:
- `@grpc/proto-loader >= 0.7.9` requires `long@5`
- `@grpc/proto-loader@0.8.0` requires `protobufjs ^7.5.3` (still v7, but `long@5`)
- The Worker's proto generation script uses `pbjs -w commonjs`, which would need to change to `-w es6`
## Current Worker State
- Worker entry point (`nodejsWorker.js`) uses `require()`
- Proto generation uses `pbjs -w commonjs` output format
- webpack bundles everything into CommonJS (`libraryTarget: 'commonjs2'`)
- All source files use CommonJS `require()`/`module.exports`
- `@grpc/grpc-js@1.8.22` and `@grpc/proto-loader@0.7.8` are pinned to avoid `long@5`
## Migration Strategy
### Phase 1: Worker internal ESM migration (no customer impact)
- Migrate Worker source from CommonJS to ESM (TypeScript with ESM output)
- Update proto generation from `pbjs -w commonjs` to `pbjs -w es6`
- Update webpack config (`libraryTarget`) or switch to ESM-compatible bundler
- Bump `@grpc/proto-loader` to 0.8.x and `@grpc/grpc-js` to latest
- Upgrade `protobufjs` to 8.x, `protobufjs-cli` to 2.x, `long` to v5
- **Validation**: E2E test with both CJS and ESM customer function apps to confirm backward compatibility
- **Target**: Can be done as a focused effort with comprehensive testing
### Phase 2: Library dual support (current 4.x)
- Ensure `@azure/functions` 4.x continues to provide dual CJS/ESM exports
- Validate that ESM Worker + CJS library + CJS customer code works end-to-end
- Validate that ESM Worker + ESM library + ESM customer code works end-to-end
### Phase 3: Library ESM-only (future 5.x, customer impact)
- Release `@azure/functions` 5.x as ESM-only
- Maintain `@azure/functions` 4.x with CJS support for at least 1 year after 5.x release
- Provide clear migration documentation for customers
- Announce timeline well in advance
- **This is the only phase that impacts customers**
## Related Issues and PRs
- #791 - Add ESM support for @azure/functions-core virtual module (open PR)
- #804 - protobufjs bump blocked protobufjs 8.x due to ESM-only `long@5` (merged)
- #803 - protobufjs vulnerability fix (closed)
- #344 - Add support for ES Modules (closed)
- #531 - Respect package.json type: module for ESM Functions (closed)
- #104 - Add ES6 Style Exports (open, 2018)
- nodejs/TSC#1445 - When to make --default-type=module the Node.js default
- nodejs/node#57460 - Mark CommonJS as legacy in docs
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.