Use
svrx-create-plugin
to help you create plugins more easily
Let’s create our first plugin —— svrx-plugin-hello-world
└── svrx-plugin-hello-world
├── client.js
├── index.js
└── package.json
Only
package.json
andindex.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 semver,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
configSchema
Plugin param definition based on JSON Schema ,checkout JSON Schema for more detail.
Here we just set a user
field, it is a stringassets
: client resource setting,they will be automatically injected into the page
style
: css resource injectionscript
: script resource injection
All resources will be merged into a single file.
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 serviceconfig
: config serviceCheck 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
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?
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
eventsvrx(config).on('plugin', async ({ logger, io }) => {
// use service here
});
We will explain these 7 services in turn
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
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.definition.onRoute
[Function]: A koa-style middleware .If definition
is a function, it will automatically become definition.onRoute
middleware.del(name)
Delete a middleware with the specified name
Usage
middleware.del('hello-world-middleware');
Param
name
: middleware nameinjector
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.
/svrx/svrx-client.css
and
injected before the closing head
tag/svrx/svrx-client.js
and
injected before the closing body
tagUsage
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 contentresource.filename
[String]: the path of resource file, must be a absolute pathContent 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] |
The usage of
injector.replace
is exactly the same asString.prototype.replace
resource injection is based upon
injector.replace
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 namefn(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 namepayload
: event datasorted
[Boolean]: default is false
, whether to pass events seriallyReturn
Promise
events.off(name, handler)
Remove event watcher
Usage
events.off('hello'); // remove all hello's handler
events.off('hello', handler); // remove specific handler
plugin
: triggered after plugin building.file:change
: triggered when any file changesready
: triggered when server starts, if you need to handle logic after server startup (such as getting the server port), you can register this eventConfig 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 valueconfig.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 changedconfig.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]
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
name
[String]: action namebuilder(payload)
'svrx'
in above examplerouter.load(filename)
Load routing file manually ,Same as options --route
Also support hot reloading
Usage
await router.load('/path/to/route.md');
Param
filename
: absolute path for routing fileReturn
Promise
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 forlogger.notify
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
type
: message typehandler(payload)
: handler for message
payload
: message dataio.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 typepayload
: message dataMessage 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 logicio.call(name, payload)
Invoke registered service
Usage
io.call('hello.world', 'svrx').then(data => {
console.log(data); // => Hello svrx
});
Param
name
[String]: service namepayload
[Any]: service payload will passed to service handlerReturn
Promise
The client APIs are uniformly exposed through global variable svrx
const { io, events, config } = svrx;
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 typehandler(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 typepayload
: 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)
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
name
[String]: service namepayload
[Any]: service payloadReturn
Promise
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
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
});
});
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
type
[String]: JSON-Schema field type ,can be an array
,string
,number
,boolean
,object
or null
default
[Any]: default valuerequired
[Boolean]: whether it is required, default is false
properties
[Object]: child fieldsui
: svrx extension ,whether show the config in svrx-uianyOf
: Choose one of the items, such as
route: {
description: 'the path of routing config file',
anyOf: [{ type: 'string' }, { type: 'array' }],
},
For further understanding, please refer to Official Documentation
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
Use Official svrx-create-plugin
to help you create plugins more easily