Continuous Native Generation (CNG)

Edit this page

Learn about managing your native projects with Continuous Native Generation (CNG) and Prebuild.


A single native project on its own is complicated to maintain, scale, and update. In a cross-platform app, you have multiple native projects that you must maintain and keep up to date with the latest operating system releases to avoid falling too far behind in any third-party dependencies.

As your native project grows, the complexity from third-party dependencies increases, complicating upgrades and slowing down developer momentum. This discourages adding advanced native functionality and leads to less powerful apps. In cross-platform apps, this complexity is multiplied across each platform.

To address this, we've introduced the concept of Continuous Native Generation. Instead of creating native projects a single time and maintaining customizations to those native projects for the lifetime of the codebase, short-lived native projects are generated only when needed, such as when debugging or building. These projects are generated from a standard template plus configuration or custom code that defines how the template should be customized. The result in a native project that can be compiled into a native app with any customizations desired by the developer. However, the developer is responsible for only maintaining the definition of their customizations, rather than all of the native project code.

CNG in React Native apps

React Native apps can use CNG by using Prebuild to automate upgrades, install or uninstall libraries, apply white label customizations, share configuration across multiple apps, reduce orphaned code, and more.

Expo as a framework enables CNG by combining the following tools:

  1. The app config file.
  2. Arguments passed to the npx expo prebuild command.
  3. Version of expo that's installed in the project and corresponding prebuild template.
  4. Autolinking, for linking native modules found in the package.json.
  5. Native subscribers, for reducing native code side-effects in entry point files, such as MainApplication or AppDelegate.
  6. EAS Credentials for code signing additional targets and entitlements.

The end result is a workflow where a developer can express any native application with the app config and generate that project continuously — by running npx expo prebuild.

Usage

Prebuild can be used by running:

Terminal
- npx expo prebuild

This creates the android and ios directories for running your React code. If you modify the generated directories manually then you risk losing your changes the next time you run npx expo prebuild --clean. Instead, use config plugins , which are functions that perform modifications on native projects during prebuild.

We highly recommend using Prebuild for the reasons listed in the common questions section, but the system is fully optional, and you can stop using it at any time.

Usage with EAS Build

If your project does not contain android and ios directories, EAS Build will run Prebuild to generate these native directories before compilation. This is the default behavior for any project created using npx create-expo-app.

For a project that has android and ios directories, EAS Build will not run Prebuild to avoid overwriting any changes you've made to the native directories.

If you troubleshoot your app by compiling it locally (running npx expo prebuild, or npx expo run:android or npx expo run:ios), you can still use Prebuild with EAS Build to generate fresh native directories during the build process. In this scenario, add the android and ios directories to .gitignore or .easignore files:

.gitignore
+ /android
+ /ios

Usage with Expo CLI run commands

You can perform a native build locally by running:

Terminal
# Build your native Android project
- npx expo run:android

# Build your native iOS project
- npx expo run:ios

If native directories are absent, npx expo prebuild will run once for the specific platform. On subsequent uses of these run commands, manually run npx expo prebuild --clean to ensure the native code is freshly synchronized with your local configuration.

Platform support

Prebuild currently supports Android and iOS. Web support is not required because there is no native project to generate for the web and the web app it runs in a web browser. Use the --platform option to run prebuild for individual platforms:

Terminal
- npx expo prebuild --platform ios

Dependencies

Prebuild begins by initializing new native projects from a template corresponding to each Expo SDK version. This also aligns with specific React and React Native versions. You will see a warning when running npx expo prebuild when your project's React and React Native versions differ from the expected versions from specified in the dependencies field of the template's package.json if they differ.

You can skip changing npm package versions with the --skip-dependency-update option:

Terminal
- npx expo prebuild --skip-dependency-update react-native,react

Package managers

When the dependencies are changed, Prebuild will reinstall libraries using the package manager that is currently used in the project (this is inferred from the lockfile). You can force a specific package manager by providing one of: --npm, --yarn, --pnpm.

All installations can be skipped by passing the --no-install command, which is useful for testing generation quickly.

Clean

The --clean option deletes any existing native directories before generating. Re-running npx expo prebuild without the --clean option will layer changes on top of the existing files, which is faster, but may not produce the same results in some cases.

For example, some config plugins aren't idempotent. When a project utilizes multiple "dangerous modifiers" to add regex changes to an app's code, it can lead to unexpected behavior. This is why using the --clean option is the safest way to use the prebuild command and is generally recommended in most cases.

Using --clean option

When using the --clean option, you'll be warned if you have any uncommitted changes to your git code repository, as this option will delete and recreate all of your native project files. This prompt is optional and will be skipped when encountered in CI. You can disable this check by enabling the environment variable EXPO_NO_GIT_STATUS=1.

There are cases where developers may want to swap between workflows often. For example, you may want to build custom functionality natively in Android Studio and Xcode, and then move that functionality into local config plugins.

Templates

You can customize how the native directories are generated by config plugins. Many config plugins already exist for lots of modifications, and community libraries often ship their own as well. You can see a list of some popular plugins for more information.

Prebuild starts from template files, which are then modified with config plugins. The template files are based on the Expo SDK version and come from the npm package expo-template-bare-minimum. You can change the template used by passing --template /path/to/template.tgz to the npx expo prebuild command. This is not generally recommended because the base modifiers in @expo/prebuild-config make some undocumented assumptions about the template files, so it may be tricky to maintain your custom template.

Note: In network environments where all packages are downloaded from a private registry and npm public registry access is blocked, a locally-available template must be passed to the prebuild command. Learn more about using a local version of the default template.

Side effects

npx expo prebuild performs several side effects outside of generating the android and ios directories. Work is in progress to eliminate these side effects — ideally, running npx expo prebuild would generate the Android and iOS projects and leave the rest of the project untouched.

In addition to generating the native directories, prebuild also makes the following modifications:

  • Modifies the scripts field in the package.json to replace expo start --android and expo start --ios with expo run:android and expo run:ios
  • Modifies the dependencies field in the package.json

The convenience change to the scripts field is the only side effect that alters how a developer works on their app before/after prebuild. All other changes can be left in place and committed to git to minimize the diff when running prebuild.

Optionality

Prebuild is optional and works seamlessly with all Expo tools and services. For existing React Native projects, where the native projects are managed manually, do not use npx expo prebuild, as that may overwrite any manual customizations. Developers can continue to make (direct changes to their native projects) while adopting other Expo tools and workflows. Later on, they can move their manual customizations to app config and/or config plugins, and then adopt CNG.

Everything offered by Expo including EAS, Expo CLI, and the libraries in the Expo SDK are built to fully support bare React Native projects as this is a minimum requirement for supporting projects using npx expo prebuild. The only exception is the Expo Go app, which can load arbitrary React Native projects only if they include JavaScript fallbacks for native code absent in the Expo Go runtime.

Common questions

CNG

How does CNG helps with project upgrades?

React Native developers who don't use Continuous Native Generation have reported that upgrading their apps to the latest version of React Native is the number one weakness of the library as per React Native Survey (2022).

When using CNG, the upgrade process simply involves upgrading the npm dependencies, app config, and re-running npx expo prebuild --clean.

How a React Native library author can adopt CNG?

React Native library authors can adopt CNG in several ways. It depends on the complexity of their libraries. Here are a few scenarios:

  • No native code or configuration side-effects: Libraries without native code or configuration side-effects, such as react-native-blurhash, can seamlessly integrate with npx expo prebuild. They can rely on Node Module Resolution without requiring any additional configuration.

  • Native code with no additional setup after install: Libraries with native code can often be installed and linked automatically with Expo Autolinking, which runs before the native app is built.

  • Additional configuration side-effects and setup: Libraries that require additional configuration side-effects can adopt CNG by creating Expo config plugins for their libraries. This approach enables library authors to automate adding values such as permission messages to the Info.plist, or injecting targets in the Xcode project.

  • Libraries Dependent on Native Runtime Hooks: Libraries that depend on specific native runtime hooks, such as intercepting the initial launch URL via the AppDelegate, MainActivity, MainApplication, and so on, can utilize Lifecycle listeners in the Expo Modules API. These lifecycle listeners allow these runtime hooks to be applied via Expo Autolinking instead of by modifying these standard native project files, eliminating the need for a config plugin.

Many complex libraries and services already support CNG via Expo Prebuild such as, MapBox, OneSignal, Stripe, and React Native Firebase.

Adopting CNG by library authors is not a preqrequisite for using npx expo prebuild. If a library author has not adopted CNG, developers can still use npx expo prebuild by creating local Config Plugins to modify the native generation pipeline. This flexibility makes CNG accessible and beneficial to all developers within the React Native community.

Is CNG limited to React Native projects?

No, CNG is a versatile pattern that can be applied to any native project. While Expo Prebuild is a tool that implements CNG specifically for React Native projects, the concept itself is not limited to this framework.

How does community uses CNG?

Here are a few community examples of difficult native features converted into simple configuration files, which have allowed developers to build more powerful apps without compromising on iteration speed:

  • iOS Safari Extensions: Here, the process of creating a Safari Extension for iOS, which is a notoriously difficult feature to implement, is reduced to a few of lines of JSON.

  • iMessage Sticker App: This Expo config plugin can generate an entire iMessage Sticker App from a JSON object.

  • Cross-platform end-to-end testing: Configure native apps to support E2E testing with Detox in a single-line.

  • The entire Firebase suite: Here you can see the entire native Firebase suite going from a multi-step native configuration process across multiple IDEs, down to basic JSON configuration.

  • Cross-platform home screen widgets: This Expo config plugin can generate a home screen widget for Android and iOS.

  • Notification extension and code signing: This Expo config plugin generates a notification extension target on iOS and it augments the EAS credentials service to keep zero-config code signing working.

  • Apple App Clips: This Expo config plugin takes the process of generating an Apple App Clip from a multi-step process, ranging across multiple targets, and reduces it to a single line ["react-native-app-clip", { "name": "My App Clip" }].

