Skip to content

Commit

Permalink
docs(2.x-advanced-plugin):fix some descriptions (#2111)
Browse files Browse the repository at this point in the history
  • Loading branch information
SunShinewyf authored and dead-horse committed Feb 22, 2018
1 parent 0a208d7 commit fd6fa24
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 60 deletions.
118 changes: 59 additions & 59 deletions docs/source/en/advanced/plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
title: 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
Expand Down Expand Up @@ -64,12 +64,12 @@ It is almost the same as the application directory, what's the difference?
- An application might have plenty of dependant plugins, routers of plugin are very possible conflict with others. It would be a disaster.
- If you really need a general router, you should implement it as middleware of the plugin.

2. The specific information of plugin should be declared in the `package.json` by `eggPlugin`
2. The specific information of plugin should be declared in the `package.json` of `eggPlugin`

- `{String} name` - plugin name(required), it must be unique, it will be used in the config of the dependencies of plugin.
- `{Array} dependencies` - the list of strong dependant plugins of current plugin(if one of these plugins here is not found, application startup plugin will be failed)
- `{Array} optionalDependencies` - this optional dependencies of this plugin.(if these plugins are not activated, only warnings would be occurred, and not affect the startup of the application).
- `{Array} env` - this option is avaliable only when specified the enviroment. The list of env please refer to [env](../basics/env.md). This is optional, most time you can leave it.
- `{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](../basics/env.md). This is optional, most time you can leave it.

```json
{
Expand All @@ -86,11 +86,11 @@ It is almost the same as the application directory, what's the difference?
3. No `plugin.js`:

- `eggPlugin.dependencies` is for declaring dependencies only, not for importing, nor activating.
- If you want to manage multiple plugins, you should do it in[framework](./framework.md)
- If you want to manage multiple plugins, you should do it in[upper framework](./framework.md)

## 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`

```json
// plugin a
Expand Down Expand Up @@ -124,29 +124,29 @@ The dependencies are managed by plugin himself, this is different from middlewar

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.
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 is plugin capable of
## What can plugin do

We've discussed what plugin is. Now what is it capable of?
We've discussed what plugin is. Now what can it do?

### Embeded Objects API Extension
### Built-in Objects API Extension

Extend the embeded objects of the framework, just like the application
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

1. First, define and implement middleware inside directory `app/middleware`
1. First, define and implement middleware under directory `app/middleware`

```js
'use strict';
Expand All @@ -173,15 +173,15 @@ Extend the embeded objects of the framework, just like the application
const assert = require('assert');

module.exports = app => {
// insert static before bodyParser
// insert static middleware before bodyParser
const index = app.config.coreMiddleware.indexOf('bodyParser');
assert(index >= 0, 'bodyParser highly needed');

app.config.coreMiddleware.splice(index, 0, 'static');
};
```

### Initializations on Application Starting
### Initialization on Application Starting

- If you want to read some local config before startup

Expand All @@ -197,7 +197,7 @@ Extend the embeded objects of the framework, just like the application
};
```

- If you want to do some async starting bussiness, you can do it with `app.beforeStart` API
- If you want to do some async starting business, you can do it with `app.beforeStart` API

```js
// ${plugin_root}/app.js
Expand All @@ -215,7 +215,7 @@ Extend the embeded objects of the framework, just like the application
};
```

- You can add starting bussiness of agent with `agent.beforeStart` API
- You can add starting business of agent with `agent.beforeStart` API

```js
// ${plugin_root}/agent.js
Expand All @@ -233,7 +233,7 @@ Extend the embeded objects of the framework, just like the application
};
```

### Setup Timing Task
### Setup Schedule Task

1. Setup dependencies of schedule plugin in `package.json`

Expand All @@ -247,7 +247,7 @@ Extend the embeded objects of the framework, just like the application
}
```

2. Create a new file in `${plugin_root}/app/schedule/` directory to edit your timing task
2. Create a new file in `${plugin_root}/app/schedule/` directory to edit your schedule task

```js
exports.schedule = {
Expand All @@ -264,27 +264,27 @@ Extend the embeded objects of the framework, just like the application

### 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
We simplify the [egg-mysql] plugin and to see how to write such a plugin:

```js
// egg-mysql/app.js
module.exports = app => {
// The first parameter mysql defined the field mounted to app, we can access MySQL singleton instance by `app.mysql`
// The second parameter createMysql accept 2 parameters (config, app), and then return a MySQL instance
// The first parameter mysql defines the field mounted to app, we can access MySQL singleton instance via `app.mysql`
// The second parameter createMysql accepts two parameters (config, app), and then returns a MySQL instance
app.addSingleton('mysql', createMysql);
}

/**
* @param {Object} config The config which already processed by the framework. If the application configured multiple MySQL instances, each config would be passed in and call the createMysql seperately
* @param {Object} config The config which already processed by the framework. If the application configured multiple MySQL instances, each config would be passed in and call multiple createMysql
* @param {Application} app the current application
* @return {Object} return the created MySQL instance
*/
Expand All @@ -304,11 +304,11 @@ function createMysql(config, app) {
}
```

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.
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 Use Case
#### Application Layer Usage Case

##### Singelton
##### Single Instance

1. Declare MySQL config in config file

Expand All @@ -327,7 +327,7 @@ module.exports = {
};
```

2. Access database by `app.mysql` directly
2. Access database through `app.mysql` directly

```js
// app/controller/post.js
Expand All @@ -340,7 +340,7 @@ class PostController extends Controller {

##### Multiple Instances

1. 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 in `default` .
1. 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, the `default` field can be used to configure the shared configuration in multiple instances(e.g. host and port).

```js
// config/config.default.js
Expand All @@ -366,7 +366,7 @@ exports.mysql = {
};
```

2. Access the right instance by `app.mysql.get('db1')`
2. Access the corresponding instance by `app.mysql.get('db1')`

```js
// app/controller/post.js
Expand All @@ -379,55 +379,55 @@ class PostController extends Controller {

##### Dynamically Instantiate

We can dynamically initialize instance without config when running the application.
Instead of declaring the configuration in the configuration file in advance, We can dynamically initialize an instance at the runtime of the application.

```js
// app.js
module.exports = app => {
app.beforeStart(async () => {
// get MySQL config from configcenter { host, post, password, ... }
// get MySQL config from configuration center { host, post, password, ... }
const mysqlConfig = await app.configCenter.fetch('mysql');
// create MySQL instance dynamically
app.database = app.mysql.createInstance(mysqlConfig);
});
};
```

Access the instance by `app.database`
Access the instance through `app.database`

```js
// app/controller/post.js
class PostController extends Controller {
async list() {
const posts = await this.app.databse.query(sql, values);
const posts = await this.app.database.query(sql, values);
},
}
```

**Attention, when creating the instance dynamically, framework would read the `default` property in the config file as the default config**
**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 no path config, search them with the package name, the search orders are:
- If there is the path configuration, load them in path directly
- If there is no path configuration, search them with the package name, the search orders are:

1. `node_modules` directory of the application root
2. `node_modules` directory of the dependencies
3. `node_modules` of current directory(generally for unit test compability)
3. `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 prefix `egg-`,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 prefix `egg-`,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` in `keywords`.
- For convenient index, add `egg`,`egg-plugin`,`eggPlugin` in `keywords`.

```json
{
Expand All @@ -453,13 +453,13 @@ We are expecting people could build new plugins. In the meantime we hope these p

## 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?
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 is not only support npm package, it also support search plugins in local directory. In Chapter [progressive](../tutorials/progressive.md) 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.
First, Egg plugin do not only support npm package, it also supports search plugins in local directory. In Chapter [progressive](../tutorials/progressive.md) 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.

More important, Egg can use this feature to make Adapter. For example, the plugin defined in[Template Develop Spec](./view-plugin.md#PluginNameSpecification) 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.
What's more, Egg can use this feature to make Adapter. For example, the plugin defined in[Template Develop Spec](./view-plugin.md#PluginNameSpecification) 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.
**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.

[egg-init]: https://github.com/eggjs/egg-init
[egg-boilerplate-plugin]: https://github.com/eggjs/egg-boilerplate-plugin
Expand Down
2 changes: 1 addition & 1 deletion docs/source/zh-cn/advanced/plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ module.exports = app => {
// app/controller/post.js
class PostController extends Controller {
async list() {
const posts = await this.app.databse.query(sql, values);
const posts = await this.app.database.query(sql, values);
},
}
```
Expand Down

0 comments on commit fd6fa24

Please sign in to comment.