低代码-渲染

488 阅读6分钟

概要

低代码平台能够通过可视化编排、简单配置以及少量的代码,实现快速开发和交付应用程序。

核心功能

布局引擎(Layout Engine)

● 功能:

○ 提供可视化页面设计器,支持通过拖拽操作快速搭建应用界面。

○ 支持多种UI组件(如表格、按钮、文本框、图表等)和布局方案(如栅格布局、自适应布局)。

○ 提供组件间的交互逻辑编排能力,例如设置按钮点击触发API调用,表单提交触发工作流等。

○ 允许配置页面事件(如页面加载、鼠标悬停、用户点击等)与数据源动态联动。

○ 支持嵌套页面结构和多页面跳转逻辑的设计。

○ 提供版本管理和预览功能,实时查看页面的动态渲染效果。

● 应用场景:

○ 快速搭建管理后台:如CRM、ERP等需要表单、表格等交互式页面的内部应用。

○ 动态仪表盘:整合图表组件和数据源,实现业务关键指标的实时可视化展示。

○ 表单驱动型应用:如审批系统、注册页面、信息采集工具等。

● 示例:

○ 在低代码平台中,用户通过页面编排引擎拖拽“表格”组件并绑定到数据库中的客户数据表,设置“编辑”按钮的交互逻辑为弹出对话框并修改客户信息。

○ 配置一个动态页面,当用户选择日期范围时,表格和图表自动联动刷新显示对应的数据分析结果。

渲染引擎(Rendering Engine)

● 功能:

○ 负责界面生成和动态交互渲染。

○ 提供组件化开发能力,支持多种 UI 组件(表格、图表、拖拽容器等)。

○ 支持跨设备兼容,自动适配不同屏幕尺寸(响应式设计)。

○ 集成主题定制,支持皮肤切换和样式配置。

● 应用场景:

○ 数据密集型系统的动态展示(如报表系统、仪表盘)。

○ 内部工具开发(如 CRM、库存管理工具)。

● 示例: 在 Glide 中,用户可通过简单配置,将表格数据渲染为移动端友好的卡片视图。

源码

github.com/missxiaolin…

实现

首先页面区域

● 左侧组件

● 中间渲染物料

● 右边属性设置

image.png

左侧

具体左侧逻辑无非就是拖拽到中间区域然后知道是什么组件进行添加,接下来我们来分析一下具体实现

布局

我们所有的组件信息是在 rawComponent 文件夹里面,引用到左侧

image.png

拖拽注册

在拖拽之前首先要把全局的组件 json 导入方便拖拽的时候知道是什么组件,在components-v2 mounted 事件里面

Promise.all([import("../map/load")]).then((res) => {
      this.$emit("onLoadFinish");
      this.eventNode = this.initCodeEntity.eventNode;
      this.init();
    });

具体组件会去执行deepLCEle 进行注册,让其有拖拽事件

<template>
  <div class="demonstration-raw">
    <rowHtml />
  </div>
</template>
<script>
import { deepLCEle } from "../../utils/initRawComponent";
import rowHtml from "./row.vue";

export default {
  components: {
    rowHtml,
  },
  data() {
    return {};
  },

  mounted() {
    this.$emit("mounted");
    // 对所有拥有lc-mark的元素进行初始化
    let countComponentCount = 0;
    deepLCEle(document.querySelector(".demonstration-raw"), () => {
      countComponentCount++;
    });
  },
  methods: {},
};
</script>

遍历dom 元素下有 lc-mark 标记的代表可以进行拖拽,具体注册在initElement方法里面

// 遍历DOM树,初始化lc-mark标记的元素
export function deepLCEle(rootElement, onCountIncrease = () => {}) {
  // 对dragParent下所有含有lc-mark属性的Element实现可拖拽能力
  function deep(ele) {
    if (ele.attributes["lc-mark"]) {
      // 统计标记组件数量
      onCountIncrease();
      initElement(ele);
    }

    if (ele.children.length > 0) {
      const length = ele.children.length;
      for (let i = 0; i < length; i++) {
        deep(ele.children.item(i));
      }
    }
  }

  deep(rootElement);
}

注册组件拖拽,让拖拽的时候会去全局 window 上查找这个组件对应的json 信息具体方法在generateRawInfo 上

// 对组件初始化,使组件可以拖拽
export function initElement(element) {
  element.draggable = true;
  // 给每个组件添加拖拽事件
  element.addEventListener("dragstart", function (event) {
    event.dataTransfer.effectAllowed = "copy";
    const raw = generateRawInfo(element);
    let title = element.innerText;
    if (element.querySelector(".title")) {
      title = element.querySelector(".title").innerText;
    }
    let asyncUrl = "";
    if (element.querySelector(".title")) {
      asyncUrl =
        element.querySelector(".title").getAttribute("component_url") || "";
    }
    let component_name = "";
    if (element.querySelector(".title")) {
      component_name =
        element.querySelector(".title").getAttribute("component_name") || "";
    }
    const str = `${element.localName}${getSplitTag()}${
      element.innerText
    }${getSplitTag()}${0}${getSplitTag()}${
      element.style.cssText
    }${getSplitTag()}${JSON.stringify(
      raw
    )}${getSplitTag()}${title}${getSplitTag()}${asyncUrl}${getSplitTag()}${component_name}`;
    event.dataTransfer.setData("text/plain", str);

    event.stopPropagation();
  });

  // 处理组件标记
  element.addEventListener("mouseenter", (event) => {
    const parentClassList = element.parentElement.classList;
    if (parentClassList && parentClassList.contains("mark-element-unit")) {
      parentClassList.remove("mark-element-unit");
      element.isRemoveParentStyle = true;
    }

    element.classList.add("mark-element-unit");
    event.stopPropagation();
  });

  element.addEventListener("mouseleave", (event) => {
    element.classList.remove("mark-element-unit");
    if (element.isRemoveParentStyle) {
      element.parentElement.classList.add("mark-element-unit");
    }

    event.stopPropagation();
  });
}

generateRawInfo方法

export function generateRawInfo(target) {
  if (target.querySelector(".title")) {
    target = target.querySelector(".title");
  }
  if (target.attributes.lc_id) {
    return findVueInfo(target);
  } else {
    // 这是一个普通的元素
    const temp = {
      [target.localName]: {
        __text__: target.innerText,
        class: target.className,
      },
    };
    return temp;
  }
}

这样就实现了拖拽了

渲染区域

然后我们看看中间渲染的具体实现

在 lib/main-panel.js 是具体实现渲染的逻辑,在此之前我们先思考一下拖拽出来的 vue 模块可以用上面方式去实现

拖拽注册

初始化拖拽事件

drop 方法是在释放的时候处理,释放的时候我们先看看是不是在我们渲染区域上,在渲染区域上去寻找lc_id代表我们区域释放后我之前我们组件注册拖拽的时候在 dataTransfer 已经把拖拽进去的组件对应的 json 上了所有直接解析回来使用即可,initDropCode方法进行插入 json

// 拖入预览容器释放时的处理
    renderControlPanel.addEventListener("drop", (event) => {
      if (!this.currentPointDropInfo.target) {
        return;
      }

      const data = event.dataTransfer.getData("text/plain");
      let newData = data.split(getSplitTag());
      const rawInfo = newData[4];
      const vccName = newData[5];
      let newDropObj = JSON.parse(rawInfo);
      if (newDropObj) {
        Object.keys(newDropObj).forEach((item) => {
          if (item !== "__key__" && !newDropObj[item].vccName) {
            newDropObj[item].vccName = vccName;
          }
          if (
            item !== "__key__" &&
            newData[6] &&
            !newDropObj[item].componentUrl
          ) {
            newDropObj[item].componentUrl = newData[6];
          }
          if (
            item !== "__key__" &&
            newData[7] &&
            !newDropObj[item].component_name
          ) {
            newDropObj[item].component_name = newData[7];
          }
        });
      }

      if (isRawComponents(newDropObj)) {
        if (this.dropIninFunction) {
          this.dropIninFunction(newDropObj, this);
          return;
        }
        this.initDropCode(newDropObj);
      } else if (isActiveComponents(newDropObj)) {
        // 移动的情况
        deleteNodeFromParent(newDropObj);
      }

      this.updateCodeStructure(newDropObj);
    });
  }
initDropCode

replaceRowID 为了方便后续操作,需要生成一个唯一的 lc_id,updateLinkTree方法是重点

/**
   * 插入
   * @param {*} newDropObj
   */
  initDropCode(newDropObj) {
    // 插入预设属性
    insertPresetAttribute(newDropObj);

    // 使新拖入的代码与原来的做脱离
    replaceRowID(newDropObj, "");

    // 更新到一个Map上面,维持引用,由于render中统一做了处理,所以这段代码是可以删除的 2021年02月04日11:59:10
    updateLinkTree(newDropObj);
  }
updateLinkTree

这个方法主要是维护了一个 json 树,这里是通过原型链的方式访问属性关系,对原型链不熟悉的各位需要重新了解一下

/** 在这里维护一棵以ID为KEY的树 */
export function updateLinkTree(codeObj) {
  if (!window.tree) {
    window.tree = {};
  }

  flatCodeObj(codeObj);
}
export function flatCodeObj(codeObj) {
  function deep(object) {
    for (const key in object) {
      if (object.hasOwnProperty(key)) {
        const element = object[key];

        // 如果对__children的子属性遍历时,它内部的元素需要指向外层的节点作为父节点
        if (
          object.hasOwnProperty("__key__") &&
          object["__key__"] === "__children" &&
          isObject(element)
        ) {
          delete object["__key__"];
        }

        if (key === "lc_id" && object.hasOwnProperty("__key__")) {
          const outerKey = object["__key__"];
          const newObj = {
            [outerKey]: object,
          };

          // 这个关系也需要链接
          newObj.__proto__ = object.__proto__;
          delete object.__key__;
          window.tree[element] = newObj;
        } else if (key === "__children") {
          object.__children.forEach((child) => {
            child["__key__"] = key;
            deep(child);
          });
        } else if (isObject(element)) {
          element["__key__"] = key;
          deep(element);
        }
      }
    }
  }
  deep(codeObj);
}

至此已经大部分完成了,json 树已经出来了,解析来我们来看看实际渲染方式

渲染实现方案

● 通过 json 树 for 循环组件

● 在线通过 babel 来编译

● 通过 json 树生成 html 然后用 vue 组件的方式进行加载

○ parseComponent

○ vue3-sfc-loader

比如我们有一个 vue 文件

<template>
  <div class="powderblue">
    ceshi
    <a-button>sssa</a-button>
  </div>
</template>

<script>
import { Button } from "ant-design-vue";
export default {
  components: {
    Button,
  },
  setup(props) {},
};
</script>

<style lang="scss" scoped>
.powderblue {
  color: red;
}
</style>
parseComponent

我们先来讲讲 parseComponent,通过parseComponent我们会解析出来 html js css,然后我们对其相应的处理

main.js

为了方便使用我们直接挂在在 window 上

/**
 * 创建实例基础方法
 * @param {*} renderComponent
 * @param {*} loadFinished
 * @returns
 */
function loadTemplate(renderComponent, loadFinished) {
  const app = createApp(renderComponent);
  /** 加载插件 */
  loadPlugins(app);
  loadFinished(app);
  return app;
}

/**
 * 异步创建实例
 * @param {*} renderComponent
 * @returns
 */
function createBaseAppAsync(renderComponent = {}) {
  return new Promise((resolve, reject) => {
    loadTemplate(renderComponent, (app) => {
      resolve(app);
    });
  });
}

let app = null;
function mount() {
  app = createApp(App);
  app.use(router);
  
  /** 加载插件 */
  loadPlugins(app);
  window.createBaseAppAsync = createBaseAppAsync;
  if (isSubMicro) {
    // 微前端环境下, 处理路由下发跳转
    handleMicroData(router, app);
  }
  app.mount("#vie-app");
}

mount();
解析

app.mount 会重新渲染到对应的节点上

import { parseComponent } from "vue-template-compiler/browser";
const { template, script, styles, customBlocks } = parseComponent(code);
this.loadStyle(styles);
let newScript = script.content.replace(/\s*export default\s*/, "");
const componentOptions = new Function(`return ${newScript}`)();
// 保存script代码
this.componentOptions = componentOptions;
componentOptions.template = template.content;
// 渲染当前代码
      const readyForMoutedElement = this.createMountedElement();
      window.createBaseAppAsync(componentOptions).then((app) => {
        app.mount(readyForMoutedElement);
      });
