Back to Flipper

Plugin structure

docs/extending/desktop-plugin-structure.mdx

0.273.011.9 KB
Original Source

import FbNpmDeps from '../fb/_adding-npm-dependencies-0.mdx'

Flipper Desktop plugins have a rigid structure. It's recommended to scaffold any new plugin using the Flipper scaffolding tooling.

Scaffolding a new plugin

<OssOnly>

flipper-pkg

The CLI tool flipper-pkg helps to initialize, validate, and package Flipper desktop plugins.

To scaffold a new plugin run npx flipper-pkg init in the directory where you want to store the plugin sources. Note that this should typically not be inside a Flipper checkout, but rather a fresh directory which you can put under your own source control.

</OssOnly> <FbInternalOnly>

scarf flipper-plugin

On a Meta machine, new plugins can be scaffolded by running scarf flipper-plugin. This takes care of both the Desktop and client-side setup of plugins. Follow the instructions offered by Scarf.

</FbInternalOnly>

Desktop Plugin structure

All Flipper Desktop plugins must be self-contained in a directory that must contain, at a minimum:

  • package.json - the plugin packet manifest.
  • An entry source file for your plugin (such as src/index.tsx).

After scaffolding a new plugin has finished, those files will exist in the relevant directory.

An example package.json file could look like the following:

json
{
  "$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
  "name": "flipper-plugin-myplugin",
  "id": "myplugin",
  "pluginType": "client",
  "version": "1.0.0",
  "main": "dist/bundle.js",
  "flipperBundlerEntry": "src/index.tsx",
  "license": "MIT",
  "keywords": ["flipper-plugin"],
  "title": "My Plugin",
  "icon": "apps",
  "bugs": {
    "email": "[email protected]"
  },
  "scripts": {
    "lint": "flipper-pkg lint",
    "prepack": "flipper-pkg lint && flipper-pkg bundle"
  },
  "peerDependencies": {
    "flipper": "latest",
    "flipper-plugin": "latest"
  },
  "devDependencies": {
    "flipper": "latest",
    "flipper-plugin": "latest",
    "flipper-pkg": "latest",
    "react": "latest",
    "antd": "latest"
  }
}

The following are important attributes of package.json:

  • $schema - must contain the URI identifying scheme according to which the plugin is defined. Currently, Flipper supports plugins defined by the specification version 2, while version 1 is being deprecated.

  • name - the NPM package name. Should start with flipper-plugin- by convention so the Flipper plugins can be easily find it on NPM.

  • id - the plugin native identifier, which must match the mobile plugin identifier returned by the getId method of your Java plugin.

  • pluginType - Specifies the plugin type: client or device. For details, see the Anatomy of a Desktop plugin, below.

  • main - points to the plugin bundle which is loaded by Flipper. The "flipper-pkg" utility uses this field to determine output location during plugin bundling.

  • flipperBundlerEntry - points to the source entry point used for plugin code bundling. flipper-pkg takes the path specified in flipperBundlerEntry as source, transpiles and bundles it, and saves the output to the path specified in main.

  • keywords - the field must contain the flipper-plugin keyword, otherwise Flipper won't discover the plugin. Additionally, the field can also contain any other keywords for better plugin discoverability.

  • title - shown in the main sidebar as the human-readable name of the plugin.

  • icon - determines the plugin icon that is displayed in the main sidebar. The list of available icons is static for now (see icons.json in GitHub).

  • bugs - specify an email and/or URL where plugin bugs should be reported.

In index.tsx you define the plugin in JavaScript, as shown in the following example:

js
export function plugin(client) {
  return {};
}

export function Component() {
  return 'hello world';
}

:::note Some public plugins use a FlipperPlugin base class. That format is deprecated. :::

Anatomy of a Desktop plugin

Flipper Desktop plugins come in three possible flavors:

  1. Client plugins - connects to a specific client plugin running in an app (recommended).
  2. Device plugins - doesn't connect to a specific client and doesn't have a native counterpart but shows data about the device obtained through some other means, like device logs, device temperatures, and so on.
  3. Table plugin - a simplified version of a client plugin that merely displays incoming data from a client plugin in a table.

Creating a Client Plugin

A plugin always exposes two elements from its entry module (typically src/index.tsx), plugin and Component, as shown in the following snippet:

js
import {PluginClient} from 'flipper-plugin';

export function plugin(client: PluginClient) {
  return {}; // API exposed from this plugin
}

export function Component() {
  // Plugin UI
  return <h1>Welcome to my first plugin</h1>;
}
<FbInternalOnly>

Client plugins must have the property pluginType set to client and should specify supported apps using the property supportedApps in package.json.

The supportedApps property should contain an array of supported apps, each defined as a conjunction of app properties, using the following format:

sh
{"appID": <"Facebook" | "Instagram" | "Messenger">,  "os": <"Android" | "iOS" | "Metro">, "type": <"physical" | "emulator"> }

For example, the array { "appID": "Facebook", "os": "Android", "type": "emulator" } indicates that the Facebook app must work on Android AND must be an emulator in order to debug it using the plugin.