At any point, these features can be easily added and removed, without any side effects. CNG allows developers to experiment with complex features and iterate on them quickly without worrying about the long-term maintenance costs or potential orphaned code in their project.

Can CNG be used for operating systems other than Android and iOS?

Absolutely! CNG is an abstract concept that can be applied to any operating system. Although Expo Prebuild officially implements CNG for Android and iOS, it also provides abstract platform support for developers to create implementations for additional platforms.

Is using Expo a requirement for CNG?

Not at all. CNG is an open pattern that can be adopted by any community. We've defined the pattern abstractly to help other communities understand how they can adopt CNG for their own projects.

How does CNG compare to web development patterns such as Static Site Generation (SSG)?

CNG shares similarities with SSG in that it generates a project from a set of inputs. However, CNG differs from SSG in its output. It generates native runtime code instead of static website code. This means the native project is generated on-demand, and the generated source code and configuration are discarded once the native project is compiled into a native app.

Is it possible to use CNG with an existing brownfield project?

CNG is designed to manage the entire state of a native project continuously. As a result, it's not intended for use with existing brownfield projects. However, you can use CNG to generate a new native project, which can then be integrated into an existing brownfield project.

Prebuild

Expo Prebuild streamlines CNG processing. Here are some issues in the React Native development cycle that are addressed by Prebuild:

How can Prebuild help with sensible project upgrades?

Building native code requires familiarity with the platform's tooling, creating a steep learning curve. This challenge intensifies in cross-platform development due to multiple platforms. Cross-platform tooling doesn't help if you must implement many features in platform-specific native code.

When bootstrapping a native app, there's initial code and configuration that you may not understand. Yet you are not responsible for maintaining it. Eventually, you'll need to understand this code to upgrade your app safely. This challenge often leads developers to upgrade incorrectly or start a new app, copying existing source code.

With Prebuild, upgrading is much closer to upgrading a pure JavaScript application. Bump the versions in your package.json, regenerate the native project, and you should be ready to continue development.

How does Prebuild simplify cross-platform configuration?

Cross-platform configurations such as the app icon, name, splash screen, and so on must be implemented manually in native code. These implementations are often quite different for each platform.

With Prebuild cross-platform configurations are handled at the config plugin level, and the developer only needs to set a single value like "icon": "./icon.png" to have all icon generation taken care of.

How can I manage dependency side-effects with Prebuild?

Many complex native packages require additional setup beyond installing and autolinking. For example, a camera library requires permission settings to be added AndroidManifest.xml for Android and Info.plist for iOS. This additional setup can be considered a configuration side effect of a package. Pasting the required side effect code into your project's native files can lead to difficult native compilation errors, and it's also code that you now own and maintain.

With Prebuild library authors, who know how to configure their library better than anyone, can create a testable and versioned script called a config plugin, to automate adding the required configuration side effects for their library. This means library side effects can be more expressive, powerful, and stable. For native code side effects, we also provide Android Lifecycle Listeners and AppDelegate Subscribers which come standard in the default prebuild template.

How does Prebuild help with orphaned code?

When you uninstall a package, you have to be certain you removed all of the side effects required to make that package work. If you miss anything, it leads to orphaned code that you cannot trace back to any particular package, this code builds up and makes your project harder to understand and maintain.

With Prebuild the only side effect is the config plugin in a project's Expo config (app.json), which will throw an error when the corresponding node module has been uninstalled, meaning a lot less orphaned configuration.

When Prebuild might not be the right fit for a project

Here are some reasons Expo Prebuild might not be the right fit for a particular project:

Platform compatibility

Prebuild can only be used for native platforms that are supported by the Expo SDK. This means Android and iOS for the time being. Except for web, which doesn't require npx expo prebuild since it uses the browser instead of a custom native runtime.

Making changes directly is quicker than modularizing and automating

All native changes must be added with native modules (using React Native's built-in Native Module APIs or the Expo Modules API) and config plugins. This means if you want to quickly add a native file to your project to experiment, then you may be better off running prebuild and adding the file manually, then working your way back into the system with a monorepo. We plan to speed this process up by adding functionality to Expo Autolinking that finds native project files outside of the native directories and links them before building.

If you want to modify the configuration, such as the gradle.properties file, you'll have to write a plugin (example). This can be easily automated with helper plugin libraries, however, it is a bit slower if you need to do it often.

Config plugin support in the community

Not all packages support Expo Prebuild yet. If you find a library that requires extra setup after installation and doesn't yet have a config plugin, we recommend opening a pull request or an issue so that the maintainer is aware of the feature request.

Many packages, such as react-native-blurhash, don't require any additional native configuration beyond what is handled by autolinking and so no config plugin is required.

Other packages, such as react-native-ble-plx, do require additional setup and therefore require a config plugin to be used with npx expo prebuild (in this case there's an external plugin called @config-plugins/react-native-ble-plx).

Alternatively, we also have a repo for out-of-tree config plugins which provides plugins for popular packages that haven't adopted the system yet. Think of this like DefinitelyTyped for TypeScript. We prefer packages ship their own config plugin, but if they haven't adopted the system yet, the community can use the packages listed in the repo.