基于express封装轻量级mvc框架chanjs

背景


基于国内koa2封装的thinkjs和eggjs基本都黄了,或者不更新了。而且koa2的市场占有量的确
不如express,加上本人不喜欢typescript和nestjs风格,所以就想实现一个mvc框架。

chanjs介绍


Chanjs 基于express 纯js研发的轻量级mvc框架。基于函数式编程思想,性能优越,代码清晰,流程易读,可持续维护高。

核心功能

  • 配置文件
  • 多模块mvc
  • 数据库支持
  •  路由控制
  • art-template模板
  • 静态资源
  • cookie
  • 日志功能

参考

  • thinkjs多模块
  • eggjs约定优于配置原则

约定结构

|- app
    |- config 配置
    |- module 模块1
        |- module1 模块1
            |- controller 控制器
            |- service 服务模型
            |- view 视图模板
            |- router.js 路由
        |- module2 模块2
            |- controller 控制器
            |- service 服务模型
            |- view 视图模板
            |- router.js路由
    |- extend 扩展
    |- middleware 中间件
    |- plugin 插件
    |- public 静态文件
|- index.js


初始化流程

框架加载默认约定结构,动态加载模块,分为以下几个流程:

-   初始化
-   加载配置
-   加载模块
    -   加载service
    -   加载controller
    -   加载router
-   加载extend
-   加载plugin
-   beforeStart() 挂在从数据库获取的配置合并到配置文件中
-   run() 启动服务


 

核心代码实现

const express = require("express");
const config = require("./lib/config/config.js");
const path = require("path");
const fs = require("fs");
const core = require("./lib/core.js");

/**
 * @description 基于express封装的mvc框架,遵循约定优于配置原则
 */
class Chan {
  constructor(options={}) {
    //配置
    Chan.config = Object.assign(config, options);
  
    //模块
    Chan.modules ={};
    //工具
    Chan.helper ={};
    //应用实例
    this.app = express();
    this.app.config = Chan.config;
    this.router = express.Router();
    //加载配置
    this.loadConfig();
    //加载扩展
    this.loadExtends();
    //加载核心(日志、favicon 图标、cookie、json、url、模板引擎、静态资源)
    this.loadCore();
    //加载实例化数据库
    this.loadKnex();
    //生命周期钩子:开始启动 
    this.beforeStart();
    //加载模块
    this.loadModules();
    //生命周期:初始化完成
    this.start()
  }

  // 加载配置
  loadConfig() {
    const configPath = path.join(config.APP_PATH, "config/index.js");
    if (fs.existsSync(configPath)) {
      const config = require(configPath);
      Chan.config = Object.assign(Chan.config, config);
    }
  }

   // 加载扩展
   loadExtends() {
    const extendPath = path.join(config.APP_PATH, "extend");
    if (fs.existsSync(extendPath)) {
      let controllers = fs
        .readdirSync(extendPath)
        .filter((file) => file.endsWith(".js"));
      for (let i = 0, file; i < controllers.length; i++) {
        file = controllers[i];
        const helper = require(path.join(extendPath, file));
        const fileName = file.replace(".js", "");
        Chan.helper[fileName] = helper;
      }
    }
  
  }

  //数据库操作
  loadKnex(){
    // 连接数据库
    const {host,port,user,password,database,client,charset} = Chan.config.database;
    const knex = require("knex")({
      client: "mysql2",
      connection: {
        host,
        port,
        user,
        password,
        database,
        charset,
      },
      debug: config.debug, //指明是否开启debug模式,默认为true表示开启
      pool: {
        //指明数据库连接池的大小,默认为{min: 2, max: 10}
        min: 0,
        max: 2,
      },
      log: {
        warn(message) {
          console.error("[knex warn]", message);
        },
        error(message) {
          console.error("[knex error]", message);
        },
      },
    });
    Chan.knex = knex;
  }


  //开始启动
  beforeStart(cb) {
    // 初始化一些配置
    cb && cb();
  }

  //启动
  start(cb){
    // 初始化一些配置
    cb && cb();
  }

  // 加载插件
  loadPlugins() {
    const configPath = path.join(config.APP_PATH, "plugin");
    if (fs.existsSync(configPath)) {
      const dirs = fs
        .readdirSync(configPath, { withFileTypes: true })
        .filter((dirent) => dirent.isDirectory())
        .map((dirent) => dirent.name);
      this.plugins = dirs;
    } else {
      this.plugins = [];
    }
  }

  /**
   * @description app核心模块:日志、favicon 图标、cookie、json、url、模板引擎、静态资源
   */
  loadCore(){
    core(this.app);
  }

 /**
  * @description 模块加载入口(路由&控制器& 服务)
  */
  loadModules() {
    const configPath = path.join(config.APP_PATH, "modules");
    if (fs.existsSync(configPath)) {
      const dirs = fs
        .readdirSync(configPath, { withFileTypes: true })
        .filter((dirent) => dirent.isDirectory())
        .map((dirent) => dirent.name);

      this.modulesDir = dirs;
        for(let i=0,item;i<dirs.length;i++){
          item = dirs[i];
          Chan.modules[item] = {
            controller: {},
            service: {},
          };
          this.loadModule(item);
        }

      //通用路由,加载错误处理和500路由和爬虫处理
      const baseRouterPath = path.join(config.APP_PATH, "router.js");
      if (fs.existsSync(baseRouterPath)) {
        const _router = require(baseRouterPath);
        _router(this.app, this.router);
      } 
    
    }
  }


  /**
   * @description 加载模块,包括 controller service router 
   * @param {String} moduleName 模块名称
   */
  loadModule(moduleName) {
    const moduleDir = path.join(config.APP_PATH, "modules", moduleName);
    this.loadServices(moduleDir,moduleName);
    this.loadControllers(moduleDir,moduleName);
    this.loadRoutes(moduleDir,moduleName);
  }


  /**
   * @description 扫描模块下所有service
   * @param {*} moduleDir 模块路径
   * @param {*} moduleName 模块名称
   */
  loadServices(moduleDir,moduleName) {
    const servicesDir = path.join(moduleDir, "service");
    if (fs.existsSync(servicesDir)) {
      let services = fs
        .readdirSync(servicesDir)
        .filter((file) => file.endsWith(".js"));
      for (let i = 0, file; i < services.length; i++) {
        file= services[i]
        const Service = require(path.join(servicesDir, file));
        const serviceName = file.replace(".js", "");
        Chan.modules[moduleName].service[serviceName] = {};
        Chan.modules[moduleName].service[serviceName] = Service;
      }
    }
  }


  /**
   * @description 扫描模块下所有controller
   * @param {*} moduleDir 模块路径
   * @param {*} moduleName 模块名称
   */
  loadControllers(moduleDir,moduleName) {
    const controllersDir = path.join(moduleDir, "controller");
    if (fs.existsSync(controllersDir)) {
      let controllers = fs
        .readdirSync(controllersDir)
        .filter((file) => file.endsWith(".js"));
      for (let i = 0, file; i < controllers.length; i++) {
        file= controllers[i]
        const controller = require(path.join(controllersDir, file));
        const controllerName = file.replace(".js", "");
        Chan.modules[moduleName].controller[controllerName] = {};
        Chan.modules[moduleName].controller[controllerName] = controller;
      }
    }
  }

  /**
   * @description 扫描模块下所有router.js
   * @param {*} moduleDir 模块路径
   * @param {*} moduleName 模块名称
   */
  loadRoutes(moduleDir,moduleName) {
    const routersDir = path.join(moduleDir, "router.js");
    if (fs.existsSync(routersDir)) {
      const routes = require(routersDir);
      routes({router:this.router,modules:Chan.modules,app:this.app});
    }
  }



  loadPlusins() {
    // 加载插件
  }

  run(port) {
    this.app.listen(port, () => {
      console.log(`Server is running on port ${port}`);
    });
  }
}

module.exports = Chan;

源码地址


【Gitee】https://gitee.com/yanyutao0402/chanjs

案例


基于chanjs实现chancms管理系统。
禅CMS是一款基于Express和MySQL研发的高质量实用型CMS管理系统。它具备多种类型网站开发,易扩展、基于模块化和插件化开发模式,适用于商用企业级程序开发。

官网https://www.chancms.top/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值