Skip to content

feat: create expo config plugin#2

Merged
MalcolmTomisin merged 7 commits into
mainfrom
create-expo-config-plugin
Jul 3, 2026
Merged

feat: create expo config plugin#2
MalcolmTomisin merged 7 commits into
mainfrom
create-expo-config-plugin

Conversation

@MalcolmTomisin

Copy link
Copy Markdown
Contributor

Added an expo config plugin to reduce set up steps on Expo

Replaces the manual app.json edits and AppDelegate snippet copy-paste with
a first-party Expo config plugin. Consumers pass `iosClientId` once and
the plugin handles the reversed Client ID injection into
CFBundleURLSchemes and the application(_:open:options:) URL forwarder
during prebuild.

`@expo/config-plugins` is declared as an optional peer dependency so
bare React Native CLI users don't install Expo tooling they won't use.

Supports both Swift and Objective-C AppDelegates. Idempotent via a
marker comment.
Replace the hand-rolled `ios.infoPlist.CFBundleURLTypes` block in
example/app.json with a plugin entry that derives the reversed iOS
Client ID at prebuild time. Demonstrates the canonical Expo pattern
and removes the brittle "remember to update the placeholder when the
client ID changes" footgun.

Declare the library as a workspace dependency so Expo's plugin
resolver can locate it in example/node_modules.
- README: add "Using Expo?" callout at the top of iOS Setup pointing
  bare-RN users to the manual steps below, and a new "Expo config
  plugin" section covering install, app.config entry, props table,
  and the Expo Go incompatibility note.
- CONTRIBUTING: add `yarn build:plugin` to the script reference and
  document the plugin test suite location.
- ROADMAP: check off Phase 6 (Expo Support) items now that the plugin,
  example coverage, and docs are all shipped.

The existing manual iOS setup snippets stay verbatim — bare React
Native CLI users see one extra callout and skip it; their setup
section is unchanged.
`withInfoPlist` and `withAppDelegate` pass the modified config to their
callback as the same parameter name (`config`) that the outer
`ConfigPlugin` already binds. Rename the inner parameter to `mod`
(matching its `modResults` field) so the no-shadow lint rule passes
without disabling it.

Also exclude `plugin/build/` from eslint so the compiled CommonJS
output doesn't get linted as TypeScript source — same pattern we
already use for `lib/`.
The build-android, build-ios, and build-web jobs all invoke commands
that load app.json plugins (`expo prebuild` and the Expo web export),
which in turn `require` the compiled plugin at
`plugin/build/withSocialAuth.js`. That file is produced by
`yarn build:plugin` (or `yarn prepare`), but neither was running in
those jobs — only `build-library` runs `yarn prepare`, and yarn 4
does not auto-run the `prepare` lifecycle script during a CI install.

Add an explicit `yarn build:plugin` step to each of the three jobs
before the Expo command. This is the minimum scope — the lint and
test jobs don't need the artifact and stay unchanged.
The `exports` field in package.json restricts which subpaths consumers
can resolve from `require.resolve('@thoughtbot/react-native-social-auth/...')`.
`app.plugin.js` was on disk and in the npm tarball, but was not listed
in the exports map, so Node's resolver refused to find it.

Expo's plugin resolver tries `require.resolve(pkg + '/app.plugin.js')`
first; when that throws, it falls back to importing the package's
`main` entry as a plugin. That fallback fails further down with
`Unexpected token 'typeof'` because the main transitively imports
`react-native`, which ships Flow type annotations that Node cannot
parse — leading to a confusing two-bullet error message.

Adding `./app.plugin.js` to the exports map fixes the original
resolution and prevents the broken fallback from ever firing.
…s base class

The plugin previously injected `import react_native_social_auth` and called
`GoogleSignIn.handleURL(url)` — both wrong. The CocoaPods module name for
this pod is `ReactNativeSocialAuth`, not `react_native_social_auth`, and
our wrapper's header is private to the pod, so Swift in the host app
can't see it.

Two further bugs: the injected method was declared `public override`
and called `super.application(...)`, but Expo's
`ExpoReactNativeFactoryDelegate` base class does not declare
`application(_:open:options:)`, so the override + super-call don't
type-check.

Switch to calling the GoogleSignIn-iOS SDK directly:

  import GoogleSignIn
  // ...
  @objc public func application(_ app: ..., open url: URL, ...) -> Bool {
    return GIDSignIn.sharedInstance.handle(url)
  }

The SDK's module name (`GoogleSignIn`) is stable, its public headers are
exposed, and dropping `override` + `super` makes the method work on
Expo's factory-style AppDelegate. UIKit still dispatches to it through
the Obj-C runtime because the class inherits from NSObject and we
annotate `@objc`.

README's iOS Setup snippet updated to the same shape. Tests assert
the new injection format.

Verified end-to-end: the example app builds, installs, and launches
on iPhone 15 Pro simulator with 0 errors.
@MalcolmTomisin MalcolmTomisin force-pushed the create-expo-config-plugin branch from c6f48dd to 51c9e77 Compare July 3, 2026 08:50
@MalcolmTomisin MalcolmTomisin merged commit 984a260 into main Jul 3, 2026
6 checks passed
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.

1 participant