features · Sep 3, 2014

Quire:用 Dart 构建大型应用

✦
AI 翻译
· 查看英文版

用 Dart 编程语言构建的 Quire 应用

最后更新:2026 年 5 月 28 日

TL;DR

Quire(53,992 行、1,620 KB 的 Dart 代码)于 2014 年构建,客户端和服务器端均使用 Dart,并以 DQuery 和 Bootjack 取代 AngularDART 或 Polymer.dart,以获得更精细的 DOM 控制。这些核心决策(全栈强类型、客户端与服务器共享数据模型、服务器端事件循环、以 tree shaking 精简 JS 输出)到 2026 年依然站得住脚。

Quire 不是第一个用 Dart 编写的 Web 应用,也不会是最后一个,但它可能是第一个在客户端和服务器端都如此深度依赖 Dart 的应用。

Quire Web 应用所用的 Dart 编程语言标志与框架

这是一款骨子里就是树状结构的趣味任务管理工具。项目本身有 53992 行、1620KB 的 Dart 代码,并使用了社区的几个开源库。

谁构建了 Quire?

在着手这个项目之前,我们是一群 Dart 爱好者,被称为 Rikulo 团队,已经发布过多个 Dart 库,包括 UI 框架、UI 库、Web 服务器、消息服务器、数据库客户端等。

2011 年 Dart 首次发布时,我们为它的未来价值兴奋不已,立刻开始在项目的一小部分中使用 Dart。最终,我们着手构建一个以 Dart 为主的完整应用。在此谦虚地分享一些经验,希望能为你用 Dart 开发提供一些参考。

Quire 为什么选择 Dart 而不是 JavaScript?

选择 Dart 的理由有很多。其中对我们最重要的是:

  • Dart 的强类型系统帮我们挡住了无数细小的错误。官方 IDE Dart Editor 能快速反馈类型错误、提供自动补全,也让代码追踪更轻松。
  • 我们认为基于类的继承模型比基于原型的模型直观得多。
  • Dart 让我们能在客户端和服务器端使用同一种语言,共享同一套数据模型和代码库。
  • Dart 修复了 JavaScript 中的大多数陷阱。Dart 在这方面并不完美,但确实解决了 99% 的旧有“惊吓”。
  • Dart 提供了几个如今流行的 JavaScript Harmony 特性:Future(promise)、箭头函数等。
  • Dart 背后有一支强大的团队,提供大量高质量的官方库和组织良好的 API。Dart 仍在演进,但规范已经相当稳定。
  • 在服务器端,我们更偏爱事件循环模型,而不是多线程。
  • Dart 编译为 JavaScript 时会执行 tree shaking。(见下文。)

选择 Dart 也有缺点:

  • Dart 社区比 JavaScript 社区小得多。
  • Dart 与 JavaScript 之间的通信并不简单。
  • 在强类型语言中,为 API 做 polyfill 更困难。
  • Dart Editor 在大型项目上的性能(目前)还不够理想。

有强类型支持,用 Dart 编写比用 JavaScript 可靠得多。而且 Dart 比 Java 简洁,有时(例如使用函数表达式时)甚至比原生 JavaScript 更简洁。总体而言,用 Dart 工作很舒服,只有几点例外:

  • final 字段必须在初始化列表中完成初始化,早于构造函数体。
  • Mixin 规范还相当不成熟,2.0 之前不会修复。
  • 函数声明没有泛型类型参数,只有类才有。(当然,这会增加编译器的工作量。)

Dart 在客户端是如何工作的?

在 Dart VM 进入 Chrome 之前,你只能通过编译为 JavaScript 来交付产品。能编译成 JavaScript 的语言有很多,但 Dart 有几项额外优势,因此脱颖而出。

  • 开发时,我们在 Dartium 上原生运行 Dart。Dartium 是从 Chromium 分支出来、内置 Dart VM 的浏览器。这一轮迭代无需编译,省去了无数次打断。
  • 测试和生产环境中,我们把代码编译为 JavaScript,在所有主流浏览器上运行。Dart 到 JavaScript 的编译器会执行 tree shaking,裁掉输出中未使用的代码,大幅缩小了 JavaScript 代码体积。

为什么在服务器端使用 Dart?

在 Dart 社区里,服务器端的 Dart 从来不是热门话题,但我们认为 Dart 是服务器端编程语言的有力候选,原因是:

  • Web 服务天生是异步的,与事件循环模型(相对于多线程)配合得非常好。
  • 服务器端代码对健壮性和安全性的要求远高于客户端,强类型语言在这方面非常好用。

相关阅读:Quire 获得 ISO 认证,守护你的数据——多年之后,对服务器端健壮性和安全性的同样重视,体现在了 Quire 正式的数据保护认证上。

Quire 的 Dart 技术栈使用了哪些库?

Quire 引入了约 30 个库,其中 10 个来自社区,其余由 Dart 团队发布。如果你是 Dart 专家,可能会猜里面有 AngularDART 和 Polymer.dart,但实际上两者都没有用。

我们没有使用 AngularDART,因为:

  • 我们想要精细的 DOM 控制。
  • 我们用独特的架构准则来设计客户端结构,其范式与 Angular 的逻辑不同。
  • 我们调研 AngularDART 时,它会让编译后的 JavaScript 代码体积产生很大开销,不过这一点后来已大幅改善。

我们也没有使用 Polymer.dart,因为:

  • 由于封装和事件重定向,ShadowDOM 无法与以选择器为导向的框架(例如 Bootstrap)协作。
  • 你无法从 ShadowDOM 外部注入样式。如果第三方组件集是用 Polymer 做的,用户几乎不可能改变它的外观和手感。 更新:自 2013 年 12 月起,你可以从外部修改内部样式。参见 Shadow DOM 201。

DQuery 和 Bootjack 是什么?

我们客户端技术栈的基石是 DQuery 和 Bootjack,它们都是 Rikulo 团队发布的开源项目:

  • DQuery 是 jQuery 到 Dart 的部分移植,重点是 jQuery 的事件委托系统。
  • Bootjack 是 Bootstrap 3 的完整移植,CSS 和 API 几乎完全一致。

我们沿着这条路径搭建应用技术栈,好让 JavaScript 世界里积累的知识和技能继续派上用场。

Stream Web 服务器是什么?

Stream 是我们的 Web 服务器,用纯 Dart 编写,具备路由、过滤器、服务器端 MVC 等能力。Stream 与事件循环模型配合顺畅,编写请求处理器只需要串联一系列非阻塞的例程。

它不仅更高效,用起来也比传统的多线程模型更愉快。我们还用 nginx 扩展 Web 服务并处理 HTTPS,再把请求委派给 Stream。有了这套架构,我们可以单独启动和停止 Dart VM,在不中断用户活动的情况下升级服务器。

结语

用 Dart 工作的体验很愉快,我们也期待 Dart 社区有更多令人兴奋的发展。最后,如果你好奇 Dart 能做出什么,欢迎来试玩我们的应用 Quire。它是免费的!

延伸阅读:Quire 后来如何被重新构想并重建为 Mark II。

展示 Dart 客户端与服务器端实现的 Quire 架构图

2014 年这次构建的哪些经验在 2026 年依然适用?

自本文写成以来,Dart 语言和 Quire 代码库都有了长足演进。Dart 2.0(健全类型系统)于 2018 年发布,Dart 3.0(空安全、记录、模式)于 2023 年发布,Flutter 改变了跨平台 UI 的格局(Quire 自己的移动团队也转用了它)。但上文记录的底层架构决策,比你预想的更经得起时间考验。

跨客户端和服务器的强类型依然值得。 强类型系统在构建阶段捕获的运行时错误,仍是同样的那些错误。JavaScript 通过 TypeScript 向类型化迈进了一半,但端到端完全类型化的技术栈依然罕见。Simon 提到的那类问题(无数细小的错误),仍是无类型技术栈会带到生产环境的那类问题。

客户端与服务器共享数据模型依然省事。 模型拷贝问题(一份在 JS,一份在 Python,悄悄地各自偏离)在 2026 年和 2014 年如出一辙。解法(端到端一种语言)也一样。用 TypeScript 搭配 Node 的团队,正是出于这个原因构建 Quire 式的架构。

Web 服务器上,事件循环依然胜过多线程。 Node、Deno、Bun 以及 Dart 自己的 Stream 模式在这一点上看法一致。这个争论每隔几年就会被重新讨论一遍,结论始终一样。

