Plugin Development
-Plugins is the most important feature in Egg framework. It keeps Egg simple, stable and effecient, and also it can make the best reuse of business logic, to build an entire ecosystem. Someone should be got confused:
+Plugins is the most important feature in Egg framework. It keeps Egg simple, stable and efficient, and also it can make the best reuse of business logic, to build an entire ecosystem. Someone should be confused:
-
-
- Since Koa has already got the plugin feature, what's the point of the Egg plugins +
- Since Koa already has the mechanism of middleware, what's the point of the Egg's plugins
- What is the differences between middleware, plugin and application, what is the relationship
- How can I use the plugin
- How do I build a plugin @@ -85,12 +85,12 @@
{Array} dependencies
- strong dependant plugins list of current plugin(if one of these plugins here is not found, application's startup will be failed)
+{Array} optionalDependencies
- optional dependencies list of this plugin.(if these plugins are not activated, only warnings would be occurred, and will not affect the startup of the application).
+{Array} env
- this option is available only when specify the environment. The list of env please refer to env. This is optional, most time you can leave it.
env. This is optional, most time you can leave it. +
{ |
framework +
# Dependencies Management of Plugins
-The dependencies are managed by plugin himself, this is different from middleware. Before loading plugins, application will read dependencies from eggPlugin > dependencies
and eggPlugin > optionalDependencies
in package.json
, and then sort out the loading orders according to their relationships, for example, the loading order of the following plugins is c => b => a
The dependencies are managed by plugin himself, this is different from middleware. Before loading plugins, application will read eggPlugin > dependencies
and eggPlugin > optionalDependencies
from package.json
, and then sort out the loading orders according to their relationships, for example, the loading order of the following plugins is c => b => a
// plugin a |
** Attention: The values in dependencies
and optionalDependencies
are the eggPlugin.name
of plugins, not package.name
. **
The dependencies
and optionalDependencies
is studied from npm
, most time we are using dependencies
, it is recommended. There are about two situations to apply the optionalDependencies
:
-
-
- Only get dependant in specific enviroment: for example, a authentication plugin, only depend on the mock plugin in development enviroment. -
- Weakly depending, for example: A depned on B, but without B, A can take other choice +
- Only be dependant in specific environment: for example, a authentication plugin, only depends on the mock plugin in development environment. +
- Weakly depending, for example: A depends on B, but without B, A can take other choice
Pay attention: if you are using optionalDependencies
, framework won't verify the activation of these dependencies, they are only for sorting loading orders. In such situation, the plugin will go through other ways such as interface detection
to decide related logic.
# What is plugin capable of
-We've discussed what plugin is. Now what is it capable of?
-# Embeded Objects API Extension
-Extend the embeded objects of the framework, just like the application
+Pay attention: if you are using optionalDependencies
, framework won't verify the activation of these dependencies, they are only for sorting loading orders. In such situation, the plugin will go through other ways such as interface detection
to decide processing logic.
# What can plugin do
+We've discussed what plugin is. Now what can it do?
+# Built-in Objects API Extension
+Extend the built-in objects of the framework, just like the application
-
-
app/extend/request.js
- extends Koa#Request
-app/extend/response.js
- extends Koa#Response
-app/extend/context.js
- extends Koa#Context
-app/extend/helper.js
- extends Helper
-app/extend/application.js
- extends Application
-app/extend/agent.js
- extends Agent
+app/extend/request.js
- extends Koa#Request object
+app/extend/response.js
- extends Koa#Response object
+app/extend/context.js
- extends Koa#Context object
+app/extend/helper.js
- extends Helper object
+app/extend/application.js
- extends Application object
+app/extend/agent.js
- extends Agent object
# Insert Custom Middlewares
-
-
- First, define and implement middleware inside directory
app/middleware
+ - First, define and implement middleware under directory
app/middleware
; |
- Insert middleware to the appropriate position in
app.js
(e.g. insert static middleware before bodyParser )
const assert = require('assert'); |
# Initializations on Application Starting
+const assert = require('assert'); |
# Initialization on Application Starting
-
If you want to read some local config before startup
// ${plugin_root}/app.js
const fs = require('fs');
const path = require('path');
module.exports = app => {
app.customData = fs.readFileSync(path.join(app.config.baseDir, 'data.bin'));
app.coreLogger.info('read data ok');
}; -
-
If you want to do some async starting bussiness, you can do it with
+app.beforeStart
APIIf you want to do some async starting business, you can do it with
app.beforeStart
API// ${plugin_root}/app.js
const MyClient = require('my-client');
module.exports = app => {
app.myClient = new MyClient();
app.myClient.on('error', err => {
app.coreLogger.error(err);
});
app.beforeStart(async () => {
await app.myClient.ready();
app.coreLogger.info('my client is ready');
});
}; -
-
You can add starting bussiness of agent with
+agent.beforeStart
APIYou can add starting business of agent with
agent.beforeStart
API// ${plugin_root}/agent.js
const MyClient = require('my-client');
module.exports = agent => {
agent.myClient = new MyClient();
agent.myClient.on('error', err => {
agent.coreLogger.error(err);
});
agent.beforeStart(async () => {
await agent.myClient.ready();
agent.coreLogger.info('my client is ready');
});
};
# Setup Timing Task
+# Setup Schedule Task
- Setup dependencies of schedule plugin in
package.json
{ |
-
-
- Create a new file in
${plugin_root}/app/schedule/
directory to edit your timing task
+ - Create a new file in
${plugin_root}/app/schedule/
directory to edit your schedule task
exports.schedule = { |
# Best Practice of Global Instance Plugin
-Some plugins are made to introduce existing service into framework, like egg-mysql,egg-oss.They all need to create corresponding instance in application. We notice something when developing this kind of plugins:
+Some plugins are made to introduce existing service into framework, like egg-mysql,egg-oss.They all need to create corresponding instance in application. We notice that there are some common problems when developing this kind of plugins:
-
-
- Use different instances of the same service in one application(e.g:connect to two different MySQL Database) -
- Dynamically intialize connection after get config from other service(get MySQL server address from config center and then initialize connection) +
- Use different instances of the same service in one application(e.g:connect to two different MySQL Databases) +
- Dynamically initialize connection after getting config from other service(gets MySQL server address from configuration center and then creates connection)
If each plugin make their own implementation, all sorts of configs and initializations will be chaotic. So the framework supply the app.addSingleton(name, creator)
API to make it clear.
If each plugin makes their own implementation, all sorts of configs and initializations will be chaotic. So the framework supplies the app.addSingleton(name, creator)
API to unify the creation of this kind of services.
# Writing Plugin
-We have simplfied the egg-mysql plugin in order to use it in this tutorial
-// egg-mysql/app.js |
As you can see, all we need to do for this plugin is passing in the field that needed to be mounted and the corresponding initialize function. Framework will be in charge of managing all the configs and the way to access the instances.
-# Application Layer Use Case
-# Singelton
+We simplify the egg-mysql plugin and to see how to write such a plugin:
+// egg-mysql/app.js |
As you can see, all we need to do for this plugin is passing in the field that need to be mounted and the corresponding initialization function. Framework will be in charge of managing all the configs and the ways to access the instances.
+# Application Layer Usage Case
+# Single Instance
- Declare MySQL config in config file
// config/config.default.js |
-
-
- Access database by
app.mysql
directly
+ - Access database through
app.mysql
directly
// app/controller/post.js |
# Multiple Instances
-
-
- Of cause we need to configure MySQL in the config file, but diffrent from singleton, we need to add
clients
in the config to configure each instance. The default configs(e.g. host and port) can be configured indefault
.
+ - Of course we need to configure MySQL in the config file, but different from single instance, we need to add
clients
in the config to declare the configuration of different instances. meanwhile, thedefault
field can be used to configure the shared configuration in multiple instances(e.g. host and port).
// config/config.default.js |
-
-
- Access the right instance by
app.mysql.get('db1')
+ - Access the corresponding instance by
app.mysql.get('db1')
// app/controller/post.js |
# Dynamically Instantiate
-We can dynamically initialize instance without config when running the application.
-// app.js |
Access the instance by app.database
// app/controller/post.js |
Attention, when creating the instance dynamically, framework would read the default
property in the config file as the default config
Instead of declaring the configuration in the configuration file in advance, We can dynamically initialize an instance at the runtime of the application.
+// app.js |
Access the instance through app.database
// app/controller/post.js |
Attention, when creating the instance dynamically, framework would read the default
configuration in the config file as the default configuration
# Plugin Locate Rule
When loading the plugins in the framework, it will follow the rules to locate them as below:
-
-
If there is the path config, load them in path directly
+If there is the path configuration, load them in path directly
-
-
If there is no path config, search them with the package name, the search orders are:
+If there is no path configuration, search them with the package name, the search orders are:
node_modules
directory of the application rootnode_modules
directory of the dependencies
-node_modules
of current directory(generally for unit test compability)
+node_modules
of current directory(generally for unit test compatibility)
# Plugin Specification
-We are expecting people could build new plugins. In the meantime we hope these plugins could follow the rules as below:
+We are very welcome your contribution to the new plugins, but also hope you follow some of following specifications:
-
Naming Rules
-
-
npm
packages must append prefixegg-
,and all letters must be lowcase,for example:egg-xxx
. The long names should be concatenate with middlelines:egg-foo-bar
.
-- The corresponding plugin should be named in camlecase. The name should be translated according to the middlelines of the
npm
name:egg-foo-bar
=>fooBar
.
- - The use of middlelines is not mandatory,for example:userservice(egg-userservice) and ser-service(egg-user-service) are both acceptable. +
npm
packages must append prefixegg-
,and all letters must be lowercase,for example:egg-xxx
. The long names should be concatenated with middle-lines:egg-foo-bar
.
+- The corresponding plugin should be named in camel-case. The name should be translated according to the middle-lines of the
npm
name:egg-foo-bar
=>fooBar
.
+ - The use of middle-lines is not compulsive,for example:userservice(egg-userservice) and user-service(egg-user-service) are both acceptable.
-
package.json
Rules- Add
eggPlugin
property according to the details discussed before.
- - For index conveniet, add
egg
,egg-plugin
,eggPlugin
inkeywords
.
+ - For convenient index, add
egg
,egg-plugin
,eggPlugin
inkeywords
.
{
"name": "egg-view-nunjucks",
"version": "1.0.0",
"description": "view plugin for egg",
"eggPlugin": {
"name": "nunjucks",
"dep": [
"security"
]
},
"keywords": [
"egg",
"egg-plugin",
"eggPlugin",
"egg-plugin-view",
"egg-view",
"nunjucks"
],
} - Add
# Why do not use the npm package name as the plugin name?
-Egg define the plugin name by the eggPlugin.name
, it is only unique in application or framework, that means many npm packages might get the same plugin name, why design this way?
First, Egg plugin is not only support npm package, it also support search plugins in local directory. In Chapter progressive we mentioned how to make progress by using these two configs. Directory is more friendly to unit test. So, Egg can not ensure uniqueness through npm package name.
-More important, Egg can use this feature to make Adapter. For example, the plugin defined inTemplate Develop Spec was named as view, but there are plugins named egg-view-nunjucks
and egg-view-react
, the users only need to change the plugin and modify the templates, no need to modify the Controller, because all these plugins have implemented the same API.
Make the same featured plugins the same name and same API, can make quick switch between them. This is really really useful in template and database.
+Egg defines the plugin name through the eggPlugin.name
, it is only unique in application or framework, that means many npm packages might get the same plugin name, why design in this way?
First, Egg plugin do not only support npm package, it also supports search plugins in local directory. In Chapter progressive we mentioned how to make progress by using these two configurations. Directory is more friendly to unit test. So, Egg can not ensure uniqueness through npm package name.
+What's more, Egg can use this feature to make Adapter. For example, the plugin defined inTemplate Develop Spec was named as view, but there are plugins named egg-view-nunjucks
and egg-view-react
, the users only need to change the plugin and modify the templates, no need to modify the Controller, because all these plugins have implemented the same API.
Giving the same plugin name and the same API to the same plugin can make quick switch between them. This is really really useful in template and database.