Categories
程式開發

Deno 1.0正式發布!


動態語言都是很有用的工具。用戶可以使用腳本快速簡潔地將復雜的系統連接在一起並表達自己的想法,而不必顧慮諸如內存管理或系統構建之類的細節。近年來,像 Rust 和 Go 這樣的編程語言讓程序員能更輕鬆地生成複雜的原生代碼;這些項目也是計算機基礎架構發展歷程中極為重要的里程碑。但是,我們認為開發工作中有一個可以應對多種問題領域的強大腳本環境還是非常重要的。

JavaScript 是應用最廣泛的動態語言,只需一個 Web 瀏覽器就能在所有設備上運行。精通 JavaScript 的程序員數不勝數,並且社區已經為了優化 JS 的執行效率而投入了大量資源。在像 ECMA International 這樣的標準組織推動下,JS 語言得到了精心而持續的改進。我們相信,無論是在瀏覽器環境中還是作為獨立進程使用,JavaScript 都是動態語言工具鏈的首選。

我們在這一領域的早期項目 Node.js 被證明是一個非常成功的軟件平台。人們發現它能很好地用於構建 Web 開發工具、構建獨立的 Web 服務器以及其他許多用例。但是,Node 是在 2009 年設計的,當時的 Ja​​vaScript 與今天的語言版本有著明顯的區別。出於用戶需求考慮,Node 在當時必鬚髮明許多概念,其中很多在後來都被標準組織採納,並通過各種方式加入了 JS 語言規範。在“Node 中的設計錯誤”演講中對這一話題有更具體的討論。

https://www.youtube.com/watch?v=M3BM9TB-8yA

由於 Node 擁有大量用戶,因此系統進化起來既困難又緩慢。

隨著JavaScript 語言的不斷變化,以及諸如TypeScript 之類的新增改進,Node 項目的構建可能會成為一項艱鉅的工作,過程中需要管理構建系統和其他需要繁重操作的工具鏈,結果大大抵消了動態語言腳本的優勢。此外,通過 NPM 存儲庫鏈接到外部庫的機製本質上是中心化的,這不符合 Web 的發展理念。

我們認為 JavaScript 與其周圍的軟件基礎架構已經在改進的道路上走得夠遠了,應該做一些簡化工作了。我們想要尋求一種可用於多種任務的有趣且高效的腳本環境。

用於命令行腳本的 Web 瀏覽器

Deno 是一個新的運行時,用於在 Web 瀏覽器之外執行 JavaScript 和 TypeScript。

Deno 試圖提供一個獨立的工具來快速編寫功能複雜的腳本。 Deno 是(並將始終是)單個可執行文件。就像 Web 瀏覽器一樣,它知道如何獲取外部代碼。在 Deno 中,單個文件可以定義任意複雜的行為,而無需其他任何工具。

import { serve } from "https://deno.land/[email protected]/http/server.ts";
for await (const req of serve({ port: 8000 })) {
  req.respond({ body: "Hello Worldn" });
}

上面的代碼段只需一行代碼就將一個完整的 HTTP 服務器模塊添加為了一個依賴項。沒有額外的配置文件,沒有預先的安裝工作,只需輸入 deno run example.js 即可。

與瀏覽器一樣,默認情況下 Deno 中的代碼會在安全的沙箱中執行。未經允許,腳本無法訪問硬盤驅動器、打開網絡連接或進行其他任何可能引入惡意行為的操作。瀏覽器提供了用於訪問相機和麥克風的 API,但用戶必須首先授予權限才能啟用它們。 Deno 在終端中提供了模擬行為。除非提供 –allow-net 命令行標誌,否則上述示例將失敗。

Deno 是精心設計的,避免偏離標準化的瀏覽器 JavaScript API。當然,並不是每個瀏覽器 API 都與 Deno 相關,但只要有 API 和 Deno 有聯繫,後者都不會偏離標準。

一流的 TypeScript 支持

我們希望 Deno 適用於廣泛的問題領域:從小型單行腳本到復雜的服務端業務邏輯都能適用。隨著程序變得越來越複雜,具有某種形式的類型檢查也成為編程語言越來越重要的特性。 TypeScript 是 JavaScript 語言的擴展,允許用戶選擇提供類型信息。

Deno 無需其他工具即可支持 TypeScript。運行時在設計時就考慮了 TypeScript 的支持。 deno types 命令為 Deno 提供的所有內容提供類型聲明。 Deno 的標準模塊全部使用 TypeScript 編寫。

Promise 的支持下放到底層

Node 是在 JavaScript 引入 Promise 或 async/await 概念之前設計的。 Node 中與 promise 對應的是 EventEmitter,像套接字(socket)和 HTTP 這樣的重要 API 則環繞其外。在 async/await 這樣的設計優勢外,EventEmitter 模式還存在一個背壓問題。以 TCP 套接字為例。套接字在收到傳入數據包時將發出“數據”事件。這些“數據”回調將以不受限制的方式發出,結果會讓事件充斥整個進程。由於 Node 會繼續接收新的數據事件,而底層 TCP 套接字沒有適當的背壓,於是遠程發送方不知道服務器已超負荷,還會繼續發送數據。為了緩解這個問題,Node 添加了 pause() 方法。這可以解決問題,但是需要額外的代碼;而且由於事件氾濫問題只在進程非常繁忙時才會出現,因此許多 Node 程序都可能出現數據洪水的現象。結果是系統的尾部延遲時間變得很長。

在 Deno 中,套接字仍然是異步的,但是接收新數據需要用戶顯式 read()。正確構造一個接收套接字不需要額外的暫停語義。這不是只針對 TCP 套接字。系統的最低綁定層從根本上綁定了 promise——我們稱這些綁定為“ops”。 Deno 中的所有回調,無論形式如何,都是來自 promise 的。

Rust 有它自己的類似於 promise 的抽象,稱為 Future。通過“op”抽象,Deno 讓開發人員可以輕鬆將 Rust 的基於 future 的 API 綁定到 JavaScript promise 中。

Rust API

我們在 Deno 中提供的主要組件是 Deno 命令行界面(CLI)。 CLI 現在是 1.0 版本。但是 Deno 並不是一個單體程序,而是設計為一個 Rust crate 的集合,以實現不同層次的集成。

deno_core crate 是 Deno 的核心骨架。它不依賴於 TypeScript 或 Tokio。它只是提供了我們的 Op 和 Resource 基礎架構。換句話說,它提供了一種有組織地將 Rust future 綁定到 JavaScript promise 的方式。 CLI 當然完全建立在 deno_core 之上。

rusty_v8 crate 為 V8 的 C++ API 提供了高質量的 Rust 綁定。該 API 試著盡可能與原始 C++ API 匹配。它是零成本綁定:Rust 中公開的對象與你在 C++ 中操作的對象完全相同。 (例如,之前針對 Rust V8 綁定的嘗試強制使用持久句柄。)這個 crate 提供了在 Github Actions CI 中內置的二進製文件,但它還允許用戶從頭開始編譯 V8 並調整一眾構建配置。所有 V8 源代碼均在 crate 自身中分發。最後,rusty_v8 嘗試成為一個安全的接口。它還不是 100%安全的,但我們正在接近這個目標。能夠以安全的方式與像 V8 這樣複雜的 VM 交互是非常棒的事情,並且讓我們發現了 Deno 本身中存在的許多難以察覺的錯誤。

穩定性

我們保證在 Deno 中維持一個穩定的 API。 Deno 有很多接口和組件,因此明確我們所說的“穩定”的含義是很重要的。我們開發的與操作系統交互的 JavaScript API 都可以在“Deno”命名空間(例如 Deno.open())中找到。這些 API 已經過仔細檢查,我們不會對它們做出向後不兼容的更改。

尚未準備穩定下來的所有功能都隱藏在 –unstable 命令行標誌後面。你可以在此處查看不穩定接口的文檔。

在後續版本中,其中一些 API 也會被穩定下來。

在全局命名空間中,你會找到其他所有對象(例如 setTimeout() 和 fetch())。我們已經盡力讓這些接口與瀏覽器中的接口保持一致。但是如果發現意外的不兼容問題,我們將發出更正。這些接口不是我們,而是瀏覽器標准定義的。我們發布的所有更正均是錯誤修復,而不是接口更改。如果存在與瀏覽器標準 API 不兼容的問題,則它可以在主要版本發布之前得到更正。

Deno 也有許多 Rust API,比如說 deno_core 和 rusty_v8 crate。這些 API 都還不是 1.0 版本。我們將繼續對它們進行迭代。

局限性

重要的是要了解 Deno 並不是 Node 的分支——它是一個全新的實現。 Deno 的開發工作只經過了兩年時間,而 Node 已經開發了超過十年。考慮到社區對 Deno 的興趣,我們希望它會繼續發展並成熟。

對於某些應用程序而言,Deno 可能是現下一種不錯的選擇,對於其他應用程序來說 Deno 還不夠合適,具體取決於需求。我們希望透明地公開這些局限性,以幫助人們在考慮使用 Deno 時做出明智的決定。

兼容性

不幸的是,許多用戶會沮喪地發現 Deno 缺乏與現有 JavaScript 工具的兼容性。 Deno 與 Node(NPM)軟件包總體來說是不兼容的。在deno.land/std/node/上建立了一個新的兼容性層,但它離完成還很遙遠。

https://deno.land/std/node/

儘管 Deno 使用強硬的方法簡化了模塊系統,但畢竟 Deno 和 Node 是非常相似的系統,有著很接近的目標。隨著時間的推移,我們希望 Deno 能夠開箱即用地運行越來越多的 Node 程序。

HTTP 服務器性能

我們不斷跟踪 Deno 的 HTTP 服務器性能。一個 hello-world 的 Deno HTTP 服務器每秒處理約 25,000 個請求,最大延遲為 1.3 毫秒。一個可比的 Node 程序每秒則處理 34,000 個請求,最大延遲介於 2 到 300 毫秒之間。

Deno 的 HTTP 服務器是在原生 TCP 套接字上面用 TypeScript 實現的。 Node 的 HTTP 服務器使用 C 語言編寫,並作為 JavaScript 的高級綁定公開。我們一直拒絕將原生 HTTP 服務器綁定添加到 Deno,因為我們要優化 TCP 套接字層,更一般地說是要優化 op 接口。

Deno 是一個不錯的異步服務器,每秒 25k 請求足以滿足大多數目的。 (還不夠用的話,那麼 JavaScript 可能不是最佳選擇。)此外,由於普遍使用 promise(如上所述),我們期望 Deno 一般來說能表現出更好的尾部延遲。綜上所述,我們確信這一系統還能有更多的性能優勢,並希望在將來的版本中實現這一目標。

TSC 瓶頸

在內部,Deno 使用微軟的 TypeScript 編譯器檢查類型並生成 JavaScript。與 V8 解析 JavaScript 所花費的時間相比,它是非常緩慢的。在項目的早期,我們曾希望“V8 Snapshots”在這裡能夠帶來明顯的提升。 Snapshots 肯定是有幫助的,但是它還是太慢了。我們當然認為可以在現有 TypeScript 編譯器的基礎上進行一些改進,但我們知道,顯然我們最終需要在 Rust 中實現類型檢查。這將是一項艱鉅的任務,不會一蹴而就。但它可以在開發體驗的關鍵路徑上提供數量級的性能改進。 TSC 必須移植到 Rust。如果你有興趣合作解決這個問題,請與我們聯繫。

插件 / 擴展

我們有一個新生的插件系統,用於通過自定義操作擴展 Deno 運行時。但這個接口仍在開發中,並已標記為不穩定。因此,訪問 Deno 提供的系統之外的原生系統是很困難的。

最後一件事想要支持這一開源項目的話,可以考慮預訂一件 Deno 1.0 外衣

Deno 1.0正式發布! 1

英文原文

https://deno.land/v1