svrx-docs

How To Write A Plugin

Use svrx-create-plugin to help you create plugins more easily

First Plugin

Let’s create our first plugin —— svrx-plugin-hello-world

File Structure

└── svrx-plugin-hello-world
  ├── client.js
  ├── index.js
  └── package.json

Only package.json and index.js are required

{
  "name" : "svrx-plugin-hello-world",
  "engines": {
    "svrx" : "0.0.x"
  }
}

Only two fields are required by package.json

- index.js

module.exports = {
  configSchema: {
    user: {
      type: 'string',
      default: 'svrx',
      description: 'username for hello world'
    }
  },

  assets: {
    script: ['./client.js']
  },

  hooks: {
    async onCreate({ logger, config }) {
      logger.log(`Hello ${config.get('user')} from server`);
    }
  }
};

Where

  1. configSchema Plugin param definition based on JSON Schema ,checkout JSON Schema for more detail. Here we just set a user field, it is a string
  2. assets: client resource setting,they will be automatically injected into the page

    • style: css resource injection
    • script: script resource injection

      All resources will be merged into a single file.

  3. hook.onCreate: a function invoke when plugin been creating, we can control it by injected service in this example, we only use two services

    • logger: logger service
    • config: config service

Check out setion service to find out other services that available in hook.onCreate

- client.js

There is a global variable named svrx will be injected into all pages, it has some built-in services

svrx is only accessible inside the plugin script, don’t worry about global pollution

const { config } = svrx;

config.get('user').then(user => {
  console.log(`Hello ${user} from browser`);
});

You will see Hello svrx from browser in console panel

Unlike in server side, config in client is passed through websocket, so api is async, and return a promise

svrx also provide other client services, please check out client api for help

publish && running

There’ll be a failure when trying to publish it by npm publish. Because svrx-plugin-hello-world has been published by official team

So we skip this step and try the plugin directly

svrx -p hello-world

Check the terminal and browser console log, we will find Hello svrx from browser, which means plugin has worked successfully.

You can also run it in programmatical way

const svrx = require('@svrx/svrx');

svrx({
  plugins: ['hello-world']
}).start();

Further reading: How to test plugin?

Server Side Service

You can use service in two places

module.exports = {
  hooks: {
    async onCreate({
      middleware,
      injector,
      events,
      router,
      config,
      logger,
      io
    }) {
      // use service here
    }
  }
};
svrx(config).on('plugin', async ({ logger, io }) => {
  // use service here
});

We will explain these 7 services in turn

middleware

Middleware is used for adding koa-style middleware to implement backend logic injection

- middleware.add(name, definition)

Adding koa-style middleware

Usage

middleware.add('hello-world-middleware', {
  priority: 100,
  async onRoute(ctx, next) {}
});

Param

- middleware.del(name)

Delete a middleware with the specified name

Usage

middleware.del('hello-world-middleware');

Param

injector

injector is used for rewriting the response and inject client resources.

- injector.add(type, resource)

Add a resource for injection , only js and css has been supported.

The rule as follow.

Usage

injector.add('script', {
  content: `
    console.log('hello world')
  `
});

The content will be merged into bundle script

Param

Content has a higher priority than filename, so you can take one of them.

- injector.replace(pattern, replacement)

injector.replace will transform response body with some or all matches of a pattern replaced by a replacement.

Usage

injector.replace(/svrx/g, 'server-x');

The above example replace all svrx with server-x

Param

The usage of injector.replace is exactly the same as String.prototype.replace

resource injection is based upon injector.replace

events

Built-in event listener, support async&sorted emitter, , which can call the listen function in turn, and can terminate the event delivery at any time.

- events.on(type, fn)

Usage

events.on('hello', async payload => {});

events.on('hello', async (payload, ctrl) => {
  ctrl.stop(); // stop events emit, only works in sorted emit mode
});

Param

If the callback returns a Promise (such as async function), it will be treated as an asynchronous watcher.

- events.emit(type, param, sorted)

Usage

// sorted emit, handler will be called one by one
events.emit('hello', { param1: 'world' }, true).then(() => {
  console.log('emit is done');
});
// parallel emit
events.emit('hello', { param1: 'world' }).then(() => {
  console.log('emit is done');
});

Param

Return

Promise

- events.off(name, handler)

Remove event watcher

Usage

events.off('hello'); // remove all hello's handler
events.off('hello', handler); // remove specific handler

builtin events

config

Config service used to modify or query the options passed by user;

- config.get(path)

Get the config of this plugin.

Config is based on immutable data , you must always use config.get to ensure getting the latest config.

Usage

config.get('user'); // get the user param  belong to this plugin
config.get('user.name'); // get the user.name param  belong to this plugin

if you need to get global config,just add prefix $.

config.get('$.port'); // get the server's port
config.get('$.root'); // get the svrx working directory

Param

Return

The value of the field

- config.set(field, value)

Modify the config

Usage

config.set('a.b.c', 'hello'); // deep set
config.get('a'); // => { b: { c: 'hello' } }

Param

- config.watch(field, handler)

Listening for configuration changes, change digest will be triggered after the set, del, splice method calls

config.watch((evt)=>{
  console.log(evt.affect('a.b.c')) => true
  console.log(evt.affect('a')) // => true
  console.log(evt.affect('a.c')) // => false
})

config.watch('a.b', (evt)=>{
    console.log('a.b has been changed')
})
config.set('a.b.c', 'hello');

Param

- config.del(field)

Remove some field

Usage

config.del('a.b.c');
config.get('a.b.c'); //=> undefined

