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。這些核心決策(全端強型別、用戶端與伺服器共用資料模型、伺服器端事件迴圈,以及輸出 JS 時的 tree shaking)到了 2026 年依然站得住腳。

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

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

這是一款骨子裡採用樹狀結構的有趣任務管理工具。整個專案有 53992 行、1620KB 的 Dart 程式碼,並搭配社群中幾個開源函式庫打造而成。

誰打造了 Quire?

在開始這個專案之前,我們是一群被稱為 Rikulo 團隊的 Dart 愛好者,已經釋出好幾個 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(null safety、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. 在技術穩定之前就採用最前線的技術。 2014 年,Quire 團隊避開 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