ekke
ekke copied to clipboard
Plugin API
Why do we need a plugin system?
There is a lot of amazing functionality that can be built on top of ekke
. It runs inside React-Native but communicates with Nodejs using WebSocket connection. So in reality, you can have best of both worlds. My go-to example for a plugin is: screenshots. With ekke
we have a render()
method that renders your component on screen. We could implement a screenshot API by calling the adb shell screencap -p
command on the CLI, run visual diffing against it using pixelmatch
and stream back the result for an assertion. Or maybe you just want to create a plugin that allows you to interact with the file system to read files or even data directly from React-Native.
As neat as all these features are, they should not belong in the core of ekke, but we should provide a programmable API that makes this possible. In fact, if we do this correctly, even our test runners could technically be plugins, but included by default.
Plugin requirements
It needs to have an API that can communicate, we already have a WebSocket bridge
which we can expose. This way plugins can:
- File System interaction storing files, retrieving files.
- Starting / Stopping servers, we don't have HTTP servers in RN.
- Executing of CLI processes, maybe you want to interact with the
adb
command and create a screenshot of the device. - Anything you would do in Node.js basically
It's not just communication with Node.js that is required, plugins should be able to modify every part of our orchestration:
- Modify our babel configuration. e.g. adding plugins and presets.
- Modify the created Metro configuration. Allow them to override our defaults.
- Introduce new methods to our API/CLI.
- Include files in the test bundle. So it requires it's own libraries that need to be bundled.
- Execute before, after, test execution. Setup additional hooks, polyfills, anything.
- Intercept, and modify test runners. Maybe introduce new config, or call additional API methods.
- Intercept error/test failures.
The plugin API should be simple, async, and event-based. It's would not be uncommon that multiple plugins leverage the same API and by making it even based, each individual plugin can subscribe to the events. This also allows us to make the bridge
function, event-based, so they can subscribe to the events they execute.
Plugin API when required in Node.js (use main
in the package.json
)
export default function myPlugin({ modify, bridge, register }) {
modify('babel', async function (babel) {
return require('babel-merge')(babel, customconfig);
});
modify('metro.config', async function (metro) {
return require('metro-config').merge(metro, { custom });
});
bridge('screenshot', async function (id, reply) {
const { old, new } = await makescreenshot(id);
const diff = await compare(old, new);
await reply(diff);
});
//
// Registers a new method on the `API`.
//
register('method', async function () {
});
}
Plugin API when required in React-Native (use react-native
in the package.json
)
export default function myPlugin({ subscribe, bridge, register }) {
modify('setup', async (runner) {
// do stuff
});
//
// Introduces new method on for the `plugin()` API.
//
register('screenshot', async function (element, name) {
const id = name || (element.type +'@'+ JSON.stringify(props));
await render(element);
return await bridge('screenshot', id);
}
});
});
Plugin API as consumed by users.
import { render, bridge, plugin } from 'ekke';
const { screenshot } = plugin('example');
it('takes a screenshot', async function () {
await render(<Component />);
const diff = await screenshot(<Component />);
assume(diff).is.a('object')
});
Acceptance Criteria
- [ ] Create a
{ bridge }
method that can be used to communicate with the CLI. - [ ] Support a
--plugin <name>
flag in our CLI/API that will require and initialize our plugins. - [ ] Create an async interface where plugins as defined above.
- [ ] Extensively document the plugin API, and provide at least 1 working example.
- [ ] The Node and
ekke
API's are unit tested
Is the order in which the plugin run relevant? Or should they be capable of defining the order? It could be as simple as using the plugin array indexing to determine order, but order of execution could be bit harder to get right. This is especially important when plugins should be capable of using methods they other plugins introduce (like when runners become plugins).
@Swaagie That is indeed a valid concern, thanks for pointing that out. What I would suggest is that we add support for a second argument in these API's which would be an options object where people could supply an order property on which we sort. Similar that what I've done with storage-engine.
Just to give an update on the progress of this task. The plugin
branch that is created by #17 is considered feature complete. So all required features and most basic plugin hooks are available. The current test runners have been transformed to plugins in an attempt dogfood our own system.
The only low hanging fruits are documentation and additional test for everything created. But we're close to a new release, which will most likely be a minor release as everything is currently backwards compatible and working as intended.