113

EggBorn.js:一款顶级Javascript全栈开发框架

 6 years ago
source link: https://juejin.im/post/59f1be7c6fb9a04515431d2c
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

EggBorn.js是什么

EggBorn.js是一款顶级Javascript全栈开发框架。

EggBorn.js是采用Javascript进行全栈开发的最佳实践。
EggBorn.js不重复造轮子,而是采用业界最新的开源技术,进行全栈开发的最佳组合。
EggBorn.js前端采用Vue.js + Framework7 / Vue Router + Webpack,后端采用Koa.js + Egg.js,数据库采用mysql。
EggBorn.js时刻跟踪开源技术的最新成果,并持续优化,使整个框架时刻保持最佳状态。

EggBorn.js重点解决什么问题:业务模块化

Javascript技术的蓬勃发展,为前后端开发带来了更顺畅的体验,显著提升了开发效率。但仍有网友质疑Javascript能否胜任大型Web应用的开发。大型Web应用的特点是随着业务的增长,需要开发大量的页面组件。面对这种场景,一般有两种解决方案:

1 采用单页面的构建方式,缺点是产生的部署包很大。
2 采用页面异步加载方式,缺点是页面过于零散,需要频繁与后端交互。

EggBorn.js实现了第三种解决方案:

3 页面组件按业务需求归类,进行模块化,并且实现了模块的异步加载机制,从而弥合了前两种解决方案的缺点,完美满足大型Web应用业务持续增长的需求。

EggBorn.js的技术特点

  • 业务模块化:页面组件按模块组织
  • 加载方式灵活:模块既可异步加载,也可同步加载
  • 模块高度内聚:模块包括前端页面组件和后端业务逻辑
  • 参数配置灵活:模块中的前后端可以单独进行参数配置
  • 国际化:模块中的前后端均支持独立的国际化
  • 模块隔离:模块的页面、数据、逻辑、路由、配置等元素均进行了命名空间隔离处理,避免模块之间的变量污染与冲突
  • 超级易用的事务处理:只需在路由记录上配置一个参数,即可完美实现数据库的事务处理。
  • 渐进式开发:由于模块的高度内聚,可以将业务以模块的形式沉淀,在多个项目中重复使用,既可贡献到npm开源社区,也可部署到公司内部私有npm仓库。

有了EggBorn.js,从此可复用的不仅仅是组件,还有业务模块。

安装EggBorn.js脚手架

$ npm install -g egg-born复制代码
$ egg-born project_name
$ cd project_name
$ npm install复制代码

EggBorn.js目前提供了2个项目脚手架,分别是

  • front-backend-mysql -- 前后端全栈项目模板
  • front -- 前端项目模板,后端可采用其他方案

配置mysql连接参数

如果采用了front-backend-mysql模板,请配置mysql连接参数(空数据库即可)

编辑src/backend/config/config.default.js文件

  // mysql
  config.mysql = {
    clients: {
      // donot change the name  
      __ebdb: {
        host: '127.0.0.1',
        port: '3306',
        user: 'travis',
        password: '',
        database: 'egg-born',
      },
    },
  };复制代码

启动后端服务

$ npm run dev:backend复制代码

启动前端服务

$ npm run dev:front复制代码

EggBorn.js架构图

b6c55b63bb50db96a89f98d184a5e139~tplv-t2oaga2asx-zoom-in-crop-mark:1304:0:0:0.awebp

项目文件结构

79ba7285c5656b7885dcc50549181142~tplv-t2oaga2asx-zoom-in-crop-mark:1304:0:0:0.awebp

模块文件结构

733e00d0a7c6e50481bf298e8c4249e0~tplv-t2oaga2asx-zoom-in-crop-mark:1304:0:0:0.awebp


cb075d46eeb7fb5533aab90b5ee2b589~tplv-t2oaga2asx-zoom-in-crop-mark:1304:0:0:0.awebp

为了不断沉淀业务模块,达到高度可复用的效果,所有模块的命名空间必须充分隔离,避免相互污染与冲突,故采用如下命名方式:

egg-born-module-{providerId}-{moduleName}

如模块egg-born-module-a-version,各环节命名信息如下:

  • providerId: a
  • moduleName: version
  • fullName: egg-born-module-a-version
  • relativeName: a-version
  • 前端页面路由地址: /a/version/{page}
  • 后端API路由地址:/a/version/{controller}/{action}

模块既支持异步加载,也支持同步加载。默认是异步加载,如果要同步加载,只需在模块名称后面加上-sync后缀,如模块egg-born-module-aa-login-sync

进入src/module目录执行脚手架,创建模块文件骨架

$ egg-born module_relative_name复制代码

EggBorn.js目前提供了2个模块脚手架,分别是

  • module -- 全栈模块模板
  • module-front -- 前端模块模板

模块前端开发

前端页面路由

front/src/routes.js中添加页面路由,如

function load(name) {
  return require(`./pages/${name}.vue`).default;
}

export default [
  { path: 'welcome/:who', component: load('welcome') },
  { path: 'profile', component: load('profile'), meta: { requiresAuth: true } },
  { path: '/login', component: load('login') },
];复制代码
  • path: 路径,支持参数。以/开头,代表根页面组件。login页面组件通常这样配置
  • component: 页面组件对象
  • meta: 路由元数据
  • meta.requiresAuth: 如果页面组件需要登录,须设为true

在页面中引用页面组件,请使用绝对路径,如

<f7-list-item link="/aa/hello/welcome/You" title="Welcome"></f7-list-item>
<f7-list-item link="/aa/hello/profile" title="Profile"></f7-list-item>复制代码

前端状态管理

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。EggBorn.js采用Vuex实现了完全隔离的模块状态管理机制。
front/src/store.js中添加状态,如

export default function(Vue) {

  return {
    state: {
      message: 'hello world',
    },
  };

}复制代码

在页面组件中访问本模块状态

const message = this.$local.state.message;复制代码

在页面组件中访问其他模块状态

const message = this.$store.state[providerId][moduleName].message;复制代码

更多信息,请参阅: Vuex

前端参数配置

front/src/config/config.js中添加配置信息,如

export default {
  mode: 1,
};复制代码

只支持在页面组件中访问本模块内部的参数配置

const mode = this.$config.mode;复制代码

前端国际化

front/src/config/locale目录添加国际化文件
zh-cn.js文件中的语言定义示例如下

export default {
  mode: '模式',
  "Hello world! I'm %s.": '您好,世界!我是%s。',  
};复制代码

国际化语言采取全局合并的方式,有利于语言资源的共享,在页面组件中访问方式如下

const mode = this.$text('mode');
const message = this.$text("Hello world! I'm %s.",'zhennann');复制代码

模块后端开发

后端api路由

backend/src/routes.js中添加api路由,如

const home = require('./controller/home.js');

module.exports = [
  { method: 'get', path: 'home/index', controller: home, action: 'index', transaction: true },
];复制代码
  • method: get/post等方法
  • path: 路径,支持参数
  • component: Controller对象
  • action: Controller方法,如果不设置,则自动采用path尾部单词
  • transaction: 默认为false,如果设为true,则启用数据库事务

在前端页面组件中访问本模块api路由

this.$api.get('home/index').then(data => {
}).catch(err => {
});复制代码

在前端页面组件中访问其他模块api路由

this.$api.get('/providerId/moduleName/home/index').then(data => {
}).catch(err => {
});复制代码

后端Controller

后端Controller的实现方式与Egg.js保持一致

module.exports = app => {
  class HomeController extends app.Controller {

    async index() {
      const message = await this.service.home.index();
      this.ctx.success(message);
    }

  }
  return HomeController;
};复制代码

更多信息,请参阅: Egg.js Controller

后端Service

Service用于封装业务逻辑,供Controller调用,实现方式与Egg.js保持一致。

module.exports = app => {
  class Home extends app.Service {

    async index() {
      const res = await this.ctx.db.queryOne('show tables');
      return res;
    }

  }

  return Home;
};复制代码

与Egg.js不同之处在于,Service使用ctx.db操作数据库,从而自动支持数据库事务。

更多信息,请参阅: Egg.js Service

后端Controller调用

为了支持大型Web系统的开发,EggBorn.js支持模块后端Controller之间的调用,如

const message = await this.ctx.performAction({
  method: 'get',
  url: 'home/index',
  query: {
    username: 'kevin',
  },
  params: {
    mode: 1,
  },
  body: {
    content: 'ready',
  },
});复制代码
  • method: get/post等方法
  • url: 访问本模块的Controller使用相对路径,访问其他模块的Controller使用以/开头的绝对路径。
  • queryparamsbody: 与常规的Controller参数保持一致

后端数据库操作

后端数据库操作与Egg.js保持一致

更多信息,请参阅: Egg.js MySQL

后端数据库事务

EggBorn.js提供了更为便利的数据库事务实现方式,只需在后端api路由记录中配置transaction参数,Service使用ctx.db操作数据库。
如果是主Controller通过ctx.performAction调用子Controller,数据库事务开启规则如下:

主Controller配置 子Controller配置 子Controller实际启用
true true true
true false true
false true true
false false false

后端参数配置

backend/src/config/config.js中添加配置信息,如

module.exports = appInfo => {
  const config = {};

  config.message = "Hello world! I'm %s.";

  return config;
};复制代码

访问本模块内部的参数配置示例如下

const message = this.ctx.config.message;复制代码

后端国际化

backend/src/config/locale目录添加国际化文件
zh-cn.js文件中的语言定义示例如下

module.exports = {
  "Hello world! I'm %s.": '您好,世界!我是%s。',
  'not found': '未发现',
};复制代码

国际化语言采取全局合并的方式,有利于语言资源的共享,访问方式如下

const notFound = this.ctx.text('not found');
const message = this.ctx.text("Hello world! I'm %s.", 'zhennann');复制代码

后端错误处理

backend/src/config/errors.js文件中添加错误代码

// error code should start from 1001
module.exports = {
  1001: 'not found',
};复制代码

返回错误信息示例如下

this.ctx.fail(1001);复制代码

也可抛出异常示例如下

this.ctx.throw(1001);复制代码

EggBorn.js通过package.json文件管理模块依赖关系。
比如,模块aa-module1依赖aa-module2,需要在模块aa-module1的package.json文件中作如下配置

{
  "name": "egg-born-module-aa-module1",
  "version": "0.0.1",
  "eggBornModule": {
    "dependencies": {
      "aa-module2": "0.0.1"
    }
  },
  "dependencies": {
    "egg-born-module-aa-module2": "^0.0.1"
  }
}复制代码

设置"egg-born-module-aa-module2": "^0.0.1",是为了在安装模块aa-module1时自动安装模块aa-module2。如果模块没有公开发布,就不必设置。

模块数据版本

模块一般都要操作数据库,当模板版本升级时,数据库结构也有可能变动。EggBorn.js实现了模块数据版本的管理,便于业务模块的积累沉淀。

在模块的package.json文件中配置fileVersion为当前数据版本

{
  "name": "egg-born-module-aa-module1",
  "version": "0.0.1",
  "eggBornModule": {
    "fileVersion": 1
  }
}复制代码

在模块后端添加Api路由

{ method: 'post', path: 'version/update', controller: version }复制代码

添加version Controller

module.exports = app => {
  class VersionController extends app.Controller {

    async update() {
      await this.service.version.update(this.ctx.getInt('version'));
      this.ctx.success();
    }

  }
  return VersionController;
};复制代码

添加version Service

module.exports = app => {

  class Version extends app.Service {

    async update(version) {
      if (version === 1) {
        // do something
      }
    }

  }

  return Version;
};复制代码

当启动后端服务时,EggBorn.js自动检测模块数据版本的变化,并执行相应的路由,完成数据的版本升级。

