diff --git a/docs/source/en/advanced/cluster-client.md b/docs/source/en/advanced/cluster-client.md index bb4d65f14d..19f62df341 100644 --- a/docs/source/en/advanced/cluster-client.md +++ b/docs/source/en/advanced/cluster-client.md @@ -16,21 +16,21 @@ In the previous [Multi-Process Model chapter](../core/cluster-and-ipc.md), we co +--------+ +--------+ ``` -In order to reuse persistent connections as much as possible (because they are very valuable resources for server), we put it into the Agent process to maintain, and then we transmit data to each Worker via messenger. It's feasible but often we need to write a lot of code to encapsulate the interface and to realize data transmission, which is very troublesome. +In order to reuse persistent connections as much as possible (because they are very valuable resources for server), we put them into the Agent process to maintain, and then we transmit data to each Worker via messenger. It's feasible but we often need to write many codes to encapsulate the interface and realize data transmission, which is very troublesome. In addition, it's relatively inefficient to transmit data via messenger, since messenger will do the transmission through the Master; In case IPC channel goes wrong, it would probably break Master process down. -Then is there any better way? The answer is yes. We provide a new type of model to reduce the complexity of this type of client encapsulation. The new Model bypasses the Master by establishing a direct socket between Agent and Worker. And as an external interface, Agent maintains shared connection among multiple Worker processes. +So is there any better way? The answer is: YES! We provide a new type of model to reduce the complexity of this type of client encapsulation. The new Model bypasses the Master by establishing a direct socket between Agent and Worker. And as an external interface, Agent maintains shared connection among multiple Worker processes. ## core idea -- Inspired by the [Leader/Follower](http://www.cs.wustl.edu/~schmidt/PDF/lf.pdf) model -- The client is divided into two roles: - - Leader: responsible for maintaining the connection with the remote server, only one Leader for the same type of client. - - Follower: delegates specific operations to the Leader. A common way is Subscribe-Model (let the Leader interact with remote server and wait for its return). +- Inspired by the [Leader/Follower](http://www.cs.wustl.edu/~schmidt/PDF/lf.pdf) model. +- The client is divided into two roles: + - Leader: Be responsible for maintaining the connection with the remote server, only one Leader for the same type of client. + - Follower: Delegate specific operations to the Leader. A common way is Subscribe-Model (let the Leader interact with remote server and wait for its return). - How to determine who Leader is, who Follower is? There are two modes: - - Free competition mode: clients determine the Leader by the competition of the local port when start up. For example: every one tries to monitor port 7777, and finally the only one instance who seizes it will become Leader, the rest will be Followers. - - Mandatory mode: the framework designates a Leader and the rest are Followers. + - Free competition mode: clients determine the Leader by the competition of the local port when start up. For example: every one tries to monitor port 7777, and finally the only one instance who seizes it will become Leader, the rest will be Followers. + - Mandatory mode: the framework designates a Leader and the rest are Followers. - we use mandatory mode inside the framework. The Leader can only be created inside the Agent, which is also in line with our positioning of the Agent. - When the framework starts up, Master will randomly select an available port as the communication port monitored by the Cluster Client, and passes it by parameter to Agent and App Worker. - Leader communicates with Follower through direct socket connection (through communication port), no longer needs Master to transit. @@ -60,9 +60,9 @@ win / +------------------+ \ lose We abstract the client interface into the following two broad categories, which is also a specification of the client interface. For clients that are in line with norms, we can automatically wrap it as Leader / Follower mode. -- Subscribe / Publish Mode - - The `subscribe(info, listener)` interface contains two parameters. The first one is the information subscribed and the second one is callback function for subscribe. - - The `publish(info)` interface contains a parameter which is the information subscribed. +- Subscribe / Publish Mode: + - The `subscribe(info, listener)` interface contains two parameters. The first one is the information subscribed and the second one is callback function for subscribe. + - The `publish(info)` interface contains a parameter which is the information subscribed. - Invoke Mode, supports three styles of interface: callback, promise and generator function, but generator function is recommended. Client example @@ -131,7 +131,7 @@ Leader and Follower exchange data via the following protocols: ``` 1. On the communication port Leader starts a Local Server, via which all Leaders / Followers communicate. -2. After Follower connects Local Server, it will firstly send a register channel packet (introduction of the channel concept is to distinguish between different types of clients) +2. After Follower connects Local Server, it will firstly send a register channel packet (introduction of the channel concept is to distinguish between different types of clients). 3. Local Server will assign Follower to a specified Leader (match based on client type). 4. Follower sends requests to Leader to subscribe and publish. 5. Leader notifies Follower through the subscribe result packet when the subscription data changes. @@ -241,7 +241,7 @@ class RegistryClient extends Base { module.exports = RegistryClient; ``` -- The second step is to encapsulate the RegistryClient using the `agent.cluster` interface. +- The second step is to encapsulate the `RegistryClient` using the `agent.cluster` interface: ```js // agent.js @@ -260,7 +260,7 @@ module.exports = agent => { }; ``` -- The third step, use the `app.cluster` interface to encapsulate RegistryClient +- The third step, use the `app.cluster` interface to encapsulate `RegistryClient`: ```js // app.js @@ -292,9 +292,9 @@ module.exports = app => { }; ``` -Is not it simple? +Isn't it so simple? -Of course, if your client is not so 『standard』, then you may need to use some other APIs, for example, your subscription function is not called subscribe, but sub. +Of course, if your client is not so 『standard』, then you may need to use some other APIs, for example, your subscription function is not named `subscribe`, but `sub`: ```js class MockClient extends Base { @@ -324,7 +324,7 @@ class MockClient extends Base { } ``` -You need to set it manually with the delegate API +You need to set it manually with the `delegate` API: ```js // agent.js @@ -358,20 +358,20 @@ module.exports = app => { }; ``` -We've already known that using cluster-client allows us to develop a 『pure』 RegistryClient without understanding the multi-process model. We can only focus on interacting with server, and use the cluster-client with a simple wrap to get a ClusterClient which supports multi-process model. The RegistryClient here is actually a DataClient that is specifically responsible for data communication with remote service. +We've already known that using `cluster-client` allows us to develop a 『pure』 RegistryClient without understanding the multi-process model. We can only focus on interacting with server, and use the `cluster-client` with a simple wrap to get a `ClusterClient` which supports multi-process model. The `RegistryClient` here is actually a `DataClient` that is specifically responsible for data communication with remote service. -You may have noticed that the ClusterClient brings with several constraints at the same time. If you want to expose the same approach to each process, RegistryClient can only support sub/pub mode and asynchronous API calls. Because all interactions in multi-process model must use socket communications, under which it is bound to bring this constraint. +You may have noticed that the `ClusterClient` brings with several constraints at the same time. If you want to expose the same approach to each process, `RegistryClient` can only support sub/pub mode and asynchronous API calls. Because all interactions in multi-process model must use socket communications, under which it is bound to bring this constraint. -Suppose we want to realize a synchronous get method. Put subscribed data directly into memory and use the get method to return data directly. How to achieve it? The real situation may be more complicated. +Suppose we want to realize a synchronous `get` method. Put subscribed data directly into memory and use the `get` method to return data directly. How to achieve it? The real situation may be more complicated. -Here, we introduce an APIClient best practice. For modules that have requirements of synchronous API such as reading cached data, an APIClient is encapsulated base on RegistryClient to implement these APIs that are not related to interaction with the remote server. The APIClient instance is exposed to the user. +Here, we introduce an `APIClient` best practice. For modules that have requirements of synchronous API such as reading cached data, an `APIClient` is encapsulated base on RegistryClient to implement these APIs that are not related to interaction with the remote server. The `APIClient` instance is exposed to the user. -In APIClient internal implementation: +In `APIClient` internal implementation: - To obtain data asynchronously, invoke RegistryClient's API base on ClusterClient. -- Interfaces that are unrelated to server, such as synchronous call, are to be implemented in APIClient. Since ClusterClient's APIs have flushed multi-process differences, there is no need to concern about multi-process model when calls to RegistryClient during developing APIClient. +- Interfaces that are unrelated to server, such as synchronous call, are to be implemented in `APIClient`. Since ClusterClient's APIs have flushed multi-process differences, there is no need to concern about multi-process model when calls to RegistryClient during developing `APIClient`. -For example, add a synchronous get method with buffer in the APIClient module: +For example, add a synchronous get method with buffer in the `APIClient` module: ```js // some-client/index.js @@ -489,8 +489,8 @@ in conclusion: ``` - RegistryClient - responsible for communicating with remote service, to access data, supports for asynchronous APIs only, and does't care about multi-process model. -- ClusterClient - a client instance that is simply wrapped by the cluster-client module and is responsible for automatically flushing differences in multi-process model. -- APIClient - internally calls ClusterClient to synchronize data, without the need to concern about multi-process model and is the final exposed module for users. APIs are exposed Through this, and support for synchronization and asynchronization. +- ClusterClient - a client instance that is simply wrapped by the `cluster-client` module and is responsible for automatically flushing differences in multi-process model. +- APIClient - internally calls `ClusterClient` to synchronize data, without the need to concern about multi-process model and is the final exposed module for users. APIs are exposed Through this, and support for synchronization and asynchronization. Students who are interested may have look at [enhanced multi-process development model](https://github.com/eggjs/egg/issues/322) discussion process. @@ -515,7 +515,7 @@ transcode | function | N/A | Serialization of interproc The above is about global configuration. If you want to do a separate setting for a client: -- You can override by setting the second argument `options` in` app/agent.cluster(ClientClass, options) ` +- You can override by setting the second argument `options` in `app/agent.cluster(ClientClass, options)`: ```js app.registryClient = app.cluster(RegistryClient, { @@ -525,7 +525,7 @@ app.registryClient = app.cluster(RegistryClient, { }); ``` -- You can also override the `getter` attribute of `clusterOptions` in `APIClientBase`. +- You can also override the `getter` attribute of `clusterOptions` in `APIClientBase`: ```js const APIClientBase = require('cluster-client').APIClientBase; diff --git a/docs/source/en/advanced/framework.md b/docs/source/en/advanced/framework.md index 4e10de9749..b1f5959209 100644 --- a/docs/source/en/advanced/framework.md +++ b/docs/source/en/advanced/framework.md @@ -1,32 +1,32 @@ title: Framework Development --- -During maintaining a number of projects, are your familiar with situations below: +If your team have met with these scenarios: - Each project contains the same configuration files that need to be copied every time, such as `gulpfile.js`, `webpack.config.js`. -- Each project has similiar dependancies. -- It's difficult to synchronize those projects based on the same configurations like those mentioned above once they have changed? +- Each project has similiar dependencies. +- It's difficult to synchronize those projects based on the same configurations like those mentioned above once they have been optimized? -Have your team got: +If your team needs: - a unified technique selection, such as the choice of databases, templates, frontend frameworks, and middlewares. - a unified default configuration to balance the deviation of different situations, which are not supposed to resolve in code level, like the differences between companies and open communities. - a unified [deployment plan](../core/deployment.md) keeping developers concentrate on code without paying attention to deployment details of connecting the framework and platforms. - a unified code style to decrease code's repetition and optimize code's appearance, which is important for a enterprise level framework. -To satisfy these demands, Egg endows developers with the capacity of `customazing a framework`. It is just an abstract layer, which is able to construct a higher level framework, supporting inheritance of unlimited times. Futhermore, Egg apply a quantity of coding conventions based on Koa. +To satisfy these demands, Egg endows developers with the capacity of `customazing a framework`. It is just an abstract layer, which can be constructed to a higher level framework, supporting inheritance of unlimited times. Futhermore, Egg apply a quantity of coding conventions based on Koa. -Therefore, a uniform spec can be applied on projects in which the differentiation fulfilled in plugins. And the best practice summed from those projects can continuously extracted from these plugins to the framework, which is available to other project by just updating the dependances' version. +Therefore, a uniform spec can be applied on projects in which the differentiation fulfilled in plugins. And the best practice summed from those projects can be continuously extracted from these plugins to the framework, which is available to other projects by just updating the dependencies' versions. See more details in [Progressive Development](../tutorials/progressive.md)。 -## Framework And Multiprocess +## Framework and Multiprocess -The framework extension is applied to Multiprocess Model, as we know [Multiprocess Model](../core/cluster-and-ipc.md) and the diffirences between Agent Worker and App Worker, which have diffirent API and both need to inherit. +The framework extension is applied to Multiprocess Model, as we know [Multiprocess Model](../core/cluster-and-ipc.md) and the differences between Agent Worker and App Worker, which have different APIs and both need to inherit. They both are inherited from [EggCore](https://github.com/eggjs/egg-core), and Agent is instantiated during the initiation of Agent Worker, while App is instantiated during the initiation of App Worker. -We could think about EggCore as the advanced version of Koa Application, which integrates built-in features such as [Loader](./loader.md)、[Router](../basics/router.md) and asynchronous launch. +We could regard EggCore as the advanced version of Koa Application, which integrates built-in features such as [Loader](./loader.md)、[Router](../basics/router.md) and asynchronous launch. ``` Koa Application @@ -40,7 +40,7 @@ We could think about EggCore as the advanced version of Koa Application, which i agent worker app worker ``` -## How To Customize A Framework +## How to Customize a Framework To setup by [egg-init](https://github.com/eggjs/egg-init) with the [framework](https://github.com/eggjs/egg-boilerplate-framework) option is a good way, in which generates a scaffold for you. @@ -134,7 +134,7 @@ As a loadUnit of framework, yadan is going to load specific directories and file ### Principle of Framework Extension -The path of framework is set as a varible named as `Symbol.for('egg#eggPath') to expose itself to Loader. Why? It seems that the simplest way is to pass a param to the constructor. The reason is to expose those paths of each level of inherited frameworks and reserve their sequences. Since Egg is a framework capable of unlimited inheritance, each layer has to designate their own eggPath so that all the eggPaths are accessiable through the prototype chain. +The path of framework is set as a varible named as `Symbol.for('egg#eggPath')` to expose itself to Loader. Why? It seems that the simplest way is to pass a param to the constructor. The reason is to expose those paths of each level of inherited frameworks and reserve their sequences. Since Egg is a framework capable of unlimited inheritance, each layer has to designate their own eggPath so that all the eggPaths are accessiable through the prototype chain. Given a triple-layer framework: department level > enterprise level > Egg @@ -164,7 +164,7 @@ const app = new Application(); app.ready(); ``` -These code are pseudocode to elaborate the framework's loading process, and we have provided scaffolds to [development](../core/development.md)和[deployment](../core/deployment.md). +These code are pseudocode to elaborate the framework's loading process, and we have provided scaffolds to [development](../core/development.md) and [deployment](../core/deployment.md). ### Custom Agent @@ -196,7 +196,7 @@ module.exports = Object.assign(egg, { }); ``` -** To be careful about that Agent and Application based on the same Class possess different APIs. ** +**To be careful about that Agent and Application based on the same Class possess different APIs.** ### Custom Loader @@ -371,5 +371,3 @@ describe('/test/index.test.js', () => { }); }); ``` - -[egg-bin]: https://github.com/eggjs/egg-bin diff --git a/docs/source/en/advanced/loader.md b/docs/source/en/advanced/loader.md index 367bae01ff..84d8cf5461 100644 --- a/docs/source/en/advanced/loader.md +++ b/docs/source/en/advanced/loader.md @@ -1,11 +1,11 @@ title: Loader --- -The most important of Egg enhance Koa is Egg based on a certain agreement, code will be placed in different directories according to the functional differences, it significantly reduce development costs. Loader supports this set of conventions and abstracts that many low-level APIs could be extended. +The most importance of Egg which enhanced Koa is that it is based on a certain agreement, code will be placed in different directories according to the functional differences, it significantly reduces development costs. Loader supports this set of conventions and abstracts that many low-level APIs could be extended. ## Application, Framework and Plugin -Egg is a low-level framework, applications could use it directly, but Egg only has a little default plugins, applications need to configure plugins to extend features, such as MySQL. +Egg is a low-level framework, applications could use it directly, but Egg only has a few default plugins, we need to configure plugins to extend features in application, such as MySQL. ```js // application configuration @@ -26,7 +26,7 @@ module.exports = { } ``` -With the increase number of applications, we find most of them have similar configurations, then we could extend a new framework based on Egg, which make the application configuration simpler. +With the increasing number of applications, we find most of them have similar configurations, so we could extend a new framework based on Egg, which makes application configurations simpler. ```js // framework configuration @@ -68,11 +68,11 @@ module.exports = { } ``` -From the above scene we can see the relationship of application, plugin and framework. +From the scene above we can see the relationship of application, plugin and framework. -- We implement business logics in application, we need to specify a framework to run, we could configure plugin to meet a special scene feature (such as MySQL). +- We implement business logics in application, and specify a framework to run, so we could configure plugin to meet a special scene feature (such as MySQL). - Plugin only performs specific function, when two separate functions are interdependent, they still need to be separated into two plugins, and requires configuration. -- Framework is a launcher (default is Egg), which is indispensable to run. Framework is also a wrapper, it aggregates plugins to provide functions unitedly, framework can also configure plugins. +- Framework is a launcher (default is Egg), which is indispensable to run. Framework is also a wrapper, it aggregates plugins to provide functions unitedly, and it can also configure plugins. - We can extend a new framework based on framework, which means that **framework can be inherited infinitely**, like class inheritance. ``` @@ -91,7 +91,7 @@ From the above scene we can see the relationship of application, plugin and fram ## loadUnit -Egg regard application, framework and plugin as loadUnit, because they are similar in code structure, here is the directory structure +Egg regards application, framework and plugin as loadUnit, because they are similar in code structure, here is the directory structure: ``` loadUnit @@ -117,7 +117,7 @@ loadUnit └── config.unittest.js ``` -However, there are still some differences +However, there are still some differences: File | Application | Framework | Plugin --- | --- | --- | --- @@ -132,13 +132,13 @@ config/config.{env}.js | ✔︎ | ✔︎ | ✔︎ config/plugin.js | ✔︎ | ✔︎ | package.json | ✔︎ | ✔︎ | ✔︎ -During the loading process, Egg will traverse all loadUnits to load the above files (application, framework and plugin are different), the loading process has priority. +During the loading process, Egg will traverse all loadUnits to load the files above(application, framework and plugin are different), the loading process has priority. -- follow the order Plugin => Framework => Application to load -- The order of loading plugins depends on the dependencies, depended plugins will be loaded first, no dependencies plugins are loaded by the object key configuration order, see [Plugin](./plugin.md) for details. +- Load according to `Plugin => Framework => Application`. +- The order of loading plugin depends on the dependencies, dependent plugins will be loaded first, independent plugins are loaded by the object key configuration order, see [Plugin](./plugin.md) for details. - Frameworks are loaded by the order of inheritance, the lower the more priority. -For example, an application is configured with the following dependencies +For example, an application is configured with the following dependencies: ``` app @@ -149,7 +149,7 @@ app └── egg ``` -The final loading order is +The final loading order is: ``` => plugin1 @@ -160,13 +160,13 @@ The final loading order is => app ``` -The plugin1 is framework1 depended plugin, the object key order of plugin1 after configuration merger is prior to plugin2/plugin3. Because of the dependencies between plugin2 and plugin3, the order is swapped. The framework1 inherits the Egg, so the order it after the egg. The application is the last to be loaded. +The plugin1 is framework1's dependent plugin, the object key order of plugin1 after configuration merger is prior to plugin2/plugin3. Because of the dependencies between plugin2 and plugin3, the order is swapped. The framework1 inherits the Egg, so the order is after the egg. The application is the last to be loaded. See [Loader.getLoadUnits](https://github.com/eggjs/egg-core/blob/65ea778a4f2156a9cebd3951dac12c4f9455e636/lib/loader/egg_loader.js#L233) method for details. ### File order -The above lists default loading files, Egg will load files by the following order, each file or directory will be loaded according to loadUnit order (application, framework and plugin are different). +The files that will be loaded by default are listed above. Egg will load files by the following order, each file or directory will be loaded according to loadUnit order (application, framework and plugin are different): - Loading [plugin](./plugin.md), find application and framework, loading `config/plugin.js` - Loading [config](../basics/config.md), traverse loadUnit to load `config/config.{env}.js` @@ -177,7 +177,7 @@ The above lists default loading files, Egg will load files by the following orde - Loading [controller](../basics/controller.md), loading application's `app/controller` directory - Loading [router](../basics/router.md), loading application's `app/router.js` -Note +Note: - Same name will be override in loading, for example, if we want to override `ctx.ip` we could define ip in application's `app/extend/context.js` directly. - See [framework development](./framework.md) for detail application launch order. @@ -186,7 +186,7 @@ Note The framework will convert file names when loading files, because there is a difference between the file naming style and the API style. We recommend that files use underscores, while APIs use lower camel case. For examplem `app/service/user_info.js` will be converted to `app.service.userInfo`. -The framework also supports hyphens and camel case. +The framework also supports hyphens and camel case: - `app/service/user-info.js` => `app.service.userInfo` - `app/service/userInfo.js` => `app.service.userInfo` @@ -258,7 +258,7 @@ module.exports = Object.assign(egg, { It's convenient for development team to customize loading via the Loader supported APIs. such as `this.model.xx`, `app/extend/filter.js` and so on. -The above is just a description of the Loader wording, see [Framework Development](./framework.md) for details. +The mention above is just a description of the Loader wording, please see [Framework Development](./framework.md) for details. ## Loader API @@ -266,7 +266,7 @@ Loader also supports some low level APIs to simplify code when extending, [here] ### loadFile -Used to load a file, such as loading `app.js` is using this method. +Used to load a file, such as loading `app/xx.js`: ```js // app/xx.js @@ -282,7 +282,7 @@ module.exports = app => { }; ``` -If the file export a function, then the function will be called with app as a parameter, otherwise using this value directly. +If the file exports a function, then the function will be called with `app` as its parameter, otherwise uses this value directly. ### loadToApp @@ -297,17 +297,17 @@ module.exports = app => { }; ``` -The method has three parameters `loadToApp(directory, property, LoaderOptions)` +The method has three parameters `loadToApp(directory, property, LoaderOptions)`: -1. directory could be String or Array, Loader will load files in those directories. -1. property is app's property. -1. [LoaderOptions](#LoaderOptions) are some configurations. +1. Directory could be String or Array, Loader will load files in those directories. +2. Property is app's property. +3. [LoaderOptions](#LoaderOptions) are some configurations. ### loadToContext -The difference between loadToApp and loadToContext is that loadToContext loading files into ctx instead of app, and it is lazy loading. Putting files into a temp object when loading, and instantiate object when calling ctx API. +The difference between loadToApp and loadToContext is that loadToContext loads files into ctx instead of app, and it's a lazy loading. It puts files into a temp object when loading, and instantiates objects when calling ctx APIs. -For example, service loading is this mode +We load service in this mode as an example: ```js // The following is just an example, using loadService in practice @@ -331,14 +331,14 @@ app.loader.loadToContext(servicePaths, 'service', { }); ``` -`app.serviceClasses.user` becomes UserService after file loading, instantiating UserService when calling `ctx.service.user`. -So this class will only be instantiated when first calling, and will be cached after instantiation, multiple calling same request will only instantiating once. +`app.serviceClasses.user` becomes UserService after file loading, it instantiates UserService when calling `ctx.service.user`. +So this class will only be instantiated when first calling, and will be cached after instantiation, multiple calling same request will be instantiated only once. ### LoaderOptions #### ignore [String] -ignore could ignore some files, supports glob, the default is empty +`ignore` could ignore some files, supports glob, the default is empty. ```js app.loader.loadToApp(directory, 'controller', { @@ -349,7 +349,7 @@ app.loader.loadToApp(directory, 'controller', { #### initializer [Function] -Processing each file exported values, the default is empty. +Processing each file exported values, the default is empty. ```js // app/model/user.js @@ -372,13 +372,13 @@ app.loader.loadToApp(directory, 'model', { File conversion rules, could be `camel`, `upper`, `lower`, the default is `camel`. -All three convert file name to camel case, but deal with the initials differently. +All three convert file name to camel case, but deal with the initials differently: - `camel`: initials unchanged. - `upper`: initials upper case. - `lower`: initials lower case. -Loading different files using different configurations. +Loading different files uses different configurations: File | Configuration --- | --- @@ -388,11 +388,11 @@ app/service | lower #### override [Boolean] -Overriding or throwing exception when encounter existing files, the default is false +Overriding or throwing exception when encounter existing files, the default is false. For example, the `app/service/user.js` is both loaded by the application and the plugin, if the setting is true, the application will override the plugin, otherwise an error will be throwed when loading. -Loading different files using different configurations. +Loading different files uses different configurations: File | Configuration --- | --- @@ -404,7 +404,7 @@ app/service | false Calling when export's object is function, and get the return value, the default is true -Loading different files using different configurations. +Loading different files uses different configurations: File | Configuration --- | --- diff --git a/docs/source/en/advanced/plugin.md b/docs/source/en/advanced/plugin.md index f66514b428..aa03f886da 100644 --- a/docs/source/en/advanced/plugin.md +++ b/docs/source/en/advanced/plugin.md @@ -2,15 +2,15 @@ title: Plugin Development --- -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: +Plugins are the most important features in Egg framework. They keep Egg simple, stable and efficient, and also they make the best reuse of business logic to build an entire ecosystem. Maybe we want to ask: -- 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 +- Since Koa already has the mechanism of middleware, Why do we need plugins? +- What are the differences / relationships among middlewares, plugins and applications? +- How can I use the plugin? +- How do I build a plugin? - ... -As we've already explained some these points in Chapter [using plugins](../basics/plugin.md) before. Now we are going through how to build a plugin. +As we've already explained some these points in chapter [using plugins](../basics/plugin.md) before. Now we are going through how to build a plugin. ## Plugin Development @@ -27,7 +27,7 @@ $ npm test ## Directory of Plugin -Plugin is actually a 'mini application', directory of plugin is as below +Plugin is actually a `mini application`, directory of plugin is as below: ```js . egg-hello @@ -56,7 +56,7 @@ Plugin is actually a 'mini application', directory of plugin is as below └── mw.test.js ``` -It is almost the same as the application directory, what's the difference? +It is almost the same as the application directory, what're the differences? 1. Plugin have no independant router or controller. This is because: @@ -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` of `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` - strong dependant plugins list of current plugin(if one of these plugins here is not found, application's startup will be failed) + - `{String} name` - plugin name(required), it must be unique, it will be used in the config of the dependencies of plugins. + - `{Array} dependencies` - strong dependent plugins list of the current plugin(if one of these plugins here is not found, application's startup will fail). - `{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. + - `{Array} env` - this option is available only when specifying the environment. For the list of env, please refer to [env](../basics/env.md). This is optional, most time you can leave it. ```json { @@ -90,7 +90,7 @@ It is almost the same as the application directory, what's the difference? ## Dependencies Management of Plugins -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` +The dependencies are managed by plugin himself, which is different from middlewares. 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 @@ -120,16 +120,16 @@ The dependencies are managed by plugin himself, this is different from middlewar } ``` -** Attention: The values in `dependencies` and `optionalDependencies` are the `eggPlugin.name` of plugins, not `package.name`. ** +**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`: +The `dependencies` and `optionalDependencies` are learnt from `npm`, most time we use `dependencies`, which is recommended. There are about two situations to apply the `optionalDependencies`: -- 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 +- Only be dependant in specific environment: for example, an 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 choices. -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. +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 +## What can plugin do? We've discussed what plugin is. Now what can it do? @@ -146,7 +146,7 @@ Extend the built-in objects of the framework, just like the application ### Insert Custom Middlewares -1. First, define and implement middleware under directory `app/middleware` +1. First, define and implement middleware under directory `app/middleware`: ```js 'use strict'; @@ -167,7 +167,7 @@ Extend the built-in objects of the framework, just like the application }; ``` -2. Insert middleware to the appropriate position in `app.js`(e.g. insert static middleware before bodyParser ) +2. Insert middleware to the appropriate position in `app.js`(e.g. insert static middleware before bodyParser): ```js const assert = require('assert'); @@ -181,9 +181,9 @@ Extend the built-in objects of the framework, just like the application }; ``` -### Initialization on Application Starting +### Initialization on application starting -- If you want to read some local config before startup +- If you want to read some local config before startup: ```js // ${plugin_root}/app.js @@ -197,7 +197,7 @@ Extend the built-in objects of the framework, just like the application }; ``` -- If you want to do some async starting business, 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 @@ -215,7 +215,7 @@ Extend the built-in objects of the framework, just like the application }; ``` -- You can add starting business of agent with `agent.beforeStart` API +- You can add initialization business of agent with `agent.beforeStart` API: ```js // ${plugin_root}/agent.js @@ -235,7 +235,7 @@ Extend the built-in objects of the framework, just like the application ### Setup Schedule Task -1. Setup dependencies of schedule plugin in `package.json` +1. Setup dependencies of schedule plugin in `package.json`: ```json { @@ -247,7 +247,7 @@ Extend the built-in objects of the framework, just like the application } ``` -2. Create a new file in `${plugin_root}/app/schedule/` directory to edit your schedule task +2. Create a new file in `${plugin_root}/app/schedule/` directory to edit your schedule task: ```js exports.schedule = { @@ -262,18 +262,18 @@ Extend the built-in objects of the framework, just like the application }; ``` -### Best Practice of Global Instance Plugin +### Best Practice of Global Instance 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: +Some plugins are made to introduce existing service into framework, like [egg-mysql], [egg-oss].They all need to create corresponding instances in applications. We notice that there are some common problems when developing plugins of this kind: -- 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) +- 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 (e.g: get the MySQL server address from configuration center and then create connection). -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. Note that while using the `app.addSingleton(name, creator)` method, the configuration file must have the `client` or `clients` key configuration as the `config` to the `creator` function. +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. Note that while using the `app.addSingleton(name, creator)` method, the configuration file must have the `client` or `clients` key configuration as the `config` to the `creator` function. -#### Writing Plugin +#### Ways of writing plugins -We simplify the [egg-mysql] plugin and to see how to write such a plugin: +We simplify the [egg-mysql] plugin to see how to write it: ```js // egg-mysql/app.js @@ -284,7 +284,7 @@ module.exports = app => { } /** - * @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 {Object} config The config is processed by the framework. If the application is configured with 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 */ @@ -303,7 +303,7 @@ function createMysql(config, app) { } ``` -The initialization function also support `Async function`, convenient for some special plugins that need to be asynchronous to get some configuration files. +The initialization function also supports `Async function`, convenient for some special plugins that need to be asynchronous to get some configuration files. ```js async function createMysql(config, app) { @@ -321,13 +321,13 @@ async function createMysql(config, app) { } ``` -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. +As you can see, all we need to do for this plugin is passing the fields 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 -1. Declare MySQL config in config file +1. Declare MySQL config in config file: ```js // config/config.default.js @@ -344,7 +344,7 @@ As you can see, all we need to do for this plugin is passing in the field that n }; ``` -2. Access database through `app.mysql` directly +2. Access database through `app.mysql` directly: ```js // app/controller/post.js @@ -357,7 +357,7 @@ As you can see, all we need to do for this plugin is passing in the field that n ##### Multiple Instances -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). Note that in this case,should use `get` function to specify the corresponding instance(eg: use `app.mysql.get('db1').query()` instead of using `app.mysql.query()` directly to get a `undefined`). +1. 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`). In this case, we should use `get` function to specify the corresponding instance(e.g: use `app.mysql.get('db1').query()` instead of using `app.mysql.query()` directly to get an `undefined`). ```js // config/config.default.js @@ -383,7 +383,7 @@ As you can see, all we need to do for this plugin is passing in the field that n }; ``` -2. Access the corresponding instance by `app.mysql.get('db1')` +2. Access the corresponding instance by `app.mysql.get('db1')`: ```js // app/controller/post.js @@ -396,7 +396,7 @@ As you can see, all we need to do for this plugin is passing in the field that n ##### Dynamically Instantiate -Instead of declaring the configuration in the configuration file in advance, We can dynamically initialize an instance at the runtime of 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 @@ -421,13 +421,13 @@ class PostController extends Controller { } ``` -**Attention, when creating the instance dynamically, framework would read the `default` configuration in the config file as the default configuration** +**Attention: when creating the instance dynamically, framework would read the `default` configuration from the config file as the default.** ### Plugin Locate Rule -When loading the plugins in the framework, it will follow the rules to locate them as below: +When loading the plugins in the framework, it will follow the rules below: -- If there is the path configuration, load them in path directly +- 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 @@ -436,15 +436,15 @@ When loading the plugins in the framework, it will follow the rules to locate th ### Plugin Specification -We are very welcome your contribution to the new plugins, but also hope you follow some of following specifications: +It's well welcomed to your contributions 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 lowercase,for example:`egg-xxx`. The long names should be concatenated with middle-lines:`egg-foo-bar`. +- Naming Rules: + - `npm` packages must append prefix `egg-`,and all letters must be lowercase, e.g: `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 + - The use of middle-lines is not compulsive, e.g: 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 convenient index, add `egg`,`egg-plugin`,`eggPlugin` in `keywords`. + - For convenient index, add `egg`,`egg-plugin`,`eggPlugin` in `keywords`: ```json { @@ -472,9 +472,9 @@ We are very welcome your contribution to the new plugins, but also hope you foll 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](../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. +First, Egg plugin does not only support npm packages, but also supports plugins-searching in local directory. In Chapter [progressive](../tutorials/progressive.md) we've mentioned how to make progress by using these two configurations. Directories are more friendly to unit tests. So, Egg can not ensure uniqueness through npm package names. -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. +What's more, Egg can use this feature to make an 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 controllers, because all these plugins have implemented the same APIs. **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. diff --git a/docs/source/en/advanced/view-plugin.md b/docs/source/en/advanced/view-plugin.md index 1e8bc28484..13848018ed 100644 --- a/docs/source/en/advanced/view-plugin.md +++ b/docs/source/en/advanced/view-plugin.md @@ -1,8 +1,8 @@ ## Title: View Plugin Development -In most cases, we need to read the data, render the template and then present it to the user. The framework does not force the use of a template engine, allowing developers to select the [template](../core/view.md) themselves. For details, see [Template Rendering](../core/view.md). +In most cases, we need to read the data, render the template and then present it to the user. The framework does not force to use one template engine, but allows developers to select the [template](../core/view.md) by themselves. For details, see [Template Rendering](../core/view.md). -This article describes the framework's specification constraints on the View plugin, and we can use this to encapsulate the corresponding template engine plugin. The following takes [egg-view-ejs] as an example +This article describes the framework's specification constraints on the View plugin, and we can use this to encapsulate the corresponding template engine plugin. The following takes [egg-view-ejs] as an example. ## Plugin directory structure @@ -83,23 +83,23 @@ Mmdule.exports = class EjsView { ### Parameters -The three parameters of the `render` method are +The three parameters of the `render` method are: -* filename: is the path to the complete file. The framework determines if the file exists when it looks for the file. It does not need to be processed here. -* locals: The data needed for rendering. The data comes from `app.locals`, `ctx.locals` and calls `render` methods. The framework also has built in `ctx`, `request`, `ctx.helper` objects. +* filename: is the path to the complete file. The framework determines if the file exists when looking for the file. It does not need to be processed here. +* locals: The data needs rendering. It comes from `app.locals`, `ctx.locals` and calls `render` methods. The framework also has built in `ctx`, `request`, `ctx.helper` objects. * viewOptions: The incoming configuration of the user, which can override the default configuration of the template engine. This can be considered based on the characteristics of the template engine. For example, the cache is enabled by default but a page does not need to be cached. -The three parameters of the `renderString` method +The three parameters of the `renderString` method: -* tpl: template string, not file path -* locals: same with `render` -* viewOptions: same with `render` +* tpl: template string, not file path. +* locals: same with `render`. +* viewOptions: same with `render`. ## Plugin configuration -According to the naming conventions mentioned above, the configuration name is generally the name of the template engine, such as ejs +According to the naming conventions mentioned above, the configuration name is generally the name of the template engine, such as ejs. -The configuration of the plugin mainly comes from the configuration of the template engine, and the configuration items can be defined according to the specific conditions, such as the [configuration of ejs](https://github.com/mde/ejs#options) +The configuration of the plugin mainly comes from the configuration of the template engine, and the configuration items can be defined according to the specific conditions, such as the [configuration of ejs](https://github.com/mde/ejs#options). ```js // config/config.default.js @@ -120,11 +120,11 @@ In template rendering, we often need to output a user-supplied html fragment, in