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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions