讲在前边
一切基于组件
有这么一种低代码平台,可视化设计器基于一套规范的DSL规范,设计完成之后,通过此DSL依靠后端生成前端页面。
这样的方式带来的优势是搭建生成的系统,后期前端维护基本和平台结构,不存在关键语法糖信息,适合系统的快速二次开发。
然鹅带来的未来当然也不少。
- 版本极其分散,后期集中升级难度大。
- 项目整体升级难度大,尤其针对页面的优化等几乎不可实施。
- 源码无限暴露,“杂草”丛生
这里特别耦合的是一个前端组件严重依赖一个JAVA jar包,即每新增一个前端组件都要开发一个jar包,除了开发效率低本身之外,组件之间的联动、事件通知等也是依赖组件内的各种配置模板以及和jar包的映射关系。同时开发一个组件需要开发两套,一套针对设计器;一套针对用户页面。
不知道是否对平台新增组件的能力的复杂有了一个概述。总之,当前的低代码平台增加一个自定义组件,尤其是具备一些事件等和其他组件有联动的组件,耗时巨大,出错概率高,给平台稳定性带来了极大的不确定性。即使针对一个有经验的前端上手成本也极高。
前端的根基-组件
按照刚才介绍的模式,开发一个自定义组件,在前端侧大致是:
除此之外,后端也需要配合引入一个jar包,或者实现一个相对通用的jar包。
如何前进一小步
事实上,当前系统中已经存量了不少的组件,而这些组件本身对于页面的交互、事件等已经完成了覆盖。例如,点击一个按钮从一个组件中取值,赋值到另外一个组件。系统中的交互、事件等已经能够完全满足,包括从数据库中获取数据复制到组件上等等。
所以目前平台上的组件已经能够满足大部分场景,如果针对平台组件实现进行调整是否就已经满足了所有场景?
按照此种思路:
- 能够在现有的基础上完成前后端解耦
- 平台稳定性能够得到保证
- 上手难度降低,只需要关注组件本身,设计器级及页面的联动等复用现有能力
- 组件独立部署,能够按需加载
- ...
架构实现思路
技术实现:

组件生态:
本地组件开发
个中细节就不再阐述了
组件离线部署
这里还要额外考虑项目需要完全离线部署,即组件还是需要在构建的时候内置到项目中。这里通过实现一个自定义插件插件即可,
大致实现方式:
compiler.hooks.emit.tapAsync(
"RemoteCustomFilePlugin",
async (compilation, callback) => {
try {
const configFile = fs.readFileSync(this.options.configFile, "utf8");
const config = JSON.parse(configFile);
const cacheDir = path.resolve(
process.cwd(),
"node_modules",
".remoteCustomComponent"
);
async function createFileWithDirectories(_filePath, _content) {
try {
// 获取目录路径
const dirPath = path.dirname(_filePath);
// 递归创建目录
fs.mkdirSync(dirPath, { recursive: true });
// 创建并写入文件
fs.writeFileSync(_filePath, _content, "utf8");
} catch (error) {
console.error("创建文件时出错:", error);
}
}
const promises = config.list.map(async (item) => {
const filePath = `${item.name}@${item.version}/dist/${item.name}.umd.js`;
let content = "";
if (fs.existsSync(path.resolve(cacheDir, filePath))) {
content = fs.readFileSync(
path.resolve(cacheDir, filePath),
"utf8"
);
} else {
const response = await axios
.get(`${this.options.server}/${filePath}`, {
headers: this.options.headers,
timeout: this.options.timeout,
})
content = response.data;
await createFileWithDirectories(path.resolve(cacheDir, filePath), content);
}
if (content) {
compilation.assets[`remoteCustomComponent/${filePath}`] = {
source: () => content,
size: () => Buffer.byteLength(content, "utf8"),
};
} else {
return Promise.reject(
new Error(
`RemoteCustomFilePlugin: Failed to fetch ${item.name}: empty content`
)
);
}
});
await Promise.all([...promises]);
callback();
} catch (error) {
// 错误处理
const errorMsg = `RemoteCustomFilePlugin: Failed to fetch ${error.message}`;
// 无fallback时报告错误
compilation.errors.push(new Error(errorMsg));
callback(new Error(errorMsg));
}
}
);
写在最后
看似简单的一小步,其实对平台的意义挺大。毕竟已经伟大不掉,只能微创了