Tree shaking 只会更重要。 2014 年,Dart 的 tree shaking 编译器是一大差异化优势。到 2026 年,它已是任何面向 JS 的构建流水线的基本配置。Webpack、Rollup、esbuild 和 Vite 都支持。这条经验依然成立,只是工具追上来了。

2014 年对 ShadowDOM 的顾虑大多已经解决。 Polymer.dart 已被弃用,Web Components 日趋成熟。Simon 当年不选 Polymer 的理由(样式注入、与框架协作),最终由平台本身解决了。总体原则(不要让生产应用的核心依赖前沿框架)在 2026 年仍然适用,无论当下“最令人兴奋”的框架是哪一个。

相关阅读:幕后故事:基于 Flutter 的移动端时间线——看看 Quire 的移动团队如何用 Flutter 构建时间轴查看模式。

为大型 Web 应用选择语言时,最常见的错误有哪些?

为一个要运行多年的应用选择语言技术栈,是少数代价高昂、难以回头的工程决策之一。团队出错时,有四种模式反复出现。

1. 默认选择社区最大的语言。 JavaScript 的社区最大,但“最大”不等于“最适合你的问题”。一门类型系统强大、工具完善但社区较小的语言,可能胜过保障薄弱的庞大生态。社区规模很重要,但不是唯一重要的因素。

2. 选择客户端和服务器语言分离的技术栈。 每一个重复定义的数据模型,都是一颗等着爆炸的 bug。服务器用 Ruby、客户端用 JS 的团队,要永远承担这份成本。两端共用一种语言(Dart、TypeScript、Kotlin)的团队,则避开了整整一类 bug。

3. 在前沿技术尚未稳定时就采用。 2014 年,Quire 团队避开 AngularDART 和 Polymer.dart,因为两者的架构问题都悬而未决。AngularDART 大幅转向,Polymer 则被弃用。通用法则(先让框架在大规模生产中证明自己,再把应用核心押在它身上)在 2026 年依然适用,无论当下“最令人兴奋”的框架是哪一个。

4. 为了短期速度而放弃类型系统。 无类型语言在第一周感觉更快。到第六个月,省下的时间会在调试中加倍还回去。亲身经历过大规模无类型和有类型技术栈的工程团队,几乎没有人会主动回头。

回头看,Quire 团队 2014 年押注 Dart 是合理的,并不是因为 Dart 成了主流(它没有),而是因为背后的标准(类型化、客户端与服务器共享、事件循环、tree shaking)在之后十年成了行业共识。

经得起时间考验的是底层的工程原则,而不是具体的工具选择。在不确定中选择语言,这个规律通常都成立:选你十年后仍能为之辩护的原则,而不是眼下可以衡量的流行度。

加入 10 万+ 使用 Quire 项目管理软件的团队,免费注册

常见问题

Quire 是用什么语言构建的?

Dart,客户端和服务器端都是。应用约有 54,000 行 Dart 代码,引入约 30 个库。

Quire 为什么选择 Dart 而不是 JavaScript 或 TypeScript?

强类型、基于类的继承模型,以及客户端和服务器端使用同一种语言。Dart 还早于 JavaScript 的广泛支持提供了一些现代特性。

Quire 为什么没有使用 AngularDART 或 Polymer.dart?

团队想要比 AngularDART 的绑定机制更精细的 DOM 控制,Quire 的架构模式也与 Angular 的逻辑不一致。不用 Polymer.dart,是因为当时 ShadowDOM 与 Bootstrap 式的选择器冲突。Polymer 后来被弃用,印证了这个决定。

DQuery 和 Bootjack 是什么?

Rikulo 团队(Quire 的工程团队)发布的开源库。DQuery 是 jQuery 到 Dart 的部分移植,专注于事件委托。Bootjack 是 Bootstrap 3 的完整移植,CSS 和 API 几乎完全一致。

Quire 技术栈中的 Stream 是什么?

Quire 的 Web 服务器,用纯 Dart 编写,采用事件循环模型,内置路由、过滤器和服务器端 MVC。Quire 把 Stream 放在 nginx 之后运行,由 nginx 负责扩展和 HTTPS。

Simon Pai
Co-Founder at Bookshow