Skip to content

fix(react-native): let the app provide expo-file-system - #2097

Merged
ChiragAgg5k merged 1 commit into
mainfrom
fix/react-native-expo-file-system
Oct 17, 2026
Merged

ChiragAgg5k merged 1 commit into
mainfrom
fix/react-native-expo-file-system

Conversation

@ChiragAgg5k

@ChiragAgg5k ChiragAgg5k commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

What does this PR do?

Makes one build of the React Native SDK work on Expo SDK 52 through 58.

The bug

The SDK lists expo-file-system: "18.*.*" as a regular dependency. react-native-appwrite@2.0.0 ships that, and it breaks newer Expo apps in two ways:

18.x (Expo SDK 52 and 53) has no /legacy entry, so no single pinned version or import path works across Expo SDKs. #1978 pinned ^57 with a /legacy import and was reverted in #1979 because a regular dependency on 57 breaks apps on 53–56 the same way 18 breaks 54+.

The fix

  • Peer, not dependency. expo-file-system moves to peerDependencies as >=18.0.0, so the app's own copy (the one matching its Expo SDK) is used and nothing gets nested. A 58.0.9 devDependency provides types for the build. The expo peer goes from * to >=52.0.0, matching the existing react-native >=0.76.7 floor.
  • Runtime fallback. A new src/lib/file-system.ts requires expo-file-system/legacy and falls back to expo-file-system if that fails. Because the /legacy require sits in a try, Metro treats it as optional: Expo's Metro config sets allowOptionalDependencies: true (checked in @expo/metro-config 0.19, used by Expo SDK 52), so apps on 18 still bundle and use the root API. PostHog's React Native SDK does the same thing (OptionalExpoFileSystemLegacy falling back to OptionalExpoFileSystem). The loader is typed against the legacy API. /legacy still ships in expo-file-system 58, and Expo hasn't announced removing it.
  • Types for the subpath. With moduleResolution: "node", expo-file-system/legacy resolves to the package's legacy.ts source rather than its declarations, and tsc then fails on expo-modules-core imports. The tsconfig now uses "bundler", which follows the package's exports map to build/legacy/index.d.ts. This is how Metro (package exports on by default since Expo SDK 53) and Rollup resolve it as well.
  • Temp chunk cleanup. On Android each upload chunk was written to cacheDirectory and never removed. Each one is now deleted once its request completes, whether it succeeded or failed.
  • README. The install command now includes expo-file-system, and the README says Expo SDK 52+ is supported and that npx expo install picks the matching version.

Release

This should ship as a minor (2.1.0). Apps on Expo SDK 52 and 53 keep working on 18 through the fallback, and every Expo SDK already includes expo-file-system, so npx expo install adds the right version. The apps that need anything new are the ones on 54+ that crash or fail to upload today.

Supersedes #1691 by @bhardwajparth51, who diagnosed the /legacy move for Expo SDK 55. That PR's bare /legacy import fixes 55+ but would break 52/53, where /legacy doesn't exist. This PR keeps that import and adds the fallback and the peer change.

Test Plan

  • Generation table testNativeModulesArePeerDependencies checks that package.json and the root of package-lock.json declare expo-file-system >=18.0.0 and expo >=52.0.0 as peers, and that neither is listed in dependencies. Against main's templates it fails (package.json does not declare expo as a >=52.0.0 peer). With this change it passes, along with the rest of the Generation suite.
  • React Native e2e (tests/e2e/ReactNativeTest.php): the browser harness now models Metro's runtime require with a module registry (shims/metro-require.js). The default layout is expo-file-system 19+: /legacy serves the working API and the root throws, as the real package does. The static-import aliases follow the same layout. After the existing uploads, the test switches Platform.OS to android and uploads the large file twice. The first run uses the 19+ layout. The second uses the 18 layout, which has no /legacy and keeps the API at the root. After each run the test prints how many temp chunk files are left, and expects 0.
    • Red: with main's templates and the new harness, the uploads go through the throwing root module and the test fails.
    • Green: passes locally (OK (1 test, 274 assertions)).
  • Regenerated react-native client. npm ci --omit=peer, format:check, lint, analyse and build all pass. With --omit=peer, the types come from the devDependency.
  • composer lint, composer lint-twig, composer refactor:check and the .twig max line length check all pass.
  • The lock template comes from ./scripts/update-lockfiles.sh react-native, cut down to the root entry and the expo-file-system entry (18.1.11 → 58.0.9). The script also moved the dev-only expo toolchain from 57 to 58, and that churn is left out.

Related PRs and Issues

Have you read the Contributing Guidelines on issues?

Yes.

Closes #2098

As a regular dependency pinned to 18, expo-file-system nested a second
native copy in apps on Expo SDK 54 and later, crashing Android at launch,
and from 19 the root legacy methods throw, breaking file uploads. No
single pinned version works across Expo SDKs, so the app now supplies it
as a peer and the SDK loads the legacy API from "expo-file-system/legacy"
when it exists, falling back to the package root on 18 (Expo SDK 52 and
53).

Android upload chunks written to the cache directory are now deleted
once their request completes.
@hansi-codes

hansi-codes Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

🟢 Tier S · Ready to merge

This change makes expo-file-system an app-provided peer dependency and adds a runtime loader that prefers the legacy API while falling back for older Expo versions. It also removes Android upload chunk temp files after requests and updates the generated README and React Native test harness.

Verdict New comments Fixed Still open
✅ Approved 0 0 0
📂 Walkthrough · 14
File Change
src/SDK/Language/ReactNative.php Registers the generated file-system compatibility loader.
templates/react-native/README.md.twig Documents Expo SDK support and adds expo-file-system to the install command.
templates/react-native/package-lock.json.twig Moves expo-file-system to dev dependencies and records Expo peers.
templates/react-native/package.json.twig Makes Expo and expo-file-system peers and pins the development types package.
templates/react-native/src/lib/file-system.ts.twig Adds runtime selection between legacy and root expo-file-system APIs.
templates/react-native/src/services/template.ts.twig Uses the compatibility loader and deletes temporary Android upload chunks.
templates/react-native/tsconfig.json.twig Uses bundler module resolution for Expo package exports.
tests/e2e/ReactNativeTest.php Adds expected results for uploads using both Expo file-system layouts and verifies chunk cleanup.
tests/e2e/languages/react-native/browser.js Exercises Android uploads with Expo 58 and Expo 18 layouts.
tests/e2e/languages/react-native/rollup.test.config.mjs Aliases legacy and root Expo file-system paths to their test shims.
tests/e2e/languages/react-native/shims/expo-file-system-root.js Models the deprecated root file API in newer Expo releases.
tests/e2e/languages/react-native/shims/expo-file-system.js Exports the temporary-file registry for cleanup assertions.
tests/e2e/languages/react-native/shims/metro-require.js Models Metro runtime module resolution for the two Expo API layouts.
tests/generation/GenerationTest.php Adds a generation check for native-module peer dependencies.
🔇 Filtered out · 1

Findings Hansi considered but did not post.

Finding Why
Read the correctly cased peerDependencies key Verifier: The generate() helper lowercases every generated file's contents before returning them (GenerationTest.php:259–267, specifically strtolower at line 263). Consequently json_decode receives "peerdependencies", so the existing lowercase lookup is correct. A PHP reproduction of that lowercasing-and-decoding path returns the expected range for peerdependencies and null for peerDependencies; applying the suggestion would introduce a test failure.

Reviewed f451ae0 · Details · Comment @hansi-codes review to re-run, or mention @hansi-codes with a question.

@hansi-codes hansi-codes Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Tier S · Looks good to merge. Summary

@ChiragAgg5k
ChiragAgg5k merged commit 492d62a into main Oct 17, 2026
61 checks passed
@ChiragAgg5k
ChiragAgg5k deleted the fix/react-native-expo-file-system branch October 17, 2026 10:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Let the app provide expo-file-system in the React Native SDK

1 participant