vue3-sfc-loader

相对而言 vue3-sfc-loader 就比较简单了

引入

为了方便我是直接在 html 引入了,当然大家可以用过 npm yarn pnpm 进行安装

<script src="/static/vue3-sfc-loader.js"></script>
main.js

为了方便使用我们直接挂在在 window 上

/**
 * 创建实例基础方法
 * @param {*} renderComponent
 * @param {*} loadFinished
 * @returns
 */
function loadTemplate(renderComponent, loadFinished) {
  const app = createApp(renderComponent);
  /** 加载插件 */
  loadPlugins(app);
  loadFinished(app);
  return app;
}

/**
 * 异步创建实例
 * @param {*} renderComponent
 * @returns
 */
function createBaseAppAsync(renderComponent = {}) {
  return new Promise((resolve, reject) => {
    loadTemplate(renderComponent, (app) => {
      resolve(app);
    });
  });
}

let app = null;
function mount() {
  app = createApp(App);
  app.use(router);
  
  /** 加载插件 */
  loadPlugins(app);
  window.createBaseAppAsync = createBaseAppAsync;
  if (isSubMicro) {
    // 微前端环境下, 处理路由下发跳转
    handleMicroData(router, app);
  }
  app.mount("#vie-app");
}

mount();
编译

具体使用大家可以再去参考下官网使用

appLoad(code, readyForMoutedElement) {
    const codeString = editTem(code, this._rawDataStructure);
    // 渲染当前代码
    const files = {
      "/mt.vue": getMtTem(),
      "/main.vue": codeString,
      "/events.json": `${JSON.stringify(this.eventNode)}`,
    };

    const options = getEditOptions(files);
    const app = Vue.createApp(
      Vue.defineAsyncComponent(() =>
        window["vue3-sfc-loader"].loadModule("/main.vue", options)
      )
    );
    window.loadApp(app);
    app.mount(readyForMoutedElement);
  }

html json 相互转换

知道了渲染方式了我们再来看看我们怎么把 json 树转换成 HTML

使用了htmlparser2 库进行想换转换

html 转换 object
import htmlparser2 from 'htmlparser2'

function getNodeContent(node) {
  return node[Object.keys(node)[0]];
}

function generateNewNode(tagName, attributes = {}) {
  // build new node
  attributes.__children = [];
  const newNode = {
    [tagName]: attributes,
  };
  return newNode;
}

function parseHtml(htmlData) {
  return new Promise((resolve, reject) => {
    const root = generateNewNode("root");
    let currentAccessObject = root;
    let lastAccessStack = [root];

    // options docment: https://github.com/fb55/htmlparser2/wiki/Parser-options
    const parser = new htmlparser2.Parser({
      onopentag(tagname, attributes) {
        const newNode = generateNewNode(tagname, attributes);
        lastAccessStack.push(newNode);
        getNodeContent(currentAccessObject).__children.push(newNode);
        currentAccessObject = newNode;
      },
      ontext(text) {
        if (text.trim()) {
          getNodeContent(currentAccessObject).__text__ = text.trim();
        }
      },
      onclosetag(tagname) {
        lastAccessStack.pop();
        currentAccessObject = lastAccessStack[lastAccessStack.length - 1];
      },
      onend() {
        resolve(root);
      },

      onerror(error) {
        reject(error);
      },
    });
    parser.write(htmlData);
    parser.end();
  });
}

export async function html2Json(htmlData) {
  return await parseHtml(htmlData);
}
object 转换 html
"use strict";
//parse Empty Node as self closing node
import { buildOptions } from "../utils/util";

const defaultOptions = {
  attributeNamePrefix: "@_",
  attrNodeName: false,
  textNodeName: "#text",
  ignoreAttributes: true,
  cdataTagName: false,
  cdataPositionChar: "\c",
  format: true,
  indentBy: "  ",
  supressEmptyNode: false,
  tagValueProcessor: function (a) {
    return a;
  },
  attrValueProcessor: function (a) {
    return a;
  },
  attributeProtectArray: [],
};

const props = [
  "attributeNamePrefix",
  "attrNodeName",
  "textNodeName",
  "ignoreAttributes",
  "cdataTagName",
  "cdataPositionChar",
  "format",
  "indentBy",
  "supressEmptyNode",
  "tagValueProcessor",
  "attrValueProcessor",
  "attributeProtectArray",
];

export function Parser(options) {
  this.options = buildOptions(options, defaultOptions, props);
  if (this.options.ignoreAttributes || this.options.attrNodeName) {
    this.isAttribute = function (/*a*/) {
      return false;
    };
  } else {
    this.attrPrefixLen = this.options.attributeNamePrefix.length;
    this.isAttribute = isAttribute;
  }
  if (this.options.cdataTagName) {
    this.isCDATA = isCDATA;
  } else {
    this.isCDATA = function (/*a*/) {
      return false;
    };
  }
  this.replaceCDATAstr = replaceCDATAstr;
  this.replaceCDATAarr = replaceCDATAarr;

  if (this.options.format) {
    this.indentate = indentate;
    this.tagEndChar = ">\n";
    this.newLine = "\n";
  } else {
    this.indentate = function () {
      return "";
    };
    this.tagEndChar = ">";
    this.newLine = "";
  }

  if (this.options.supressEmptyNode) {
    this.buildTextNode = buildEmptyTextNode;
    this.buildObjNode = buildEmptyObjNode;
  } else {
    this.buildTextNode = buildTextValNode;
    this.buildObjNode = buildObjectNode;
  }

  this.buildTextValNode = buildTextValNode;
  this.buildObjectNode = buildObjectNode;
}

Parser.prototype.parse = function (jObj) {
  return this.j2x(jObj, 0).val;
};

Parser.prototype.j2x = function (jObj, level) {
  let attrStr = "";
  let val = "";
  const keys = Object.keys(jObj);
  const len = keys.length;
  for (let i = 0; i < len; i++) {
    const key = keys[i];
    if (typeof jObj[key] === "undefined") {
      // supress undefined node
    } else if (jObj[key] === null) {
      val += this.indentate(level) + "<" + key + "/" + this.tagEndChar;
    } else if (jObj[key] instanceof Date) {
      val += this.buildTextNode(jObj[key], key, "", level);
    } else if (key === "__children") {
      const item = jObj[key];

      if (item instanceof Array) {
        item.forEach((i) => {
          const result = this.j2x(i, level + 1);
          val += result.val;
        });
      } else if (typeof item === "object") {
        console.warn(`some exception`);
      } else {
        val += this.buildTextNode(item, key, "", level);
      }
    } else if (typeof jObj[key] !== "object") {
      //premitive type
      const attr = this.isAttribute(key);

      if (key === "__text__") {
        val = jObj[key] + val;
        continue;
      }

      if (attr) {
        if (typeof jObj[key] === "boolean" && jObj[key]) {
          attrStr += ` ${key} `;
        } else if (
          jObj[key] ||
          this.options.attributeProtectArray.includes(key)
        ) {
          attrStr +=
            " " +
            key +
            '="' +
            this.options.attrValueProcessor("" + jObj[key]) +
            '"';
        } else {
          attrStr += " " + key;
        }
      } else if (this.isCDATA(key)) {
        if (jObj[this.options.textNodeName]) {
          val += this.replaceCDATAstr(
            jObj[this.options.textNodeName],
            jObj[key]
          );
        } else {
          val += this.replaceCDATAstr("", jObj[key]);
        }
      } else {
        //tag value
        if (key === this.options.textNodeName) {
          if (jObj[this.options.cdataTagName]) {
            //value will added while processing cdata
          } else {
            val += this.options.tagValueProcessor("" + jObj[key]);
          }
        } else {
          val += this.buildTextNode(jObj[key], key, "", level);
        }
      }
    } else if (Array.isArray(jObj[key])) {
      //repeated nodes
      if (this.isCDATA(key)) {
        val += this.indentate(level);
        if (jObj[this.options.textNodeName]) {
          val += this.replaceCDATAarr(
            jObj[this.options.textNodeName],
            jObj[key]
          );
        } else {
          val += this.replaceCDATAarr("", jObj[key]);
        }
      } else {
        //nested nodes
        const arrLen = jObj[key].length;
        for (let j = 0; j < arrLen; j++) {
          const item = jObj[key][j];
          if (typeof item === "undefined") {
            // supress undefined node
          } else if (item === null) {
            val += this.indentate(level) + "<" + key + "/" + this.tagEndChar;
          } else if (typeof item === "object") {
            const result = this.j2x(item, level + 1);
            val += this.buildObjNode(result.val, key, result.attrStr, level);
          } else {
            val += this.buildTextNode(item, key, "", level);
          }
        }
      }
    } else {
      //nested node
      if (this.options.attrNodeName && key === this.options.attrNodeName) {
        const Ks = Object.keys(jObj[key]);
        const L = Ks.length;
        for (let j = 0; j < L; j++) {
          attrStr +=
            " " +
            Ks[j] +
            '="' +
            this.options.attrValueProcessor("" + jObj[key][Ks[j]]) +
            '"';
        }
      } else {
        const result = this.j2x(jObj[key], level + 1);
        val += this.buildObjNode(result.val, key, result.attrStr, level);
      }
    }
  }
  return { attrStr: attrStr, val: val };
};

function replaceCDATAstr(str, cdata) {
  str = this.options.tagValueProcessor("" + str);
  if (this.options.cdataPositionChar === "" || str === "") {
    return str + "<![CDATA[" + cdata + "]]" + this.tagEndChar;
  } else {
    return str.replace(
      this.options.cdataPositionChar,
      "<![CDATA[" + cdata + "]]" + this.tagEndChar
    );
  }
}

function replaceCDATAarr(str, cdata) {
  str = this.options.tagValueProcessor("" + str);
  if (this.options.cdataPositionChar === "" || str === "") {
    return (
      str + "<![CDATA[" + cdata.join("]]><![CDATA[") + "]]" + this.tagEndChar
    );
  } else {
    for (let v in cdata) {
      str = str.replace(
        this.options.cdataPositionChar,
        "<![CDATA[" + cdata[v] + "]]>"
      );
    }
    return str + this.newLine;
  }
}

function buildObjectNode(val, key, attrStr, level) {
  if (attrStr && !val.includes("<")) {
    if (key === "img" || key === "input") {
      return this.indentate(level) + "<" + key + attrStr + "/>";
    }

    return (
      this.indentate(level) +
      "<" +
      key +
      attrStr +
      ">" +
      val +
      //+ this.newLine
      // + this.indentate(level)
      "</" +
      key +
      this.tagEndChar
    );
  } else {
    return (
      this.indentate(level) +
      "<" +
      key +
      attrStr +
      this.tagEndChar +
      val +
      //+ this.newLine
      this.indentate(level) +
      "</" +
      key +
      this.tagEndChar
    );
  }
}

function buildEmptyObjNode(val, key, attrStr, level) {
  if (val !== "") {
    return this.buildObjectNode(val, key, attrStr, level);
  } else {
    return this.indentate(level) + "<" + key + attrStr + "/" + this.tagEndChar;
    //+ this.newLine
  }
}

function buildTextValNode(val, key, attrStr, level) {
  return (
    this.indentate(level) +
    "<" +
    key +
    attrStr +
    ">" +
    this.options.tagValueProcessor(val) +
    "</" +
    key +
    this.tagEndChar
  );
}

function buildEmptyTextNode(val, key, attrStr, level) {
  if (val !== "") {
    return this.buildTextValNode(val, key, attrStr, level);
  } else {
    return this.indentate(level) + "<" + key + attrStr + "/" + this.tagEndChar;
  }
}

function indentate(level) {
  return this.options.indentBy.repeat(level);
}

function isAttribute(name /*, options*/) {
  return true;
}

function isCDATA(name) {
  return name === this.options.cdataTagName;
}

//formatting
//indentation
//\n after each closing or self closing tag
代码库

组装 vue 模块文件

lib/bundle-core-esm.js

定义模板