- config.splice(field, start[, delCount[, items...])

The Array.prototype.slice

Except for field,params are identical to Array.prototype.splice

Example

config.set('a.b.c', [1, 2, 3]);
config.splice('a.b.c', 1, 1);
config.get('a.b.c'); // => [1,3]

router

Extending Routing DSL

- router.route(register)

Register route,as same as Routing DSL

Usage

const {route} = router;

route(({all, get, post})=>{

  all('/blog').to.send('Hi, Blog')
  get('/user').to.send('Hi, user')
  post('/user').to.send({code: 200, data: 'Success!'})

})

Param

- router.action(name, builder)

Register an action , like proxy or json

Usage

const { action, route } = router;
action('hello', user => ctx => {
  ctx.body = `hello ${user}`;
});
route(({ all }) => {
  all('/blog').hello('svrx'); //=> response 'hello svrx'
});

Param

- router.load(filename)

Load routing file manually ,Same as options --route

Also support hot reloading

Usage

await router.load('/path/to/route.md');

Param

Return

Promise

logger

Logger module, who’s level can be controlled by logger.level (default is warn)

svrx({
  logger: {
    level: 'error'
  }
});

Or cli way

svrx --logger.level error

Above example will output log that more than warn, such as notify, error

logger[level](msg)

Svrx provides multiple levels of logging: silent, notify, error, warn (default), info, debug

Usage

logger.notify('notify'); // show `notify`
logger.error('error'); // show `error` and `notify`
logger.warn('warn'); // show `warn`、`error` and `notify`

logger.log is an alias for logger.notify

io

io is used for the communication between server and client. Please check it out in client-side io

- io.on( type, handler )

Listening for client messages (send by client side io.emit)

io.on('hello', payload => {
  console.log(payload); // =>1
});

Param

- io.emit(type, payload)

Send message to client

Usage

Server side

io.emit('hello', 1);

Client side

const { io } = svrx;
io.on('hello', payload => {
  console.log(payload); //=>1
});

Param

Message payload must be serializable because they are transmitted over the network

- io.off(type[, handler])

Remove the message watcher

Usage

io.off('hello'); //=> remove all hello handlers
io.off('hello', handler); //=> remove specific handler

- io.register(name, handler)

Register io service, which can be invoked by io.call in client and server.

Usage

io.register('hello.world', async payload => {
  return `Hello ${payload}`;
});

Param

- io.call(name, payload)

Invoke registered service

Usage

io.call('hello.world', 'svrx').then(data => {
  console.log(data); // => Hello svrx
});

Param

Return

Promise

Client API

The client APIs are uniformly exposed through global variable svrx

const { io, events, config } = svrx;

io

Responsible for communicating with the server

- io.on(type, handler)

Listening server-side message

Usage

Server side

io.emit('hello', 1);

Client side

const { io } = svrx;
io.on('hello', payload => {
  console.log(payload); //=>1
});

Note that the io.emit() in server-side is a broadcast and all pages will receive a message for server.

Param

- io.emit(type, payload)

Send client message to server side

Usage

Client side

const { io } = svrx;
io.emit('hello', 1);

Server side

{
  hooks: {
    async onCreate({io}){
      io.on('hello', payload=>{
        console.log(payload) // =>1
      })
    }
  }
}

Param

payload must be serializable because it is transmitted over the network.

- io.off(type[, handler])

Remove io watcher

Usage

io.off('hello'); //=> remove all hello handlers
io.off('hello', handler); //=> remove specific handler

- io.call(name, payload)

io.call on the client side is exactly the same as the server, but make sure the payload is serializable** because it will be transmitted over the network

Usage

io.call('hello.world', 'svrx').then(data => {
  console.log(data);
});

Param

Return

Promise

events

This part is exactly the same as server events, no retelling

Usage

const { events } = svrx;
events.on('type', handler);
events.emit('type', 1);
events.off('type');

The difference between events and io is that io is used for the communication between the server and the client, but events is a single-ended communication.

config

config in client is almost identical to the server, the only difference is: the client version is asynchronous (because of network communication)

- config.get(field)

Usage

config.get('$.port').then(port => {
  // get the server port
});

config.get('user').then(user => {
  //get the param belongs to current plugin
});

if you need to get global config,just add prefix $.

- config.setconfig.spliceconfig.del

The above three methods are the same as get, which is consistent with the server, but the return value becomes Promise.

Usage

config.set('a.b', 1).then(() => {
  config.get('a.b').then(ab => {
    console.log(ab); // => 1
  });
});

config schema

Svrx config schema is based on JSON Schema

Usage

module.exports = {
  root: {
    type: 'string',
    default: process.cwd(),
    description: 'where to start svrx',
    ui: false
  },
  route: {
    description: 'the path of routing config file',
    anyOf: [{ type: 'string' }, { type: 'array' }]
  },
  logger: {
    description: 'global logger setting',
    type: 'object',
    properties: {
      level: {
        type: 'string',
        default: 'error',
        description:
          "set log level, predefined values: 'silent','notify','error','warn', 'debug'"
      }
    }
  }
};

Field Details

How to test plugin?

It is not recommended that you publish the test plugin to npm. You can do local testing in the following ways.

svrx({
  plugins: [
    {
      name: 'hello-world',
      path: '/path/to/svrx-plugin-hello-world'
    }
  ]
}).start();

After specifying the path parameter, svrx loads the local package instead of installing it from npm

More easier plugin development

Use Official svrx-create-plugin to help you create plugins more easily