elpis-core 简单来说就类似于一个轻量级(简化版的)eggs.js的内核
前言:
在这一个内核的实现中,‘约定优于配置’这一句话是贯穿于整个elpis-core的实现的,即我们约定好了,相应的文件名称,类:app/router/xx/xx.js文件是来存放有关于所有router配置的地方,我们可以通过解析器(elpis-core)对应的loader,类:router.loader 来解析,生成对应的路由。
核心:
整个内核的解析器就是围绕着下面7个loader所构建成的。
* elpis-core
* |--env.js
* |--index.js
* |--loader
* |--config.js
* |--controller.js
* |--extend.js
* |--middleware.js
* |--roter-schema.js
* |--router.js
* |--service.js
*/
而所有的loader都会解析我们定义好的app目录下相应的文件,以转变成我们运行时能编译成功的效果
* app
* |--controller 业务逻辑
* |--extend 拓展工具
* |--middleware 中间件
* |--public 静态根目录
* |--router 路由
* |--router-schema 路由校验
* |--service 服务层
* |--middleware.js 全局中间件
*/
env.js:
环境配置文件
index.js文件:
是整个内核的启动文件,内核是基于koa去做服务端的启动的,需要引入koa,并且把loader全部加载,才能使得服 务端能正常的解析相应的文件,编译成可运行的业务代码。
middlewareLoader:
中间件加载器,引入了一个洋葱圈的模型,能够不断的为其添加不同的中间件,来对输入的东西进行对应中间件的处理再进行输出,里面能进行类似:错误捕获,参数校验,这种筛选、处理、兜底等行为。
先获取app/middleware下的所有文件,然后再遍历所有文件目录,通过路径截取、修改名称、通过类似于层递嵌套的方式,把middleware挂载到app对象上。
代码示例:
const glob = require('glob');
const path = require('path');
const { sep } = path;
/**
* middleare loader
* @param {object} app Koa 实例
*
* 加载所有 middleware, 可通过'app.middleware.${目录}.${文件}'访问
*
* 例子:
* app/middleware
* |
* | -- custome-module
* |
* | -- custome-middleware.js
*
* => app.middleware.customeModule.customeMiddleware
*/
module.exports = (app) => {
// 读取 app/middleware/**/**.js 下所有文件
const middlewarePath = path.resolve(app.businessPath, `.${sep}middleware`);
const fileList = glob.sync(path.resolve(middlewarePath, `.${sep}**${sep}**.js`));
// 遍历所有文件目录,把内容加载到app.middlewares 下
const middlewares = {};
fileList.forEach(file => {
// 提取文件名称
let name = path.resolve(file)
// 截取路径 app/middlewares/custome-module/custome-middleware.js => custome-module/custome-middleware
name = name.substring(name.lastIndexOf(`middleware${sep}`) + `middleware${sep}`.length, name.lastIndexOf('.'))
// 把'-'统一改为驼峰式 , custom-module/custom-middleware.js =>customModule.customMiddleware.js
name = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());
// 挂载 middleware 到内容app对象上
let tempMiddleware = middlewares;
const names = name.split(sep);
for (let i = 0; len = names.length, i < len; i++) {
if (i === len - 1) {
// 解析到文件的时候
tempMiddleware[names[i]] = require(path.resolve(file))(app);
} else {
// 当我们解析到目录的时候,它里面是没有这个值的,就初始化为一个对象
if (!tempMiddleware[names[i]]) {
tempMiddleware[names[i]] = {};
}
// tempMiddleware === {customeModule:{a:{b:{c:{}}}}
//再把当前的值赋值给这个临时tempMiddleware
tempMiddleware = tempMiddleware[names[i]];
// tempMiddleware === {}
}
}
})
app.middlewares = middlewares;
}
roterSchemaLoader:
这个loader主要做的是路由api这块的解析,再配合相应的插件在app中做api参数的签名校验
controllerLoader:
与middlewareLoader的解析方式大致相同
serviceLoader:
与middlewareLoader的解析方式大致相同
configLoader:
主要做的是对当前运行的环境配置的获取,并挂载到app.config中
/**
* config loader
* @param {Object} app koa 实列
*
* 配置区分 本地/测试/生产,通过env环境读取不同文件配置env.config
* 通过env。config 覆盖 default.config 加载到 app.config 中
*
* 目录下对应的config配置
* 默认配置 config/config.default.js
* 本地配置 config/config.local.js
* 测试配置 config/config.beta.js
* 生产配置 config/config.prod.js
*
*/
module.exports = (app) => {
// 找到config/目录
const configPath = path.resolve(app.baseDir, `.${sep}config`)
// 获取 default.config
let defaultConfig = {};
try {
defaultConfig = require(path.resolve(configPath, `.${sep}config.default.js`))
} catch (e) {
console.log('[execption] there is no default.config file')
}
// 获取env.config
let envConfig = {};
try {
if (app.env.isLocal()) { //本地环境
envConfig = require(path.resolve(configPath, `.${sep}config.local.js`))
} else if (app.env.isBeta()) {//测试环境
envConfig = require(path.resolve(configPath, `.${sep}config.beta.js`))
} else if (app.env.isProduction()) {//生产环境
envConfig = require(path.resolve(configPath, `.${sep}config.prod.js`))
}
} catch (e) {
console.log('[execption] there is no env.config file')
}
// 覆盖并加载 config配置
app.config = Object.assign({}, defaultConfig, envConfig)
}
extendLoader:
与middlewareLoader的解析方式大致相同
routerLoader:
先获取app/router下的文件,再通过实例化KoaRouter,再为路由进行额外的健壮性配置,再注册
/**
* router loader
* @param {Object} app koa 实例
*
* 解析所有 app/router/ 下所有 js文件 ,加载到KoaRouter 下
*/
module.exports = (app) => {
// 找到路由文件路径
const routerPath = path.resolve(app.businessPath, `.${sep}router`)
// 实例化 KoaRouter
const router = new KoaRouter()
// 注册所有路由
const fileList = glob.sync(path.resolve(routerPath, `.${sep}**${sep}**.js`));
fileList.forEach((file) => {
require(path.resolve(file))(app, router)
})
// 路由兜底 (健壮性)
router.get('*', async (ctx, next) => {
ctx.status = 302; //临时重定向
ctx.redirect(`${app?.options?.homePage ??'/'}`);
})
// 路由注册到app上
app.use(router.routes());
app.use(router.allowedMethods());
}
应用:
loader里面的所有解析都会自行找到app中约定好的相对应的文件,再通过编译以得到我们想要的效果
例:以下是app.router里面的一段代码,我们的routerloader就能对这段代码进行编译,以生成我们想要的页面路由。
module.exports = (app,router)=>{
const {view:viewController} = app.controller
// 用户输入 http://ip:port/view/page1 能渲染出对应的页面
// koa router :page 动态路由
router.get('/view/:page',viewController.renderPage.bind(viewController))
}
总结:
完成这一章elpis-core内核的搭建,使得我了解到了前端并不只局限于页面和业务代码的构建,还可以通过node.js搭建一个服务端框架的BFF层,以简化前端或者web的一个开发流程。 而且还提供了一种模块化设计和约定优于配置的理念,以拓宽见识。 以上就是我对这一章内核搭建的理解。
注:引用: 抖音“哲玄前端”,《全栈实践课》