
最后更新:2026 年 5 月 28 日
Quire(53,992 行、1,620 KB 的 Dart 代码)于 2014 年构建,客户端和服务器端均使用 Dart,并以 DQuery 和 Bootjack 取代 AngularDART 或 Polymer.dart,以获得更精细的 DOM 控制。这些核心决策(全栈强类型、客户端与服务器共享数据模型、服务器端事件循环、以 tree shaking 精简 JS 输出)到 2026 年依然站得住脚。
Quire 不是第一个用 Dart 编写的 Web 应用,也不会是最后一个,但它可能是第一个在客户端和服务器端都如此深度依赖 Dart 的应用。

这是一款骨子里就是树状结构的趣味任务管理工具。项目本身有 53992 行、1620KB 的 Dart 代码,并使用了社区的几个开源库。
在着手这个项目之前,我们是一群 Dart 爱好者,被称为 Rikulo 团队,已经发布过多个 Dart 库,包括 UI 框架、UI 库、Web 服务器、消息服务器、数据库客户端等。
2011 年 Dart 首次发布时,我们为它的未来价值兴奋不已,立刻开始在项目的一小部分中使用 Dart。最终,我们着手构建一个以 Dart 为主的完整应用。在此谦虚地分享一些经验,希望能为你用 Dart 开发提供一些参考。
选择 Dart 的理由有很多。其中对我们最重要的是:
选择 Dart 也有缺点:
有强类型支持,用 Dart 编写比用 JavaScript 可靠得多。而且 Dart 比 Java 简洁,有时(例如使用函数表达式时)甚至比原生 JavaScript 更简洁。总体而言,用 Dart 工作很舒服,只有几点例外:
在 Dart VM 进入 Chrome 之前,你只能通过编译为 JavaScript 来交付产品。能编译成 JavaScript 的语言有很多,但 Dart 有几项额外优势,因此脱颖而出。
在 Dart 社区里,服务器端的 Dart 从来不是热门话题,但我们认为 Dart 是服务器端编程语言的有力候选,原因是:
相关阅读:Quire 获得 ISO 认证,守护你的数据——多年之后,对服务器端健壮性和安全性的同样重视,体现在了 Quire 正式的数据保护认证上。
Quire 引入了约 30 个库,其中 10 个来自社区,其余由 Dart 团队发布。如果你是 Dart 专家,可能会猜里面有 AngularDART 和 Polymer.dart,但实际上两者都没有用。
我们没有使用 AngularDART,因为:
我们也没有使用 Polymer.dart,因为:
我们客户端技术栈的基石是 DQuery 和 Bootjack,它们都是 Rikulo 团队发布的开源项目:
我们沿着这条路径搭建应用技术栈,好让 JavaScript 世界里积累的知识和技能继续派上用场。
Stream 是我们的 Web 服务器,用纯 Dart 编写,具备路由、过滤器、服务器端 MVC 等能力。Stream 与事件循环模型配合顺畅,编写请求处理器只需要串联一系列非阻塞的例程。
它不仅更高效,用起来也比传统的多线程模型更愉快。我们还用 nginx 扩展 Web 服务并处理 HTTPS,再把请求委派给 Stream。有了这套架构,我们可以单独启动和停止 Dart VM,在不中断用户活动的情况下升级服务器。
用 Dart 工作的体验很愉快,我们也期待 Dart 社区有更多令人兴奋的发展。最后,如果你好奇 Dart 能做出什么,欢迎来试玩我们的应用 Quire。它是免费的!
延伸阅读:Quire 后来如何被重新构想并重建为 Mark II。

自本文写成以来,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 构建时间轴查看模式。
为一个要运行多年的应用选择语言技术栈,是少数代价高昂、难以回头的工程决策之一。团队出错时,有四种模式反复出现。
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)在之后十年成了行业共识。
经得起时间考验的是底层的工程原则,而不是具体的工具选择。在不确定中选择语言,这个规律通常都成立:选你十年后仍能为之辩护的原则,而不是眼下可以衡量的流行度。
Dart,客户端和服务器端都是。应用约有 54,000 行 Dart 代码,引入约 30 个库。
强类型、基于类的继承模型,以及客户端和服务器端使用同一种语言。Dart 还早于 JavaScript 的广泛支持提供了一些现代特性。
团队想要比 AngularDART 的绑定机制更精细的 DOM 控制,Quire 的架构模式也与 Angular 的逻辑不一致。不用 Polymer.dart,是因为当时 ShadowDOM 与 Bootstrap 式的选择器冲突。Polymer 后来被弃用,印证了这个决定。
Rikulo 团队(Quire 的工程团队)发布的开源库。DQuery 是 jQuery 到 Dart 的部分移植,专注于事件委托。Bootjack 是 Bootstrap 3 的完整移植,CSS 和 API 几乎完全一致。
Quire 的 Web 服务器,用纯 Dart 编写,采用事件循环模型,内置路由、过滤器和服务器端 MVC。Quire 把 Stream 放在 nginx 之后运行,由 nginx 负责扩展和 HTTPS。