背景
基于国内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管理系统。它具备多种类型网站开发,易扩展、基于模块化和插件化开发模式,适用于商用企业级程序开发。