ExpoKit 2019

And bringing Expo APIs to bare React Native

I want to start this post off by apologizing to ExpoKit users. I am sorry that the developer experience after ejecting has not been as good as it could and should be. We have treated it as secondary to the default workflow, so tools like expo-cli don't have an interface tuned specifically for ExpoKit apps, documentation is sparse, and the quality bar for the developer experience has been lower (see step #6 in “ExpoKit might not be so scary” for some examples). This post is to signal our commitment to improving the experience and to outline our plan for the future, both short and long-term.

What’s wrong with ExpoKit now

The default workflow for using Expo currently looks like this: develop your app using expo-cli and the client app, publish new code and assets using expo publish , and build new versions of the binaries on the build services using expo build:[ios/android] . We handle the native projects for you, you just write JavaScript / React. We’ll call this the “managed” workflow.

Many projects find that this is too limiting — you may need to write native code to build a feature that can’t be implemented using the current set of primitives provided by React Native & the Expo SDK, or you may want to integrate with a service that works best with the native SDK and isn’t included in Expo. At this point, you may run expo eject to generate iOS and Android projects. These projects are configured to load your app code and assets in the same way as in the client app and come with the Expo SDK installed & linked. Now you can continue development using Xcode and Android Studio instead of the client. We’ll call this the “advanced” workflow, because the services provided are very similar to the managed workflow except now you will manage the native projects on your own. The advanced workflow is made possible with a native library that we call ExpoKit.

Behind the scenes, ExpoKit is the same library that we use in the client app, but now rather than it opening to “Expo home” (the UI that you see in the client app, which is mostly just another app built with Expo) it will open directly and only to your app. ExpoKit contains code for supporting multiple SDK versions (versioning) and for opening different projects from the same binary (multiplexing). It also inherits the same code & asset loading infrastructure as in the client so that you can continue to use expo publish to push over-the-air updates to both your app code, images, fonts, and other assets.

We intended for the ejected experience to be as close to the Expo client experience as possible, so that when you eject your app it “Just Works.” From that perspective, this design made a lot of sense. It would also make it easier for users to move back to using the managed workflow if desired, for example if we added an API that they had previously ejected for.

But versioning, multiplexing, and the code & asset delivery infrastructure require control of several important parts of your app, from coordinating startup to loading every font, image, video, and sound. Control given to ExpoKit is control that you do not have. This introduced complexity and rigidity to ExpoKit apps; for example, you have to use a specific class at the root of your app and this makes it difficult to integrate libraries like react-native-navigation . These limitations that came with ExpoKit made some users feel like we were trying to lock them in, which was never a goal of ours —sometimes attempting to provide the best end-to-end experience can feel like lock-in during its early days.

What we’ve learned

I believe that it was a mistake to not make the Expo APIs compatible with apps using bare React Native. They are open source but have not been easily installable without ExpoKit, so some folks have had to resort to copying and pasting entire modules from the Expo repository into another. In every software project you encounter the choice of building a monolithic system and moving quickly, or building a modular system and dealing with more generalization and complex workflows. We chose monolith initially for speed and because it fit with our design for ExpoKit, but now we see that the community has different needs which are not well served by this design.

How we’re changing ExpoKit

Earlier this year we started a project to decouple the APIs from ExpoKit infrastructure and from other sibling modules (eg: camera requires permissions and filesystem access, but they are all separate modules). We have already converted more than half of the APIs to this new specification that we call “universal modules”. It’s still a work in progress, but if you’re willing to get your hands dirty you can use some of these on a react-native init project by following the READMEs in the expo/expo packages directory — although your mileage may vary right now. We still have some work to do to improve ergonomics and to finish converting the remaining APIs, and you can expect more information about this very soon.

In the short term, I believe that universal modules are the key to solving ergonomics issues with ejecting from ExpoKit. Secondly, we will add an option to eject to a plain React Native app with most Expo APIs installed in the project but none of the infrastructure for versioning, multiplexing, or code & asset delivery. You will be in the same position as if you had started your project with react-native init but with a set of high quality and well maintained APIs already installed and configured. You will also be able to initialize your project like this if you know it’s what you want from the start, or add most of the Expo APIs to a React Native project that you have already started. We aim to have our first pass at this ready for you by February. This will become a first-class workflow, supported to the same degree as the managed workflow (to the extent that it involves the Expo SDK), and we’ll tentatively call it the “custom” workflow. This will become the new default behavior when ejecting, and we will mark the current ExpoKit eject workflow as beta.

We still believe that the motivations behind the advanced workflow, to have an experience as close as possible to the managed workflow while still allowing developers to write their own native code, is the right balance for many users. But the specific implementation needs work. Once the custom workflow is polished we will work on the next iteration of the advanced workflow. Until then we will maintain it as usual and make gradual improvements to support existing users. More news on the concrete steps we will take to improve this are to follow.

If you have feedback on any of the ideas expressed in this post, please don’t hesitate to reach out to me at brent@expo.io or on Twitter.