Skip to content

Plan ESM migration for Azure Functions Node.js Worker #807

@TsuyoshiUshio

Description

@TsuyoshiUshio

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions