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
Javascript技術的蓬勃發展,爲先後端開發帶來了更順暢的體驗,顯著提高了開發效率。但仍有網友質疑Javascript可否勝任大型Web應用的開發。大型Web應用的特色是隨着業務的增加,須要開發大量的頁面組件。面對這種場景,通常有兩種解決方案:前端
1 採用單頁面的構建方式,缺點是產生的部署包很大。
2 採用頁面異步加載方式,缺點是頁面過於零散,須要頻繁與後端交互。vue
EggBorn.js實現了第三種解決方案:java
3 頁面組件按業務需求歸類,進行模塊化,而且實現了模塊的異步加載機制,從而彌合了前兩種解決方案的缺點,完美知足大型Web應用業務持續增加的需求。node
有了EggBorn.js,今後可複用的不只僅是組件,還有業務模塊。mysql
$ npm install -g egg-born複製代碼
$ egg-born project_name
$ cd project_name
$ npm install複製代碼
EggBorn.js目前提供了2個項目腳手架,分別是nginx
front-backend-mysql
-- 先後端全棧項目模板front
-- 前端項目模板,後端可採用其餘方案
若是採用了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複製代碼
爲了避免斷沉澱業務模塊,達到高度可複用的效果,全部模塊的命名空間必須充分隔離,避免相互污染與衝突,故採用以下命名方式:
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');複製代碼
在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的實現方式與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用於封裝業務邏輯,供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
爲了支持大型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倉庫複製代碼
目前只支持後端測試驅動
在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);
});
});複製代碼
在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託管前端靜態資源,並反向代理後端服務,配置以下
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;
}
}複製代碼