Every icon vanished: a Metro debugging story (featuring a babel package from 2017)

작성자

카테고리:

← 피드로
DEV Community · Ibukun Demehin · 2026-07-21 개발(SW)

I added a font and a few icons. Then I ran a development build, and Metro died 2,513 modules in with an error that had nothing to do with either:

iOS Bundling failed
TypeError: Cannot read properties of undefined (reading 'transformFile')

Enter fullscreen mode Exit fullscreen mode

No filename. No line number. Every build failed the same way. The trail ran through two rewrites of an icon library and ended at a @babel/types published in 20177.0.0-beta.4 — sitting in my node_modules, dragged in by a package I never installed on purpose. Here’s the hunt.

TL;DR — A Metro TypeError: Cannot read properties of undefined (reading 'transformFile') with no filename almost always means a broken Babel toolchain, not your code. Mine was an ancient @babel/[email protected] (2017) resolved for the transform, pulled in transitively by @aws-amplify/graphql-types-generator. Fix: pin @babel/types and @babel/generator (and react-native-worklets) with overrides in the root package.json.

(Part 3 of Building CannyCart, a voice-first shopping app I’m building in public. This one’s a self-contained debugging story — no earlier context needed.)

The build that wouldn’t build

The setup, for anyone who lands here from a search: Expo SDK 57, expo-router, NativeWind, with an Amplify Gen 2 backend in the same npm-workspaces monorepo. Icons come from react-native-iconify, which uses a Babel plugin to scan JSX for <Iconify icon="…" /> and inline the SVG at build time. I was wiring up the app’s icon set and building a dev client.

Metro started bundling, churned through 2,513 modules, and threw:

TypeError: Cannot read properties of undefined (reading 'transformFile')

Enter fullscreen mode Exit fullscreen mode

No file. No stack into my code. Fully reproducible. When the error names nothing you wrote, the call is coming from inside the toolchain.

Wrong theory #1: the icon library’s Babel plugin

react-native-iconify runs during the Babel transform Metro invokes — exactly the kind of thing that can nuke transformFile. And I’d recently bumped its version. Version 2 had restructured its exports: the Babel plugin moved from ./plugin (v1) to ./babel (v2), so my babel.config.js reference looked like a prime suspect.

I fixed the reference to match v2. The crash didn’t clear — it moved. Plausible theory, wrong theory: a broken plugin path was real, but it wasn’t what was killing the bundler.

Wrong theory #2: it must be worklets

Now the error mutated into something more specific, from react-native-worklets‘ Babel plugin:

unknown node TSUnknownKeyword

Enter fullscreen mode Exit fullscreen mode

So worklets was choking on a TypeScript AST node. Maybe worklets was the culprit and I needed to pin it. But TSUnknownKeyword is a perfectly normal node type — it exists in any modern @babel/types. If a plugin claims a standard node is “unknown,” the plugin isn’t broken; it’s been handed an ancient @babel/types that predates that node. That was the tell. I stopped chasing plugins and went looking at versions.

The turn: reading the lockfile

I grepped package-lock.json for every @babel/types version in the tree:

 13 "@babel/types": "^7.29.7"
  3 "@babel/types": "^7.29.0"
  1 "@babel/types": "^7.27.1"
  # … a few more 7.2x ranges …
  1 "@babel/types": "7.0.0-beta.4"

Enter fullscreen mode Exit fullscreen mode

One line didn’t belong. Everything modern asked for a caret range around 7.29. And then, alone at the bottom: 7.0.0-beta.4. Not a range — an exact pin. A beta. Of a package whose current release is 7.29. 7.0.0-beta.4 shipped in 2017.

A 2017 beta hiding in the tree

Who pins an eight-year-old beta of @babel/types? Tracing it back, the culprit was @aws-amplify/graphql-types-generator — an Amplify GraphQL codegen tool, itself a museum of old transitive deps (its own private copies of yargs, prettier, camelcase from another era). One of them locks @babel/types to that exact 2017 build.

Here’s the mechanism. @babel/generator and the worklets plugin construct and inspect AST nodes through @babel/types helpers. Between the 2017 beta and 7.29, @babel/types grew an enormous number of those helpers — including builders and validators for TypeScript nodes like TSUnknownKeyword. When npm’s resolution let the old copy serve part of the transform, a modern plugin reached for a helper that didn’t exist in the 2017 version, got undefined, and called a method on it. Metro surfaced that as Cannot read properties of undefined (reading 'transformFile'). The “unknown node” error and the “transformFile” error were the same root cause wearing two masks.

The fix: one overrides block

npm’s overrides (Yarn calls it resolutions) forces a single version of a package across the entire dependency tree, no matter who asked for what. In the root package.json:

"overrides": {
  "@babel/types": "7.29.7",
  "@babel/generator": "7.29.7",
  "react-native-worklets": "0.10.0"
}

Enter fullscreen mode Exit fullscreen mode

@babel/types and @babel/generator pinned to a matching modern pair; react-native-worklets pinned to the build that plays nicely with SDK 57’s reanimated. Then rm -rf node_modules && npm install. The bundle went green.

overrides is a sledgehammer — it bypasses semver for everyone in the tree — so it’s a last resort. But when the problem is a transitive dependency four levels down pinning something incompatible, it’s the only wrench that reaches.

The coda: a green bundle that rendered nothing

The bundler lived, but the icons still weren’t right. iconify v2 now threw at build time:

[React Native Iconify] Icon 'mdi:account-circle-outline' not found in babel.config.js

Enter fullscreen mode Exit fullscreen mode

v2 had changed its whole model: instead of auto-scanning JSX, it wants an explicit icon allowlist registered in an app entry file. Under expo-router there is no classic App.js entry to host that registry, so the icons never got registered.

The trap was the feedback. expo export completed successfully — green, no errors — while the icons rendered as null in the actual app, silently. A passing bundle told me nothing about whether a single icon would appear on screen. I confirmed the name wasn’t a typo (Iconify’s API answers https://api.iconify.design/mdi.json?icons=account-circle-outline), then tested at the Babel-transform level and watched v2’s registry simply not inject under expo-router. I downgraded to react-native-iconify@^1.0.2, whose plugin auto-scans JSX and inlines the SVG with no registry and no entry file. Icons came back.

What I took away

  • A transformFile error that names no file is almost never your code. It’s the Babel/Metro toolchain. Check the installed versions of your @babel/* packages before you touch your own config.
  • overrides / resolutions is the tool for transitive-dependency conflicts. When something deep in the tree pins an incompatible version, this is how you reach past it.
  • A green build is not a green render. expo export passing meant the bundle compiled, not that the UI worked. For anything that can fail silently at runtime — icons, fonts, native modules — verify on a device, not in CI logs.

Next up

The main branch that refused to deploy while dev worked perfectly:

CREATE_FAILED … Validation failed with 1 error(s).
ROLLBACK_COMPLETE

Enter fullscreen mode Exit fullscreen mode

That’s CloudFormation forensics on Amplify Gen 2, and the cause was a name two git branches were quietly fighting over. Post 4.

What’s the oldest package you’ve found lurking in your node_modules? 7.0.0-beta.4 from 2017 is my record — I’d love to be beaten.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다