function vueTemplate() {
  return `
  <template> 
    <!--在此自动生成--> 
  </template>
  
<script>
import { // $vueExport } from "vue";
import events from "./events.json";
const vccEvents = events;
// $script
</script>
  
  <style scoped>
  /** $stylesTemplate */
  </style>
    `;
}
生成 vue 文件
async replaceKeyInfo() {
    let $data,
      funs,
      vueExport,
      str = "",
      settingData;

    // 解析外层代码
    if (this.externalJS) {
      const js = jsSetup(this.externalJS);
      $data = js.$data || "";
      funs = js.funs || [];
      vueExport = js.vueExport || [];
      str = js.str || "";
      settingData = js.settingData || [];
    }
    // 将对象转换为html并替换
    const templateTemp = replaceHtmlTemplate(getVueTemplate(), this.jsonObj);

    // ==================== 生成脚本 ====================

    // 生成方法
    const methodTemp = replaceMethods(
      scriptTemplate,
      this.methodSet,
      this.options
    );

    // 生成data
    const dataTemp = replaceDatas(methodTemp, this.dataSet, this.options);

    // 转化为对象
    const JSCodeInfo = eval(`(function(){return ${dataTemp}})()`);

    // 合并外部脚本对象
    let externalData = {};
    if ($data) {
      const regExp = /toRefs(([\s\S]+?))/;
      const match = $data.match(regExp);
      if (match && match[1]) {
        const objStr = match[1].trim();
        try {
          const newObj = eval(`(${objStr})`);
          externalData = newObj;
        } catch (e) {}
      }
    }

    // 生成新的data返回值
    const newData = merge({}, JSCodeInfo.data(), externalData, this.customData);

    const dataFunction = new Function(`return ${stringifyObject(newData)}`);

    JSCodeInfo.data = dataFunction;

    let externalJSLogic = {
      methods: {},
    };

    if (funs && funs.length > 0) {
      funs.forEach((methodString) => {
        // 获取方法名
        const match = methodString.match(/(let|const)\s+(\w+)\s*=\s*([\s\S]+)/);
        if (match && match[1] && match[2] && match[3]) {
          const variableName = match[2];
          externalJSLogic.methods[variableName] = match[3];
        }
      });
    }

    const mergedJSObject = merge(JSCodeInfo, externalJSLogic);

    let toRefsData = mergedJSObject.data(),
      functionData = ``;
    Object.keys(mergedJSObject.methods).forEach((key) => {
      functionData += `const ${key} = ${mergedJSObject.methods[key]};\n`;
    });
    let dataStr = ``,
      dataKeys = Object.keys(toRefsData) || [];
    Object.keys(toRefsData).forEach((key) => {
      let v = toRefsData[key];
      dataStr = `${dataStr}const ${key} = ref(${
        typeof v === "string" ? `'${v}'` : v
      });\n`;
    });
    const finalJSCode = `
{
setup(props, {emit}) {
  const instance = getCurrentInstance();
  ${dataStr}

  // 执行事件流
  const eventFun = (eventStr, e = null) => {
    const eventObj = vccEvents[eventStr];
    if (!eventStr || !eventObj) return;
    
    instance.proxy.$execEventFlow(instance, eventObj.children, e);
  };

  onMounted(() => {
    eventFun("init")
  })

  provide('main', instance);
  
  ${str}
  ${functionData}
  return {
    ${dataKeys && dataKeys.length > 0 ? dataKeys.join(",") + "," : ""}
    eventFun,
    ${settingData && settingData.length > 0 ? settingData.join(",") + "," : ""}
    ${Object.keys(mergedJSObject.methods).join(",")}
  }
}

}
    `;

    // ==================== 生成脚本 ====================

    const beautiful = prettier.format(`export default ` + finalJSCode, {
      semi: false,
      parser: "babel",
      plugins: [parserBabel],
    });

    const excludeUnuseal = beautiful;

    // 插入到最终模板
    const JSTemp = templateTemp.replace("// $script", excludeUnuseal);
    // 生成class
    const styleTemp = await replaceStyles(
      JSTemp,
      this.classSet,
      this.options,
      this.customCss
    );

    vueExport.push("onMounted", "ref", "getCurrentInstance", "provide");
    vueExport = Array.from(new Set(vueExport));
    const zTemp = styleTemp.replace("// $vueExport", vueExport.join(","));
    return zTemp;
  }

讲到现在,我们大概知道了我们整个逻辑渲染了,当然了还有很多方式,大家可以自己探索