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

zhennann發表於2019-02-28

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

系統架構

專案檔案結構

模組檔案結構


模組開發

命名約定

為了不斷沉澱業務模組,達到高度可複用的效果,所有模組的名稱空間必須充分隔離,避免相互汙染與衝突,故採用如下命名方式:

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

相關文章