DEV Community

titan00001
titan00001

Posted on

"pod install" Fixed My Expo Package Version (And Taught Me the Difference Between "npm install" and "npx expo install")

The Problem That Didn't Make Sense

I was working on an Expo-managed React Native application targeting iOS. Everything looked normal until I noticed a version mismatch involving expo-file-system.

My package.json contained:

{
  "expo": "^54.0.0",
  "expo-file-system": "^55.0.16"
}
Enter fullscreen mode Exit fullscreen mode

However, the Expo SDK 54 documentation showed:

expo-file-system ~19.0.23
Enter fullscreen mode Exit fullscreen mode

At first glance, this looked wrong. How could the package be version 55.0.16 in my project while the documentation recommended 19.0.23?

The confusion became even greater when I ran:

pod install
Enter fullscreen mode Exit fullscreen mode

and saw:

Installing ExpoFileSystem 19.0.23 (was 55.0.16)
Enter fullscreen mode Exit fullscreen mode

The build succeeded.

At that moment I realized I didn't actually understand how Expo packages, native modules, CocoaPods, and version compatibility worked together.


My Initial Assumption

Like many JavaScript developers, I assumed:

npm install expo-file-system
Enter fullscreen mode Exit fullscreen mode

means:

Install the package and everything needed for it.

This mental model works reasonably well for pure JavaScript libraries such as:

npm install axios
npm install zod
npm install date-fns
Enter fullscreen mode Exit fullscreen mode

There is no native code involved. The package version is the package version.

Expo modules are different.


Understanding the Layers

An Expo package actually exists in multiple worlds.

Expo SDK
│
├── JavaScript Package
│   └── expo-file-system
│
├── Native iOS Module
│   └── ExpoFileSystem Pod
│
├── Native Android Module
│   └── Gradle Module
│
└── Expo Compatibility Matrix
Enter fullscreen mode Exit fullscreen mode

When you use:

npm install expo-file-system
Enter fullscreen mode Exit fullscreen mode

npm only knows about the JavaScript package published to npm.

It does not know:

  • Which Expo SDK you're using

  • Which React Native version you're using

  • Which native iOS pod version is compatible

  • Which Android native implementation should be used

Its job is simply:

Install the latest package matching the version range.

Nothing more.


What npx expo install Actually Does

Expo provides its own installation command:

npx expo install expo-file-system
Enter fullscreen mode Exit fullscreen mode

This command behaves differently.

Before installing anything, Expo checks:

Current Expo SDK Version
          ↓
Compatible Package Version
          ↓
Install That Version
Enter fullscreen mode Exit fullscreen mode

In my case:

Expo SDK 54
        ↓
expo-file-system ~19.0.23
        ↓
Install 19.0.23
Enter fullscreen mode Exit fullscreen mode

Instead of grabbing the newest package available on npm, Expo chooses the version tested and validated against the current SDK.

This is why Expo documentation almost always recommends:

npx expo install <expo-package>
Enter fullscreen mode Exit fullscreen mode

instead of:

npm install <expo-package>
Enter fullscreen mode Exit fullscreen mode

Then Why Did pod install Fix It?

This was the part that confused me the most.

I expected CocoaPods to simply install whatever was in package.json.

That's not what happens.

During:

pod install
Enter fullscreen mode Exit fullscreen mode

Expo autolinking runs.

Autolinking scans installed Expo modules and reads their native configuration files.

A simplified view looks like:

node_modules
      ↓
Expo Package
      ↓
Podspec
      ↓
CocoaPods
      ↓
Native Pod Installation
Enter fullscreen mode Exit fullscreen mode

When CocoaPods encountered the Expo File System module, it discovered that the native pod version expected for SDK 54 was:

ExpoFileSystem 19.0.23
Enter fullscreen mode Exit fullscreen mode

and corrected the native dependency accordingly.

That's why I saw:

Installing ExpoFileSystem 19.0.23 (was 55.0.16)
Enter fullscreen mode Exit fullscreen mode

The native side was effectively protecting itself from an incompatible version.


The Important Lesson

The successful build was actually misleading.

The build succeeding does not mean the dependency setup is healthy.

I still had a mismatch between:

JavaScript Layer
expo-file-system 55.x
Enter fullscreen mode Exit fullscreen mode

and

Native Layer
ExpoFileSystem 19.x
Enter fullscreen mode Exit fullscreen mode

A future build, CI pipeline, runtime feature, or TypeScript API could easily break because the JavaScript package and native implementation are expecting different things.

The fact that CocoaPods repaired the native side doesn't mean the project is correctly configured.


The Rule I Follow Now

Whenever I add an Expo package:

npx expo install expo-file-system
npx expo install expo-camera
npx expo install expo-location
Enter fullscreen mode Exit fullscreen mode

Whenever I add a normal JavaScript library:

npm install axios
npm install zod
npm install lodash
Enter fullscreen mode Exit fullscreen mode

A simple way to remember it is:

If the package belongs to the Expo ecosystem, let Expo decide the version. If it's a pure JavaScript dependency, let npm decide.


A Helpful Command I Learned

If you inherit an existing Expo project and suspect version drift, run:

npx expo install --fix
Enter fullscreen mode Exit fullscreen mode

Expo will inspect installed Expo packages and align them with the currently installed SDK.

Follow it with:

npx expo doctor
Enter fullscreen mode Exit fullscreen mode

to identify remaining compatibility issues.


Final Mental Model

The mental model that finally made everything click for me was:

npm install
    =
Install package

expo install
    =
Find compatible version
+
Install package

pod install
    =
Install native iOS dependencies
Enter fullscreen mode Exit fullscreen mode

Each tool solves a different problem.

The mistake is assuming npm understands Expo SDK compatibility. It doesn't.

Expo does.

And that small distinction is the reason a seemingly mysterious pod install ended up teaching me how Expo's dependency system actually works.

Top comments (0)