Skip to content

feat(expo-native-components): move native components into @clerk/expo-native-components - #9955

Draft
mikepitre wants to merge 52 commits into
mainfrom
mike/expo-native-package
Draft

mikepitre wants to merge 52 commits into
mainfrom
mike/expo-native-package

Conversation

@mikepitre

@mikepitre mikepitre commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Description

Stacked on #9989, which moves biometric credential enrollment and sign-in to @clerk/expo-biometrics on today's sync. Stack: main ← #9989 ← this PR ← #9957 ← #9954 ← #9956 ← #9958 ← #9962.

Moves everything in @clerk/expo that depends on the Clerk native SDKs (clerk-ios / clerk-android) into a new optional package, @clerk/expo-native-components. Apps that don't install it no longer get the Clerk native SDKs, the native module, native client sync, or the iOS 17 minimum.

What this gains

For apps that don't use native components:

  • No Clerk native SDKs in the app. Expo autolinks every native module in an installed package, so today every @clerk/expo build pulls in clerk-ios and clerk-android. After this change they're only linked when @clerk/expo-native-components is installed. A JS-only test app builds with no ClerkExpo/ClerkKit pods and no ClerkKit symbols in the iOS binary, and with no Clerk native classes in the Android APK.
  • No forced iOS 17 minimum. clerk-ios requires iOS 17, and @clerk/expo's plugin raised every app's deployment target to it. The JS-only test app builds at iOS 16.4.
  • No second Clerk client running in the background. ClerkProvider configures the native SDK in every native build today. That means extra /client and /environment requests at startup, token polling every 5s, refreshes when the app returns to the foreground, and the native↔JS sync engine, all in apps that never render a native component. None of it runs unless @clerk/expo-native-components is installed.
  • Fewer ways the build can break. The Swift Package dependency, Android packaging exclusions and Kotlin metadata flag only apply to apps that opt in. For example, React Native Screens' gamma mode corrupts Pods.xcodeproj for pods with Swift Package dependencies. JS-only apps no longer hit that.

For the SDK:

  • One opt-in rule: installing @clerk/expo-native-components turns native Clerk on. __experimental_disableNativeClientSync is no longer needed to keep JS-only apps unaffected.
  • Native SDK version bumps stay contained. clerk-ios and clerk-android releases only affect @clerk/expo-native-components users.

What it costs

  • Apps using AuthView, UserButton or UserProfileView have to install @clerk/expo-native-components and add its config plugin. Existing @clerk/expo/native imports keep working through a shim that throws a clear install error when the package is missing. This ships as a minor because native components are beta.
  • One more package to release and maintain.

This is a move plus wiring only. The JS sync engine is not rewritten: ClerkProvider, native client sync and useBiometricCredentials().reverify() stay in @clerk/expo and keep resolving the ClerkExpo native module by name, so they work when @clerk/expo-native-components is installed and are no-ops or unavailable when it isn't.

