自定义组件系统是什么
这里的自定义组件系统是基于低代码平台的能力扩展出相应的api和工具提供给用户自定义组件的能力。
为什么要写自定义组件系统
低代码平台是把很多通用的东西抽象出来的东西。那么就会诞生一个问题:如果通用能力不满足场景需要怎么办?
举个例子:低代码平台的按钮组件都是通用的,假如此时业务方想要一些符合业务方独有的组件怎么办?
解决办法:
-
低代码平台去开发
-
业务方开发
低代码平台打造的是通用平台,通用是一个基本原则。
如果前者的话,平台会夹杂一些业务方的特殊逻辑,这就违背了通用的原则,而后者的话,就没有这些问题,业务方想怎么改都是他们的事情,平台不用关心,平台只要提供对应的api和工具就可以了。
自定义组件开发步骤
-
工具和平台说明
工具名称 地址 能力 magic-publish www.npmjs.com/package/mag… 发布打包好的组件代码到静态资源服务器上 magichotreplacementplugin www.npmjs.com/package/mag… 提供组件调试时的热更新能力 远方低代码平台 ----- 多场景互动页面制作工具,可以制作类似bilibili法外狂徒交互视频這种交互形式的互动页面
-
初始化组件项目
可以使用脚手架初始化一个项目,当然你也可以自己搭一个。这里和我们平时搭项目没啥区别
-
组件项目改造
-
根目录增加component.config.json-组件描述
{
"id": "12",
"name": "按钮",
"img": "..."
}
-
webpack配置
// 输出文件改成umd filename固定static/js/bundle.js
output: {
filename: "static/js/bundle.js",
library: "webpackUMD",
libraryTarget: "umd",
libraryExport: "default",
}
// 增加远方代码平台的热更新组件
plugins: [
isEnvDevelopment &&
new MagicHotReplacementPlugin(require("../component.config.json"))
-
入口文件导出组件
import App from "./App";
export default App;
-
run start script
-
平台开启调试功能
-
调试组件
当组件项目运行时,并且开启了平台自定义功能,则会往当前场景添加你自定义的组件
-
组件发布
打包组件,然后通过magic-publish命令行工具进行发布
原理
核心逻辑
-
调试原理
-
调试更新原理
-
线上逻辑
核心逻辑代码实现
- 组件渲染
原理其实和微组件差不多
export const Custom: React.FC<IProps> = React.memo(({ scriptPath, id }) => {
const containRef = useRef<HTMLDivElement>(null);
const renderComponent = () => {
const current = containRef.current;
if (current) {
fetch(scriptPath)
.then(res => res.text())
.then(code => {
/**
1.伪造CommonJS
2.执行代码 - 实际上是执行一个匿名函数
3.拿到导出的组件
4.render一次
*/
const exports = {};
const module = { exports };
eval(code);
const C = module.exports as React.FunctionComponent;
ReactDOM.render(<C />, current);
});
}
};
useEffect(() => {
const current = containRef.current;
if (current) {
renderComponent();
if (dep.includes(id)) { // 发布订阅 收集组件重新打包后要执行的回调
dep.on('magicOk', renderComponent);
}
}
}, [containRef]);
return <div className={$style.cutomComponent} ref={containRef}></div>;
-
热更新原理
const express = require("express");
const http = require("http");
const cors = require("cors");
class MagicHotReplacementPlugin {
constructor(componentConfig) {
const app = express();
app.use(cors());
const server = http.createServer(app);
//开启一个websocket服务
const io = require("socket.io")(server, {
cors: {
origin: "*", //前端请求地址
methods: ["GET", "POST"],
credentials: true,
allowEIO3: true,
},
transport: ["websocket"],
});
this.clientSockets = [];
io.on("connection", (client) => {
client.emit("ready", componentConfig);
this.clientSockets.push(client);
client.on("disconnect", () => {
/* … */
const socketIndex = this.clientSockets.indexOf(client);
this.clientSockets.splice(socketIndex, 1);
});
});
server.listen(1024);
}
apply(compiler) {
compiler.hooks.done.tap("MagicHotReplacementPlugin", () => {
this.clientSockets.forEach((socket) => socket.emit("magicOk")); // 打包结束 广播
});
}
}
module.exports = MagicHotReplacementPlugin;
这个本来想用webpack提供的热更新能力的,但是遇到一个问题就是当代码重新打包后,HotReplacementPlugin拉取的hot-update.json路径是根据当前的host,看了一下配置发现好像没有提供配置这个路径的能力。 因此就根据热更新的原理重新实现了一个简单的更新逻辑。但是实际上性能还是没有webpack的热更新能力好。webpack的热更新可以去重新加载变更的模块,而我的是重新走一遍渲染逻辑。
-
渲染sdk的逻辑
实际上和编辑器的渲染能力一样
写在最后
工具和平台说明
工具名称 | 地址 | 能力 |
---|---|---|
magic-publish | www.npmjs.com/package/mag… | 发布打包好的组件代码到静态资源服务器上 |
magichotreplacementplugin | www.npmjs.com/package/mag… | 提供组件调试时的热更新能力 |
远方低代码平台 | ----- | 多场景互动页面制作工具,可以制作类似bilibili法外狂徒交互视频這种交互形式的互动页面 |
初始化组件项目
可以使用脚手架初始化一个项目,当然你也可以自己搭一个。这里和我们平时搭项目没啥区别
组件项目改造
-
根目录增加component.config.json-组件描述
{ "id": "12", "name": "按钮", "img": "..." }
-
webpack配置
// 输出文件改成umd filename固定static/js/bundle.js output: { filename: "static/js/bundle.js", library: "webpackUMD", libraryTarget: "umd", libraryExport: "default", } // 增加远方代码平台的热更新组件 plugins: [ isEnvDevelopment && new MagicHotReplacementPlugin(require("../component.config.json"))
-
入口文件导出组件
import App from "./App"; export default App;
-
run start script
-
平台开启调试功能
-
调试组件 当组件项目运行时,并且开启了平台自定义功能,则会往当前场景添加你自定义的组件
-
组件发布
打包组件,然后通过magic-publish命令行工具进行发布
原理
核心逻辑
-
调试原理
-
调试更新原理
-
线上逻辑
调试原理
调试更新原理
线上逻辑
原理其实和微组件差不多
export const Custom: React.FC<IProps> = React.memo(({ scriptPath, id }) => {
const containRef = useRef<HTMLDivElement>(null);
const renderComponent = () => {
const current = containRef.current;
if (current) {
fetch(scriptPath)
.then(res => res.text())
.then(code => {
/**
1.伪造CommonJS
2.执行代码 - 实际上是执行一个匿名函数
3.拿到导出的组件
4.render一次
*/
const exports = {};
const module = { exports };
eval(code);
const C = module.exports as React.FunctionComponent;
ReactDOM.render(<C />, current);
});
}
};
useEffect(() => {
const current = containRef.current;
if (current) {
renderComponent();
if (dep.includes(id)) { // 发布订阅 收集组件重新打包后要执行的回调
dep.on('magicOk', renderComponent);
}
}
}, [containRef]);
return <div className={$style.cutomComponent} ref={containRef}></div>;
热更新原理
const express = require("express");
const http = require("http");
const cors = require("cors");
class MagicHotReplacementPlugin {
constructor(componentConfig) {
const app = express();
app.use(cors());
const server = http.createServer(app);
//开启一个websocket服务
const io = require("socket.io")(server, {
cors: {
origin: "*", //前端请求地址
methods: ["GET", "POST"],
credentials: true,
allowEIO3: true,
},
transport: ["websocket"],
});
this.clientSockets = [];
io.on("connection", (client) => {
client.emit("ready", componentConfig);
this.clientSockets.push(client);
client.on("disconnect", () => {
/* … */
const socketIndex = this.clientSockets.indexOf(client);
this.clientSockets.splice(socketIndex, 1);
});
});
server.listen(1024);
}
apply(compiler) {
compiler.hooks.done.tap("MagicHotReplacementPlugin", () => {
this.clientSockets.forEach((socket) => socket.emit("magicOk")); // 打包结束 广播
});
}
}
module.exports = MagicHotReplacementPlugin;
这个本来想用webpack提供的热更新能力的,但是遇到一个问题就是当代码重新打包后,HotReplacementPlugin拉取的hot-update.json路径是根据当前的host,看了一下配置发现好像没有提供配置这个路径的能力。 因此就根据热更新的原理重新实现了一个简单的更新逻辑。但是实际上性能还是没有webpack的热更新能力好。webpack的热更新可以去重新加载变更的模块,而我的是重新走一遍渲染逻辑。
渲染sdk的逻辑
实际上和编辑器的渲染能力一样
其实整个系统还是在初步阶段,之后低代码平台还会继续迭代,渲染的方式也会继续探索weex、原生动态化等等。
最后愿诸君心想事成。