svrx
  • Languages
  • svrx
  • svrx
    • SUMMARY
    • Contributing to svrx
    • Getting Started
    • Blog
      • Server-X: A Pluggable Platform For Local Frontend Development
    • Guide
      • API Reference
      • Option Reference
      • How To Use Routes
    • Plugins
      • How To Write A Plugin
      • How To Use Plugins
    • Practice
      • Integrations
      • Recipes
  • svrx
    • 概要
    • 贡献指南
    • 快速上手
    • Blog
      • Server-X:一款可能提升你十倍工作效率的工具
      • 使用 svrx 实现更优雅的接口 Mock
      • 说说 Server-X 的免安装插件机制
    • 进阶指南
      • API 索引
      • 参数列表
      • Routing 路由的使用
    • 插件体系
      • 如何写一个插件
      • 插件的使用
    • 项目实战
      • 结合主流脚手架使用
      • 特定场景使用
Powered by GitBook
On this page
  • First Plugin
  • File Structure
  • publish && running
  • Server Side Service
  • middleware
  • injector
  • events
  • config
  • router
  • logger
  • logger[level](msg)
  • io
  • Client API
  • io
  • events
  • config
  • config schema
  • How to test plugin?
  • More easier plugin development

Was this helpful?

  1. svrx
  2. Plugins

How To Write A Plugin

PreviousPluginsNextHow To Use Plugins

Last updated 3 years ago

Was this helpful?

Use to help you create plugins more easily

First Plugin

Let's create our first plugin ——

File Structure

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

Only package.json and index.js are required

  • package.json

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

Only two fields are required by package.json

  • name: package name must be a string beginning with svrx-plugin, to help svrx find it in npm.

  • engines.svrx: Define the runnable svrx version of this plugin,svrx will automatically load the latest matching plugin engines.svrx can be a valid ,like 0.1.x or ~0.1

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

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

- 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

publish && running

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();

Server Side Service

You can use service in two places

  • hooks.onCreate

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

svrx(config).on('plugin', async ({ logger, io }) => {
  // use service here
});

We will explain these 7 services in turn

middleware

- middleware.add(name, definition)

Usage

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

Param

  • name [String]: A unique middleware name

    in debug mode, it can be used to track the middleware call process

  • definition.priority [Number]: default is 10. Svrx will assemble the middleware according to the priority from high to low, that is, the request will be passed to the high priority plugin first.

- middleware.del(name)

Delete a middleware with the specified name

Usage

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

Param

  • name: middleware name

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.

  • The style will be merged into /svrx/svrx-client.css and injected before the closing head tag

  • The script will be merged into /svrx/svrx-client.js and injected before the closing body tag

Usage

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

The content will be merged into bundle script

Param

  • type: Only support script and style

  • resource.content [String]: resource content

  • resource.filename [String]: the path of resource file, must be a absolute path

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

  • pattern [String|RegExp]

  • replacement [String|Function]

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

  • type [String]: event name

  • fn(payload, ctrl): callback that has two params

    • payload [String]: event data that pass through emit

    • ctrl [Object]: control object, call ctrl.stop() to stop 'sorted emit'

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

  • type [String]: event name

  • payload: event data

  • sorted [Boolean]: default is false, whether to pass events serially

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

  • plugin: triggered after plugin building.

  • file:change: triggered when any file changes

  • ready: triggered when server starts, if you need to handle logic after server startup (such as getting the server port), you can register this event

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

  • field: field path,deep getter must be separated by ., such as user.name

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

  • field: field path,deep setter must be separated by ., such as user.name

  • value: field value

- 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

  • field: field path,deep watcher must be separated by ., such as user.name

  • handler(evt): watch handler

    • evt.affect(field) [Function]: detect whether specific field has been changed

- 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

Example

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

router

- router.route(register)

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

  • register({...methods}):

- router.action(name, builder)

Usage

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

Param

  • name [String]: action name

  • builder(payload)

    • payload: payload that passed to action,like 'svrx' in above example

- router.load(filename)

Also support hot reloading

Usage

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

Param

  • filename: absolute path for routing file

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.on( type, handler )

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

Param

  • type: message type

  • handler(payload): handler for message

    • payload: message data

- 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

  • type: message type

  • payload: message data

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

  • name [String]: service name used by io.call

  • handler: a Function return Promise, implement service logic

- io.call(name, payload)

Invoke registered service

Usage

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

Param

  • name [String]: service name

  • payload [Any]: service payload will passed to service handler

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

  • type: message type

  • handler(payload): message handler

    • payload: message payload passed by io.emit

- 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

  • type: message type

  • payload: message data passed to message handler

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)

Usage

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

Param

  • name [String]: service name

  • payload [Any]: service payload

Return

Promise

events

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.set、config.splice、config.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

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

  • default [Any]: default value

  • required [Boolean]: whether it is required, default is false

  • properties [Object]: child fields

  • anyOf: Choose one of the items, such as

    route: {
      description: 'the path of routing config file',
      anyOf: [{ type: 'string' }, { type: 'array' }],
    },

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

configSchema Plugin param definition based on JSON Schema ,checkout for more detail. Here we just set a user field, it is a string

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

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

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

Further reading:

Middleware is used for adding to implement backend logic injection

Adding

definition.onRoute [Function]: A middleware .If definition is a function, it will automatically become definition.onRoute

The usage of injector.replace is exactly the same as

Except for field,params are identical to

Extending

Register route,as same as

methods: corresponding to

Register an action , like or

Return: builder must return a middleware

Load routing file manually ,Same as

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

Listening for client messages (send by )

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

This part is exactly the same as , no retelling

Svrx config schema is based on

type [String]: ,can be an array,string,number,boolean,object or null

ui: svrx extension ,whether show the config in

For further understanding, please refer to

Use Official to help you create plugins more easily

svrx-create-plugin
svrx-plugin-hello-world
semver
official team
koa-style middleware
koa-style middleware
koa-style
String.prototype.replace
Array.prototype.splice
Routing DSL
koa style
options --route
JSON Schema
JSON-Schema field type
svrx-ui
Official Documentation
svrx-create-plugin
JSON Schema
service
client api
How to test plugin?
client-side io
client side io.emit
io.call
server events
Routing DSL
HTTP methods
proxy
json