Moved to packages/expo-native-components (git mv):

  • All of ios/ and android/ (module, views, app delegate subscriber, bridge, Compose hosts, theme loading, biometric functions, native tests), ClerkExpo.podspec, android/build.gradle, expo-module.config.json, react-native.config.js, and codegenConfig.
  • src/native/* (now the package root: AuthView, UserProfileView, UserButton, useAuthViewState, custom pages API and types) and the native view specs, with their unit tests.
  • app.plugin.js native-SDK parts: iOS 17 deployment target, ClerkExpoVersion, Android META-INF exclusion and -Xskip-metadata-version-check, keychainService, theme. The theme validation tests moved with it.

@clerk/expo-native-components doesn't depend on @clerk/expo (it reads auth state through @clerk/react), so the two packages don't form a workspace cycle. Its config plugin still reports the installed @clerk/expo version in x-clerk-host-sdk-version: it writes it to ClerkExpoVersion in Info.plist and to clerkExpo.hostSdkVersion in gradle.properties, falling back to the @clerk/expo-native-components version when the plugin hasn't run. The plugin also fails prebuild with an upgrade message if the installed @clerk/expo still bundles the native module, since both would register the ClerkExpo pod and Android module.

Backwards compatibility in @clerk/expo (shipped as a minor since native components are beta):

  • @clerk/expo/native re-exports from @clerk/expo-native-components using a require() in try/catch, which Metro treats as an optional dependency. Without the package, rendering a component or calling a hook throws an error explaining how to install @clerk/expo-native-components and add its plugin. Its types come from a checked-in native/index.d.ts that re-exports @clerk/expo-native-components.
  • The @clerk/expo config plugin keeps the hosted auth intent filter, appleSignIn and faceIDPermission, and no longer forces iOS 17. When @clerk/expo-native-components is resolvable from the project, it applies that package's plugin (run once, forwarding keychainService / theme, with options on an explicit @clerk/expo-native-components entry taking precedence), so apps that only list @clerk/expo keep their native configuration. If it isn't installed and native-only options are passed, it warns with install instructions.
  • @clerk/expo-native-components is an optional peer dependency of @clerk/expo. useBiometricCredentials().reverify(), the only biometric method that still uses the native SDK after feat(expo): move biometric credentials to JS with @clerk/expo-biometrics #9989, now tells you to install @clerk/expo-native-components when the native module is missing. Enrollment and sign-in only need @clerk/expo-biometrics.

The native fixture workflow now packs and installs @clerk/expo-native-components, and the fixture lists its plugin.

Recordings of both halves are attached in the comments: a JS-only app without @clerk/expo-native-components, and an app with it rendering AuthView, UserButton and UserProfileView.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e6d2f6f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@clerk/expo Minor
@clerk/expo-native-components Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
clerk-js-sandbox Ready Ready Preview Oct 2, 2026 4:04pm UTC
swingset Ready Ready Preview Oct 2, 2026 4:04pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Comment @coderabbitai help to get the list of available commands.

@mikepitre

mikepitre commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Superseded by #9955 (comment) (re-recorded with full-frame-rate capture).

@mikepitre

mikepitre commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Superseded by #9955 (comment) (re-recorded so push and modal animations are captured).

@mikepitre mikepitre changed the title feat(expo-native): move native components into @clerk/expo-native feat(expo-native-components): move native components into @clerk/expo-native-components Sep 28, 2026
@mikepitre

mikepitre commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Superseded by #9955 (comment) (re-recorded from a plain create-expo-app app with no native workarounds).

@mikepitre

mikepitre commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Superseded by #9955 (comment) (re-recorded from a plain create-expo-app app with no native workarounds).

@mikepitre

Copy link
Copy Markdown
Contributor Author

Native components from a plain app: a fresh create-expo-app (SDK 57) with the template reset to a blank Stack, Clerk set up per the docs (ClerkProvider + tokenCache, the @clerk/expo and @clerk/expo-native-components config plugins, a sign-in screen that renders <AuthView />, a profile screen that renders <UserProfileView />), Expo's own expo-build-properties ios.enableSceneSupport for Xcode 27, and a stock expo run:ios build, with no native edits. Packages from the top of the stack. Recorded with full-frame-rate simulator capture; only long still stretches between steps were shortened.

  1. Push the sign-in screen (AuthView), then pop back
  2. Sign in with a test-mode email code
  3. The native UserButton in the header opens its account sheet, then it's dismissed
  4. Push the profile screen (UserProfileView)
components-final.mp4

@mikepitre

Copy link
Copy Markdown
Contributor Author

JS-only app from a plain app: a fresh create-expo-app (SDK 57) with the template reset to a blank Stack, Clerk set up per the docs, Expo's own ios.enableSceneSupport for Xcode 27, and a stock expo run:ios build, with no native edits. It installs this stack's @clerk/expo and not @clerk/expo-native-components (it also has @clerk/expo-biometrics from later in the stack, which doesn't use the Clerk native SDKs). iPhone Air simulator (iOS 27), full-frame-rate capture.

  • Podfile.lock has no ClerkExpo or ClerkKit pods, and the deployment target stays at iOS 16.4
  • The app binary has no ClerkKit / ClerkExpoModule symbols
  • On screen, requireOptionalNativeModule('ClerkExpo') reports "not linked" while JS email-code sign-in and sign-out work normally
jsonly-final.mp4

wobsoriano and others added 17 commits September 30, 2026 20:54
…g and require biometrics for Android keys

- Sign-in rejects a challenge for another credential or an expired one before the biometric prompt, as reverification does.
- Reverification no longer signs a second factor with the key that just satisfied the first; an outstanding second factor is returned.
- Android key creation requires a strong biometric for every policy, matching iOS and the documented policy contract.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
BiometricPrompt.authenticate() returns without calling back once the
activity's state is saved, and a recreated activity never reattaches
the callback, so sign() could leave its promise pending forever. It now
rejects with system_canceled in both cases and cancels the prompt when
the activity is destroyed.

sign() also rejects with secure_key_storage_unavailable before Android 9,
matching createKey(), and CI now runs the module's Robolectric tests in
the Android fixture build.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…install

The packed @clerk/expo-biometrics leaves out android/src/test, so the fixture's
testDebugUnitTest found no tests and failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Base automatically changed from mike/expo-biometrics-package to main October 1, 2026 19:59
mikepitre and others added 4 commits October 1, 2026 16:02
#9989's squash commit is already applied through its branch history (d82987a).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rate

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… fixture

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

2 active deployments
Preview – swingset — e6d2f6ff Deployed Oct 2, 2026 by vercel[bot]
Preview – clerk-js-sandbox — e6d2f6ff Deployed Oct 2, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants