features · Sep 3, 2014

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

AI 翻譯
· 查看英文版

My helpful screenshot

Last updated: May 28, 2026

TL;DR:Quire(53,992 行、1,620 KB 的 Dart 程式碼)於 2014 年誕生,前後端皆採用 Dart;我們選擇 DQuery 與 Bootjack,而非 AngularDART 或 Polymer.dart,藉此換取更細膩的 DOM 掌控力。當年的幾項核心決策——整套技術堆疊使用強型別、前後端共用資料模型、伺服器採用事件迴圈、JS 輸出進行 tree shaking——時至 2026 年依然站得住腳。

Quire 不是第一個用 Dart 寫成的網頁應用程式,也不會是最後一個,但它或許是第一個如此重度仰賴 Dart 的應用程式——前端、後端都一樣。

My helpful screenshot

它是一款以樹狀圖為核心、用起來很有趣的任務管理工具。整個專案有 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 大部分的「地雷」。雖然不是百分之百完美,但已經處理掉 99% 的舊有意外行為。
  • Dart 提供了幾個熱門的 JavaScript Harmony 特性:Future(promise)、箭頭函式等。
  • Dart 背後有強力的開發團隊,提供眾多高品質的官方函式庫與條理分明的 API。語言本身仍在演進,但規格已相當穩定。
  • 在伺服器端,我們偏好事件迴圈模型,勝過多執行緒模型。
  • 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——它是一款內建 Dart VM 的 Chromium 分支瀏覽器。在這個流程裡完全不必編譯,省下無數次被中斷的麻煩。
  • 測試與正式環境中,我們會把程式碼編譯為 JavaScript,並讓所有主流瀏覽器都能執行。Dart-to-JavaScript 編譯器會執行 tree shaking,將最終產物中沒用到的程式碼剔除,大幅縮減 JavaScript 的檔案大小。

為什麼後端也用 Dart?

Dart 在伺服器端從來不是 Dart 社群裡的熱門話題,但我們認為 Dart 是後端程式語言的有力候選,原因如下:

  • 網頁服務本質上是非同步的,與事件迴圈模型(相較於多執行緒)合作得非常好。
  • 伺服器端程式碼對於穩健性與安全性的要求遠勝於前端,這正是強型別語言能發揮所長的地方。

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。它是免費的!

My helpful screenshot

這套 2014 年的架構,有哪些經驗到 2026 年仍然受用?

從這篇文章寫成至今,Dart 語言與 Quire 的程式庫都有了長足的演進。Dart 2.0(健全的型別系統)於 2018 年登場,Dart 3.0(null safety、records、patterns)於 2023 年問世,Flutter 也徹底改變了跨平台 UI 的版圖。但上述記錄下來的核心架構決策,其禁得起時間考驗的程度,遠比你預期得高。

前後端共用強型別,至今依然回本。 強型別系統能在編譯期攔下的執行期錯誤,今日仍是同樣的執行期錯誤。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 年依然適用,無論當下「正夯」的框架是哪一個。

為大型網頁應用程式挑選語言時,最常見的錯誤是什麼?

替一個會運作多年的應用程式挑選語言堆疊,是少數一旦做下去就難以回頭的工程決策。團隊踩雷時,最常出現以下四種模式。

1. 預設選擇社群最大的語言。 JavaScript 確實擁有最龐大的社群,但「最大」並不等於「最契合你的問題」。一個強型別、工具完善但社群較小的語言,表現可以勝過一個龐雜卻保證薄弱的生態系。社群規模當然重要,但它絕不是唯一的考量。

2. 選擇前後端使用不同語言的堆疊。 每一份重複的資料模型,都是一顆未爆彈。後端用 Ruby、前端用 JS 的團隊,會永遠背著這份代價。能在前後端共用同一種語言(Dart、TypeScript、Kotlin)的團隊,等於避開整類錯誤。

3. 在新技術尚未穩定前就搶先採用。 Quire 團隊在 2014 年沒有採用 AngularDART 與 Polymer.dart,因為當時兩者在架構上都還有未解的問題。後來 AngularDART 大幅轉向,Polymer 被棄用。更普遍的原則是:在把應用程式核心壓在某個框架上之前,先讓它在大型正式環境中證明自己。這個原則到 2026 年仍然適用,無論當下「正夯」的框架是哪一個。

4. 為了搶短期速度而捨棄型別系統。 無型別語言在開發第一週的感覺確實比較快。但到了第六個月,省下來的時間早已加倍償還在除錯上。曾在規模化情境下同時體驗過無型別與型別化堆疊的工程團隊,幾乎沒有人會主動倒退回去。

Quire 團隊在 2014 年押注 Dart,事後看來相當合理——並不是因為 Dart 後來稱霸(它沒有),而是因為當初評估時所依據的條件(強型別、前後端共用、事件迴圈、tree shaking)在接下來的十年裡逐漸成為業界共識。真正歷久彌新的,並非那些具體工具的選擇,而是背後的工程原則。每當要在不確定中挑選語言時,這個道理多半成立:請依據十年後仍能說得通的原則來選,而不是憑今日可量的人氣。

常見問題

Quire 是用什麼語言開發的?

前後端皆採用 Dart。整個應用程式約有 54,000 行 Dart 程式碼,匯入約 30 個函式庫。

Quire 為什麼選擇 Dart,而不是 JavaScript 或 TypeScript?

因為強型別、類別式繼承模型,以及前後端共用同一種語言。Dart 也比 JavaScript 更早提供幾個現代特性。

Quire 為什麼不用 AngularDART 或 Polymer.dart?

團隊希望對 DOM 有比 AngularDART 的繫結機制更細膩的掌控,而且 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