ekke icon indicating copy to clipboard operation
ekke copied to clipboard

Plugin API

Open 3rd-Eden opened this issue 5 years ago • 3 comments

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

3rd-Eden avatar May 23 '19 11:05 3rd-Eden

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 avatar May 23 '19 12:05 Swaagie

@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.

3rd-Eden avatar May 23 '19 13:05 3rd-Eden

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.

3rd-Eden avatar Jun 14 '19 15:06 3rd-Eden