Migrating a Plugin to Sandy
Migrating a plugin to the new Sandy plugin architecture consists of three steps:
- Enabling Sandy for a plugin.
- Update state and connection management to use the Sandy APIs.
- Update the UI to use Sandy / Antd components only.
Opting in to Sandyβ
Converting a Flipper plugin to use Sandy is best done by running Flipper from source.
For open-source users, clone the repository and run yarn install
in the desktop
folder.
Enabling Sandy for a plugin requires two steps:
- The
flipper-plugin
should be added as peer dependency to thepackage.json
of the plugin:
"peerDependencies": {
"flipper-plugin": "*"
},
- Make sure to run
yarn install
again in thedesktop/
folder. Sandy is now enabled for this plugin" the plugin has to be restructured to the new architecture, which you'll be able to do in the next step.
Using Sandy for state and connection managementβ
The goal of this step is to use and leave the plugin UI largely as is but convert state and connection management to use the new Sandy APIs as exposed through the flipper-plugin
package.
Compared to 'classic' plugins, there are a few fundamental differences when it comes to the plugin structure of Sandy plugins. A class extending from FlipperPlugin
that is exported as default
is no longer used to define a plugin.
Instead, a plugin definition consists of two parts:
- A definition of the state and logic of the plugin that is exported under the name
plugin
:export function plugin(client: PluginClient<Events, Methods>) { ... }
. Most of the state and all connection logic will move here. - A definition of the root of the UI is exported under the name
Component
:export function Component() { ... }
There are a few conceptual changes that are important to understand, as they are different compared to classic plugins:
The
plugin
function is called exactly once when a plugin is set up for an application. This means that all state that is created inside theplugin
definition is kept as long as the app is connected, even when the user is navigating away. It used to be necessary to usepersistedState
for this kind of state, but that is no longer the case.In contrast, the
Component
component is mounted whenever the user opens the plugin, so any state stored locally in that React component will be lost if the user navigates away. It's recommended avoiding this and, instead, store state (including selection) in theplugin
definition, using thecreateState
abstraction.
The relation between plugin
, its parameter client
, and how to use it in your Component
definition is documented in detail in the Plugin Declaration section. Please read it before continuing as it explains in detail how to manage state, handle receiving and sending data, and testing.
The full set of available APIs on client
is documented in the Desktop Plugin API page.
This step is completed if the plugin follows the next plugin
/ component
structure and is working again. Make sure to test extensively!
Tipsβ
- To quickly verify the plugin compiles, the simplest way is to keep
yarn tsc -w
running in thedesktop
folder. - Similarly
yarn watch
can be used to run the unit tests in watch mode. Use thep
key to filter for your specific plugin ifjest
doesn't do so automatically. - For an example of migrating the network plugin to use Sandy APIs, see diff D24108772 / Github commit.
- For an example of migrating the example plugin to use Sandy APIs, see diff D22308265 / Github commit.
- Other plugins that can be checked for inspiration are Logs and Network plugins.
- These steps typically don't change the UI much or touch other files than
index.tsx
. Typically, the root component needs to be changed, but most other components can remain as is. However, if a ManagedTable is used (see the next section), it might be easier to already convert the table in this step. - Sandy has first class support for unit testing your plugin and mocking device interactions. Please do set up unit tests per documentation linked above!
- If the original plugin definition contained
state
, it is recommended to create one new state atoms (createState
) per field in the originalstate
, rather than having one big atom. - If the original plugin definition contained
persistedState
, it is recommended to create one new state atoms (createState
) per field in the originalstate
, rather than having one big atom. By setting thepersist
option of the state, you can make sure this piece of state becomes part of the import / export functionality of Flipper. Which is important if the data stored here is relevant for bug reports. - For deeply nested state updates, using
state.update
is often simpler than usingstate.set
, as it uses Immer under the hood to make immutable state updates straight forward. - For the same reason, you don't need to shallowly clone your state anymore, as long as
state.update
is used for state updates. - When dealing a lot with promises, using
async
/await
is usually simpler.
Migration tableβ
Some abstractions that used to be static methods on FlipperPlugin
are now exposed from the client
object. Following are a few examples:
Old | New |
---|---|
persistedState | Use createState and set the persist option |
persistedStateReducer | Create message handlers and update the state objects directly |
exportPersistedState | Use the client.onExport hook |
getActiveNotifications | Use client.showNotification for persistent notifications, or message / notification from antd for one-off notifications. |
createTablePlugin | TBD, so these conversions can be skipped for now |
init | client.onReady |
Using Sandy / Ant.design to organise the plugin UIβ
The goal of this step is to update the UI of the plugin to use Sandy / Ant Design components. These will provide a more consistent user experience, usually provide better UX and they support dark mode! Roughly speaking this typically involves replacing all imported components with their modern counterpart.
For Sandy plugins, components can be found here:
- Interactive data displaying components are exposed from
flipper-plugin
:DataTable
(for tables),DataInspector
(for JSON trees) andElementInspector
(for element trees). flipper-plugin
also provides the primitives to organise theLayout
of the plugin.- Practically all other, more generic components are provided by Ant Design, a proven mature open source component library, which is much richer than the components that are offered from
flipper
.
In Sandy, the layout is typically built by using a combination of the following:
Layout.Top
(or.Right
,.Left
,.Bottom
), which divides all available space in a fixed and dynamic sectionLayout.Scrollable
, which takes all available space and provides scrollbars if its content is still greater,Layout.Container
which organizes paddings, borders and spacing between elements etc.
Generally, it's recommended not to use margins
; use padding and gap
instead.
Ideally, use theme.spacing
to get standard numbers for margins and paddings instead of hard-coded numbers. This will help with achieving consistency in look and feel.
Design resourcesβ
There are three important resources to check for documentation on the components available:
- Flipper style guide - a general overview of the Flipper design system that demonstrates colors, typography and creating layouts including some examples.
- Ant Design component overview
- API reference documentation for the components provided by
flipper-plugin
Old and new componentsβ
For conversion, the following table maps the old components to the new ones:
Old flipper component | New component | Providing package | Notes |
---|---|---|---|
DetailsSidebar | DetailsSidebar | flipper-plugin | as-is |
Sidebar | Layout.Top (or .Right , .Left , .Bottom ) | flipper-plugin | Set the resizable flag to allow the user to resize the pane. |
FlexColumn / Pane / View | Layout.Container | flipper-plugin | Use the gap property to provide some spacing between the children! |
FlexRow | Layout.Horizontal | flipper-plugin | Use the gap property to provide some spacing between the children! |
Scrollable | Layout.ScrollContainer | flipper-plugin | |
Link | Typography.Link | antd | |
Text / Heading | Typography.Text / Typography.Title | antd | |
Button | Button | antd | |
Glyph | Icon | antd | |
ManagedDataTable | DataTable | flipper-plugin | Requires state to be provided by a createDataSource |
ManagedDataInspector / DataInspector | DataInspector | flipper-plugin | |
ManagedElementInspector / ElementInspector | ElementInspector | flipper-plugin | |
Panel | Panel | flipper-plugin | |
Tabs / Tab | Tabs / Tab | `flipper-plugin | Note that Tab 's title property is now called tab . |
Most other components, such as select
elements, tabs, and date-pickers can all be found in the Ant documentation.
Theming & custom styled componentsβ
Creating your own components / styling using styled
is still supported.
But ideally, you should need custom styled components a lot less!
Since Sandy plugins are expected to support dark mode, (use the settings pane to quickly toggle), it's recommended not to use hard-coded colors. Instead, use one of the semantic colors that are provided through the theme
object that can be imported from flipper-plugin
.
Ideally, there should be no hard-coded colors anymore either, and little need to use width: 100%
/ height: 100%
anywhere, as needing those typically signals a layout issue.
It's recommended to keep components as much as possible outside the entry file, as components defined outside the index.tsx file will benefit from fast refresh.
Wrapping upβ
This step of the process is completed as soon as there are no imports from the flipper
package anymore. Don't forget to remove flipper
from the peerDependencies
in the package.json
section if present.
If you have any questions, feel free to reach out to the Flipper Support Workplace group.