【Elpis】前端全栈框架学习思考

183 阅读6分钟

前言

Elpis,古希腊希望女神, 寓意为前端开发乃至程序员岗位的希望

Elpis 是一个企业级全栈应用框架,采用前后端一体化设计,通过配置驱动的方式快速构建企业级应用。项目分为五个核心阶段:核心框架搭建、前端工程化、DSL 设计、动态组件库建设和 NPM 包发布。

npm 地址: www.npmjs.com/package/@mo…

npm i @moranliunian/elpis

特别鸣谢哲玄前端(全栈)

核心特性

  • 模块化加载器:通过 elpis-core/loader/ 目录下的各种 loader 实现模块的自动加载
  • 约定优于配置:基于目录结构的自动发现和注册机制
  • 插件化架构:支持业务模块的扩展和自定义

Koa 洋葱圈模型工作原理

Koa的洋葱圈模型指的是中间件的执行方式:请求从外层中间件开始处理,逐步向内传递,到达核心处理逻辑后,再逐层向外返回响应。

  • Middleware(中间件):处理HTTP请求的各个环节,如日志记录、身份验证、数据解析等。
  • Router(路由):根据URL路径将请求分发到对应的Controller。
  • Controller(控制器):处理请求参数,调用Service层,组织响应数据。
  • Service(服务):包含业务逻辑,处理数据操作,与数据库交互。
  • Config Loader(配置加载器):加载和管理应用程序的配置信息。
  • 这种分层架构使代码更加模块化,易于维护和测试,每层都有明确的职责。

启动流程

// elpis-core/index.js 核心启动逻辑
start(options = {}) {
  const app = new Koa();
  
  // 1. 加载中间件
  middlewareLoader(app);
  
  // 2. 加载路由配置
  routerSchemaLoader(app);
  
  // 3. 加载控制器
  controllerLoader(app);
  
  // 4. 加载服务层
  serviceLoader(app);
  
  // 5. 加载配置
  configLoader(app);
  
  // 6. 加载扩展
  extendLoader(app);
  
  // 7. 注册路由
  routerLoader(app);
}

I. elpis-core 内核

graph TD
    A[Elpis Core] --> B[Loader 模块]
    B --> C[Middleware Loader]
    B --> D[Router Loader] 
    B --> E[Controller Loader]
    B --> F[Service Loader]
    B --> G[Config Loader]
    B --> H[Extend Loader]
    
    A --> I[Koa 应用实例]
    I --> J[中间件注册]
    I --> K[路由注册]
    I --> L[服务启动]
    

目录结构

elpis-core/
├── index.js                   # 核心启动入口
├── env.js                     # 环境配置管理
└── loader/                    # 模块加载器
    ├── config.js              # 配置加载器
    ├── controller.js          # 控制器加载器
    ├── extend.js              # 扩展加载器
    ├── middleware.js          # 中间件加载器
    ├── router-schema.js       # 路由配置加载器
    ├── router.js              # 路由加载器
    └── service.js             # 服务层加载器

II. 前端工程化模块,webpack 配置

graph LR
    A[Webpack 配置] --> B[基础配置]
    A --> C[开发配置]
    A --> D[生产配置]
    
    B --> E[入口配置]
    B --> F[模块解析]
    B --> G[插件配置]
    B --> H[优化策略]
    
    E --> I[动态入口发现]
    F --> J[Vue Loader]
    F --> K[Babel Loader]
    G --> L[HtmlWebpackPlugin]
    H --> M[代码分割]
    H --> N[Tree Shaking]

核心特性

  • 动态入口发现:自动扫描 app/pages/**/entry.*.js 文件作为入口
  • 多页面应用支持:支持多个独立页面的构建
  • 智能代码分割:vendor、common、entry 三层分割策略
  • 业务扩展支持:通过 alias 配置支持业务模块扩展

关键配置

// 动态入口配置
const elpisEntryList = path.resolve(__dirname, '../../pages/**/entry.*.js');
glob.sync(elpisEntryList).forEach((file) => {
  handleFile(file, elpisPageEntries, elpisHtmlWebpackPluginList);
});

// 代码分割策略
splitChunks: {
  cacheGroups: {
    vendor: { 
      test: /[\\/]node_modules[\\/]/, 
      name: 'vendor' 
    },
    common: { 
      test: /[\\/]common|widgets[\\/]/, 
      name: 'common' 
    }
  }
}

目录结构

app/webpack/
├── dev.js                     # 开发环境启动
├── prod.js                    # 生产环境构建
├── config/                    # 配置文件
│   ├── webpack.base.js        # 基础配置
│   ├── webpack.dev.js         # 开发配置
│   └── webpack.prod.js        # 生产配置
└── libs/                      # 工具库
    └── blank.js               # 空白模块

III. DSL 设计和实现 schemaview

graph TD
    A[Schema 配置] --> B[Schema Parser]
    B --> C[Table Schema]
    B --> D[Search Schema]
    B --> E[Component Schema]
    
    C --> F[Table Panel]
    D --> G[Search Panel]
    E --> H[Dynamic Components]
    
    F --> I[数据展示]
    G --> J[条件搜索]
    H --> K[表单操作]
    H --> L[详情展示]

DSL 核心概念

  • 统一 Schema 定义:一个 Schema 配置支持多种视图(table、search、form)
  • Option 扩展机制:通过 tableOptionsearchOptioncreateFormOption 等扩展字段行为
  • 动态组件系统:基于配置的组件动态渲染

“那什么是 JSON Schema 呢?”

JSON Schema​ 本身是一个 ​JSON 对象,它是一份强大的声明式合约,用来描述和验证另一种 JSON 数据的结构和内容。

你可以把它理解为:

  • 数据的蓝图或元数据​:它定义了 JSON 数据应该长什么样。
  • 数据的契约​:它规定了数据必须遵守的规则。
  • 数据的验证工具​:你可以用程序(验证器)根据 Schema 来检查任何 JSON 数据是否符合要求。

它的核心作用是 ​验证(Validation)​。在数据传输(如 API 请求/响应)或数据存储时,确保 JSON 数据的格式、类型和内容符合预期,从而提高数据的质量和程序的健壮性。

Schema 配置示例

{
  type: 'object',
  properties: {
    name: {
      type: 'string',
      label: '姓名',
      tableOption: { 
        visible: true, 
        width: 120 
      },
      searchOption: { 
        comType: 'input', 
        default: '' 
      },
      createFormOption: { 
        comType: 'input', 
        required: true 
      }
    }
  }
}

Schema 解析流程

// hook/schema.js 中的核心解析逻辑
const buildDtoSchema = (_schema, comName) => {
  const dtoSchema = { 
    type: 'object', 
    properties: {} 
  };
  
  for (const key in _schema.properties) {
    const props = _schema.properties[key];
    if (props[`${comName}Option`]) {
      // 提取对应组件的配置
      dtoSchema.properties[key] = {
        ...props,
        option: props[`${comName}Option`]
      };
    }
  }
  return dtoSchema;
};

目录结构

app/pages/dashboad/complex-view/schema-view/
├── schema-view.vue            # 主视图组件
├── hook/                      # 业务逻辑
│   └── schema.js              # Schema 解析 Hook
├── components/                # 动态组件
│   ├── component-config.js    # 组件配置映射
│   ├── create-form/           # 创建表单
│   ├── detail-panel/          # 详情面板
│   └── edit-form/             # 编辑表单
└── complex-view/              # 复杂视图
    ├── search-panel/          # 搜索面板
    └── table-panel/           # 表格面板

IV. 动态组件库搭建

graph TD
    A[组件库] --> B[Schema Form]
    A --> C[Schema Table]
    A --> D[Schema Search Bar]
    A --> E[Header Container]
    A --> F[Sider Container]
    
    B --> G[Form Item Config]
    C --> H[Table Config]
    D --> I[Search Item Config]
    
    G --> J[Input]
    G --> K[Select]
    G --> L[Input Number]
    
    H --> M[Column Config]
    H --> N[Button Config]
    
    I --> O[Date Range]
    I --> P[Dynamic Select]

组件扩展机制

  • 配置驱动:通过配置文件定义组件映射关系
  • 插件化扩展:支持业务自定义组件注册
  • 统一接口:所有组件遵循相同的接口规范

组件配置示例

// form-item-config.js
const FormItemConfig = {
  input: { component: input },
  select: { component: select },
  inputNumber: { component: inputNumber }
};

// 支持业务扩展
export default {
  ...FormItemConfig,
  ...BusinessFormItemConfig
};

动态组件渲染

<template>
  <component
    :is="FormItemConfig[itemSchema.option?.comType]?.component"
    :schema="itemSchema"
    :model="model"
  />
</template>

目录结构

app/pages/widgets/
├── schema-form/               # 表单组件
│   ├── schema-form.vue        # 表单主组件
│   ├── form-item-config.js    # 表单项配置
│   └── complex-view/          # 复杂视图
│       ├── input/             # 输入框组件
│       ├── input-number/      # 数字输入框
│       └── select/            # 选择器组件
├── schema-table/              # 表格组件
│   └── schema-table.vue       # 表格主组件
├── schema-search-bar/         # 搜索组件
│   ├── schema-search-bar.vue  # 搜索栏主组件
│   ├── search-item-config.js  # 搜索项配置
│   └── complex-view/          # 复杂视图
│       ├── date-range/        # 日期范围组件
│       ├── dynamic-select/    # 动态选择器
│       ├── input/             # 搜索输入框
│       └── select/            # 搜索选择器
├── header-container/          # 头部容器
│   ├── header-container.vue   # 头部容器组件
│   └── asserts/               # 资源文件
│       ├── avatar.png         # 头像
│       └── logo.png           # 标志
└── sider-container/           # 侧边栏容器
    └── sider-container.vue    # 侧边栏容器组件

V. 发包 elpis npm 包

发布流程设计

graph LR
    A[源码开发] --> B[代码审查]
    B --> C[版本管理]
    C --> D[构建打包]
    D --> E[NPM 发布]
    E --> F[文档更新]
    F --> G[版本标签]

包结构设计

@moranliunian/elpis/
├── elpis-core/                # 核心框架
├── app/                       # 前端应用
│   ├── pages/                 # 页面模块
│   ├── widgets/               # 组件库
│   └── webpack/               # 构建配置
├── config/                    # 配置文件
├── model/                     # 数据模型
└── index.js                   # 主入口

发布配置

{
  "name": "@moranliunian/elpis",
  "version": "1.0.1",
  "main": "index.js",
  "scripts": {
    "lint": "eslint --quiet --ext js,vue .",
    "test": "_ENV='local' mocha 'test/**/*.js'"
  }
}

项目特色与优势

1. 全栈一体化

  • 前后端统一开发框架,减少技术栈切换成本
  • 统一的配置管理和开发规范

2. 配置驱动开发

  • 通过 DSL 配置快速构建 CRUD 页面
  • 减少重复代码,提高开发效率

3. 高度可扩展

  • 插件化架构支持业务定制
  • 组件库支持动态扩展

4. 工程化完善

  • 完整的构建、测试、发布流程
  • 支持多环境部署

5. 企业级特性

  • 支持多页面、权限控制、错误处理
  • 完善的日志和监控机制

使用示例

服务端启动

const { start } = require('@moranliunian/elpis');

// 启动 elpis 服务
const app = start({
  name: 'my-project',
  homePage: '/dashboard'
});

前端构建

const { frontendBuild } = require('@moranliunian/elpis');

// 编译构建前端工程
frontendBuild(process.env._ENV);

Schema 配置

const schemaConfig = {
  api: '/api/users',
  schema: {
    type: 'object',
    properties: {
      name: {
        type: 'string',
        label: '姓名',
        tableOption: { visible: true },
        searchOption: { comType: 'input' },
        createFormOption: { comType: 'input', required: true }
      }
    }
  }
};

总结

总体理念上,设计大于编码,从程序员的角度去考虑业务问题,而不是局限于前端开发工程师。虽然课程中选择了 koa + express + webpack + vue,但是在思维层面是完全跳出框架限制的,框架只是实现思维的工具。在思考到设计再到实现的过程中,思考和设计占据了绝大多数时间,无时无刻不在灌输作为一个程序员的素养。

最后,在互联网这个日趋激烈的大环境下面,希望同学们可以保持初心,保持对技术的热情,保持学习,时刻提升自身的打铁水平,与君共勉!

🎉完结撒花🎉