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:
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
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
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()orrequire()based on the customer's ownpackage.json"type"field and file extension (.mjs/.cjs). SeeloadScriptFile.ts: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:
This means:
@azure/functionslibrary should maintain dual CJS/ESM support in the current major version (4.x)Ecosystem Facts
Node.js: ESM as default is in progress
--default-type=modulethe default (When to make--default-type=modulethe Node.js default nodejs/TSC#1445)require()of ESM modules)--experimental-default-typeand--experimental-transform-types, continuing the trend toward ESM-firstnpm init --type=module: The--init-typeflag should default tomodulenpm/cli#8159Key npm packages going ESM-only
Several packages in the Worker's dependency chain have shipped ESM-only versions:
@grpc/proto-loader >= 0.7.9andprotobufjs >= 8.0protobufjs 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.9requireslong@5@grpc/proto-loader@0.8.0requiresprotobufjs ^7.5.3(still v7, butlong@5)pbjs -w commonjs, which would need to change to-w es6Current Worker State
nodejsWorker.js) usesrequire()pbjs -w commonjsoutput formatlibraryTarget: 'commonjs2')require()/module.exports@grpc/grpc-js@1.8.22and@grpc/proto-loader@0.7.8are pinned to avoidlong@5Migration Strategy
Phase 1: Worker internal ESM migration (no customer impact)
pbjs -w commonjstopbjs -w es6libraryTarget) or switch to ESM-compatible bundler@grpc/proto-loaderto 0.8.x and@grpc/grpc-jsto latestprotobufjsto 8.x,protobufjs-clito 2.x,longto v5Phase 2: Library dual support (current 4.x)
@azure/functions4.x continues to provide dual CJS/ESM exportsPhase 3: Library ESM-only (future 5.x, customer impact)
@azure/functions5.x as ESM-only@azure/functions4.x with CJS support for at least 1 year after 5.x releaseRelated Issues and PRs
long@5(merged)--default-type=modulethe Node.js default nodejs/TSC#1445 - When to make --default-type=module the Node.js default