Note that if the os field is missing, it indicates that both Android and iOS are supported by the app and that a specific plugin, working for a physical device, also works for the emulator. In such a case, the type field is assumed to be 'true' for both physical and emulator devices, unless only one of them is specifically stated in the .json file.

To specify that a plugin supports Facebook on Android/iOS physical/emulator devices, Messenger on only Android physical/emulator and does not support Instagram, the plugin package.json should look like the following:

json
{
  "$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
  "name": "flipper-plugin-myclientplugin",
  "id": "myclientplugin",
  "pluginType": "client",
  "supportedApps": [
    {"AppID": "Facebook"},
    {"AppID": "Messenger", "os": "Android"}
  ]
...
}
</FbInternalOnly>

For details on how to write custom Flipper plugins, see the Building a Desktop Plugin - Custom UI tutorial page.

<FbInternalOnly>

:::note After adding new plugins, the metadata of the supportedApps field needs to be updated. This has to be done in the package.json file in the directory by the name of the plugin under plugins/fb/\{directory name\} or plugins/public/\{directory name\}/fb.

If an App is not listed in the supportedApps definition, it may still be supported by Flipper; it could be the case that the plugin was recently added and the .json files were not updated.

Plugins will work for apps that register them, even when they are not registered here in the metadata. The most important reason to add supported apps to the suppertedApps is to allow Flipper to signal to the user which plugins are supposed to work on which apps during troubleshooting. :::

</FbInternalOnly>

Creating a Device Plugin

Flipper also supports so-called device plugins (plugins that are available for an entire device) but don't receive a connection to a running app, so are a bit more limited in general.

Their entry module anatomy is as follows:

js
import {DevicePluginClient} from 'flipper-plugin';

export function devicePlugin(client: DevicePluginClient) {
  return {}; // API exposed from this plugin
}

export function Component() {
  // Plugin UI
  return <h1>Welcome to my first plugin</h1>;
}

Client plugins must have the property pluginType set to device and should specify supported devices using the property supportedDevices in package.json.

The supportedDevices property should contain an array of supported devices, each defined as a conjunction of device properties, using the following format:

sh
{ "os": <"Android" | "iOS" | "Metro">, "type": <"physical" | "emulator">, "archived": <true | false> }

For example, the array { "os": "Android", "type": "emulator" } indicates that device must work on Android AND must be an emulator in order to debug it using the plugin.

To specify that a plugin supports all types of Android devices, and physical iOS devices, and does not support imported (archived) data, the plugin package.json should look like the following:

json
{
  "$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
  "name": "flipper-plugin-mydeviceplugin",
  "id": "mydeviceplugin",
  "pluginType": "device",
  "supportedDevices": [
    {"os": "Android", "archived": false},
    {"os": "iOS", "type": "physical", "archived": false}
  ]
...
}

:::information Generally, device plugins function in a similar manner to normal client plugins. :::

The available APIs for device plugins are listed in the Desktop Plugin API page.

Creating a simple table plugin

Flipper provides a standard abstraction to render data received from a Client plugin in a table, see createTablePlugin in the 'Desktop Plugin API' page.

Validation

<OssOnly>

Plugin definition can be validated using command flipper-pkg lint. The command shows all the mismatches that need to be fixed to make the plugin definition valid.

</OssOnly> <FbInternalOnly>

Make sure to address any lint errors shown in the IDE or surfaced on phabricator. To manually run the linter run yarn lint in ~/fbsource/xplat/sonar/desktop.

</FbInternalOnly> <OssOnly>

Transpilation and bundling

Flipper has tooling for transpiling and bundling that enables the creation of plugins in plain ES6 JavaScript or TypeScript.

The following are recommended:

  • Use TypeScript for the best development experience.
  • Use .tsx when using TypeScript, which adds support for inline React expressions.

You may recall that the Flipper development build automatically transpiles and bundles plugins on loading. It's capable of all ES6 functionality, Flow annotations, TypeScript, as well as JSX, and applies the required babel-transforms.

In contrast, the Flipper release build does not transpile or bundle plugins on loading. For production usage, plugins should be bundled before publishing using flipper-pkg. This utility applies the same modifications as the plugin loader of the development build.

The flipper-pkg tool is published to npm and should be installed as a devDependency for the plugin package.

Then, to bundle the plugin, execute the following command in its folder:

sh
yarn flipper-pkg bundle

This command reads the package.json, takes the path specified in the flipperBundleEntry field as entry point, transpiles and bundles all the required code, and outputs the produced bundle to the path specified in field main.

You can get the list of other available commands by invoking flipper-pkg help, and get detailed description for any command by invoking flipper-pkg help [COMMAND]. For usage details, see the flipper-pkg page on the npmjs.com web site.

</OssOnly>

npm dependencies

<OssOnly>

If you need any dependencies in your plugin, you can install them using yarn add.

</OssOnly> <FbNpmDeps /> <OssOnly>

:::caution Flipper plugins must be designed to work inside browsers. For that reason, you should avoid using Node.JS APIs directly (such as modules like fs, child_process, path), or packages that depend on them. For alternative APIs, see using Node.js APIs in Flipper plugins. :::

</OssOnly>