当项目中的模块代码稳定后,可以将模块公开发布,贡献到开源社区。也可以在公司内部建立npm私有仓库,然后把模块发布到私有仓库,形成公司资产,便于重复使用。
模块发布步骤如下

$ cd path/to/module      -- 进入模块目录
$ npm install            -- 安装模块依赖
$ npm run build:front    -- 构建前端代码
$ npm run build:backend  -- 构建后端代码
$ npm publish            -- 发布至npm仓库复制代码

目前只支持后端测试驱动

后端Controller测试

backend/test/controller目录添加Controller测试文件

// controller/home.test.js
const { app, mock, assert } = require('egg-mock/bootstrap');
const parseMockUrl = function(url) {
  const prefix = app.mockUtil.parseUrlFromPackage(__dirname);
  return `${prefix}${url}`;
};

describe('test/controller/home.test.js', () => {

  it('action:index', async () => {
    const result = await app.httpRequest().get(parseMockUrl('home/index'));
    assert(result.body.code === 0);
  });

});复制代码

后端Service测试

backend/test/service目录添加Service测试文件

// service/home.test.js
const { app, mock, assert } = require('egg-mock/bootstrap');
const parseMockUrl = function() {
  return app.mockUtil.parseUrlFromPackage(__dirname);
};

describe('test/service/home.test.js', () => {

  it('index', async () => {
    const ctx = app.mockContext({ mockUrl: parseMockUrl() });
    const message = await ctx.service.home.index();
    assert(message);
  });

});复制代码

在项目根目录执行测试

$ npm run test:backend
$ npm run cov:backend复制代码

前端架构配置

前端启动文件

前端架构提供两种方案

  1. Vue.js + Framework7
  2. Vue.js + Vue Router

Framework7是移动开发专属UI界面库,内置路由机制。
Vue Router是Vue.js官方路由库,使用Vue Router可搭配其他各种UI界面库。

src/front/main.js文件中进行切换

// choose one

//   framework7
import main from './framework7/main.js';

//   vuerouter
// import main from './vuerouter/main.js';

// export
export default main;复制代码

前端参数配置

src/front/config/config.js文件中的参数配置可以覆盖模块的参数

export default{
  module: {
    'aa-hello': {
      mode: 2,
    },
  },
};复制代码

前端国际化

src/front/config/locale目录添加国际化文件,可以覆盖模块的国际化语言
zh-cn.js文件中的语言定义示例如下

export default {
  mode: '模式',
};复制代码

后端架构配置

后端架构基于Egg.js,完整支持Egg.js提供的所有功能与特性

更多信息,请参阅: Egg.js

后端参数配置

src/backend/config/config.default.js文件中的参数配置可以覆盖模块的参数

module.exports = appInfo => {
  const config = {};

  // module config
  config.module = {
    'aa-hello': {
      mode: 2,
    },
  };

  return config;
};复制代码

后端国际化

src/backend/config/locale目录添加国际化文件,可以覆盖模块的国际化语言
zh-cn.js文件中的语言定义示例如下

module.exports = {
  mode: '模式',
};复制代码

构建前端代码

$ npm run build:front复制代码

启动后端服务

$ npm run start:backend复制代码

停止后端服务

$ npm run stop:backend复制代码

后端服务启动参数配置

编辑build/config.js文件

// backend
const backend = {
  port: 7002,
  hostname: '127.0.0.1',
};复制代码

nginx配置

强烈建议使用nginx托管前端静态资源,并反向代理后端服务,配置如下

server {
  listen 80;
  server_name example.com www.example.com;
  set $node_port 7002;

  root /path/to/www;

  location  /api/ {
    proxy_http_version 1.1;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-NginX-Proxy true;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass http://127.0.0.1:$node_port$request_uri;
    proxy_redirect off;
  }

}复制代码

GitHub贡献

有任何疑问,欢迎提交 issue, 或者直接修改提交 PR


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK