MetaMask / MetaMask/metamask-mobile
Replace `from 'lodash'` imports with `lodash/<method>` submodule imports
- Dominant language
- TypeScript
- Stars
- 3k
- Forks
- 1.7k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 669
Description
> **Performance audit finding** · Severity: **Medium** · Effort: Easy · Fix risk: Simple · Test safety net: Uncovered
> Owner: `@MetaMask/mobile-platform (suggested)`
> File: `app/ (100 occurrences across ~95 files)`
### What is this about?
100 non-test files import named methods from the lodash main package (`import { merge } from 'lodash'`) instead of from the per-method submodule (`import merge from 'lodash/merge'`). Metro does not tree-shake CommonJS lodash, so `import { merge } from 'lodash'` pulls in the entire lodash module graph at evaluation time rather than the single method. Two files import the whole default export (`import _ from 'lodash'`).
**Why it matters**
There is no `babel-plugin-lodash` / `lodash-webpack-plugin` in this project (verified: no lodash babel plugin in `babel.config.js` / `package.json`). Without it, every `from 'lodash'` import evaluates the full library on the JS bundle's startup path, increasing bundle size and module-init (TTI) cost. Switching to `lodash/` imports only the needed function (and its internal deps), which is a mechanical, behavior-preserving change. lodash is pinned via `resolutions` and `dependencies` to `"lodash": "4.18.1"` (`package.json:187` and `package.json:457`).
### Scenario
N/A — see Technical Details.
### Design
N/A — internal performance change; no UI/design impact.
### Technical Details
**Evidence**
`grep -rn "from 'lodash'" app --include="*.ts" --include="*.tsx" | grep -v "\.test\.\|lodash/" | wc -l` => **100** (515+ including tests/all). Method breakdown (counts include tests):
```
123 import { merge }
54 import { cloneDeep }
18 import { noop }
15 import { isEqual }
13 import { debounce }
9 import { cloneDeep, merge }
5 import { capitalize }
...
```
Representative offenders:
- `app/core/Engine/messengers/index.ts:1` — `import { noop } from 'lodash';` (Engine startup path)
- `app/core/Engine/controllers/transaction-controller/data-helpers.ts:1` — `import { merge } from 'lodash';`
- `app/core/redux/slices/bridge/index.ts:15` — `import { uniqBy } from 'lodash';`
- `app/util/theme/index.ts:9` — `import { throttle } from 'lodash';`
- `app/components/UI/Earn/components/EmptyStateCta/index.tsx:2` — `import _ from 'lodash';` (full default import)
- `app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.tsx:8` — `import lodash from 'lodash';` (full default import)
**Fix**
Codemod the imports:
- `import { merge } from 'lodash'` -> `import merge from 'lodash/merge'`
- `import { cloneDeep, merge } from 'lodash'` -> two submodule imports
- `import _ from 'lodash'` -> import only the methods actually used as submodules
Optionally add a lint rule (`eslint-plugin-lodash` `import-scope` or `no-restricted-imports`) to forbid `from 'lodash'` going forward, or add `babel-plugin-lodash`.
### Threat Modeling Framework
N/A — performance-only change; behavior is preserved, no new data flow / trust boundary / attack surface.
### Acceptance Criteria
- - After codemod, `grep -rn "from 'lodash'" app --include="*.ts" --include="*.tsx" | grep -v "lodash/"` should return 0 (excluding intentional cases).
- `yarn lint:tsc` passes (method default exports are typed).
- Compare Metro bundle size before/after (`yarn build:android:main:prod` source-map / bundle stats) to confirm the reduction; unit/E2E suites are unaffected (size is not asserted).
### References
- File: `app/ (100 occurrences across ~95 files)`
- Source: MetaMask Mobile performance audit — finding `bundle-lodash-main-package-imports`
- Owner (CODEOWNERS / best-effort): @MetaMask/mobile-platform (suggested)
- Status: **UNVALIDATED**
Contributor guide
Research direction
Start with the representative imports in app/core/Engine/messengers/index.ts, app/core/Engine/controllers/transaction-controller/data-helpers.ts, and the grep command in the issue. Inspect the two full lodash imports to identify their actual method usage. Done means no unintended app imports use the lodash main package, yarn lint:tsc passes, and the Android production bundle is compared.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react-native, typescript
- Domain
- mobile-dev, performance
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100