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
: amoduleName
: versionfullName
: egg-born-module-a-versionrelativeName
: 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使用以/
開頭的絕對路徑。query
、params
、body
: 與常規的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複製程式碼
前端架構配置
前端啟動檔案
前端架構提供兩種方案
- Vue.js + Framework7
- 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;
}
}複製程式碼