React Playground 的系统架构:幕后到底发生了什么

发布: (2026年5月1日 GMT+8 21:55)
5 分钟阅读
原文: Dev.to

Source: Dev.to

高层概览

  • 前端 表现得像一个 IDE。
  • 后端 充当协调者。
  • 工作者 实际运行提交的代码。
  • Redis 将所有组件连接起来。
  • S3 存储用户的代码。

前端:编辑器和预览

编辑

  1. Monaco Editor 捕获你输入的 JSX。
  2. Babel(standalone) 将 JSX 转译为 JavaScript。
  3. 生成的 JavaScript 被注入到 iframe 中。

为什么使用 iframe?

如果用户的代码崩溃,iframe 能将错误隔离,保证其余应用保持运行。iframe 提供:

  • 独立的 DOM
  • 独立的 JavaScript 上下文

React 和 ReactDOM 通过 CDN 加载到 iframe 中,组件在其中渲染,从而实现即时反馈。

提交代码

当用户点击 “Submit” 时:

  1. 原始 JSX(而不是已转译的代码)被发送到后端。
  2. 后端创建一个 solutionId 并返回给前端。

此时代码尚未执行。

实时通信

  • 前端打开一个 WebSocket 连接,并使用 solutionId 进行注册。
  • 后端通过该 socket 推送结果给客户端(无需轮询)。

后端协调

队列化任务

后端并不直接在主服务器上执行代码,而是:

  1. 将任务推入 BullMQ 队列。
  2. 任务负载包含代码及相关元数据。

为什么使用队列?

  • 执行过程慢、不可预测且可能有风险。
  • 在主服务器上执行会阻塞其他请求,影响性能。

工作进程

专门的工作者持续监听 BullMQ 队列。任务到达时:

  1. 工作者取出任务并 再次使用 Babel 转译 JSX
  2. 启动 Puppeteer,将代码注入真实浏览器环境,并模拟用户操作(例如查找按钮、点击、检查 UI 更新)。

这种方式提供:

  • 真正的 DOM 与事件循环
  • 浏览器级别的行为,Node.js 单独无法模拟

隔离与安全

  • 每次执行都在 新的 Puppeteer 页面 中进行,互不共享状态。
  • 工作者运行在与后端分离的进程中,故障被局部化。

结果交付

  1. 执行完毕后,工作者在 BullMQ 中将任务标记为 completed
  2. 后端监听队列事件,使用 solutionId 找到对应的 WebSocket 并发送结果。
  3. 前端即时收到结果并更新 UI。

Redis 用途

  • 存储 BullMQ 队列数据。
  • 发布任务事件(completed/failed)。
  • 充当缓存层,加速读取。

从原始 Pub/Sub 切换到 BullMQ 简化了架构,提供了内置的生命周期事件、重试处理和更清晰的代码。

存储用户代码

  • 代码保存在 AWS S3 中,数据库只存储文件键。
  • 需要时,后端生成 signed URL 供前端获取代码。

优势

  • 安全(无公共访问)
  • 可扩展
  • 元数据与大块代码分离,结构清晰

端到端流程

  1. 用户编写代码 → 预览在 iframe 中运行。
  2. 用户提交 → 后端返回 solutionId
  3. 前端使用该 ID 打开 WebSocket
  4. 后端将任务推入 BullMQ 队列
  5. 工作者通过 Puppeteer 执行代码
  6. 工作者标记任务完成;后端收到事件。
  7. 后端通过 WebSocket 发送结果
  8. 前端更新 UI,显示验证结果。

关键优势

  • 前端 负责快速预览;后端 负责使用真实 DOM 的可靠验证。
  • 工作者独立运行,避免阻塞主服务器。
  • 通过 WebSocket 实时更新,降低负载并提升可扩展性。
  • 职责清晰分离,使架构更简洁、更易维护。
0 浏览
Back to Blog

相关文章

阅读更多 »

模型越智能,节省越多。

神话:更智能的模型会让插件变得多余。自从 WOZCODE 推出以来,许多 Claude Code 高级用户低声说插件的优势将会消失。