本文是《大前端全栈实践》学习实践的总结,参考自抖音"哲玄前端"老师的课程内容,在此基础上进行了深度思考与实践。
一、背景与目标
在完成了前四个里程碑的开发后,我们的项目已经具备了完整的后端服务(Koa)、以及基于Vue3的动态组件库前端。然而,随着功能模块的增加,整个项目的代码结构开始变得臃肿。业务逻辑与底层架构代码混杂在一起,导致核心架构难以复用,新项目的启动成本也越来越高。
里程碑五的核心目标是 "架构与业务分离"。
如果不进行抽象,每次新建项目都需要拷贝通过一大堆基础代码。通过将底层的通用能力(路由加载、中间件管理、控制器装载等)封装为一个独立的 npm 包,我们可以实现:
- 框架复用:一次编写,多处使用。
- 业务聚焦:开发者只需关注
app目录下的业务逻辑,无需关心底层启动流程。 - 版本管理:框架的更新与维护独立于具体业务项目。
二、架构设计:从单体到分层
我们将项目拆解为两个部分:
- 框架层 (@aikun4588/elpis):负责底层驱动,包括 Koa 实例创建、环境识别、自动加载器(Loader)等。
- 业务层 (User Project):负责具体业务,遵循框架约定的目录结构编写 Controller、Service 和 Config。
目录结构约定(Convention over Configuration)
框架采用了约定优于配置的设计理念。只要业务层按照以下结构组织代码,框架就能自动识别并加载:
Project/
├── app/
│ ├── controller/ # 业务逻辑控制器
│ ├── service/ # 业务逻辑服务层
│ ├── middleware/ # 业务中间件
│ ├── router/ # 路由定义
│ └── router-schema/ # 参数校验Schema
├── config/ # 配置文件
│ ├── config.default.js
│ └── config.local.js
├── index.js # 入口文件
└── package.json
三、核心实现原理:Loader机制
框架的核心在于Loader(加载器)。它利用 Node.js 的模块系统和文件系统能力(如 glob),自动扫描指定目录并挂载到 app 实例上。
3.1 统一入口与启动
在 elpis-core/index.js 中,我们导出一个 start 方法,该方法按顺序执行各个 Loader:
// elpis-core/index.js
start(options = {}) {
const app = new Koa();
// 确定业务代码根路径
app.businessPath = path.resolve(process.cwd(), './app');
// 按顺序加载核心模块
app.env = env(); // 环境识别
middlewareLoader(app); // 中间件加载
routerSchemaLoader(app); // 参数校验加载
controllerLoader(app); // 控制器加载
serviceLoader(app); // 服务层加载
configLoader(app); // 配置加载
// ...最后启动监听
app.listen(port);
}
3.2 智能控制器加载(Controller Loader)
controllerLoader 负责扫描 app/controller 下的所有脚本,并根据文件路径自动转换命名(如 user-info.js -> userInfo),最终挂载到 app.controller 上。
这种设计使得我们在路由定义时,可以直接通过 app.controller.home.index 来引用,而不需要手动 require。
// loader/controller.js 核心逻辑
module.exports = (app) => {
// 扫描 app/controller 下所有 js 文件
const fileList = glob.sync(path.resolve(app.businessPath, './controller/**/*.js'));
fileList.forEach(file => {
// 解析文件名,转换为驼峰命名
// 动态 require 并实例化
// 挂载到 app.controller 对象树上
});
}
3.3 路由与Schema的结合
配合里程碑四的理念,我们还实现了 routerSchemaLoader。它自动读取 parameter 校验规则,与 API 接口进行绑定。这体现了框架层对规范的强制性支持 —— 在框架层面就集成了参数校验能力,业务开发只需填写 JSON Schema。
四、用户自定义与框架结合
抽象后的框架极大地简化了用户端的使用。开发者只需安装 npm 包,并在入口文件中简单调用即可启动服务。
发布包名:@aikun4588/elpis
NPM地址:www.npmjs.com/package/@ai…
使用示例
用户项目的 index.js 变得非常简洁:
// 引入核心框架
const ElpisCore = require('@aikun4588/elpis');
// 自定义配置并启动
ElpisCore.start({
name: 'MyProject',
homePage: '/view/dashboard'
});
框架运行时,会主动读取当前工作目录 (process.cwd()) 下的 app 文件夹,将用户的自定义代码注入到框架的生命周期中。这就实现了**"框架现有内容(底层能力)"与"用户自定义(业务逻辑)"**的完美结合。
五、流程图解
flowchart TD
User[用户入口 index.js] --> Framework[Framework Start]
Framework --> Init[初始化 Koa & Env]
subgraph Loaders [自动加载器序列]
L1[加载 Middleware]
L2[加载 RouterSchema]
L3[加载 Controller]
L4[加载 Service]
L5[加载 Config]
end
Init --> Loaders
Loaders -- 扫描 --> UserCode[用户目录 ./app]
UserCode -- 注入 --> Context[App上下文对象]
Context --> Server[启动 HTTP 服务]
六、总结
里程碑五的完成,标志着 Elpis 从一个单一的练手项目,进化为一个可复用的企业级 Node.js 开发框架。
通过将通用逻辑抽离为 npm 包 (@aikun4588/elpis),我们不仅解耦了代码,更重要的是确立了一套开发规范。未来的微服务或新功能模块,都可以基于此框架快速构建,真正实现了"提质增效"。
这一步抽象,是全栈工程师向架构师思维转变的关键一步。