Skip to content

feat(expo-native-components): Move native components into its own package - #9955

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

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

Conversation

@mikepitre

@mikepitre mikepitre commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Description

Builds on #9989 (merged), which moved biometric credential enrollment and sign-in to @clerk/expo-biometrics on today's sync. Stack: main ← 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 rebuild. npx expo install adds its config plugin to a static app config, and the @clerk/expo plugin applies it either way. 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, useAuthViewState 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, custom pages API and types) and the native view specs, with their unit tests. useAuthViewState and its test stay in @clerk/expo.
  • 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 or on any other Clerk JS package, so the two packages don't form a workspace cycle and an app can't end up with a second copy of @clerk/react. useAuthViewState is the one part of src/native that reads Clerk session state, so it stays in @clerk/expo and finds the ClerkExpo module by name, like native client sync. 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 useUserProfileCustomPageNavigation throws an error explaining how to install @clerk/expo-native-components. useAuthViewState is exported by @clerk/expo itself and falls back to the JS session state, as it does on main when the native module is missing. The entry's types come from a checked-in native/index.d.ts that re-exports @clerk/expo-native-components plus the hook's own types.
  • 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: c708f9c

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 8, 2026 5:55pm UTC
swingset Ready Ready Preview Oct 8, 2026 5:55pm 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

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

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

The hook reads Clerk session state through useAuth, so moving it into
@clerk/expo-native-components made that package depend on @clerk/react.
An app whose @clerk/expo resolved an older @clerk/react could then get a
second copy, and the hook would read a different React context than
ClerkProvider.

Keep the hook where it is on main. It finds the ClerkExpo native module by
name, like native client sync and useNativeClientEvents, and falls back to
the JS session state when the module is missing. @clerk/expo-native-components
no longer depends on any Clerk JS package, which matches @clerk/expo-biometrics
and @clerk/expo-google-signin.
…lit for users upgrading

Merge the two changesets into one entry for both packages, in the shape of
the @clerk/expo-biometrics one. It leads with what an app has to do, drops
the claim that native client sync moved, and no longer mentions where
useAuthViewState lives because its import path does not change.
@wobsoriano wobsoriano changed the title feat(expo-native-components): move native components into @clerk/expo-native-components feat(expo-native-components): Move native components into its own package Oct 8, 2026
npx expo install adds the config plugin to a static app config on its own,
and the @clerk/expo plugin applies it and forwards keychainService and theme
when the package is installed. Neither step needs to be in the changelog.

This branch was successfully deployed

2 active deployments
Preview – swingset — c708f9c9 Deployed Oct 8, 2026 by vercel[bot]
Preview – clerk-js-sandbox — c708f9c9 Deployed Oct 8, 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