Skip to content

fix: peer-depend expo-blob and @bugsplat/react, develop against SDK 57 - #23

Open
bobbyg603 wants to merge 2 commits into
mainfrom
fix/expo-blob-peer-dependency
Open

fix: peer-depend expo-blob and @bugsplat/react, develop against SDK 57#23
bobbyg603 wants to merge 2 commits into
mainfrom
fix/expo-blob-peer-dependency

Conversation

@bobbyg603

@bobbyg603 bobbyg603 commented Aug 9, 2026

Copy link
Copy Markdown
Member

Problem

expo-blob is declared as a dependency at ^55.0.13. It ships native code versioned in lockstep with the Expo SDK, and the caret range can never reach 57.x — so every consumer is pinned to the SDK 55 build no matter which SDK they're on.

On SDK 57 that crashes the app at startup:

java.lang.NoClassDefFoundError: Failed resolution of: Lexpo/modules/kotlin/types/AnyTypeProvider;
    at expo.modules.blob.BlobModule.definition(BlobModule.kt:64)
    at expo.modules.kotlin.ModuleRegistry.register(ModuleRegistry.kt:29)
    at expo.modules.adapters.react.ModuleRegistryAdapter.createNativeModules(ModuleRegistryAdapter.java:65)

AnyTypeProvider doesn't exist anywhere in expo-modules-core@57. The Gradle build succeeds and the APK installs cleanly — it dies during native module registration, before any JS runs, which makes it read as a runtime bug rather than a dependency one. Minification is off, so this isn't R8 stripping.

Found while upgrading an app to Expo SDK 57. 0.7.0 is affected identically.

Changes

1. expo-blob → peer dependency. Matches how expo, react, and react-native are already declared. The app installs the copy matching its SDK, and there's exactly one in the tree.

2. @bugsplat/react → peer dependency. It exports React components (ErrorBoundary, withErrorBoundary) and owns the BugSplat client singleton. A consumer who installs it directly alongside this package gets two copies, two clients, and two React contexts. Peering it (at ^2.1.1, since we control that release line) keeps one.

3. Develop against SDK 57. devDependencies and the example app move to expo 57.0.11, react-native 0.86.2, react/react-dom 19.2.3, jest-expo 57.0.3, expo-blob 57.0.1. The package was previously developed and tested against SDK 55 only — which is precisely why this shipped unnoticed. expo-module-scripts has no 57 line published yet, so it goes to the latest available (56.0.3).

4. README install line becomes npx expo install @bugsplat/expo expo-blob @bugsplat/react, with a note that expo-blob is not in Expo's bundled native modules manifest (see below), so expo install will not pin it for you.

No source changes were needed: src/BugsplatExpoModule.ts uses requireOptionalNativeModule from expo, which is stable across 55→57.

This is an install-breaking change for consumers (they must now install expo-blob and @bugsplat/react), so it wants a minor bump rather than a patch.

Not done here

  • Lockfiles are not regenerated. package-lock.json at the root and in example/ still describe the SDK 55 tree. Run npm install in both before merging. I was working on a machine with ~3 GB free and did not want a half-written lockfile.
  • The example app has not been built against SDK 57. I do have one useful data point: this library's Android Kotlin compiles cleanly against expo-modules-core@57 — it built and packaged fine in the consuming app where this was found. Only the expo-blob dependency was stale. iOS is unverified.

Notes — we still need to test whether expo-blob is actually required

I deliberately did not remove expo-blob, even though a first read suggests the import 'expo-blob' in BugsplatExpo.ts may be doing nothing. Bobby is fairly confident it's load-bearing for attachment paths — componentStack, log files, screenshots, and other attachments — and that needs real device testing before anything is removed. Peer-dep'ing it unblocks SDK 57 without changing any runtime behavior, which keeps that question separate.

What the code reads like today, for whoever picks this up:

The bare import does not appear to patch globalThis.Blob.

  • expo-blob's JS is export * from './ExpoBlob'export class Blob extends NativeBlobModule.Blob. No globalThis assignment in src/ or build/ in either 55.0.13 or 57.0.1.
  • Its Android module is a plain Module() named ExpoBlob — no JSI global installation.
  • Expo SDK 57's expo/src/winter/runtime.native.ts installs TextDecoder, URL, URLSearchParams, DOMException, structuredClone, and fetch — not Blob. And expo/src/winter/fetch/createBlob.ts still carries TODO(kudo,20260706): remove this when we install expo-blob as globalThis.Blob.

Which means binary attachments may already be broken on native. bugsplat core does:

if (data instanceof Uint8Array) {
  body.append(filename, new Blob([data]), filename);
}

If globalThis.Blob is React Native's Blob, BlobManager.createFromParts throws outright:

if (part instanceof ArrayBuffer || ArrayBuffer.isView(part)) {
  throw new Error("Creating blobs from 'ArrayBuffer' and 'ArrayBufferView' are not supported");
}

A Uint8Array is an ArrayBufferView, so any binary attachment — log file, screenshot — would hit that throw. String attachments are fine: new Blob([componentStack]) produces an RN Blob, and expo/fetch's convertFormData serializes it via the FileReader.readAsArrayBuffer fallback in blobToArrayBufferAsync.

Note that src/__tests__/BugsplatExpo.expoGo.test.ts:262 covers exactly this case ({ filename: 'log.txt', data: new Uint8Array([1]) }) but runs under a Node/jsdom Blob, so it would pass while a device fails.

Suggested follow-up, on a real device (not Expo Go, not a JS-only path):

  1. componentStack via ErrorBoundary — string path, expected to work today.
  2. A log file as Uint8Array — the case most likely to throw.
  3. A screenshot as binary data.
  4. Any other attachment shape the sample app exercises.

Run each with and without expo-blob installed. If binary attachments fail in both cases, the import isn't providing the protection it looks like it should, and the real fix is to construct attachments with expo-blob's Blob explicitly (or install it as globalThis.Blob) rather than relying on a global nothing patches. If they pass only with it installed, then something does wire up the global and the README claim around line 245 stands — worth pinning down which, either way.

I could not check whether SDK 55's winter runtime installed Blob as a global (no node_modules in the clone I looked at), so it's possible this worked when it was written and silently stopped.

Keeping this in sync going forward

expo-blob is not in Expo's bundled native modules manifest — neither the local expo/bundledNativeModules.json nor the API (https://api.expo.dev/v2/sdks/57.0.0/native-modules, 123 packages, no blob entry). So npx expo install expo-blob installs whatever npm's latest is, which today is 57.0.1 and would be wrong for an SDK 55 or 56 app. Until Expo adds it, the README has to tell people which line to install, and npx expo-doctor won't catch a mismatch either.

🤖 Generated with Claude Code

expo-blob ships native code that is versioned in lockstep with the Expo
SDK, so the app — not this library — has to own which copy is installed.
Declaring it as a dependency at ^55.0.13 pins consumers to the SDK 55
build, and the caret range can never reach 57.x.

On SDK 57 that combination crashes at startup. expo-blob 55's
BlobModule references expo.modules.kotlin.types.AnyTypeProvider, which
no longer exists in expo-modules-core 57, so native module registration
throws NoClassDefFoundError before any JS runs. The build itself
succeeds, which makes it look like a runtime bug rather than a
dependency one.

Moving it to peerDependencies lets each app install the expo-blob build
matching its SDK, and keeps a single copy in the tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 9, 2026 16:46

Copilot AI 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.

Pull request overview

This PR updates @bugsplat/expo’s dependency model to avoid Expo SDK mismatches by making expo-blob a peer dependency, ensuring consuming apps install the SDK-matching native module.

Changes:

  • Move expo-blob from dependencies to peerDependencies (with a local devDependencies entry for development/testing).
  • Update installation instructions to explicitly install expo-blob via expo install.
  • Document why expo-blob must be peer-installed to match the project’s Expo SDK.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
README.md Updates install command and explains why expo-blob is a peer dependency.
package.json Moves expo-blob to peerDependencies and adds it to devDependencies for local development.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread package.json Outdated
Comment on lines 39 to 43
"devDependencies": {
"@types/react": "~19.1.1",
"expo": "~55.0.8",
"expo-blob": "~55.0.13",
"expo-module-scripts": "^55.0.2",
The package was developed and tested against SDK 55 only, which is how
an SDK 57 incompatibility shipped unnoticed in the first place. Move the
devDependencies and the example app to SDK 57 (expo 57.0.11,
react-native 0.86.2, react 19.2.3) so local builds exercise the same
core the library is published against. expo-module-scripts has no 57
line yet, so it goes to the latest available, 56.0.3.

@bugsplat/react becomes a peer dependency as well. It exports React
components and owns the BugSplat client singleton, so a consumer that
installs it directly alongside this package would otherwise end up with
two copies and two clients. Peering it keeps exactly one in the tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bobbyg603 bobbyg603 changed the title fix: make expo-blob a peer dependency fix: peer-depend expo-blob and @bugsplat/react, develop against SDK 57 Aug 9, 2026
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.

2 participants