fix: mark @orpc packages as sideEffect-free for cross-pkg tree-shaking#1540
fix: mark @orpc packages as sideEffect-free for cross-pkg tree-shaking#1540voidborne-d wants to merge 1 commit intomiddleapi:mainfrom
Conversation
…shaking Adds `"sideEffects": false` to @orpc/contract, @orpc/json-schema, @orpc/openapi, @orpc/server, @orpc/shared, and @orpc/zod. Without this flag, bundlers (Vite/Rollup/webpack/esbuild) conservatively preserve cross-package top-level imports even when the imported binding is unused, because they cannot prove the import target is side-effect free. Closes middleapi#1490, where importing only `oz.file()` from `@orpc/zod` pulled ~21 kB of `@orpc/server` runtime into client bundles via the chain `@orpc/zod` -> `@orpc/openapi` (re-exports `JSONSchemaFormat` etc.) -> top-level `import { ORPCError, createRouterClient } from '@orpc/server'`. All published modules in these packages are pure (no global registration, no prototype patches, no side-effecting class registrations at import time), so this annotation is safe. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughSix packages declare ChangesPackage Metadata Optimization
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~2 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request adds the "sideEffects": false property to the package.json files of several packages, including contract, json-schema, openapi, server, shared, and zod, to enable more efficient tree-shaking by bundlers. There are no review comments to address, and I have no further feedback to provide.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/client
@orpc/contract
@orpc/experimental-durable-iterator
@orpc/hey-api
@orpc/interop
@orpc/json-schema
@orpc/nest
@orpc/openapi
@orpc/openapi-client
@orpc/otel
@orpc/experimental-pino
@orpc/experimental-publisher
@orpc/experimental-publisher-durable-object
@orpc/experimental-ratelimit
@orpc/react
@orpc/react-query
@orpc/experimental-react-swr
@orpc/server
@orpc/shared
@orpc/solid-query
@orpc/standard-server
@orpc/standard-server-aws-lambda
@orpc/standard-server-fastify
@orpc/standard-server-fetch
@orpc/standard-server-node
@orpc/standard-server-peer
@orpc/svelte-query
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/vue-colada
@orpc/vue-query
@orpc/zod
commit: |
|
Thanks for your PR, @voidborne-d, I didn't even know this existed 😅 I'm wondering why you didn't add this config to all packages. Do you think we should disable |
Closes #1490.
Background
Importing only
oz.file()from@orpc/zodwas pulling ~21 kB of@orpc/serverruntime into client bundles via the chain:Because
@orpc/zodre-exportsJSONSchemaFormatetc. from@orpc/openapi, and@orpc/openapi/dist/index.mjshas top-levelimport { ORPCError, createRouterClient } from '@orpc/server'. Those top-level cross-package imports are preserved by the consumer's bundler unless the package is annotated as side-effect free, because the bundler cannot otherwise prove the import has no observable effect.Fix
Add
"sideEffects": falseto the six runtime packages in the relevant chain:@orpc/contract@orpc/json-schema@orpc/openapi@orpc/server@orpc/shared@orpc/zodI audited the source for global mutations / prototype patches / side-effecting class registrations at import time and found none — the published modules are pure, so the annotation is safe.
Reproducer
Stand-alone Vite app that mirrors the issue body:
Bundled with
vite build --minify esbuild --target es2022andexternal: ['zod', 'zod/v3']:@orpc/serversymbols leakedORPCError,Procedure,createRouterClient,isProcedure,resolveContractProcedures@orpc/server(the remainingORPCErroris from@orpc/contract, which is legitimately needed becauseoc.router()returns objects that use it)Net savings: 23.1 kB minified (61% reduction) / 4.78 kB gzipped (50%).
Why this rather than the subpath split suggested in #1490
The reporter (correctly) suggested splitting
ZodToJsonSchemaConverterinto a separate@orpc/zod/json-schemasubpath and removing it from the main entry. That would also fix the bug, but it is a breaking change for everyone currently importing the converter from@orpc/zod(incl. all in-repo playgrounds that use@orpc/zod/zod4's sister export, README example, etc.) and the subpath split is bundler-independent only insofar as users don't accidentally re-export the heavy entry transitively.sideEffects: falseis non-breaking, lands in 6 lines, and — as the numbers show — actually achieves the bundle reduction the reporter expected. If the cross-package tree-shaking turns out insufficient for some consumer's bundler, the subpath split is still available as a follow-up.The reporter's original claim that
sideEffects: falsealone wouldn't fix this assumed it was set on@orpc/zodonly; the chain only tree-shakes when every package along the cross-package import path is marked, which is what this PR does.Local gates
pnpm --filter='./packages/*' run -r build✓ all packages build, dist outputs unchanged in shape (sideEffects flag does not alter the rolled-up bundle, only thepackage.jsonof the published artifact)pnpm test✓ 4720 pass / 48 skipped / 5 todo across 280 test files (untouched by this change)pnpm run type:check✓ clean across all packagespnpm lint✓ clean (auto-formatter sorted the new key afterkeywordsperjsonc/sort-keys)I was happy to find this fits in 6 lines — happy to fold in the subpath split as a follow-up if any reviewer measures their bundler still needs it.
🤖 Generated with Claude Code
Summary by CodeRabbit