🚀 perf(icons): declare the package side-effect free so barrel imports tree-shake - #968
Merged
Merged
Conversation
…ree-shake Without "sideEffects": false, webpack cannot prune the re-exports in src/index.ts (export * from './ui' / './flags'), so any consumer that names a single icon receives all 233 UI icons in the chunk that imports them. That is not hypothetical for consumers of @gympass/yoga: 49 modules inside it import the icon barrel (Accordion, AutoComplete, Avatar, Card, Checkbox, Datepicker, Dialog, Dropdown, Feedback, Heading, Input, NavigationMenu, Rating, Result, Snackbar), so importing ThemeProvider is enough to pull the whole set into an app's shared bundle. Measured on a Next.js consumer (buzzlightyear) by patching the installed package, rebuilding, and attributing generated bytes to source modules via the chunk source maps: shared first-load JS 330 kB -> 314 kB (gzipped) shared _app chunk 187 kB -> 171 kB (gzipped) icons in that chunk 78.7 KB across 233 modules -> 5.6 KB across 5 (raw) The 5 that remain are the ones the app actually renders. A census of every chunk in the build shows 26 distinct icon modules afterwards, exactly the set the app imports, relocated into the page chunks that use them rather than dropped. The claim is accurate for this package: the icons are generated SVG components with no module-scope work and no CSS imports, and src/index.ts only re-exports. packages/yoga already declares the same field, so this brings icons in line with it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
JoaoPCAzevedo
requested review from
a team,
flaviafmoraes,
matheus-wh and
wellhub-gsevla
as code owners
September 9, 2026 15:30
|
flaviafmoraes
approved these changes
Sep 10, 2026
mavericardo
approved these changes
Sep 10, 2026
matheus-wh
approved these changes
Sep 10, 2026
JVCDomingues
approved these changes
Sep 10, 2026
nataliogympass
approved these changes
Sep 10, 2026
wellhub-gsevla
approved these changes
Sep 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Description 📄
One line:
"sideEffects": falseon@gympass/yoga-icons. It brings the icons package in line withpackages/yoga, which already declares it.Why it matters.
src/index.tsisexport * from './ui'/export * from './flags'. WithoutsideEffects: false, webpack has to assume evaluating any of those re-exported modules might do something observable, so it cannot prune them — and a consumer that names one icon gets all 233 UI icons in the chunk that imports them.That is not a hypothetical for anyone using
@gympass/yoga, because 49 modules inside it import the icon barrel:Since the Yoga barrel pulls in every component, importing something as ordinary as
ThemeProvideris enough to drag the whole icon set into an app's shared bundle. Every consumer is paying for it.Measured impact on a Next.js consumer (
buzzlightyear, the sign-up app), by patching the installed package, rebuilding, and attributing generated bytes to source modules through the chunk source maps:_appchunkThe five that survive are exactly the ones that app's shell renders:
alert_triangle,info,close,checked_full,arrow_left.For scale: that single line saved more in that app than a whole PR of hand-written optimisations we had just finished (which moved 337 kB → 330 kB).
Why the claim is accurate, not just convenient.
sideEffects: falseis an assertion about this package, so it should only be made if true. For icons it is: they are generated SVG components with no module-scope work and no CSS imports, andsrc/index.tsonly re-exports. Worth remembering that if something side-effectful is ever added here, this needs to become an array of exceptions rather than a blanketfalse.Platforms 📲
sideEffectsis consumer-side bundler metadata. It changes nothing about this package's build output, and Metro does not consume it, so React Native is unaffected either way.Type of change 🔍
How Has This Been Tested? 🧪
Three checks, because a wrong
sideEffectsclaim silently deletes code rather than failing loudly — and unit tests cannot catch it, since they don't tree-shake.1. Nothing that is used got dropped. A census of every chunk in the consumer's production build, read from the source maps: 233+ distinct icon modules before, 26 after, and those 26 are precisely the set that app imports (
help_circle,visibility,visibility_off,checkin,heartbeat,money,dumbbell,play,price_tag,user,booking,family_member,star,icon_wellhub,mail_unread,information, plus the ones Yoga uses internally). They were not removed — they moved into the page chunks that actually use them, which is the intended outcome.2. Identical rendering in a real browser. Both builds served and driven through Chromium across five routes. Same SVG count on every one (13 with paths on the company-search screen, 10 on welcome), zero page errors on either. A dropped icon module would surface as a missing SVG or a React "element type is invalid" error; neither appeared.
3. The field reaches the published artifact.
publishConfig.directoryisdist, so the publishedpackage.jsonis generated rather than taken from source. I ran thecreatePackageJson()transform fromscripts/prepublish.jsagainst the patched source and confirmedsideEffects: falseis carried through — it spreads everything exceptscriptsandtsup.Not applicable: the change is a single package-manifest field with no runtime surface. I did not run the monorepo's own suite locally (it needs a full install and this cannot interact with it), so CI is the check there.
Checklist: 🔍
Screenshots 📸
No visual change — icons render identically. The measurable difference is bundle composition in consumers: