EggBorn.js:一款頂級Javascript全棧開發框架

EggBorn.js是什麼

EggBorn.js是一款頂級Javascript全棧開發框架。javascript

EggBorn.js是採用Javascript進行全棧開發的最佳實踐。
EggBorn.js不重複造輪子,而是採用業界最新的開源技術,進行全棧開發的最佳組合。
EggBorn.js前端採用Vue.js + Framework7 / Vue Router + Webpack,後端採用Koa.js + Egg.js,數據庫採用mysql。
EggBorn.js時刻跟蹤開源技術的最新成果,並持續優化,使整個框架時刻保持最佳狀態。html

EggBorn.js重點解決什麼問題:業務模塊化

Javascript技術的蓬勃發展,爲先後端開發帶來了更順暢的體驗,顯著提高了開發效率。但仍有網友質疑Javascript可否勝任大型Web應用的開發。大型Web應用的特色是隨着業務的增加,須要開發大量的頁面組件。面對這種場景,通常有兩種解決方案:前端

1 採用單頁面的構建方式,缺點是產生的部署包很大。
2 採用頁面異步加載方式,缺點是頁面過於零散,須要頻繁與後端交互。vue

EggBorn.js實現了第三種解決方案:java

3 頁面組件按業務需求歸類,進行模塊化,而且實現了模塊的異步加載機制,從而彌合了前兩種解決方案的缺點,完美知足大型Web應用業務持續增加的需求。node

EggBorn.js的技術特色

  • 業務模塊化:頁面組件按模塊組織
  • 加載方式靈活:模塊既可異步加載,也可同步加載
  • 模塊高度內聚:模塊包括前端頁面組件和後端業務邏輯
  • 參數配置靈活:模塊中的先後端能夠單獨進行參數配置
  • 國際化:模塊中的先後端均支持獨立的國際化
  • 模塊隔離:模塊的頁面、數據、邏輯、路由、配置等元素均進行了命名空間隔離處理,避免模塊之間的變量污染與衝突
  • 超級易用的事務處理:只需在路由記錄上配置一個參數,便可完美實現數據庫的事務處理。
  • 漸進式開發:因爲模塊的高度內聚,能夠將業務以模塊的形式沉澱,在多個項目中重複使用,既可貢獻到npm開源社區,也可部署到公司內部私有npm倉庫。

有了EggBorn.js,今後可複用的不只僅是組件,還有業務模塊。mysql

快速上手

安裝EggBorn.js腳手架

$ npm install -g egg-born複製代碼

新建項目

$ egg-born project_name
$ cd project_name
$ npm install複製代碼

EggBorn.js目前提供了2個項目腳手架,分別是nginx

  • front-backend-mysql -- 先後端全棧項目模板
  • front -- 前端項目模板,後端可採用其餘方案

配置mysql鏈接參數

若是採用了front-backend-mysql模板,請配置mysql鏈接參數(空數據庫便可)git

編輯src/backend/config/config.default.js文件github

// 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架構圖

系統架構

項目文件結構

模塊文件結構


模塊開發

命名約定

爲了避免斷沉澱業務模塊,達到高度可複用的效果,全部模塊的命名空間必須充分隔離,避免相互污染與衝突,故採用以下命名方式:

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

相關文章
相關標籤/搜索