DSL 介绍
DSL 指的是通过针对特定领域,设计出一套更加简洁,且直观的语言语法和功能,通过DSL能够将高重复性的模块进行抽象,并且设计出不同领域中特定的语法,从而减少重复性代码的编写,提高开发的效率。
DSL 设计思路
DSL框架的总体思路
- 领域模型
- 通过针对各个领域设计出该领域系统中特有的功能模块,此类模块内的文档描述会被各个项目模块继承。
- 项目模型
- 继承领域模型中的内容,然后根据项目的需求设置各个项目中的内容
- 解析器
- 解析器将对各个项目的DSL文件进行继承与解析,构造出属于当前项目模型的数据结构
Elpis 模型DSL规则设计
通过设置一份DSL设计标准,此份标准决定了后续模型的编写标准,程序员只需要配置相应DSL文档与特定的功能模块,既可以实现相应的页面搭建。
首先是项目的基本配置,此类配置用于描述项目所使用的DSL模板类型,也就是模板标准。并且描述项目的名称、项目描述等基本信息。
其次是对项目内容的描述,该内容块会以顶部导航栏出现在页面之中,将功能模块类型(menuType)分为两类:
- ① 更多模块类,此类型表示其下有更多的单一模块或更多层级,具体页面展示可以理解为下拉菜单。
- ② 单一模块类,此类型为最终模块展示,具体将其分成 sider | iframe | custom | schema 四大类,不同的类别都将呈现不同的内容展示方式。
单一模块类型介绍
siderConfig
siderConfig: {
menu : [{
moduleType: iframe | custom | schema
}, ...menu]
},
siderConfig 会在页面中以侧边栏的方式显示,其中侧边栏内的内容会在侧边栏的右侧进行展示。
iframeConfig
iframeConfig: {
path: '' // 相应的 iframe 路径
},
iframeConfig 在页面中的展示是将外部的页面的链接进行放置,从使用第三方页面的功能
schemaConfig
schemaConfig: {
api: '', // 数据源 API (遵循 RESTFUL 规范)
schema: {
type: 'object',
properties: {
key: {
...schema, // 基础配置
type: '', // 字段的类型
label: '', // schema 的中文名称
// 字段在 table 中的相关配置
tableOption: {
...elTableColumnConfig, // 标准 el-table-column 配置
toFixed: 0, // 保留小数点后几位
visiable: true, // 默认为 true 该属性表示内容在不在表单中展示
},
// 字段在 search-bar 中的相关配置
searchOption: {
...elComponentConfig, // 标准 el-conponent-column 配置
comType: '', // 配置控件类型 AS: inpiut | select | button |.....
default: '', // 默认值
},
// 当 comType === select
enumList: [], // 下拉框可选项
// 当 comType === dynamicSelect
api: ''
},
},
},
}
schemaConfig 用于配置基本页面内的配置,其中 schema 中的内容为配置不同功能模块(例如按钮、搜索栏)的基本配置。然后会在下方配置相应的模块配置,例如 tableConfig(表格的相关配置),具体里面的配置可以为 headerButtons 和 rowButtons 两个配置。
tableConfig: { // 关于 table 内容的配置
headerButtons: [{
label:'', //按钮中文名
eventKey: '', // 按钮触发事件的名称
eventOption: {}, // 按钮具体配置
...elButtonConfig // 标准 el-button 配置
}, ...headerButtons],
rowButtons: [{
label:'', //按钮中文名
eventKey: '', // 按钮触发事件的名称
eventOption: {
// 当 eventKey === 'remove'
params: {
// paramKey = 参数的键值
// rowValueKey = 参数值格式为 schema::tableKey 时,到 table 中找到相应的字段
paramKey: rowValueKey
}
}, // 按钮具体配置
...elButtonConfig // 标准 el-button 配置
}, ...rowButtons]
},
customConfig
customConfig: {
path: '' // 相应的 custom(自定义)路径
},
customConfig 用于配置自定义页面的路径。
DSL 解析引擎
// 返回的数据结构
modelList = [{
model: ${model},
project: {
proj1key: ${project1},
proj2key: ${project2}
}]
引擎实现步骤:
- 遍历文件,获取文件绝对路径,然后将路径组合为对象
- 对对象进行遍历,将类型划分为 model | project 两种处理思路
- 将类型为 project 的数据继承 model 中的结构信息
- 将处理完的数据集合到一个列表之中返回。
// 实现代码
{
const modelList = [];
// 遍历当前的文件夹,构造模型数据结构,然后挂载到 modelList 上
// 获取文件夹的绝对路径
const modelPath = path.resolve(app.baseDir, `.${sep}model`);
// 将路径组合为对象
const fileList = glob.sync(path.resolve(modelPath, `.${sep}**${sep}**.js`));
// 进行遍历
fileList.forEach(file =>{
// 判断是否为 index ,是则返回
if(file.indexOf('index.js') > -1){ return };
let fileName = path.resolve(file)
// 区分配置类型 (model / project)
const type =fileName.indexOf(`${sep}project${sep}`) > -1 ? 'project' : 'model';
if(type === 'project'){
const modelkey = file.match(/\/model\/(.*?)\/project/)?.[1];
const projkey = file.match(/\/project\/(.*?)\.js/)?.[1];
let modelItem = modelList.find(item => item.model?.key === modelkey);
if(!modelItem){ //初始化 model 结构
modelItem ={};
modelList.push(modelItem);
}
if(!modelItem.project){ // 初始化 project 结构
modelItem.project = {};
}
// 将相应的 key 挂载到 modelItem 上
modelItem.project[projkey] = require(path.resolve(file));
modelItem.project[projkey].key = projkey; //注入 projkey
modelItem.project[projkey].modelKey = modelkey; // 注入 modelKey
}
if(type === 'model'){
const modelkey = file.match(/\/model\/(.*?)\/model\.js/)?.[1];
let modelItem = modelList.find(item => item.model?.key === modelkey);
if(!modelItem){ // 初始化 model 结构
modelItem = {};
modelList.push(modelItem);
}
// 将相应的 model 挂载到 modelItem 上
modelItem.model = require(path.resolve(file));
modelItem.model.key = modelkey; //注入 modelkey
}
})
// 数据进一步整理: project => 继承 model
modelList.forEach(item =>{
const { model, project } = item;
for (const key in project) {
project[key] = projectExtendModel(model,project[key]);
}
})
return modelList
}
DSL的设计,减少了在编写代码时,那些重复性较高的复用代码,通过这套框架,使得系统的构建只需要根据 schema 规则,编写相应的配置,然后增加特殊的功能模块,大大提升了编写的效率。