Important
The DevTools Plugins work is moving into https://github.com/expo/dev-plugins. This repository is now archived and some doc may be out of date. Please look into the new repository.
This is a proof-of-concept project that demonstrates how to use Expo CLI devtools plugins, with some plugins included.
- react-native-apollo-devtools
- TinyBase Inspector
- Hello world example - a simple ping-pong demonstrate example
With the upcoming deprecation of Flipper, there is currently no alternative solution for Flipper plugins. However, if we break down Flipper plugins, we can see that a Flipper plugin is essentially a UI with a message bus to send messages between the UI and the app. This project aims to show that it is possible to implement a similar solution on top of the Expo infrastructure, as a replacement for the Flipper Apollo Plugin.
Screen.Recording.2023-08-07.at.8.25.34.PM.mov
-
App: This is an Expo app that includes some Apollo Client requests.
-
Expo Patches: Before adding official devtools plugin support, we would like to get feedback from the community. To achieve this, we are using patch-package to add the devtools plugin into Expo.
-
Modified
react-native-apollo-devtools
: This is a fork of react-native-apollo-devtools that removes Flipper dependencies and adds theexpo-plugin-react-native-apollo-devtools
- a web-based UI interface that is modified from the original Flipper-based UI. -
Plugins: Some plugin examples inside this monorepo.
To try out the apollo devtools plugin on your own app, follow these steps:
-
Apply the Expo Patches
-
Add
expo-plugin-react-native-apollo-devtools
: https://github.com/Kudo/expo-devtools-plugin-demo/commit/3cc450415193fa01d2c808daae21a22ab1848f35
That's it! With these changes, you should be able to use the devtools plugin with your own Expo app.
-
Create a web based UI by Expo
$ yarn create expo expo-plugin-helloworld $ cd expo-plugin-helloworld $ npx expo install react-native-web@~0.19.6 react-dom@18.2.0 @expo/webpack-config
-
Update package.json
- Move all
dependencies
intodevDependencies
, given that we willexpo export:web
later. Most dependencies are not necessary for the app. - Add
homepage
field to define the exported web base URL:"homepage": "/_expo/plugins/expo-plugin-helloworld"
. Note that URL should be/_expo/plugins/{pluginName}
.
- Move all
-
Use the
connectPluginFromDevToolsAsync
API to send/receive messagesimport { connectPluginFromDevToolsAsync } from 'expo/devtools'; const client = await connectPluginFromDevToolsAsync(); client.sendMessage('ping', { from: 'expo-plugin-helloworld' }); client.addMessageListener('pong', (data) => { console.log('receiving pong', data); });
-
Export web assets
$ npx expo export:web $ mv web-build dist
-
Create expo-module.config.json for autolinking
{ "name": "expo-plugin-helloworld", "platforms": ["devtools"], "devtools": { "webpageRoot": "dist" } }
or you could also combine the plugin with a debug build only native module. Learn more for writing native modules
{ "name": "expo-plugin-helloworld", "platforms": ["devtools", "ios"], "devtools": { "webpageRoot": "dist" }, "ios": { "modules": [ // ... ], "debugOnly": true } }
-
Now you could either add the
expo-plugin-helloworld
in your app or publish the package on npm for your to install.
-
Use the
connectPluginFromAppAsync
API to send/receive messagesimport { connectPluginFromAppAsync } from 'expo/devtools'; if (__DEV__) { const client = await connectPluginFromAppAsync(); client.addMessageListener('ping', () => { client.sendMessage('pong', { from: 'app' }); }); }
The expo/devtools
exports APIs for the message communication between the plugin and the app. You can check the API declarations here. Please note that the APIs are temporary and may change from official releases.
declare module 'expo/devtools' {
import { EventSubscription } from 'fbemitter';
/**
* This class is used to communicate with the Expo CLI DevTools plugin.
*/
export class DevToolsPluginClient {
close(): void;
sendMessage(method: string, params: any): void;
addMessageListener(
method: string,
listener: (params: any) => void
): EventSubscription;
addMessageListenerOnce(
method: string,
listener: (params: any) => void
): void;
isConnected(): boolean;
}
/** For DevTools Plugin to get a `DevToolsPluginClient` */
export function connectPluginFromDevToolsAsync(): Promise<DevToolsPluginClient>;
/** For App to get a `DevToolsPluginClient` */
export function connectPluginFromAppAsync(): Promise<DevToolsPluginClient>;
}