十五万字长文 - 由浅入深设计一套低代码平台(4-5)

22 阅读10分钟

低代码平台架构设计方案 - 从浅入深完整指南(4-5)

前言: 本文系统阐述了一套完整的低代码平台架构设计方案,涵盖从基础架构到企业级应用的全链路技术实现

核心内容:

🏗️ 四层架构体系:可视化设计器、Schema协议、运行时引擎、物料体系的完整设计

🔄 全局状态管理:基于Zustand的页面级Store架构,支持跨组件数据流动

⚡ 性能优化方案:三种发布模式(纯运行时、Schema编译、混合模式)对比与实践

🎯 动作系统:枚举化业务操作设计,实现配置化与安全性的平衡

🔧 Schema编译器:深度解析编译优化策略,在保持Runtime架构一致性的同时实现70%体积优化

🚀 SSR支持:Next.js集成方案,满足SEO与首屏性能需求

📦 发布流程:从Schema保存到产物部署的完整工程化实践

适合人群:前端架构师、低代码平台开发者、对前端工程化感兴趣的技术人员

全文15万+字,涵盖架构设计、核心实现、性能优化、工程实践等多个维度,提供可直接落地的技术方案。

5.12.9 Schema 编译器深度实现(方案B详解)

对于 C 端高性能场景,编译方案是最优选择。编译器的核心目标是:保持 Runtime 架构不变,优化依赖加载方式,将运行时的动态加载转换为编译时的静态引入。

核心原则

  • ✅ 保持 GlobalStoreManager、ExpressionEngine、Renderer 等核心架构
  • ✅ Schema 内联到代码中(避免网络请求)
  • ✅ 物料静态引入(支持 Tree Shaking)
  • ✅ 动作静态引入(避免动态查找)
  • ✅ 使用精简版 Runtime(移除动态加载逻辑)
  • ❌ 不转换为原生 React 代码(保持架构一致性)

编译器架构设计

编译器采用三阶段架构,专注于依赖分析和代码生成:

// packages/compiler/src/Compiler.js

class SchemaCompiler {
  constructor(options = {}) {
    this.options = {
      runtimePackage: '@lowcode/runtime-slim',  // 使用精简版 Runtime
      outputFormat: 'esm',
      minify: true,
      ...options
    };

    // 编译器三阶段流水线
    this.pipeline = [
      new SchemaValidator(),      // 1. Schema 验证
      new DependencyAnalyzer(),   // 2. 依赖分析(物料、动作)
      new CodeGenerator()         // 3. 生成代码(保持 Runtime 架构)
    ];
  }

  /**
   * 编译 Schema 为优化的 React 组件
   * @param {Object} schema - Schema JSON
   * @returns {CompileResult}
   */
  async compile(schema) {
    let context = { schema, options: this.options };

    // 执行编译流水线
    for (const stage of this.pipeline) {
      context = await stage.execute(context);
    }

    return {
      success: true,
      code: context.generatedCode,        // 生成的 React 组件代码
      dependencies: context.dependencies   // 依赖清单
    };
  }
}

阶段1:Schema 验证

// packages/compiler/src/stages/SchemaValidator.js

class SchemaValidator {
  async execute(context) {
    const { schema } = context;

    // 验证 Schema 基本结构
    if (!schema.id || !schema.componentTree) {
      throw new Error('Invalid schema: missing required fields');
    }

    // Schema 直接传递,不做结构转换
    return { ...context };
  }
}

阶段2:依赖分析

编译器的核心工作是分析 Schema 中用到的物料和动作,生成静态 import 语句:

// packages/compiler/src/stages/DependencyAnalyzer.js

class DependencyAnalyzer {
  async execute(context) {
    const { schema } = context;

    // 递归分析组件树,提取所有用到的物料
    const materials = this.analyzeMaterials(schema.componentTree);

    // 分析方法中用到的动作
    const actions = this.analyzeActions(schema.methods || {});

    const dependencies = {
      materials,  // { 'antd': ['Button', 'Table'], ... }
      actions     // Set(['request', 'confirm', 'message', ...])
    };

    return { ...context, dependencies };
  }

  /**
   * 递归遍历组件树,收集所有物料
   */
  analyzeMaterials(tree) {
    const materials = new Map();

    const traverse = (node) => {
      const { componentName, children } = node;

      // 记录物料使用
      const library = this.resolveLibrary(componentName);  // 'Button' → 'antd'
      if (!materials.has(library)) {
        materials.set(library, new Set());
      }
      materials.get(library).add(componentName);

      // 递归子节点
      if (children) {
        children.forEach(child => traverse(child));
      }
    };

    traverse(tree);

    // 转换为对象格式
    const result = {};
    materials.forEach((components, library) => {
      result[library] = Array.from(components);
    });

    return result;
  }

  /**
   * 分析方法中用到的动作类型
   */
  analyzeActions(methods) {
    const actions = new Set();

    Object.values(methods).forEach(method => {
      method.actions?.forEach(action => {
        actions.add(action.type);  // 'request', 'confirm', 'setState', ...
      });
    });

    return actions;
  }

  /**
   * 根据组件名推断所属的物料库
   */
  resolveLibrary(componentName) {
    // 简化示例:实际需要查询物料注册表
    const antdComponents = ['Button', 'Table', 'Form', 'Modal', 'Input'];
    if (antdComponents.includes(componentName)) {
      return 'antd';
    }
    return '@lowcode/materials';
  }
}

阶段3:代码生成

生成包含 Runtime 渲染器的 React 组件:

// packages/compiler/src/stages/CodeGenerator.js

class CodeGenerator {
  async execute(context) {
    const { schema, dependencies, options } = context;

    // 生成 import 语句
    const imports = this.generateImports(dependencies, options.runtimePackage);

    // 生成预注册代码
    const registrations = this.generateRegistrations(dependencies);

    // Schema 内联(序列化为 JavaScript 对象)
    const schemaCode = this.serializeSchema(schema);

    // 组装完整代码
    const generatedCode = \`
\${imports}

/**
 * \${schema.name || schema.id}
 * Auto-generated by @lowcode/compiler
 * 
 * ⚠️ 架构说明:
 * - 使用精简版 Runtime(移除动态加载逻辑)
 * - Schema 内联(避免网络请求)
 * - 物料静态引入(支持 Tree Shaking)
 * - 动作静态引入(避免运行时查找)
 * 
 * ✅ 核心架构与运行时模式完全一致:
 * - GlobalStoreManager:全局状态管理
 * - ExpressionEngine:表达式解析
 * - Renderer:组件渲染
 * - EventHandler:事件处理
 */

// Schema 内联
const schema = \${schemaCode};

\${registrations}

export default function \${this.toPascalCase(schema.id)}Page() {
  return (
    <LowCodeRenderer 
      schema={schema}
      materials={materials}
      actions={actions}
    />
  );
}
    \`.trim();

    return { ...context, generatedCode };
  }

  /**
   * 生成 import 语句
   */
  generateImports(dependencies, runtimePackage) {
    const lines = [];

    // 1. 导入 React
    lines.push("import React from 'react';");

    // 2. 导入精简版 Runtime
    lines.push(\`import { LowCodeRenderer } from '\${runtimePackage}';\`);

    // 3. 导入物料(静态引入,支持 Tree Shaking)
    Object.entries(dependencies.materials).forEach(([library, components]) => {
      lines.push(\`import { \${components.join(', ')} } from '\${library}';\`);
    });

    // 4. 导入动作实现
    const actionsList = Array.from(dependencies.actions);
    if (actionsList.length > 0) {
      const actionImports = actionsList.map(action => \`\${action}Action\`).join(', ');
      lines.push(\`import { \${actionImports} } from '@lowcode/actions';\`);
    }

    return lines.join('\n');
  }

  /**
   * 生成预注册代码
   */
  generateRegistrations(dependencies) {
    const code = [];

    // 物料预注册
    code.push('// 物料预注册(避免动态加载)');
    code.push('const materials = {');
    Object.values(dependencies.materials).flat().forEach(comp => {
      code.push(\`  \${comp},\`);
    });
    code.push('};');
    code.push('');

    // 动作预注册
    const actionsList = Array.from(dependencies.actions);
    if (actionsList.length > 0) {
      code.push('// 动作预注册(避免运行时查找)');
      code.push('const actions = {');
      actionsList.forEach(action => {
        code.push(\`  \${action}: \${action}Action,\`);
      });
      code.push('};');
    }

    return code.join('\n');
  }

  /**
   * 序列化 Schema 为 JavaScript 对象
   */
  serializeSchema(schema) {
    return JSON.stringify(schema, null, 2);
  }

  toPascalCase(str) {
    return str.replace(/(^\w|-\w)/g, s => s.replace('-', '').toUpperCase());
  }
}

完整示例:编译前后对比

输入:运行时模式的页面组件

// 运行时模式:需要动态加载 Schema 和物料
import { LowCodeRenderer } from '@lowcode/runtime';  // 200KB

export default function RuntimePage() {
  const [schema, setSchema] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 1. 网络请求加载 Schema(~200ms)
    fetch('/api/schema?route=/user-manage')
      .then(res => res.json())
      .then(data => {
        setSchema(data);
        setLoading(false);
      });
  }, []);

  if (loading) return <div>Loading...</div>;

  // 2. Runtime 内部动态加载物料和动作(~300ms)
  return <LowCodeRenderer schema={schema} />;
}

输出:编译模式的页面组件

// 编译模式:所有依赖静态引入
import React from 'react';
import { LowCodeRenderer } from '@lowcode/runtime-slim';  // 50KB(精简版)
import { Button, Table } from 'antd';  // 静态引入物料
import { requestAction, confirmAction, messageAction, setStateAction } from '@lowcode/actions';  // 静态引入动作

/**
 * 用户管理页面
 * Auto-generated by @lowcode/compiler
 * 
 * ⚠️ 架构说明:
 * - 使用精简版 Runtime(移除动态加载逻辑)
 * - Schema 内联(避免网络请求)
 * - 物料静态引入(支持 Tree Shaking)
 * - 动作静态引入(避免运行时查找)
 * 
 * ✅ 核心架构与运行时模式完全一致:
 * - GlobalStoreManager:全局状态管理
 * - ExpressionEngine:表达式解析
 * - Renderer:组件渲染
 * - EventHandler:事件处理
 */

// Schema 内联(避免网络请求)
const schema = {
  "id": "user-manage",
  "name": "用户管理页面",
  "store": {
    "users": [],
    "loading": false
  },
  "methods": {
    "loadUsers": {
      "params": [],
      "actions": [
        { "type": "setState", "payload": { "loading": true } },
        {
          "type": "request",
          "method": "GET",
          "url": "/api/users",
          "successAction": {
            "type": "setState",
            "payload": { "users": "\${response.data}", "loading": false }
          }
        }
      ]
    },
    "handleDelete": {
      "params": ["userId"],
      "actions": [
        { "type": "confirm", "message": "确认删除该用户?" },
        {
          "type": "request",
          "method": "DELETE",
          "url": "/api/users/\${userId}",
          "successAction": { "type": "message", "content": "删除成功" }
        },
        { "type": "method", "method": "loadUsers" }
      ]
    }
  },
  "lifecycle": {
    "onLoad": [{ "type": "method", "method": "loadUsers" }]
  },
  "componentTree": {
    "componentName": "div",
    "props": { "className": "page" },
    "children": [
      {
        "componentName": "Button",
        "props": { "type": "primary", "loading": "\${store.loading}" },
        "events": { "onClick": { "type": "method", "method": "loadUsers" } },
        "children": [{ "componentName": "text", "value": "刷新" }]
      },
      {
        "componentName": "Table",
        "props": {
          "dataSource": "\${store.users}",
          "loading": "\${store.loading}"
        }
      }
    ]
  }
};

// 物料预注册(避免动态加载)
const materials = {
  Button,
  Table,
};

// 动作预注册(避免运行时查找)
const actions = {
  request: requestAction,
  confirm: confirmAction,
  message: messageAction,
  setState: setStateAction,
};

export default function UserManagePage() {
  return (
    <LowCodeRenderer 
      schema={schema}
      materials={materials}
      actions={actions}
    />
  );
}

Runtime 分层设计

为了支持编译模式,Runtime 需要分为两个版本:

// packages/runtime/src/index.js (完整版 - 用于运行时渲染)
export { LowCodeRenderer } from './Renderer';
export { MaterialLoader } from './MaterialLoader';      // 动态加载物料
export { ActionRegistry } from './ActionRegistry';      // 动作注册表
export { SchemaLoader } from './SchemaLoader';          // Schema 加载器
// 体积:~200KB

// packages/runtime-slim/src/index.js (精简版 - 用于编译模式)
export { LowCodeRenderer } from '../runtime/src/Renderer';
// ❌ 不包含 MaterialLoader(物料已静态引入)
// ❌ 不包含 ActionRegistry(动作已静态引入)
// ❌ 不包含 SchemaLoader(Schema 已内联)
// 体积:~50KB(减少 75%)

LowCodeRenderer 支持预注册

// packages/runtime/src/Renderer.jsx

export function LowCodeRenderer({ schema, materials, actions }) {
  const [runtime] = useState(() => {
    // 创建 Runtime 实例
    const rt = new LowCodeRuntime(schema);

    // 如果提供了预注册的物料,直接使用(编译模式)
    if (materials) {
      rt.materialRegistry.registerBatch(materials);
    }

    // 如果提供了预注册的动作,直接使用(编译模式)
    if (actions) {
      rt.actionRegistry.registerBatch(actions);
    }

    return rt;
  });

  // 渲染逻辑完全一致(运行时模式和编译模式共享同一套渲染逻辑)
  return renderComponentTree(runtime, schema.componentTree);
}

性能对比:运行时 vs 编译

指标运行时渲染编译模式性能提升
Schema 加载`fetch('/api/schema')` ~200ms内联(0ms)100% ↓
物料加载动态 `import()` ~300ms静态 `import`(Webpack处理)100% ↓
动作查找`ActionRegistry.get('request')` ~1ms直接引用 `requestAction`100% ↓
Runtime 体积200KB(含加载逻辑)50KB(精简版)75% ↓
首屏 JS 总体积220KB(Runtime 200KB + Schema 20KB)80KB(Runtime 50KB + Code 30KB)64% ↓
首次加载时间2.5s0.8s68% ↓
GlobalStore✅ GlobalStoreManager✅ GlobalStoreManager(一致)-
表达式解析✅ ExpressionEngine✅ ExpressionEngine(一致)-
事件处理✅ EventHandler✅ EventHandler(一致)-
跨组件数据共享✅ `${store.xxx}`✅ `${store.xxx}`(一致)-

关键发现

  • ✅ 性能提升来自依赖优化,不是架构改变
  • ✅ 核心架构(Store、表达式、事件)完全一致
  • ✅ 跨组件数据流向机制保持不变
  • ✅ 开发和维护成本最低

集成到发布流程

// 发布接口
app.post('/api/pages/:id/publish', async (req, res) => {
  const { id } = req.params;
  const { schema, publishMode } = req.body;

  if (publishMode === 'compile') {
    // 方案B:编译模式
    const compiler = new SchemaCompiler({
      runtimePackage: '@lowcode/runtime-slim',
      outputFormat: 'esm',
      minify: true
    });

    const result = await compiler.compile(schema);

    if (result.success) {
      // 写入文件系统
      const outputPath = path.join(__dirname, \`../pages/\${id}.jsx\`);
      await fs.writeFile(outputPath, result.code);

      // 触发 Webpack/Vite 构建
      await buildPage(id);

      res.json({
        success: true,
        mode: 'compile',
        entry: \`/pages/\${id}.js\`,
        dependencies: result.dependencies
      });
    }
  } else {
    // 方案A:运行时模式
    await db.pageVersions.create({
      pageId: id,
      schema: schema,
      mode: 'runtime'
    });

    res.json({ success: true, mode: 'runtime' });
  }
});

编译方案的优势

  1. 性能最优:消除动态加载开销,首屏时间减少 68%
  2. 体积最小:Tree Shaking 剔除未使用的物料,体积减少 64%
  3. 架构一致:核心逻辑与运行时模式完全相同,维护成本低
  4. 调试友好:生成的是标准 React 代码,有完整的 Source Map
  5. SEO 友好:可进行 SSR/SSG 等服务端渲染优化
  6. 独立部署:编译产物可独立部署到 CDN

编译方案的劣势

  1. 失去热更新:修改 Schema 后需重新编译和部署
  2. 构建时间:编译过程需要 1-3 秒(可通过增量编译优化)

服务端渲染(SSR)支持

编译模式的重要优势之一是天然支持 SSR/SSG,这是运行时模式难以实现的。

为什么编译模式适合 SSR?

// 编译后的组件是标准 React 组件
export default function UserManagePage() {
  return (
    <LowCodeRenderer
      schema={schema}
      materials={materials}
      actions={actions}
    />
  );
}

// ✅ 可以直接用于 Next.js
// pages/user-manage.jsx (Next.js App Router)
export default UserManagePage;

// ✅ 支持 SSR
export async function getServerSideProps() {
  // 可以在服务端预加载数据
  const users = await fetchUsers();

  return {
    props: {
      initialData: { users }
    }
  };
}

// ✅ 支持 SSG
export async function getStaticProps() {
  const users = await fetchUsers();

  return {
    props: { initialData: { users } },
    revalidate: 60 // ISR: 每60秒重新生成
  };
}

Runtime 需要支持 SSR

为了在服务端渲染,Runtime 需要:

  1. 避免浏览器 API 依赖
// packages/runtime-slim/src/Renderer.jsx

export function LowCodeRenderer({ schema, materials, actions, initialData }) {
  const [runtime] = useState(() => {
    const rt = new LowCodeRuntime(schema);

    rt.materialRegistry.registerBatch(materials);
    rt.actionRegistry.registerBatch(actions);

    // ✅ 如果有服务端预加载的数据,初始化到 Store
    if (initialData) {
      rt.storeManager.updateStore(initialData);
    }

    return rt;
  });

  // ⚠️ 避免在 render 阶段使用浏览器 API
  // ❌ window.location
  // ❌ document.cookie
  // ❌ localStorage

  // ✅ 使用 useEffect 包裹浏览器 API
  useEffect(() => {
    if (typeof window !== 'undefined') {
      // 客户端才执行的逻辑
    }
  }, []);

  return renderComponentTree(runtime, schema.componentTree);
}
  1. 动作系统的 SSR 适配
// packages/actions/src/requestAction.js

export async function requestAction(config, context) {
  const { url, method, data } = config;

  // ✅ 服务端和客户端都可以用 fetch
  const response = await fetch(url, {
    method,
    body: data ? JSON.stringify(data) : undefined,
    headers: {
      'Content-Type': 'application/json',
      // ⚠️ 服务端请求需要处理认证
      ...(context.isServer && context.cookies ? {
        'Cookie': context.cookies
      } : {})
    }
  });

  return response.json();
}
  1. SSR 时的数据预加载
// 发布时生成支持 SSR 的代码
class SSRCodeGenerator extends CodeGenerator {
  generateCode(schema) {
    const code = super.generateCode(schema);

    // 分析 lifecycle.onLoad 中的数据请求
    const dataFetchActions = this.analyzeDataFetch(schema.lifecycle?.onLoad);

    // 生成 getServerSideProps
    const ssrCode = this.generateSSRFunction(dataFetchActions);

    return code + '\n\n' + ssrCode;
  }

  generateSSRFunction(dataFetchActions) {
    return `
// SSR 数据预加载
export async function getServerSideProps(context) {
  try {
    // 在服务端预执行数据请求
    ${dataFetchActions.map(action => `
    const ${action.key} = await fetch('${action.url}', {
      headers: {
        cookie: context.req.headers.cookie || ''
      }
    }).then(r => r.json());
    `).join('\n')}

    return {
      props: {
        initialData: {
          ${dataFetchActions.map(a => a.key).join(',\n          ')}
        }
      }
    };
  } catch (error) {
    return { props: { initialData: {} } };
  }
}
    `;
  }
}

完整的 SSR 示例

编译产物(支持 Next.js)

// pages/user-manage.jsx
import React from 'react';
import { LowCodeRenderer } from '@lowcode/runtime-slim';
import { Button, Table } from 'antd';
import { requestAction, messageAction } from '@lowcode/actions';

const schema = {
  "id": "user-manage",
  "store": {
    "users": [],
    "loading": false
  },
  "methods": {
    "loadUsers": {
      "actions": [
        { "type": "request", "url": "/api/users" }
      ]
    }
  },
  "lifecycle": {
    "onLoad": [{ "type": "method", "method": "loadUsers" }]
  },
  "componentTree": {
    "componentName": "div",
    "children": [
      {
        "componentName": "Table",
        "props": { "dataSource": "${store.users}" }
      }
    ]
  }
};

const materials = { Button, Table };
const actions = { request: requestAction, message: messageAction };

export default function UserManagePage({ initialData }) {
  return (
    <LowCodeRenderer
      schema={schema}
      materials={materials}
      actions={actions}
      initialData={initialData}  // 服务端预加载的数据
    />
  );
}

// SSR:每次请求时在服务端获取数据
export async function getServerSideProps(context) {
  try {
    const users = await fetch('https://api.example.com/users', {
      headers: {
        cookie: context.req.headers.cookie || ''
      }
    }).then(r => r.json());

    return {
      props: {
        initialData: { users }
      }
    };
  } catch (error) {
    return { props: { initialData: {} } };
  }
}

// 或者 SSG:构建时生成静态页面
/*
export async function getStaticProps() {
  const users = await fetch('https://api.example.com/users').then(r => r.json());

  return {
    props: { initialData: { users } },
    revalidate: 60  // ISR: 60秒后重新生成
  };
}
*/

性能对比:CSR vs SSR

指标编译模式 CSR编译模式 SSR提升
首屏内容渲染客户端请求数据后渲染服务端直接返回 HTMLFCP 减少 60%
SEO⚠️ 需要等 JS 执行✅ 搜索引擎直接抓取SEO 友好
LCP~2s(等待 JS + 数据)~0.5s(HTML 已渲染)75% ↓
TTI~3s~1.5s50% ↓
弱网环境❌ 体验差✅ 体验好显著提升

业界实践

  1. 阿里低代码引擎:支持 SSR,通过 @alilc/lowcode-engine-ssr 包实现
  2. 百度 Amis:支持服务端渲染,用于 SEO 敏感场景
  3. 腾讯 TMagic:提供 SSR 方案用于 H5 营销页面
  4. 现代电商平台:商品详情页大量使用低代码 + SSR(性能和 SEO 双重要求)

SSR 的关键优势

  1. SEO 优化:搜索引擎可以直接抓取完整 HTML
  2. 首屏速度:减少客户端渲染等待时间
  3. 弱网友好:减少客户端 JS 执行负担
  4. 社交分享:Open Graph 元数据可以被正确解析

SSR 的适用场景

  • 营销落地页:需要 SEO + 快速首屏
  • 电商商品页:SEO + 性能双重要求
  • 内容类页面:新闻、博客、文档
  • H5 活动页:移动端性能优化
  • 高度交互页面:后台管理系统(CSR 更合适)
  • 实时数据页面:Dashboard(SSR 收益低)

适用场景

  • C端高频页面:首页、商品详情、列表页等
  • 性能敏感场景:移动端、弱网环境
  • SEO需求页面:营销落地页、内容页
  • 高度动态页面:需要频繁调整的运营活动页
  • 个性化页面:千人千面、A/B测试场景

5.13 运行时能力总结