MetaMask / MetaMask/metamask-mobile

Reduce/avoid `export *` barrel files that defeat Metro tree-shaking

Open
#31,280 1 comment 0 reactions 0 assignees View on GitHub
area-performance needs-engineer Sev2 size-XL ta-triaged team-mobile-platform
Dominant language
TypeScript
Stars
3k
Forks
1.7k
Avg merge
1d 14h
Merged PRs (30d)
669

Description

> **Performance audit finding** · Severity: **Medium** · Effort: Hard · Fix risk: Risky · Test safety net: Uncovered
> Owner: `@MetaMask/mobile-platform (suggested)`
> File: `app/ (966 re-export index files; ~158 use `export * from`)`

### What is this about?

966 `index.ts(x)` files re-export other modules; 158 of them use the wildcard form `export * from './x'`. Metro does not reliably tree-shake barrels: importing a single symbol from a barrel that does `export *` evaluates *every* module the barrel touches at startup. The most impactful are the aggregate barrels that fan out to many submodules and sit on hot import paths.

**Why it matters**

Barrels that re-export with `export *` force eager evaluation of the whole subtree on first import, inflating module-init time (TTI) and bundle reachability. Two categories matter most here:
1. **Selectors** — 870 files import from `app/selectors/...`; the directory barrels use `export *` (e.g. `app/selectors/snaps/index.ts`, `app/selectors/multichain/index.ts`), so importing one selector can pull a whole controller's selector tree.
2. **Engine** — `app/core/Engine/index.ts` does `export * from './types'` and is on the app-init path; `app/core/Engine/` contains 21 `export *` index files.

Note: the bulk of `app/component-library/components/**/index.ts` and `app/components/Views/**/index.ts` barrels are single-line `export { default } from './X'` re-exports (1020 files import from `component-library/components`). These single-default barrels are far cheaper than `export *` fan-out barrels and are NOT the priority — flag only the wildcard aggregate barrels.

### Scenario

N/A — see Technical Details.

### Design

N/A — internal performance change; no UI/design impact.

### Technical Details

**Evidence**

`grep -rln "export \* from" app --include="*.ts" --include="*.tsx" | grep -E "index\.(ts|tsx)$" | wc -l` => **158** wildcard barrels. By top directory:
```
81 app/components/Views
28 app/components/UI
21 app/core/Engine
6 app/util/notifications
5 app/components/hooks
```
- `app/selectors/snaps/index.ts:1`
```ts
export * from './interfaceController';
export * from './permissionController';
export * from './snapController';
```
- `app/core/Engine/index.ts:2` — `export * from './types';` (app-init path)
- 870 files import `from '.../selectors/...'`, 1020 from `component-library/components`.

**Fix**

- For aggregate `export *` barrels on hot paths (selectors, Engine), prefer deep imports at call sites (`import { selectX } from 'app/selectors/foo/specific'`) and/or convert `export *` to explicit named `export { selectX } from './specific'` so unused modules are not transitively reachable.
- Add an ESLint `no-restricted-imports` / `no-barrel-files` rule for new `export *` index files in `app/selectors` and `app/core/Engine`.
- Do NOT mass-rewrite the single-default `export { default }` component barrels — low payoff, high churn.

### Threat Modeling Framework

N/A — performance-only change; behavior is preserved, no new data flow / trust boundary / attack surface.

### Acceptance Criteria

- - Metro bundle/module-count diff before/after debarreling a target directory (`react-native bundle` stats or source-map-explorer).
- TTI trace (`trace()` instrumentation, see `mms-performance`) on cold start before/after.
- `yarn lint`, `yarn lint:tsc`, full unit suite pass (import churn is the main risk; size/startup are not asserted by tests => Uncovered).

### References

- File: `app/ (966 re-export index files; ~158 use `export * from`)`
- Source: MetaMask Mobile performance audit — finding `bundle-barrel-export-files`
- Owner (CODEOWNERS / best-effort): @MetaMask/mobile-platform (suggested)
- Status: **UNVALIDATED**

Contributor guide

Open the contributing guide

Research direction

Start by inspecting app/selectors/snaps/index.ts and app/core/Engine/index.ts, then identify wildcard aggregate barrels on hot import paths using the provided grep command. Measure a selected change with react-native bundle stats or source-map-explorer and mms-performance cold-start traces; done means the targeted imports are debarreled, lint and TypeScript checks pass, and the unit suite remains green.

Written by the indexing model from the issue text.

Assessment

Tech stack
react-native, typescript
Domain
mobile-dev, performance
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.