# Bundle Optimization: barrel import patches (react-aria, react-stately, zod) ## The Problem Meteor's bundler (`standard-minifier-js`) does **not** perform tree-shaking. When any code imports from the `react-aria` barrel package: ```typescript import { FocusScope } from 'react-aria'; ``` Meteor resolves the barrel's entry point, which re-exports **all 43 sub-packages** (`@react-aria/dnd`, `@react-aria/calendar`, `@react-aria/table`, etc.). Since there is no dead-code elimination, every sub-package ends up in the main JS bundle — even if only `FocusScope` is used. The same applies to `react-stately` (20 sub-packages). **Measured impact before the fix:** 832 KB of react-aria + react-stately in the main bundle, of which ~700 KB was unused code. ## The Solution Two complementary changes: ### 1. Yarn patches on the barrel packages `yarn patch` replaces the barrel entry points (`dist/main.js`, `dist/module.js`, `dist/import.mjs`) with slim versions that only re-export the sub-packages actually used. The patches live in `.yarn/patches/` and are referenced in `package.json` resolutions. They are applied automatically on every `yarn install`. **react-aria** — retained sub-packages: `button`, `focus`, `i18n`, `interactions`, `listbox`, `menu`, `overlays`, `select`, `separator`, `slider`, `utils`, `visually-hidden`, `dialog` **react-stately** — retained sub-packages: `collections`, `menu`, `overlays`, `select`, `slider`, `tree` ### 2. Direct sub-package imports in source code All imports across `apps/meteor` and workspace packages (`gazzodown`, `ui-client`, `ui-contexts`, `ui-voip`) were changed from barrel to direct sub-package imports: ```typescript // Before import { FocusScope } from 'react-aria'; import { useOverlayTriggerState } from 'react-stately'; // After import { FocusScope } from '@react-aria/focus'; import { useOverlayTriggerState } from '@react-stately/overlays'; ``` This is the correct long-term fix, but alone it was not enough because `@rocket.chat/fuselage` (published npm package) still uses `require("react-aria")` internally via its UMD build. The yarn patch covers that case. ## Results (react-aria + react-stately) | Metric | Before | After | Delta | |---|---|---|---| | Main JS (minified) | 3688 KB | 3199 KB | **-489 KB (-13%)** | | Main JS (gzip) | 939 KB | 841 KB | **-98 KB (-10%)** | --- ## Zod v4 locale barrel ### The Problem Zod v4 re-exports all 50 locale files from its main entry point: ```js // zod/v4/classic/external.js export * as locales from "../locales/index.js"; ``` This means `import { z } from 'zod'` pulls in error messages for Arabic, Hebrew, Thai, Russian, and 46 other languages — even though Rocket.Chat only uses the English locale (loaded by default via `config(en())`). **Measured impact:** 147 KB (50 locale files) out of 278 KB total for zod — **53% of the zod bundle was unused locale data**. ### The Solution A `yarn patch` on `zod@4.3.6` removes the `export * as locales` line from the barrel. The English locale (`en.js`) remains loaded since it's imported separately by `config(en())`. All other locales remain available for explicit import if needed: ```typescript // Still works: import pt from 'zod/v4/locales/pt'; import { config } from 'zod'; config(pt()); ``` ### What requires attention #### Upgrading zod When upgrading zod, the patch must be re-created: 1. Remove the old patch reference from `package.json` resolutions 2. Delete `.yarn/patches/zod-*.patch` 3. Run `yarn install` 4. Run `yarn patch zod@npm:` 5. Remove the `export * as locales` line from `v4/classic/external.js` 6. Remove the `exports.locales` require from `v4/classic/external.cjs` 7. Run `yarn patch-commit -s ` #### If zod locales are needed at runtime If a feature requires localized zod error messages (e.g., form validation in the user's language), import the specific locale directly instead of relying on the barrel: ```typescript import de from 'zod/v4/locales/de'; import { config } from 'zod'; config(de()); ``` This loads only the one locale needed (~3 KB) instead of all 50 (~147 KB). ## What requires attention ### Adding new react-aria hooks or components If you need to use a react-aria hook or component that is **not** in the slim barrel (e.g., `useCalendar`, `useTable`, `useDrag`), you must: 1. **Import from the sub-package directly** (preferred): ```typescript import { useCalendar } from '@react-aria/calendar'; ``` 2. **Or update the yarn patch** if the import comes from a dependency you don't control (like `@rocket.chat/fuselage`): ```bash yarn patch react-aria@npm:3.37.0 # Add the missing export to dist/import.mjs, dist/module.js, and dist/main.js yarn patch-commit -s ``` ### Upgrading react-aria / react-stately versions When upgrading these packages, the yarn patches must be re-created for the new version: 1. Remove the old patch references from `package.json` resolutions 2. Delete the old `.yarn/patches/react-aria-*.patch` and `react-stately-*.patch` files 3. Run `yarn install` to get the unpatched version 4. Run `yarn patch react-aria@npm:` and re-apply the slim barrel 5. Run `yarn patch react-stately@npm:` and re-apply the slim barrel 6. Verify no new exports are needed by running the app and checking for runtime errors ### Upgrading @rocket.chat/fuselage When the fuselage package is updated, check if it uses any new react-aria exports. You can verify with: ```bash grep -oE 'react_aria_1\.[a-zA-Z]+' node_modules/@rocket.chat/fuselage/dist/fuselage.development.js | sort -u grep -oE 'react_stately_1\.[a-zA-Z]+' node_modules/@rocket.chat/fuselage/dist/fuselage.development.js | sort -u ``` If there are new exports, add them to the yarn patch. ### The long-term fix The react-aria/react-stately yarn patches are a workaround. The proper fix is for `@rocket.chat/fuselage` to import from sub-packages directly instead of the barrel, which eliminates the need for those patches entirely. Once that is done, only the direct sub-package imports in source code are needed. The zod patch is a workaround for a design choice in zod v4 (`export * as locales` in the main barrel). This may be addressed upstream — track https://github.com/colinhacks/zod for changes to the locale export strategy. ## How to analyze the bundle Meteor generates a `.stats.json` file alongside each build. To inspect it: ```bash # Find the latest stats file ls -t .meteor/local/build/programs/web.browser/*.stats.json | head -1 # Quick summary python3 -c " import json with open('') as f: data = json.load(f) print(f'Total: {data[\"totalMinifiedBytes\"]/1024:.0f} KB') print(f'Gzip: {data[\"totalMinifiedGzipBytes\"]/1024:.0f} KB') " ``` The stats file contains a per-package breakdown in `minifiedBytesByPackage`, with `packages/modules.js` containing a nested tree of every npm module included in the main bundle.