crossplane / crossplane/function-sdk-typescript
ts-proto ships 25MB of build-time codegen into every function image; @bufbuild/protobuf is undeclared
- Dominant language
- TypeScript
- Stars
- 3
- Forks
- 2
- Avg merge
- 9h 49m
- Merged PRs (30d)
- 14
Description
### What happened?
Three of the SDK's seven runtime `dependencies` are not imported by the shipped code, and one dependency that *is* imported at runtime is not declared at all. The largest consequence is that `ts-proto` — a build-time `protoc` plugin — drags ~25MB of native binary into every function image built against the SDK.
### Evidence
`ts-proto` is used only as a `protoc` plugin, in `scripts/protoc-gen.sh`:
```
--plugin=./node_modules/.bin/protoc-gen-ts_proto
```
It is imported nowhere in `src/`, and nothing in a fully installed function tree imports it either (grepped every `.js`/`.mjs`/`.cjs`). But it is in `dependencies`, so it ships — and it brings a formatter with it:
```
@crossplane-org/function-sdk-typescript
└─ ts-proto (908K)
└─ ts-poet (216K)
└─ dprint-node (24M) <- native binary, does not compress
```
Mapping each declared runtime dependency to actual imports in `src/`:
| dependency | files importing it | verdict |
|---|---|---|
| `@grpc/grpc-js` | 4 | runtime |
| `pino` | 4 | runtime |
| `ts-deepmerge` | 2 | runtime |
| `kubernetes-models` | 1 | runtime |
| `ts-proto` | 0 | build-time only (protoc plugin) |
| `google-protobuf` | 0 | appears unused |
| `@grpc/proto-loader` | 0 | unused directly; `@grpc/grpc-js` depends on it anyway |
| `@bufbuild/protobuf` | 3 | **runtime, but undeclared** |
### The undeclared dependency
The ts-proto-generated wire code imports `@bufbuild/protobuf`:
```
src/proto/run_function.ts:8: import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
src/proto/google/protobuf/duration.ts:8: import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
src/proto/google/protobuf/struct.ts:8: import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
```
`@bufbuild/protobuf` is not in `package.json`. It resolves today only because `ts-proto` depends on it. **This makes the two changes order-dependent**: moving `ts-proto` to `devDependencies` without first declaring `@bufbuild/protobuf` would remove it from the runtime tree and break every function at import time.
### Proposed change
1. Add `@bufbuild/protobuf` to `dependencies` (currently resolving as 2.11.0). Do this first — on its own it is a correctness fix regardless of the rest.
2. Move `ts-proto` to `devDependencies`.
3. Drop `google-protobuf` and `@types/google-protobuf` if nothing needs them — they look like leftovers from the pre-ts-proto `--ts_out` path still commented out in `scripts/protoc-gen.sh`.
4. Optionally drop `@grpc/proto-loader` from `dependencies`; it is not imported directly and `@grpc/grpc-js` declares it, so this changes nothing on disk.
### Impact
Measured on a real function project (`configuration-aws-network-ts`), installed with `--omit=dev`:
- Removing the `ts-proto` → `ts-poet` → `dprint-node` chain took `node_modules` from **62MB to 37MB**, and the function still started and served requests.
- Dropping `google-protobuf` saves a further ~944K.
`dprint-node` is a native binary, so unlike the `.d.ts` bulk it does not compress away in the packaged layer — it is close to 25MB of image size for every TypeScript function, and it is streamed out of the build container on every build.
`protobufjs` (3.1M) is *not* removable this way; it arrives via `@grpc/proto-loader` ← `@grpc/grpc-js`.
### Caveat
The 62MB → 37MB check verified process startup, a served gRPC listener, and the static import graph. It did not run a full compose request against the stripped tree. Worth confirming before release.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.