前言
Elpis是一套沉淀80%的重复性工作,可支持配置化的企业级方案,并且可以针对20%定制化工作进行开发。方案落地为系统平台,且可持续集成;平台需要支持多系统建设,且系统间功能可复用;方案属于全栈开发,会引入nodejs,devOps等流程。
这套企业级开发框架是由大佬手把手指导(抖音“哲玄前端”,《大前端全栈实践课》)完成。
进度: 1、Elpis-core基于Koa构建
关于Elpis-core基于Koa构建
Elpis-core 是基于Koa框架构建的,其中包括:接入层、业务层、服务层。
- 接入层:包括 router接口路由分发、router-schema路由规则、middleware路由中间件
- 业务层:包括 controller处理器、env环境分发、config提取、extend服务拓展、schedule定时任务
- 服务层:service处理器
其中的核心是const app = new Koa() 中的 app对象,这个app对象贯穿了整个项目,将约定好的app/文件下的配置挂载到app对象上。项目的要点就是约定大于配置。
Elpis目录结构
elpis/
|-- app/ // app 文件目录 -- 约定大于配置
|-- controller/ // 业务逻辑目录
|-- extend/ // 拓展工具目录
|-- middleware/ // 中间件目录
|-- public/ // 静态根目录
|-- router/ // 路由目录
|-- router-schema/ // 路由校验规则
|-- service/ // service服务目录
|-- middleware.js // 全局的中间件引入文件
|-- config/ // 不同环境的配置文件目录
|-- elpis-core/ // elpis-core 文件目录
|-- loader/
|-- config.js // 配置区分 本地/测试/生产 环境
|-- controller.js // 加载所有controller,可通过`app.controller.${目录}.${文件}`访问
|-- extend.js // 加载所有extend,可通过`app.extend.${文件}`访问
|-- middleware // 加载所有 middleware, 可通过`app.middleware.${目录}.${文件}`访问
|-- router-schema.js // 通过`json-schema & ajv` 对API 规则进行约束
|-- router.js // 解析 app/router 下所有js 文件,加载到 KoaRouter 下
|-- service.js // 加载所有 service,可通过 `app.servce.${目录}.${文件}` 访问
|-- env.js // 环境变量文件
|-- index.js // eplis-core入口文件
|-- index.js // 入口文件
整个项目的入口
引入elpis-core,启动项目。
// elpis/index.js
// 引入 elpis-core
const ElpisCore = require('./elpis-core');
// 启动项目
ElpisCore.start({
name: 'Elpis',
homePath: '/',
});
elpis-core 入口
引入env环境、options配置,以及elpis-core/loader目录下的各个loader,再将其加载到const app = new Koa() 中的 app对象,并且启动一个服务。
// elpis/elpis-core/index.js
const Koa = require('koa');
const path = require('path');
const { sep } = path; // 兼容不同操作系统的斜杠
const env = require('./env')
const middlewareLoader = require('./loader/middleware');
const routerSchemaLoader = require('./loader/router-schema');
const routerLoader = require('./loader/router');
const controllerLoader = require('./loader/controller')
const serviceLoader = require('./loader/service');
const configLoader = require('./loader/config');
const extendLoader = require('./loader/extend');
module.exports = {
start(options={}) {
// koa 实例
const app = new Koa();
// 应用配置
app.options = options;
// 基础路径
app.baseDir = process.cwd();
// 业务文件路径
app.businessPath = path.resolve(app.baseDir, `.${sep}app`);
// 初始化环境配置
app.env = env();
// 加载 middleware
middlewareLoader(app);
// 加载 routerSchema
routerSchemaLoader(app);
// 加载 controller
controllerLoader(app);
// 加载 service
serviceLoader(app);
// 加载 config
configLoader(app);
// 加载 extend
extendLoader(app);
// 注册全局中间件
try {
require(`${app.businessPath}${sep}middleware.js`)(app);
console.log(`-- [start] load global middleware done --`);
} catch(e) {
console.log('[exception] there is no middleware file.');
}
// 加载 router, 注册路由
routerLoader(app);
// 启动服务
try {
const port = process.env.PORT || 8080;
const host = process.env.IP || '0.0.0.0';
app.listen(port, host);
console.log(`Server runin on port: ${port}`);
} catch(e) {
console.error(e);
}
}
}
其中的每个loader,都是在约定条件下将对应模块目录下文件进行加载,并挂载到app对象里。举一个比较容易理解的loader —— controller loader
const glob = require('glob');
const path = require('path');
const { sep } = path;
/**
* controller loader
* @param {Object} app Koa 实例
* 加载所有 controller,可通过 'app.controller.${目录}.${文件}' 访问
* 例子:
* app/controller
* |
* | -- custom-module
* | -- custom-controller.js
* => app.controller.customModule.customController
*/
module.exports = (app) => {
// 读取 app/controller/**/**.js 下所有文件
const controllerPath = path.resolve(app.businessPath, `.${sep}controller`);
const fileList = glob.sync(path.resolve(controllerPath, `.${sep}**${sep}**.js`));
// 遍历所有文件目录,把内容加载到 app.controller 下
const controller = {};
fileList.forEach(file => {
// 提取文件名称
let name = path.resolve(file);
// 截取路径 app/controller/custom-module/custom-controller.js => custom-module/custom-controller
name = name.substring(name.lastIndexOf(`controller${sep}`) + `controller${sep}`.length, name.lastIndexOf('.'));
// 把 '-' 统一改为驼峰式,custom-module/custom-controller => customModule.customController
name = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());
// 挂载 controller 到内存 app 对象中
let tempController = controller;
const names = name.split(sep);
for(let i = 0, len = names.length; i < len; ++i) {
if (i === len - 1) {
const controllerModule = require(path.resolve(file))(app);
tempController[names[i]] = new controllerModule();
} else {
if (!tempController[names[i]]) {
tempController[names[i]] = {};
}
tempController = tempController[names[i]];
}
}
})
app.controller = controller
};
至此,elpis的BFF后端层就搭建好了,其中通过相应的loader将约定好的模块文件夹下的文件,挂载到Koa实例app上,而Koa框架属于洋葱模型,按顺序将不同模块加载到app实例上了。
总结
本项目是基于学习的目的搭建的,实现了自动扫描和加载应用中的各个组件,为开发提供了一个高效、可扩展且易于维护的应用程序框架。
下一次,就是通过webpack工程化实现一个前端的框架。期待......