Skip to content

Expo CNG support: config plugin that patches Podfile/Gradle on every prebuild #707

@TomKalina

Description

@TomKalina

Summary

Rock's iOS autolinking patch in Podfile only survives until the next expo prebuild --clean. For Expo CNG projects (where ios/ and android/ are gitignored and generated from app.config.ts), this means Rock breaks every time the user regenerates native dirs — which they do whenever they bump SDK / add an Expo plugin / change app.config.ts.

Rock today has two prebuild paths that don't bridge:

  1. expo prebuild (or pnpm clear:prebuild) — uses Expo's templates → produces Expo's Podfile use_native_modules!(config_command) → Rock's autolinking is not wired in.
  2. rock prebuild (@rock-js/plugin-expo-config-plugins) — uses Rock's templates from packages/platform-ios/template/ios/Podfile (which already calls use_native_modules!(['npx', 'rock', 'config', '-p', 'ios'])), then runs Expo config plugins on top.

For an existing Expo CNG project the second path is a non-starter: Rock's templates don't carry the project's Expo SDK-version-specific generation, custom plugin output, Sentry / Hermes / etc. tweaks that expo prebuild applies. Switching from expo prebuild to rock prebuild means giving up the canonical Expo CNG output.

What's missing

An Expo config plugin (in the expo/config-plugins sense, not the @rock-js/plugin-* sense) that:

  1. Runs as the last withDangerousMod('ios', ...) step during expo prebuild.
  2. Patches the freshly generated ios/Podfile:
    config = use_native_modules!(config_command)
    # → into:
    config = use_native_modules!(['npx', 'rock', 'config', '-p', 'ios'])
    Same regex as updatePodfile in packages/create-app/src/lib/utils/initInExistingProject.ts (after create-app: Podfile patch leaves invalid Ruby on Expo prebuild projects #702 / fix(create-app): preserve existing arg list when patching Podfile #703 lands).
  3. Does the equivalent for Android: patch the generated android/settings.gradle / android/app/build.gradle to invoke Rock's autolinking instead of Community-CLI's cliFile = file("../../node_modules/react-native/cli.js").
  4. Is idempotent — re-running prebuild applies the same transformation again without breaking.

User wires it once into app.config.ts:

export default {
  // …
  plugins: [
    '@rock-js/config-plugin',          // hypothetical name
    // … all the other existing plugins
  ],
};

After that, every expo prebuild / expo prebuild --clean produces a Podfile that Rock can drive, with no manual npm create rock re-run.

Why this matters

The Rock README points existing Expo teams at Rock for build caching, but Expo is also Expo's recommended starting point for new RN projects. The majority of Expo projects use CNG. Without a config plugin, Rock effectively requires opting out of CNG (commit ios/ + android/, lose the rest of Expo's value) or accepting that every prebuild breaks the build.

In a real CMS4 / Shoptet repo (Expo SDK 55.0.23, pnpm 10.30, EAS Workflows CI), I migrated via npm create rock and the first pnpm clear:prebuild immediately wiped the Podfile patch. With a config plugin this would have stayed correct.

Related

Suggested implementation outline

  • New file under packages/plugin-expo-config-plugins/src/lib/config-plugin/:
    • withRockAutolinking (top-level ConfigPlugin)
      • composes withRockIosAutolinking + withRockAndroidAutolinking
    • withRockIosAutolinking: withDangerousMod('ios', ...) patches Podfile
    • withRockAndroidAutolinking: withDangerousMod('android', ...) patches android/settings.gradle + android/app/build.gradle (and android/build.gradle if Rock injects there too)
  • Export it from package root so users can list it in plugins: ['@rock-js/plugin-expo-config-plugins'] (or split into a sibling @rock-js/config-plugin if the existing package name should stay focused on the rock prebuild command).
  • Tests:
    • Patch is applied on a Community-CLI Podfile.
    • Patch is applied on the Expo-SDK 55 prebuild Podfile (use_native_modules!(config_command)).
    • Idempotent — applying twice yields the same file.
    • Equivalent set for Android.

Environment that surfaced this

  • Rock: 0.13.0
  • Expo: SDK 55.0.23
  • React Native: 0.83.6
  • pnpm: 10.30.0
  • macOS, Apple Silicon

Happy to send a PR with the iOS plugin first if there's interest — the Android side I'd need to dig deeper on to make sure I cover Sentry / Hermes / monorepo paths.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions