写点什么

Dagger:我们用 Go 和 WebAssembly 替换了 React 前端

  • 2025-02-19
    北京
  • 本文字数:3090 字

    阅读完需:约 10 分钟

Dagger:我们用Go和WebAssembly替换了React前端

几周前,我们正式推出 Dagger Cloud v3,全新的 Dagger Cloud 用户界面。v3 与 v2 之间一个显著的区别在于,新的用户界面是用 Go 语言编写,并基于 WebAssembly(WASM)实现的。乍看之下,这似乎是一个不同寻常的选择——在开发 Web UI 时,Go 通常不是人们首先想到的语言——但我们有充分的理由。在这篇博文中,我将详细阐述我们选择 WebAssembly 的原因,我们在实现过程中遇到的挑战(以及我们是如何解决的这些问题)以及最终的结果。


两套代码库 = 更多的工作量,更少的功能


Dagger 的工作原理是构建操作的有向无环图(DAG),并对其进行评估(通常是并行评估)。本质上,这是一个很难直观展示的过程。为了帮助用户更好地理解,我们提供了两种实时可视化界面:Dagger Terminal UI(TUI),包含在 Dagger CLI 中,以及 Dagger Cloud,一个在线 Web 仪表盘。Dagger TUI 是用 Go 实现的,而 Dagger Cloud(v3 之前)是用 React 编写的。


我们希望这两个用户界面尽可能保持一致。然而,实时解析 Dagger 事件流并生成用户界面的过程相当复杂。在一些更复杂的场景中,事件流可能包含数十万个 OpenTelemetry Span 数据,管理这些数据结构的复杂性会迅速攀升。这导致 Web UI 常常因数据量过大而变得卡顿和缓慢。为了解决这一性能瓶颈,我们不得不为 React 应用程序采用一种不同的实现方式。


因此,我们最终有了两个实现相同目标的界面,一个使用 TypeScript 和 React 语言及生态系统,另一个则使用完全不同的语言和生态系统(Go)。我们无法在这两者之间轻松地共享业务逻辑。作为一个小团队,我们需要快速交付功能,不得不为每个功能分别实现两次,这对我们的开发速度造成了巨大的阻碍。


我们开始考虑一种新的方法,主要要达成两个目标:


  • 统一代码库,消除重复,能够更快推出新功能。

  • 构建清晰、流畅的 Web UI,与 Terminal UI 的速度和性能相当。

Go + WebAssembly


我们的初步目标是让 Dagger Cloud 和 TUI 共享同一个代码库。我们很早就决定采用 Go 代码库。从技术上讲,我们也可以反过来,使用 TypeScript 开发 TUI,但考虑到我们主要是 Go 工程师团队,选择 Go 能让其他成员更容易参与其中,无论是添加新功能还是帮忙调试问题。除了统一为一种语言外,这一选择还为我们带来了灵活性,打破了团队内部的隔阂。


既然我们决定直接在浏览器中运行 Go 代码,WebAssembly 就成了顺理成章的选择。但这仍然带来了一些挑战:


  • Go + WebAssembly 组合不如 React 和其他 JavaScript 框架成熟。没有现成的组件库可用,开发工具也不够丰富,等等。我们清楚地意识到,我们将不得不从头开始构建大部分用户界面组件。

  • 大多数浏览器对 WebAssembly 应用程序设定了硬性 2 GB 内存限制。我们预计在查看大量追踪信息时这可能会是一个问题,我们知道我们需要进行大量的优化,减少内存使用并确保用户界面的稳定性。不过,这也有积极的一面——对 WebAssembly UI 所做的任何内存优化也会惠及 TUI 用户,因为它们现在共享同一个代码库。

降低项目风险


接下来的问题便是:“我们该如何构建它?”我们选择使用 go-app(https://go-app.dev/)框架开发新的基于 WebAssembly 的用户界面。go-app 是一个专为 WebAssembly 渐进式 Web 应用程序(PWA)设计的高级框架。它保留了 Go 语言的核心优势,例如快速编译和原生静态类型支持,同时采用了基于组件的用户界面模型,与 React 类似,这使得迁移过程变得更加容易。


由于 Go + WebAssembly 组合并不是主流,Dagger 团队内部对其可行性自然存在一些怀疑。例如,目前没有真正成熟的 go-app UI 组件生态系统,我们不得不自行开发,但我们不清楚这一过程会有多容易或多困难。我们还担心与其他服务(Tailwind、Auth0、Intercom、PostHog)的集成,以及同时渲染数百个需要实时更新的组件的性能问题。


为了回答这些问题并降低项目风险,我花了将近一个月的时间进行原型设计,目标是尽可能多地使用 go-app 重新实现现有的用户界面。事实证明,我们没有遇到太多障碍:WebAssembly 已经是一个文档完备的开放标准,而大多数其他问题都可以在 go-app 的文档中找到答案。正如预期的那样,最大的挑战是内存使用限制,这需要进行精心的设计和优化。

从原型到生产


在成功完成一个可行的概念验证后,团队的信心大幅提升,我们随即启动了“awesome wasm”项目,致力于打造一个生产级的实现。以下是这段旅程的一些关键要点:


  • 内存使用无疑是项目成功面临的最大挑战。我化了大量时间研究如何渲染超过 20 万行的日志输出而不崩溃。我们因此对虚拟终端渲染库进行了深度优化,同时这也显著降低了 TUI 的内存使用(正如前面提到的,共享代码库意味着在一个界面的优化成果可以无缝惠及另一个界面)。

  • 在处理大量 JSON 数据时,Go WASM 的解析速度较慢,这促使我们对架构进行了重大调整,我们构建了一个“智能后端”,通过 WebSockets 增量加载数据,并采用了 Go 语言中相对鲜为人知的 encoding/gob 格式。

  • 最初,WASM 文件大小约为 32MB。通过应用 Brotli 压缩技术,我们将其缩减至约 4.6MB。我们曾尝试在 CDN 中实时进行 Brotli 压缩,但由于文件太大,最终决定将压缩步骤放在构建流程中。

  • 除了内存挑战之外,我们最初的大多数担忧都证明是多余的。编写用户界面组件并不难,与其他服务的集成也非常顺利,我还找到了高效处理组件实时更新的方法。

  • 我找到了一些有用的 NPM 包,想知道是否可以在 Go 中使用它们。WebAssembly 提供了一个可以连接 Go 和 JavaScript 的接口,于是我构建了一个使用 Browserify 加载 NPM 包的 Dagger 模块。这个模块允许我们生成一个可以嵌入到 Go 应用程序中的 JavaScript 文件。这样一来,我们就可以主要使用 Go 开发,在必要时也能加载用原生 JavaScript 实现的辅助工具。

  • 我不是 React 专家,在我看来,React 的组件实现方式有点僵化,而 go-app 则灵活得多。在 go-app 中,你可以随时更新任何组件,有更多优化的自由度。例如,我需要优化一个渲染超过 15 万行输出的组件,我能够尝试不同的方法,并从中选择效果最好的一种,让整个优化过程变得更加轻松!

  • 虽然 go-app 没有像 React 那样的内置浏览器开发工具,但我可以借助 Go 自身的工具(例如 pprof)以及浏览器自带的分析器来进行分析和调试。这非常有助于检查函数调用、跟踪 CPU 和内存使用情况以及评估不同优化方法对内存使用的影响。

  • 使用 go-app 的另一个意外收获是:由于 Dagger Cloud 是一个 PWA,它可以打包成桌面或移动应用。这意味着用户可以像启动原生应用一样启动 Dagger Cloud,获得全屏浸入式体验,而无需先打开浏览器,它甚至可以在桌面任务栏或移动设备的程序坞拥有一个专有的图标。


几周前,我们向 Dagger 指挥官们推出了 Dagger Cloud v3,用以收集反馈,并计划在不久后向所有人正式开放。

优势分析


从 React 切换到 WASM,不仅使所有 Dagger 界面的用户体验更加一致,还在渲染大型和复杂的追踪信息时实现了更高的整体性能和更低的内存使用。


从工程角度来看,这也为我们团队带来了显著的好处。优化工作往往与实际实现功能的工作量相当,甚至更多。我们因此能够避免分别优化 Web UI 和 TUI,而是将精力集中在交付新功能上,实在是令人欣慰。

你们也应该这么做吗?


Dagger Cloud v3 在 Dagger 社区引起了热议,最近我们被问到的一个较多的问题是:谁应该考虑这种技术,谁又不适合?


我们想强调的是,我们并不是推荐普遍都用 Go 开发前端。我们之所以选择这条路线,是基于一系列具体且充分的理由:我们拥有一支出色的 Go 工程师团队;面对一个复杂且难以用 TypeScript/React 扩展的 UI;需要在两个代码库之间实现标准化和代码复用;以及在公司范围内整体提升开发速度的要求。这确实是一个相当特殊的情况。如果你也有类似的情况,那么这种方案当然值得考虑。但如果你的情况有所不同,那么或许应该优先考虑其他更通用的工具和标准。


原文链接:https://dagger.io/blog/replaced-react-with-go

2025-02-19 17:489842
用户头像
李冬梅 加V:busulishang4668

发布了 1043 篇内容, 共 657.1 次阅读, 收获喜欢 1209 次。

关注

评论 1 条评论

发布
用户头像
有一个优秀的团队挺好的。
2025-02-20 22:32 · 北京
回复
没有更多了

Window下Redis的安装和部署详细教程

明金同学

redis

选轻量应用服务器还是云服务器ECS?一图彻底搞懂

阿里云弹性计算

轻量应用 玩转ECS

从中心走向边缘——深度解析云原生边缘计算落地痛点

阿里巴巴云原生

阿里云 Kubernetes 云原生 边缘计算

千万级学生管理系统考试试卷存储方案

唐尤华

架构实战营

Blinn-Phong反射模型

CRMEB

企业级 APIs 安全实践指南 (建议初中级工程师收藏)

领创集团Advance Intelligence Group

API

来看看字节跳动内部的数据血缘用例与设计

字节跳动数据平台

大数据 字节跳动 数据血缘

计算IIS

杉数科技

求解器 优化求解器 计算IIS 混合整数规划 杉数科技

2022写作计划2月文章排行榜

TGO鲲鹏会

TGO鲲鹏会 写作计划

你知道钓鱼网站的形成步骤吗?一次网络钓鱼演练带你了解(增强安全意识)

H

网络安全 钓鱼网站

Nacos 在 Apache APISIX API 网关中的服务发现实践

API7.ai 技术团队

nacos 注册中心 服务发现 API网关 APISIX

如何使用 Apache APISIX CSRF 安全插件拦截跨站点伪造攻击

API7.ai 技术团队

CSRF API网关 Apache APISIX

常青藤开源科技加入,龙蜥社区再迎 HPC 和开源领域新伙伴

OpenAnolis小助手

Linux 开源 高性能计算

Apache APISIX 集成 Open Policy Agent

API7.ai 技术团队

开源 后端 API网关 OPA Apache APISIX

Apache APISIX 集成 HashiCorp Vault,生态系统再添一员

API7.ai 技术团队

开源 安全 后端 API网关 APISIX

万字通俗讲解何为复杂度

华为云开发者联盟

数据结构 时间复杂度 复杂度 空间复杂度 复杂度分许

Apache APISIX 集成 Google Cloud Logging

API7.ai 技术团队

Google 网关 APISIX Google Cloud

【场景化集成方案】如何让企业快速集成钉钉各种能力

钉钉开发者

钉钉能力中心 钉钉官网 场景化能力包 场景化解决方案 应用集成方案

斯图飞腾Stratifyd入选「2022爱分析·营销科技厂商全景报告」

极客天地

MASA Framework - DDD设计(2)

MASA技术团队

C# .net .net core 框架 Framework

新插件上线,public API 处理能力更进一步

API7.ai 技术团队

HTTP APISIX APISIX 网关

Apache APISIX 新技能,代理 gRPC-Web 请求

API7.ai 技术团队

gRPC HTTP 网关 APISIX

Apache APISIX 集成 Kafka 实现高效率实时日志监控

API7.ai 技术团队

kafka 开源 日志 网关 Apache APISIX

云服务器ECS选购指南及省钱法宝(强烈建议收藏)

阿里云弹性计算

玩转ECS 选购指南

生态扩大进行中!Apache APISIX 支持 Azure Functions 集成

API7.ai 技术团队

microsoft azure API网关 Apache APISIX

极速生成缩略图,Serverless 支撑赛事转播锁定冬奥亮点

Serverless Devs

StarRocks 元数据管理及 FE 高可用机制

邸星星

BerkeleyDB-JE bdbje StarRocks元数据管理

学生外包管理系统架构设计文档

孙强

#架构实战营

生态扩大进行中!Apache APISIX 集成 Splunk HTTP Event Collector

API7.ai 技术团队

API网关 Apache APISIX

第十五节:SpringBoot使用JPA访问数据库

入门小站

spring-boot

JWT Token在线编码生成

入门小站

工具

Dagger:我们用Go和WebAssembly替换了React前端_编程语言_Alex Suraci_InfoQ精选文章