I maintain @constantant/openapi-resource-gen,
an Nx generator that emits one InjectionToken per OpenAPI endpoint, each in its own file, so that
endpoints you never inject cost nothing. I claimed that for months without checking it against other
tools. So I wrote a script and measured. The result was not entirely flattering, which is why it is
worth sharing.
The setup
- Spec: the YouTube Data API v3 (76 endpoints).
- A minimal Angular 22 app that bootstraps and calls 1 endpoint, then 8 endpoints, built for production with the esbuild application builder.
- Contenders, each generating from the same spec with default-ish settings: this project (both its
httpResourceand itsHttpClientflavours), Orval 8 (angularclient, tags-split), ng-openapi-gen 1.1, Hey API'sclient-angular. - Two controls, so every row can be read against what Angular itself costs: a plain
HttpClientcall and a barehttpResourcecall with no generator.
Numbers are the production main.js, gzipped, as growth over the plain HttpClient baseline
(38.4 kB). Everything is reproducible: npm run bench in the repo.
Results (gzip, growth over baseline)
| Variant | 1 endpoint | 8 endpoints |
|---|---|---|
bare httpResource (no generator) |
+6.1 kB | +6.1 kB |
openapi-resource-gen, httpResource
|
+7.1 kB | +7.2 kB |
openapi-resource-gen, httpClient
|
+0.9 kB | +1.0 kB |
| Orval (tags-split services) | +0.3 kB | +1.2 kB |
| ng-openapi-gen, deep imports | +1.9 kB | +2.3 kB |
ng-openapi-gen, functions barrel |
+7.0 kB | +7.0 kB |
| Hey API | +3.5 kB | +3.5 kB |
What I learned
1. The one-file-per-endpoint idea does tree-shake. Going from 1 to 8 endpoints costs about 0.2 kB
in both of my flavours. That part held up.
2. httpResource is not free, and that is Angular's cost, not the generator's. A bare
httpResource with no generated code at all adds 6.1 kB gzip over a plain HttpClient call. My
generated code adds about 1 kB on top. If your app already uses httpResource or rxResource
elsewhere, you are paying for it anyway; if it does not, and every kilobyte counts, the HttpClient
flavour (--clientType=httpClient) costs about +1 kB in total.
3. Barrels can defeat tree-shaking in surprising ways. ng-openapi-gen's generated functions end
with a side-effecting assignment (fn.PATH = '…'), so importing them from its functions barrel
keeps all of them: main.js more than doubles. Deep imports (fn/<tag>/<operation>) avoid it. I
only found this because the first benchmark run looked wrong; check your own barrels.
4. Class-based services scale with tags, not endpoints. Orval's tags-split output is the smallest
for a single endpoint, but a service class is kept whole, so touching eight tags costs more than
touching one. Fine for most apps; worth knowing for large APIs.
Caveats
One spec, one scenario, one builder, gzip only, no runtime measurements. Orval's httpResource mode and
other configurations were not measured. Versions move, and the script prints exactly what it used. If
your numbers differ, I would like to hear about it.
Reproduce it
git clone https://github.com/constantant/angular-openapi-gen
cd angular-openapi-gen
npm run bench
The results and methodology are also in
docs/benchmarks/bundle-size.md.
Top comments (0)