Skip to content

Migrate proto codegen from grpc-web to Connect-ES / Protobuf-ES (ESM-native) #9246

Description

@nabramow

Summary

Our web frontend (app/web) generates gRPC clients with protoc-gen-grpc-web, which emits CommonJS — 64 proto/*_pb.js + *_grpc_web_pb.js files. This keeps the app as a CommonJS package (no "type": "module") and forces build/test shims every time we adopt an ESM-only dependency.

This surfaced in #9236 (Temporal adoption), which required two workarounds purely for ESM/CJS interop:

  • transpilePackages in next.config.js
  • a custom jest.resolver.js (to request the import export condition)

Why now

ESM-only packages are increasingly common (e.g. temporal-polyfill). The official grpc-web generator has no real ESM support — grpc/grpc-web#535 has been open for years — so we can't fix this by bumping a version or flipping a flag. The ecosystem's modern, ESM-native answer is Buf's Connect-ES + Protobuf-ES.

Proposed direction

  • Replace protoc-gen-grpc-web with protoc-gen-es (+ protoc-gen-connect-es).
  • Use @connectrpc/connect-web's createGrpcWebTransport — this speaks the same gRPC-web wire protocol, so no backend / Envoy changes needed.
  • Result: native ESM generated code (tree-shakeable, TS-first), removes all 64 CommonJS proto files, and unblocks moving app/web to "type": "module".

Scope / cost (not a version bump — a migration)

  • ~319 proto/* import sites across the app, plus the service/*.ts layer.
  • Message-construction API changes: const r = new Foo(); r.setBar(x) (google-protobuf) → Protobuf-ES field style. Sweeping, mostly mechanical.
  • Recommend a spike first: migrate ONE service end-to-end to size the full effort before committing.

Notes

  • No backend change required (gRPC-web transport retained).
  • Separately/independently: the Temporal polyfill shims (transpilePackages, jest.resolver.js) can be deleted once native Temporal reaches Baseline (~mid-2026). Smaller, standalone cleanup.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    No fields configured for Task.

    Projects

    Status
    Coming Up Next

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions