🥚 Born to build better enterprise frameworks and apps with Node.js & Koa

Overview

NPM version NPM quality NPM download

Continuous Integration Test coverage Known Vulnerabilities Open Collective backers and sponsors

Features

  • Built-in Process Management
  • Plugin System
  • Framework Customization
  • Lots of plugins

Quickstart

Follow the commands listed below.

$ mkdir showcase && cd showcase
$ npm init egg --type=simple
$ npm install
$ npm run dev
$ open http://localhost:7001

Node.js >= 8.0.0 required.

Documentations

Contributors

contributors

How to Contribute

Please let us know how can we help. Do check out issues for bug reports or suggestions first.

To become a contributor, please follow our contributing guide.

Sponsors and Backers

sponsors backers

License

MIT

Comments
  • [RFC] Rethink view

    [RFC] Rethink view

    我们现在的模板规范,约定插件名为 view,这样可以通过配置不同插件来切换模板,controller 不需要修改。

    但是会遇到多模板的情况的,比如 markdown 渲染会和普通模板同时存在,而且应用在模板过渡场景也会遇到多模板的情况,所以这种模板约定就会存在问题。

    方案

    提供模板注册机制,开发者可根据习惯来配置模板。

    提供 app.view,插件可通过 app.view.register 来注册模板

    app.view.use('ejs', EjsView);
    

    这时框架会将这个映射关系存到 map 中,EjsView 为类而非实例,和现在实现的类一致。

    开发者使用时需要配置映射关系

    // config/config.default.js
    exports.view = {
      root: '',
      cache: true,
      defaultExt: '.html',
      mapping: {
        '.ejs': 'ejs',
        '.html': 'ejs',
      }
    };
    
    exports.ejs = {
      // ejs config
    }
    

    在渲染时,会根据后缀匹配对应的 view 进行渲染,内置 loader 功能并带缓存。

    插件开发

    插件开发者跟原来会有一些差异

    插件名和配置不需要约定 view,自定义插件名

    {
      "eggPlugin": {
        "name": "ejs"
      }
    }
    

    config/config.default.js

    exports.ejs = { ... };
    

    注册 view 实例,renderrenderString 支持 promise、async function 和 generator function。

    // app.js
    module.exports = app => {
      class EjsView {
        constructor(ctx) {}
        * render() {}
        // render() { return Promise.resolve('html'); }
        // async render() {}
        * renderString() {}
        static getCache() {}
      }
      app.view.register('ejs', EjsView);
    };
    

    view 配置

    view 自带默认的 loader 功能,但不支持各种 view 的 include 特性。

    root

    默认为 app/view,也支持多目录,可以遍历多个目录搜索文件。但是模板的 include 特性需要模板自己实现,这个差异比较大。

    cache

    文件缓存(非渲染缓存,这个由模板引擎实现),如果文件已经搜索过了就不会再去搜索。

    不做渲染缓存是因为需要缓存 compile 结果,但不是每个模板引擎都支持的。

    defaultExt

    默认后缀,如果在 ctx.render 时不填后缀,则使用默认后缀。

    mapping

    后缀和模板的匹配关系。

    viewOptions

    ctx.render(name, locals, [viewOptions]) viewOptions 可以覆盖 view 配置,优先级最高。

    比如渲染时可以手动指定 viewEngine,而不根据后缀探测

    ctx.render('xx.html', {}, {
      viewEngine: 'nunjucks',
    });
    

    是否兼容

    考虑还未发布 1.0,可以去除原来的 view 方案,插件改造成本不高。

    已有插件改造

    • [x] egg-view https://github.com/eggjs/egg-view/pull/1
    • [x] egg 依赖 egg-view https://github.com/eggjs/egg/pull/402
    • [x] egg-view-nunjucks https://github.com/eggjs/egg-view-nunjucks/pull/11
    • [ ] egg-view-react
    • [x] egg-view-ejs https://github.com/eggjs/egg-view-ejs/pull/2
    • [ ] egg-view-xtpl
    • [x] egg-view-vue
    type: discussion type: proposals 
    opened by popomore 72
  • egg-scripts 部署脚本

    egg-scripts 部署脚本

    功能

    • [ ] 构建
      • [ ] 支持 nodeinstall
      • [ ] 支持语言层编译,如 typescript, babel
      • [ ] 自定义构建,如静态文件编译
    • [x] 启动

    构建

    环境依赖 cnpm,cnpm 会根据配置自动安装 node 到应用包中(需要实现)。

    应用添加 install-node 配置 node 版本,并添加 egg-scripts 依赖

    {
      "dependencies": {
        "egg-scripts": "*"
      },
      "engines": {
        "install-node": "6.9.1"
      }
    }
    

    构建顺序

    1. 下载应用代码
    2. 在应用代码执行 cnpm install --production
    3. 执行 egg-scripts build
      1. 预编译处理(ts,babel 等)
      2. 调用应用的 scripts.build(一般常用于处理静态文件构建)
      3. 修改 scripts.start/scripts.stop 指向 egg-scripts

    启动

    egg 构建出的应用包没有任何依赖,可直接部署

    1. 设置环境变量 PATH={app_root}/node_modules/.bin:$PATH
    2. 启动 npm startegg-scripts start

    扩展

    如果框架想提供部署功能,那么可以在框架层依赖 egg-scripts,这样应用就不需要添加了。但是层级比较深,可以在 postinstall 修改 ./node_modules/.bin/egg-scripts.

    egg-scritps 主要提供 build 扩展,可以继承 egg-scritps 处理自定义逻辑。

    type: feature type: proposals tools: egg-scripts 
    opened by popomore 71
  • [RFC] egg-socket.io

    [RFC] egg-socket.io

    Directory Structure

    app
    ├── io
    │   ├── controller
    │   │   └── chat.js
    │   └── middleware
    │       ├── auth.js
    │       ├── filter.js
    ├── router.js
    config
     ├── config.default.js
     └── plugin.js
    

    Configuration

    // {app_root}/config/config.default.js
    exports.io = {
      namespace: {
        '/': {
          connectionMiddleware: [],
          packetMiddleware: [],
        },
      },
      redis: {
        host: '127.0.0.1',
        port: 6379
      }
    };
    

    Middleware

    middleware are functions which every connection or packet will be processed by.

    Connection Middleware

    • Write your connection middleware app/io/middleware/auth.js
    module.exports = app => {
        return function* (next) {
            this.socket.emit('res', 'connected!');
            yield* next;
            // execute when disconnect.
            console.log('disconnection!');
        };
    };
    
    • then config this middleware to make it works.

    config/config.default.js

    exports.io = {
      namespace: {
        '/': {
          connectionMiddleware: ['auth'],
        },
      },
    };
    

    pay attention to the namespace, the config will only work for a specific namespace.

    Packet Middleware

    • Write your packet middleware app/io/middleware/filter.js
    module.exports = app => {
        return function* (next) {
            this.socket.emit('res', 'packet received!');
            console.log('packet:', this.packet);
            yield* next;
        };
    };
    
    • then config this middleware to make it works.

    config/config.default.js

    exports.io = {
      namespace: {
        '/': {
          packetMiddleware: ['filter'],
        },
      },
    };
    

    pay attention to the namespace, the config will only work for a specific namespace.

    Controller

    controller is designed to handle the emit event from the client.

    example:

    app/io/controller/chat.js

    module.exports = app => {
      return function* () {
        const message = this.args[0];
        console.log(message);
        this.socket.emit('res', `Hi! I've got your message: ${message}`);
      };
    };
    

    next, config the router at app/router.js

    module.exports = app => {
      // or app.io.of('/')
      app.io.route('chat', app.io.controllers.chat);
    };
    
    type: feature type: proposals 
    opened by atian25 68
  • nodejs-ClusterClientNoResponseError-client-no-response.md

    nodejs-ClusterClientNoResponseError-client-no-response.md

    • Node Version: 6.9.4 npm 4.1.1
    • Egg Version: 0.12.0
    • Plugin Name:
    • Plugin Version:
    • Platform: centos

    egg.js部署在alpha环境下,发现时常出现卡的现象,首次卡差不多在5-20秒之间。查看日志记录如下:

    • egg-web.log
    2017-02-27 10:40:19,002 INFO 25022 [tcp-base] the connection: 127.0.0.1:43504 is closed by other side
    2017-02-27 10:40:19,004 WARN 25022 [ClusterClient#Watcher] follower closed, and try to init it again
    2017-02-27 10:40:19,017 INFO 25022 [ClusterClient#Watcher] failed to seize port 43504, and this is follower client.
    2017-02-27 10:40:19,020 INFO 25022 [Follower#Watcher] register to channel: Watcher success
    2017-02-27 10:41:40,605 INFO 25022 [tcp-base] the connection: 127.0.0.1:43504 is closed by other side
    2017-02-27 10:41:40,606 WARN 25022 [ClusterClient#Watcher] follower closed, and try to init it again
    2017-02-27 10:41:40,611 INFO 25022 [ClusterClient#Watcher] failed to seize port 43504, and this is follower client.
    2017-02-27 10:41:40,613 INFO 25022 [Follower#Watcher] register to channel: Watcher success
    2017-02-27 10:43:05,202 INFO 25022 [tcp-base] the connection: 127.0.0.1:43504 is closed by other side
    2017-02-27 10:43:05,204 WARN 25022 [ClusterClient#Watcher] follower closed, and try to init it again
    2017-02-27 10:43:05,216 INFO 25022 [ClusterClient#Watcher] failed to seize port 43504, and this is follower client.
    2017-02-27 10:43:05,219 INFO 25022 [Follower#Watcher] register to channel: Watcher success
    2017-02-27 10:44:36,837 INFO 25022 [tcp-base] the connection: 127.0.0.1:43504 is closed by other side
    2017-02-27 10:44:36,838 WARN 25022 [ClusterClient#Watcher] follower closed, and try to init it again
    2017-02-27 10:44:36,845 INFO 25022 [ClusterClient#Watcher] failed to seize port 43504, and this is follower client.
    2017-02-27 10:44:36,847 INFO 25022 [Follower#Watcher] register to channel: Watcher success
    

    egg-agent.log

    2017-02-27 10:27:44,949 INFO 25012 [Leader#Watcher] socket connected, port: 52294
    2017-02-27 10:29:12,108 INFO 25012 [Leader#Watcher] socket connected, port: 52332
    2017-02-27 10:30:35,432 INFO 25012 [Leader#Watcher] socket connected, port: 52398
    2017-02-27 10:32:01,630 INFO 25012 [Leader#Watcher] socket connected, port: 52584
    2017-02-27 10:33:23,592 INFO 25012 [Leader#Watcher] socket connected, port: 52618
    2017-02-27 10:34:47,405 INFO 25012 [Leader#Watcher] socket connected, port: 52628
    2017-02-27 10:36:10,506 INFO 25012 [Leader#Watcher] socket connected, port: 52680
    2017-02-27 10:37:32,651 INFO 25012 [Leader#Watcher] socket connected, port: 52690
    2017-02-27 10:38:55,057 INFO 25012 [Leader#Watcher] socket connected, port: 52744
    2017-02-27 10:40:19,019 INFO 25012 [Leader#Watcher] socket connected, port: 52784
    2017-02-27 10:41:40,612 INFO 25012 [Leader#Watcher] socket connected, port: 53028
    2017-02-27 10:43:05,218 INFO 25012 [Leader#Watcher] socket connected, port: 53096
    2017-02-27 10:44:36,846 INFO 25012 [Leader#Watcher] socket connected, port: 53196
    

    common-error.log

    2017-02-27 00:01:25,445 ERROR 25012 nodejs.ClusterClientNoResponseError: client no response in 12397ms, maybe the connection is close on other side.
        at Timeout.Leader._heartbeatTimer.setInterval [as _onTimeout] (/pathxx/node_modules/cluster-client/lib/leader.js:59:23)
        at ontimeout (timers.js:365:14)
        at tryOnTimeout (timers.js:237:5)
        at Timer.listOnTimeout (timers.js:207:5)
    name: 'ClusterClientNoResponseError'
    pid: 25012
    hostname: host-192-168-51-80
    
    2017-02-27 00:02:45,501 ERROR 25012 nodejs.ClusterClientNoResponseError: client no response in 17120ms, maybe the connection is close on other side.
        at Timeout.Leader._heartbeatTimer.setInterval [as _onTimeout] (/pathxx/node_modules/cluster-client/lib/leader.js:59:23)
        at ontimeout (timers.js:365:14)
        at tryOnTimeout (timers.js:237:5)
        at Timer.listOnTimeout (timers.js:207:5)
    name: 'ClusterClientNoResponseError'
    pid: 25012
    hostname: host-192-168-51-80
    

    此日志文件记录内容最多

    web.log 此日志文件为空

    启动命令为

    nohup node index.js > np-show20170220.log 2>&1 &
    
    type: bug type: question 
    opened by itsky365 66
  • 中文文档

    中文文档

    • 介绍 intro
      • [x] 什么是 egg(是什么,解决什么问题,egg 重要特性,cluster,插件机制) https://github.com/eggjs/egg/pull/246 @popomore
      • [x] 基于 koa(介绍 koa,并介绍做了什么扩展) https://github.com/eggjs/egg/pull/179 @dead-horse
      • [x] hello world 快速上手(给一个 example 链接,解读代码,过一下 egg 基本功能) #217 @atian25
    • 功能详解 basics(只有 app,感知不到 agent)
      • [x] env(默认环境,自定义环境) https://github.com/eggjs/egg/pull/178 @popomore
      • [x] config(根据环境加载,顺序,appInfo,如何获取)https://github.com/eggjs/egg/pull/188 @popomore
      • [x] middleware(koa 中间件,参数,中间件顺序)#194 @dead-horse
      • [x] router @leoner (路由规则,原则,API)#203
      • [x] controller(controller, session ...)#209 @dead-horse
      • [x] service https://github.com/eggjs/egg/pull/221 @leoner
      • [x] schedule @dead-horse https://github.com/eggjs/egg/pull/202
      • [x] extend(extend koa api,加载顺序) https://github.com/eggjs/egg/pull/187 @shaoshuai0102
      • [x] 启动自定义(app.js)https://github.com/eggjs/egg/pull/193 @shaoshuai0102
    • 核心功能(内置插件功能) core
      • [x] security(安全介绍) https://github.com/eggjs/egg/pull/196 @jtyjty99999
      • [ ] passport/userservice
      • [ ] userrole @gxcsoccer
      • [ ] tracer @fengmk2
      • [x] httpclient https://github.com/eggjs/egg/pull/197 @fengmk2
      • [x] 日志(logrotator,logger,自定义,transport)https://github.com/eggjs/egg/pull/204 @shaoshuai0102
      • [x] 异常处理 @dead-horse
      • [x] i18n https://github.com/eggjs/egg/pull/208 @shaoshuai0102
      • [x] 本地开发和 debug https://github.com/eggjs/egg/pull/214 @shaoshuai0102
      • [x] 应用单元测试(用例写法,常用工具介绍,egg-mock) https://github.com/eggjs/egg/pull/199 @fengmk2
    • 最佳实践 practice
      • [x] view and assets(介绍 egg-view-nunjucks,如何使用 view,自定义 view,静态文件的例子) https://github.com/eggjs/egg/pull/228 @atian25
      • [x] 访问 MySQL https://github.com/eggjs/egg/pull/234 @jtyjty99999
      • [x] 实现 RESTful API https://github.com/eggjs/egg/pull/247 @dead-horse
      • [ ] 渐进式开发, 介绍 app -> plugin -> framework 的演变 https://github.com/eggjs/egg/pull/268 @atian25
    • 高级功能 advanced
      • [ ] ~~部署(构建打包,install-node,环境依赖,部署,可以添加一个 docker 的例子)~~
      • [x] cluster 模式,客户端规范(cluster-client,这里引入 agent 概念)https://github.com/eggjs/egg/pull/191 @gxcsoccer
      • [x] 开发插件(包括插件原理)https://github.com/eggjs/egg/pull/224 @gxcsoccer
      • [x] 开发框架(框架继承,如何扩展,原理) https://github.com/eggjs/egg/pull/225 @popomore
      • [x] 加载器(加载顺序,自定义加载) https://github.com/eggjs/egg/pull/198 @popomore
    type: document 
    opened by popomore 59
  • 每次修改文件重启报这么一堆错误

    每次修改文件重启报这么一堆错误

    每次修改文件重启报这么一堆错误

    017-02-22 15:39:37,488 WARN 184300 [master] App Worker#2:183904 started at 7001, remain 0 (15314ms)
    2017-02-22 15:39:37,489 INFO 184300 [master] App Worker#1:170060 disconnect, suicide: true, state: disconnected, current workers: ["2"]
    [Wed Feb 22 2017 15:39:37 GMT+0800 (中国标准时间)] [cfork:master:184300] worker:170060 disconnect (suicide: true, state: disconnected, isDead: false)
    [Wed Feb 22 2017 15:39:37 GMT+0800 (中国标准时间)] [cfork:master:184300] don't fork new work (refork: false)
    2017-02-22 15:39:37,493 ERROR 183332 nodejs.unhandledExceptionError: read ECONNRESET
        at exports._errnoException (util.js:1022:11)
        at TCP.onread (net.js:569:26)
    code: 'ECONNRESET'
    errno: 'ECONNRESET'
    syscall: 'read'
    name: 'unhandledExceptionError'
    pid: 183332
    hostname: zhanqi-pc
    
    
    [Wed Feb 22 2017 15:39:37 GMT+0800 (中国标准时间)][pid: 183332][Leader] Error: read ECONNRESET 
    Error Stack:
      Error: read ECONNRESET
        at exports._errnoException (util.js:1022:11)
        at TCP.onread (net.js:569:26)
    Error Additions:
      code: "ECONNRESET"
      errno: "ECONNRESET"
      syscall: "read"
    
    2017-02-22 15:39:37,494 ERROR 183332 nodejs.ECONNRESETError: read ECONNRESET
        at exports._errnoException (util.js:1022:11)
        at TCP.onread (net.js:569:26)
    code: 'ECONNRESET'
    errno: 'ECONNRESET'
    syscall: 'read'
    name: 'ECONNRESETError'
    pid: 183332
    hostname: zhanqi-pc
    
    [Wed Feb 22 2017 15:39:37 GMT+0800 (中国标准时间)] [cfork:master:184300] worker:170060 exit (code: null, suicide: true, state: dead, isDead: true, isExpected: true)
    
    type: question 
    opened by zlab 55
  • 1.0 Milestone

    1.0 Milestone

    Core

    • [x] egg @fengmk2
    • [x] egg-loader @gxcsoccer
    • [x] egg-logger @fengmk2
    • [x] egg-cluster @dead-horse
    • [x] egg-cookies @fengmk2

    Tool

    • [x] egg-mock @popomore
    • [x] egg-bin @fengmk2
    • [x] egg-init @fengmk2

    Plugin

    • [x] onerror @dead-horse
    • [x] userservice @shaoshuai0102
    • [x] userrole @atian25
    • [x] session @dead-horse
    • [x] i18n @gxcsoccer
    • [x] validate @dead-horse
    • [x] watcher @luckydrq
    • [x] multipart @gxcsoccer
    • [x] security @jtyjty99999
    • [x] development @jtyjty99999
    • [x] rest @shaoshuai0102
    • [x] static @dead-horse
    • [x] cors @dead-horse
    • [x] logrotater @jtyjty99999
    • [x] schedule @dead-horse
    • [x] view-nunjucks @@atian25

    framework

    • [x] aliyun-egg

    Doc

    • [x] egg README
    • [x] 其他 README 规范
    • [x] CONTRIBUTING
    • [x] Tutorial
    • [x] benchmark @fengmk2

    release ready

    • [x] session 需要优化,支持非 cookie 存储 koajs/session#66

    开发方式

    1. 先提交源码保证可以用,依赖层级比较深。
    2. 补测试,测试的目录接口和源码保持一致。
    3. 新仓库先提交空的 README,提交 PR 完成功能,补测试需要测试通过
    opened by popomore 54
  • graphql插件草案

    graphql插件草案

    需求

    egg暴露 graphql 服务

    GraphQL使用 Schema 来描述数据,并通过制定和实现 GraphQL 规范定义了支持 Schema 查询的 DSQL (Domain Specific Query Language,领域特定查询语言,由 FACEBOOK 提出。

    graphql

    传统 web 应用通过开发服务给客户端提供接口是很常见的场景。而当需求或数据发生变化时,应用需要修改或者重新创建新的接口。长此以后,会造成服务器代码的不断增长,接口内部逻辑复杂难以维护。而 GraphQL 则通过以下特性解决这个问题:

    • 声明式。查询的结果格式由请求方(即客户端)决定而非响应方(即服务器端)决定。你不需要编写很多额外的接口来适配客户端请求
    • 可组合。GraphQL 的查询结构可以自由组合来满足需求。
    • 强类型。每个 GraphQL 查询必须遵循其设定的类型才会被执行。

    也就是说,通过以上的三个特性,当需求发生变化,客户端只需要编写能满足新需求的查询结构,如果服务端能提供的数据满足需求,服务端代码几乎不需要做任何的修改。

    这里不会过多介绍 GraphQL 的概念,而会着重于确定如何通过 eggjs 来搭建 GraphQL 查询服务。

    技术选型

    使用 GraphQL Tools配合 eggjs 完成 GraphQL 服务的搭建。 GraphQL Tools 建立了一种 GraphQL-first 的开发哲学,主要体现在以下三个方面:

    • 使用官方的 GraphQL schema 进行编程。 GraphQL Tools 提供工具,让你可以书写标准的 GraphQL schema,并完全支持里面的特性。
    • schema 与业务逻辑分离。 GraphQL Tools 建议我们把 GraphQL 逻辑分为四个部分: Schema, Resolvers, Models, 和 Connectors。
    • 为很多特殊场景提供标准解决方案。最大限度标准化 GraphQL 应用。

    我们也会使用 GraphQL Server 来完成 GraphQL 查询语言 DSQL 的解析。

    同时我们会使用 dataloader 来优化数据缓存。

    使用 graphql-tag 来书写并解析graphql的schema。

    这些我们都会集成到 (egg-graphql)[https://github.com/eggjs/egg-graphql] 插件中。

    编写方式

    目录结构

    image

    跟 controller 平行有个 graphql 目录用来编写 graphql 服务

    代码

    根据 GraphQL Tools 的建议,我们把 GraphQL 逻辑分为四个部分: Schema, Resolvers, Models, 和 Connectors

    • schema.graphql 用于编写 graphql 的 schema
    • connector.js 编写 connector 逻辑,主要是跟数据库之间的侨接
    • Resolvers 编写对每个 query 的 resolver 逻辑。

    注意, Resolver 可能会通过 app.connector.xxx引用其他 graphql 类型下的connector。

    • Models 如果某个 graphql 服务跟orm框架对接,这个 Models 就是定义数据模型的 model

    之后应用会加载 graphql 目录下的所有服务,并使用GraphQL ToolsmakeExecutableSchema进行处理后挂载到app.schema上。

    并且将所有服务的connector挂载到app.connector下面。

    服务暴露方式

    暴露一个路由专门处理 graphql 服务。并针对这个路由封装一个 graphql 的 service,参考 https://github.com/eggjs/egg-graphql/pull/1 的实现。

    例子

    https://github.com/eggjs/egg-graphql/tree/in/test/fixtures/apps/graphql-app

    对 egg 框架的要求

    目前没有,可以讨论下是否把这种加载方式集成到egg-loader中。

    type: feature type: discussion 
    opened by jtyjty99999 52
  • 增强多进程研发模式

    增强多进程研发模式

    讨论结论

    1. 开发 DataClient

    • 模块开发者开发 DataClient(原来的 RealClient),只做异步 API,负责和远端服务通讯。
    • 一般只有 publish subscribe 和 generator/promise API
    • 开发这个客户端时无需了解多进程知识,只和服务端交互
    // DataClient
    const Base = require('sdk-base');
    
    class DataClient extends Base {
      constructor(options) {
        super(options);
        this.ready(true);
      }
    
      subscribe(info, listener) {
        // 向服务端 subscribe
      }
    
      publish(info) {
        // 向服务端 publish
      }
    
      * getData(id) {}
    }
    

    2. 包装出 clusterClient

    cluster-client 模块保持不变,通过 cluster(DataClient).create() 生成支持多进程的 ClusterClient

    const cluster = require('cluster-client');
    const clusterClient = cluster(DataClient).create();
    

    3. 开发 APIClient

    • 对于有数据缓存等同步 API 需求的模块,再额外封装一个 APIClient
    • 用户使用到的是这个 Client 的实例
    • 异步数据获取,通过调用 ClusterClient 的 API 实现。
    • 由于 clusterClient 的 API 已经被抹平了多进程差异,所以在开发 APIClient 时,也无需关心多进程知识。

    例如增加带缓存的 get 同步方法:

    const cluster = require('cluster-client');
    const DataClient = require('./data_client');
    
    class APIClient extends Base {
      constructor(options) {
        super(options);
        
        this._client = (options.cluster || cluster)(DataClient).create(options);
    
        this._client.ready(() => this.ready(true));
    
        this._cache = {};
    
        // config.subMap:
        // {
        //   foo: reg1,
        //   bar: reg2,
        // }
        for (const key in config.subMap) {
          this.subscribe(onfig.subMap[key], value => {
            this._cache[key] = value;
          });
        }
      }
    
      subscribe(reg, listener) {
        this._client.subscribe(reg, listener);
      }
    
      publish(reg) {
        this._client.publish(reg);
      }
    
      get(key) {
        return this._cache[key];
      }
    }
    

    4. 模块向外暴露 API

    module.exports = APIClient;
    

    5. plugin 开发

    // app.js || agent.js
    const APIClient = require('client-module'); // 上面那个模块
    module.exports = app => {
      const config = app.config.client;
      app.client = new APIClient(Object.assign({}, config, { cluster: app.cluster.bind(this) });
      app.beforeStart(function* () {
        yield app.client.ready();
      });
    };
    

    附:

    |------------------------------------------------|
    | APIClient                                      |
    |       |----------------------------------------|
    |       | clusterClient                          |
    |       |      |---------------------------------|
    |       |      | DataClient                      |
    |-------|------|---------------------------------|
    

    issue 原始内容如下

    cluster-client 模块给我们提供了强大的功能,在 sub/pub 模式下使得我们能够只开发一个客户端,在不同的进程中运行同样的 API,且复用同一个远端连接。

    一个最简单的例子:

    const Base = require('sdk-base');
    const cluster = require('cluster-client');
    
    class RealClient extends Base {
      constructor(options) {
        super(options);
        this.ready(true);
      }
    
      subscribe(info, listener) {
        // 向服务端 subscribe
      }
    
      publish(info) {
        // 向服务端 publish
      }
    
      * getData(id) {}
    }
    
    const client = cluster(RealClient).create({});
    

    这段代码运行于多个进程,我们在每个进程都可以使用:

    client.subscribe()
    client.publish()
    

    brilliant!

    但 ClusterClient 同时带来了一些约束,如果想在各进程暴露同样的方法,那么只能是 subscribe publish 和 generator 函数。

    假设我要实现一个同步的 get 方法,sub 过的数据直接放入内存,使用 get 方法时直接返回。要怎么实现呢?

    cluster-client 提供了 override 方法,你可能会想:

    const client = cluster(RealClient)
                           .overide('get', function() {})
                           .create({});
    

    这时你会发现不可行,因为还没有 create 之前,我们无法拿到实例进行提前 subscribe,进行缓存。

    所以这里建议一种模式,再包一个 API class:

    • 异步方法可以直接转调 clusterClient 实例
    • 同步方法直接实现
    class APIClient extends Base {
      constructor(options) {
        super(options);
    
        this._client = cluster(RealClient).create({});
        this._client.ready(() => this.ready(true));
    
        this._cache = {};
    
        // config.subMap:
        // {
        //   foo: reg1,
        //   bar: reg2,
        // }
        for (const key in config.subMap) {
          this.subscribe(onfig.subMap[key], value => {
            this._cache[key] = value;
          });
        }
      }
    
      subscribe(reg, listener) {
        this._client.subscribe(reg, listener);
      }
    
      publish(reg) {
        this._client.publish(reg);
      }
    
      get(key) {
        return this._cache[key];
      }
    }
    

    这样我们就可以:

    client.get('foo')
    

    当然这个例子太简单,看起来收益并不大。但是有些功能不仅仅是 sub 到数据就可以了,还需要做出复杂的处理,这种方式就变得有意义了。

    好处还有提供一种统一的拓展模式,否则只要是同步的 API,开发者都需要实现一种 patch 的方式,写法太灵活了。

    总结一下:

    |------------------------------------------------|
    | SDKClient                                      |
    |       |----------------------------------------|
    |       | ClusterClient                          |
    |       |      |---------------------------------|
    |       |      | RealClient                      |
    |-------|------|---------------------------------|
    

    这种增强可以进一步固化到 cluster-client 中。

    const client = cluster(RealClient, SDKClient)
    

    通过这种方式一键返回最后需要使用的客户端。当然,对于只需要 pub/sub 的简单客户端,直接 cluster(RealClient) 即可,和原来是一样的。

    type: feature core: agent 
    opened by shaoshuai0102 52
  • egg-core

    egg-core

    Loader

    做到灵活性,扩展性强。

    API

    loader 只提供原子粒度的 API,由框架来自行组织

    Env

    • [x] getServerEnv
    • [x] getEggPaths
    • [x] getLoadUnits
    • [x] getAppname

    Low Level API

    • [x] loadFile
    • [x] loadToApp
    • [x] loadToContext
    • [x] loadExtend

    High Level API

    • [x] loadPlugin
    • [x] loadConfig
    • [x] loadController
    • [x] loadMiddleware
    • [x] loadApplicationExtend
    • [x] loadContextExtend
    • [x] loadRequestExtend
    • [x] loadResponseExtend
    • [x] loadHelperExtend
    • [x] loadCustomApp
    • [x] loadCustomAgent
    • [x] loadService

    和插件的关系

    插件需要用到 loader 功能需要增加一个 loader 插件,比如 db 的功能

    |- app
      `- db
        `- a.js
    

    新增 egg-loader-db 插件

    // app.js
    module.exports = function(loader) {
      app.loader.loadToApp('app/db', 'db');
    };
    

    db 插件直接依赖 egg-loader-db 插件

    load unit

    每个加载单元的目录结构都是类似的,框架、插件、应用的路径都是称为 loadDir。

    |- app
      |- extend
      |- service
      |- controller
      |- middleware
      `- router.js
    |- config
      |- config.js
      `- plugin.js
    |- agent.js
    `- app.js
    

    LoaderOptions

    • directory: null,
    • target: null,
    • ignore: undefined,
    • lowercaseFirst: false,
    • initializer: null,
    • call: true,
    • override: false,
    • inject: undefined,

    Action

    • [x] 去除 loading
    • [x] app.js/agent.js 支持返回一个 promise https://github.com/eggjs/egg/issues/51
    • [x] 调整 console
    • [x] 去除 lib/core 潜规则?
    • [x] 去除 eggPath 和 customEgg 参数
    • [x] 增加 strict 模式 https://github.com/eggjs/egg/issues/52

    Core

    egg-core 是包含了 loader 和应用初始化的功能,简单理解就是一个有目录约定的 koa,初始化包括了 loader。

    • [x] loader
    • [x] ready
    • [x] logger
    • [x] router

    egg 就像其他框架一样只需要继承 egg-core 就可以直接使用


    @atian25

    嗯, 只需提供一个机制给框架开发者扩展, 如集团那边是有 proxy, 其他开发者可以定义:

    this.db.xxx -> app/db/xxx.js
    this.filters.xxx -> app/filters/xxx.js
    
    type: enhancement deps: egg-core 
    opened by atian25 52
  • [RFC] egg-core增加应用启动阶段

    [RFC] egg-core增加应用启动阶段

    背景描述

    有的插件可能需要异步操作后才能加载, 需要使用beforeStart来执行异步操作, 挂载到app上。

    同时应用启动前, 又在beforeStart中使用了这样的插件, 就可能不能正常启动。

    例如:

    // pluginA/index.js
    
    app.beforeStart(async ()=>{
      const bar = await pull();
      app.addSingleton('foo', (config,app) => {
        return new Foo(bar);
      });
    });
    
    
    // app.js
    
    app.beforeStart(async ()=>{
      await app.foo.foooo(); // app.foo 可能还没注入
    });
    
    

    解决方案

    ~~在EggCore上增加appReady。~~

    beforeAppStart(scope) {
      const done = this.appReady.readyCallback(name);
    
      process.nextTick(() => {
        this.ready()
        .then(()=>utils.callFn(scope))
        .then(() => done(), done);
      });
    }
    

    ~~服务器启动时也应该修改为app.appReady.ready(startServer);~~

    定义一个新目录, 暂时命名为app/checker

    在应用启动阶段(所有的beforeStart都执行完毕),

    调用app/checker目录下的checker实现, 调用完成后调用通过app.ready注册的回调。

    现征集该目录的命名, 暂有两个选项:

    • checker
    • boot

    cc: @gxcsoccer @XadillaX @popomore @coolme200

    core: ready type: discussion type: proposals 
    opened by killagu 48
  • egg-bin 开启 mochawesome 在 node 14 下有问题

    egg-bin 开启 mochawesome 在 node 14 下有问题

    在此输入你需要反馈的 Bug 具体信息(Bug in Detail):

    依赖找不到的问题,由于 mochawesome 被提升到 root 路径,导致它找不到 mocha

    可复现问题的仓库地址(Reproduction Repo)

    https://github.com/eggjs/egg-bin/pull/195

    Node 版本号:

    14

    Eggjs 版本号:

    3

    相关插件名称与版本号(PlugIn and Name):

    egg-bin 5

    操作平台与版本号(Platform and Version):

    macOS

    tools: egg-bin 
    opened by fengmk2 0
  • 升级 debug 写法

    升级 debug 写法

    请详细告知你的新解决思路(Your new RFCs):

    debug 模块我们一直在使用,过去几年有过多次的 major 版本更新,在 v2 v3 v4 之间都有混用。 随着 node util 的 debuglog 已经比较稳定和完善,计划在接下来逐步将 debug 使用 util.debuglog 代替。

    const debug = require('util').debuglog('egg:{moduleName}:{featureName}');
    

    跟进类型(Follow-up Types):

    • [X] 这是某个任务
    • [ ] 这是一个具体的 PR 的地址(URL)
    deps: egg-logger 
    opened by fengmk2 0
  • eggjs npm run start 怎么取消启动日志的输出

    eggjs npm run start 怎么取消启动日志的输出

    在此输入你需要反馈的 Bug 具体信息(Bug in Detail):

    npm run start 命令执行, 在云函数里每次冷启动会输出启动日志到日志服务里, 影响日志查找,也浪费存储空间

    可复现问题的仓库地址(Reproduction Repo)

    Node 版本号:

    Eggjs 版本号:

    相关插件名称与版本号(PlugIn and Name):

    操作平台与版本号(Platform and Version):

    opened by 3338606790 1
  • npm init egg --type=simple初始化项目报错 'create-egg' 不是内部或外部命令,也不是可运行的程序

    npm init egg --type=simple初始化项目报错 'create-egg' 不是内部或外部命令,也不是可运行的程序

    Your detail info about the Bug:

    node版本是v16.16.0 使用框架命令npm init egg --type=simple初始化项目 报错 'create-egg' 不是内部或外部命令,也不是可运行的程序

    Reproduction Repo

    nothing

    Node Version

    16.16.0

    Eggjs Version

    3.5

    Plugin Name and its version

    nothing

    Platform and its version

    Windows 10 专业版 21H1

    opened by chinesesnow 3
Releases(v3.9.2)
Owner
egg
A web framework's framework for Node.js
egg
🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈

Midway - 一个面向未来的云端一体 Node.js 框架 English | 简体中文 ?? 欢迎观看 Midway Serverless 2.0 发布会回放: https://www.bilibili.com/video/BV17A411T7Md 《Midway Serverless 发布

Midway.js 6.3k Jan 8, 2023
📦🔐A lightweight private proxy registry build in Node.js

Version 6 (Development branch) Looking for Verdaccio 5? Check branch 5.x. Verdaccio is a simple, zero-config-required local private npm registry. No n

Verdaccio 14.3k Dec 31, 2022
Catberry is an isomorphic framework for building universal front-end apps using components, Flux architecture and progressive rendering.

Catberry What the cat is that? Catberry was developed to help create "isomorphic/Universal" Web applications. Long story short, isomorphic/universal a

Catberry.js 801 Dec 20, 2022
LoopBack makes it easy to build modern API applications that require complex integrations.

LoopBack makes it easy to build modern applications that require complex integrations. Fast, small, powerful, extensible core Generate real APIs with

StrongLoop and IBM API Connect 4.4k Jan 4, 2023
Build Amazon Simple Queue Service (SQS) based applications without the boilerplate

sqs-consumer Build SQS-based applications without the boilerplate. Just define an async function that handles the SQS message processing. Installation

BBC 1.4k Dec 26, 2022
Fast and low overhead web framework, for Node.js

An efficient server implies a lower cost of the infrastructure, a better responsiveness under load and happy users. How can you efficiently handle the

Fastify 26k Jan 2, 2023
🚀 The Node.js Framework highly focused on developer ergonomics, stability and confidence

Sponsored by FOSS United is a non-profit foundation that aims at promoting and strengthening the Free and Open Source Software (FOSS) ecosystem in Ind

AdonisJS Framework 13.4k Dec 31, 2022
MVC framework making it easy to write realtime, collaborative applications that run in both Node.js and browsers

Derby The Derby MVC framework makes it easy to write realtime, collaborative applications that run in both Node.js and browsers. Derby includes a powe

DerbyJS 4.7k Dec 23, 2022
Marble.js - functional reactive Node.js framework for building server-side applications, based on TypeScript and RxJS.

Functional reactive Node.js framework for building server-side applications, based on TypeScript and RxJS. Ecosystem Name Description @marblejs/core F

Marble.js 2.1k Dec 16, 2022
Easily add filtering, sorting, and pagination to your Node.js REST API through your old friend: the query string!

QueryQL QueryQL makes it easy to add filtering, sorting, and pagination to your Node.js REST API through your old friend: the query string! Read our i

Truepic 99 Dec 27, 2022
Fast, unopinionated, minimalist web framework for node.

Fast, unopinionated, minimalist web framework for node. const express = require('express') const app = express() app.get('/', function (req, res) {

null 59.5k Jan 5, 2023
Expressive middleware for node.js using ES2017 async functions

Expressive HTTP middleware framework for node.js to make web applications and APIs more enjoyable to write. Koa's middleware stack flows in a stack-li

Koa.js 33.5k Jan 4, 2023
Realtime MVC Framework for Node.js

Website Get Started Docs News Submit Issue Sails.js is a web framework that makes it easy to build custom, enterprise-grade Node.js apps. It is design

Balderdash 22.4k Dec 31, 2022
The future of Node.js REST development

restify is a framework, utilizing connect style middleware for building REST APIs. For full details, see http://restify.com Follow restify on Usage Se

restify 10.6k Jan 2, 2023
Use full ES2015+ features to develop Node.js applications, Support TypeScript.

ThinkJS Use full ES2015+ features to develop Node.js applications, Support TypeScript. 简体中文文档 Installation npm install -g think-cli Create Application

ThinkJS 5.3k Dec 30, 2022
:rocket: Progressive microservices framework for Node.js

Moleculer Moleculer is a fast, modern and powerful microservices framework for Node.js. It helps you to build efficient, reliable & scalable services.

MoleculerJS 5.5k Jan 4, 2023
Node.js framework

Node.js framework Total.js framework is a framework for Node.js platfrom written in pure JavaScript similar to PHP's Laravel or Python's Django or ASP

Total.js 4.2k Jan 2, 2023
API Services Made Easy With Node.js

Nodal API Services Made Easy with Node.js View the website at nodaljs.com. Nodal is a web server and opinionated framework for building data manipulat

Keith Horwood 4.5k Dec 26, 2022
A microservices toolkit for Node.js.

A Node.js toolkit for Microservice architectures This open source module is sponsored and supported by Voxgig. seneca Lead Maintainer: Richard Rodger

Seneca Microservices Framework 3.9k Dec 19, 2022