elpis-core 内核(服务端) 学习总结

82 阅读3分钟

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的一个开发流程。 而且还提供了一种模块化设计和约定优于配置的理念,以拓宽见识。 以上就是我对这一章内核搭建的理解。

注:引用: 抖音“哲玄前端”,《全栈实践课》