features · Sep 3, 2014

Quire:以 Dart 打造大型應用程式

✦
AI 翻譯
· 查看英文版

以 Dart 程式語言打造的 Quire 應用程式

最後更新:2026 年 5 月 28 日

TL;DR

Quire(53,992 行、1,620 KB 的 Dart 程式碼)於 2014 年以 Dart 打造,客戶端與伺服器端皆使用 Dart,並以 DQuery 和 Bootjack 取代 AngularDART 或 Polymer.dart,以便更精細地控制 DOM。核心決定包括全端強型別、客戶端與伺服器端共用資料模型、伺服器端採用事件迴圈,以及以 tree shaking 精簡 JS 輸出。這些決定到 2026 年依然適用。

Quire 不是第一個以 Dart 編寫的網頁應用程式,也不會是最後一個,但它可能是第一個在客戶端和伺服器端都如此倚重 Dart 的應用程式。

Quire 網頁應用程式所用的 Dart 程式語言標誌與框架

這是一款以樹狀結構為核心的趣味任務管理工具。整個專案共 53992 行、1620KB 的 Dart 程式碼,並採用了幾個來自社群的開源程式庫。

誰打造了 Quire?

在開展這個專案之前,我們就是一群 Dart 愛好者,人稱 Rikulo 團隊。我們已發布多個 Dart 程式庫,包括 UI 框架、UI 程式庫、網頁伺服器、訊息伺服器和資料庫客戶端等。

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 是伺服器端程式語言的有力人選,原因是:

  • 網路服務本質上是非同步的,與事件迴圈模型(而非多執行緒)配合得天衣無縫。
  • 伺服器端程式碼比客戶端更需要穩健和安全,強型別語言在這方面大派用場。

相關閱讀: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 網頁伺服器是甚麼?

Stream 是我們的網頁伺服器,以純 Dart 編寫,具備路由、過濾器和伺服器端 MVC 等功能。Stream 與事件迴圈模型配合順暢,編寫請求處理器,只需串連一連串非阻塞的常式。

這不僅比傳統的多執行緒模型更有效率,用起來也更愉快。我們亦透過 nginx 擴展網路服務並處理 HTTPS,再把請求交給 Stream。憑此架構,我們可以逐一啟動和停止 Dart VM,在不打斷用戶操作的情況下升級伺服器。

總結

使用 Dart 的體驗令我們十分愉快,我們也期待 Dart 社群有更多令人興奮的發展。最後,如果你好奇 Dart 能做出甚麼成果,歡迎來試用我們的應用程式 Quire。它是免費的!

了解更多:Quire 後來如何重新構想並重建為 Mark II。

展示 Dart 客戶端與伺服器端實作的 Quire 架構圖

2014 年這次開發的經驗,到 2026 年還適用嗎?

自本文撰寫以來,Dart 語言和 Quire 的程式碼庫都有大幅演進。Dart 2.0(健全型別系統)於 2018 年推出,Dart 3.0(空值安全、records、patterns)於 2023 年推出,而 Flutter 改變了跨平台 UI 的格局(Quire 自家的流動版團隊也改用了它)。

不過,上文記錄的底層架構決定,比你想像中更經得起時間考驗。

全端強型別依然物有所值。 強型別系統在建置階段攔截的執行期錯誤,與當年是同一類錯誤。JavaScript 透過 TypeScript 走近了型別化,但完全型別化的端到端技術架構依然罕見。Simon 所指的那類問題(無數細微的錯誤),仍是無型別技術架構送上正式環境的那類問題。

客戶端與伺服器端共用資料模型,依然省時省力。 資料模型被複製兩份(一份用 JS、一份用 Python,悄悄各自偏離)的問題,2026 年與 2014 年並無分別,解決方法(端到端使用同一種語言)也一樣。TypeScript-on-Node 的團隊建立 Quire 式的架構,正是基於這個原因。

網頁伺服器採用事件迴圈而非多執行緒,依然勝出。 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 年對任何當下「令人興奮」的框架依然適用。

相關閱讀:How It's Made: A Flutter-Based Mobile Timeline — 看看 Quire 的流動版團隊如何運用 Flutter 打造時間軸檢視模式。

為大型網頁應用程式選擇語言時,最常見的錯誤有哪些?

為一個行之多年的應用程式選擇語言技術架構,是少數事後代價高昂、難以回頭的工程決定。團隊選錯時,通常有四種情況反覆出現。

1. 預設選擇社群最大的語言。 JavaScript 的社群最大,但「最大」不等於「最適合你的問題」。型別系統強、工具完善而社群較小的語言,可能勝過保障薄弱、生態龐雜的語言。社群規模固然重要,卻不是唯一重要的因素。

2. 選用客戶端和伺服器端語言分家的技術架構。 每一個被複製的資料模型,都是一枚等待引爆的臭蟲。伺服器端用 Ruby、客戶端用 JS 的團隊,要永遠承擔這項成本。兩端共用同一種語言(Dart、TypeScript、Kotlin)的團隊,則可避開整類臭蟲。

3. 在最前沿技術尚未穩定時就採用。 Quire 團隊在 2014 年沒有採用 AngularDART 和 Polymer.dart,因為兩者都存在未解決的架構問題。AngularDART 後來大幅轉向,Polymer 則被棄用。大原則是:先讓框架在大規模正式環境中證明自己,才把應用程式的核心押在它身上。到 2026 年,這點對任何當下「令人興奮」的框架依然適用。

4. 為短期速度而放棄型別系統。 無型別語言在第一週感覺較快,但到第六個月,省下的時間會在除錯上加倍還回去。親身經歷過大規模無型別和強型別技術架構的工程團隊,幾乎不會自願走回頭路。

Quire 團隊 2014 年押注 Dart,事後看來是合理的,並不是因為 Dart 成為主流(它沒有),而是因為背後的準則(強型別、客戶端與伺服器端共用、事件迴圈、tree shaking)在其後十年成為業界共識。

經得起時間考驗的,是底層的工程原則,而非具體的工具選擇。在不確定下選擇語言,這個規律通常都成立:要為十年後仍能自圓其說的原則而選,而不是為今天量度得到的人氣而選。

加入 100,000+ 個使用 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 的網頁伺服器,以純 Dart 編寫,採用事件迴圈模型,內建路由、過濾器和伺服器端 MVC。Quire 把 Stream 置於 nginx 之後,由 nginx 負責擴展和 HTTPS。

Simon Pai
Co-Founder at Bookshow