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#1540dinwwwh merged 2 commits 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>
📝 WalkthroughWalkthroughAdded a top-level ChangesSide-effects metadata update
trpc exports addition
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested labels
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 |
|
Good question — kept it narrow on purpose to keep #1490 review cheap, but the answer is: yes, ideally every package should have it, and I just audited all 35 to confirm none have actual side effects. Quick rundown of the audit (
So: safe to flip the default across the full
Either works for me. (1) is logically cleaner since the policy decision is global; (2) is friendlier if you'd rather merge the bug fix first and discuss the broader policy in a fresh thread. Let me know which you prefer and I'll push. Two notes for the broader rollout:
|
There was a problem hiding this comment.
Pull request overview
Marks a broad set of @orpc/* packages as side-effect free ("sideEffects": false) so consumer bundlers can safely tree-shake unused cross-package imports, addressing the transitive bundle-size leak described in #1490.
Changes:
- Add
"sideEffects": falseto multiple@orpc/*package manifests to enable cross-package tree-shaking. - Applies the metadata change not only to the
@orpc/zod → @orpc/openapi → @orpc/serverchain, but also to many related runtime/integration packages to keep bundling behavior consistent across the monorepo.
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/ai-sdk/package.json | Mark package as side-effect free for bundlers. |
| packages/arktype/package.json | Mark package as side-effect free for bundlers. |
| packages/client/package.json | Mark package as side-effect free for bundlers. |
| packages/contract/package.json | Mark package as side-effect free for bundlers. |
| packages/durable-iterator/package.json | Mark package as side-effect free for bundlers. |
| packages/hey-api/package.json | Mark package as side-effect free for bundlers. |
| packages/interop/package.json | Mark package as side-effect free for bundlers. |
| packages/json-schema/package.json | Mark package as side-effect free for bundlers. |
| packages/nest/package.json | Mark package as side-effect free for bundlers. |
| packages/openapi/package.json | Mark package as side-effect free for bundlers. |
| packages/openapi-client/package.json | Mark package as side-effect free for bundlers. |
| packages/otel/package.json | Mark package as side-effect free for bundlers. |
| packages/pino/package.json | Mark package as side-effect free for bundlers. |
| packages/publisher/package.json | Mark package as side-effect free for bundlers. |
| packages/publisher-durable-object/package.json | Mark package as side-effect free for bundlers. |
| packages/ratelimit/package.json | Mark package as side-effect free for bundlers. |
| packages/react/package.json | Mark package as side-effect free for bundlers. |
| packages/react-query/package.json | Mark package as side-effect free for bundlers. |
| packages/react-swr/package.json | Mark package as side-effect free for bundlers. |
| packages/server/package.json | Mark package as side-effect free for bundlers. |
| packages/shared/package.json | Mark package as side-effect free for bundlers. |
| packages/solid-query/package.json | Mark package as side-effect free for bundlers. |
| packages/standard-server/package.json | Mark package as side-effect free for bundlers. |
| packages/standard-server-aws-lambda/package.json | Mark package as side-effect free for bundlers. |
| packages/standard-server-fastify/package.json | Mark package as side-effect free for bundlers. |
| packages/standard-server-fetch/package.json | Mark package as side-effect free for bundlers. |
| packages/standard-server-node/package.json | Mark package as side-effect free for bundlers. |
| packages/standard-server-peer/package.json | Mark package as side-effect free for bundlers. |
| packages/svelte-query/package.json | Mark package as side-effect free for bundlers. |
| packages/tanstack-query/package.json | Mark package as side-effect free for bundlers. |
| packages/trpc/package.json | Mark package as side-effect free for bundlers. |
| packages/valibot/package.json | Mark package as side-effect free for bundlers. |
| packages/vue-colada/package.json | Mark package as side-effect free for bundlers. |
| packages/vue-query/package.json | Mark package as side-effect free for bundlers. |
| packages/zod/package.json | Mark package as side-effect free for bundlers (primary repro path). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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 all oRPC's packagesI 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