【CSDN編者按】很多人仍然對Deno的模型感到懷疑,很多新手對其採取的一些策略持反對意見,但真正接觸過Deno並嘗試瞭解它的不同工作方式後,你還這麼認爲嗎?

作者 | Kitson Kelly
譯者 | 彎月,責編 | 夕顏
頭圖 | Deno官網
出品 | CSDN(ID:CSDNnews)

以下爲譯文:

2018年5月,在Ry發佈Deno的原型後不久,我便開始爲其貢獻代碼。人們最常問的問題是:“打包管理在哪裏?”通常人們都不是以提問的形式,他們會說:“我認爲Deno非常重視安全性,只不過從互聯網上下載資源不是很安全。”或者“我該如何管理依賴關係?”

我認爲,我們需要改變思維模式。無處不在的打包管理器和集中式的代碼倉庫,讓很多人認爲擁有一個軟件包管理器和一個集中式代碼倉庫是必須的。雖然它們存在,但並不意味着它們就是必需品。它們之所以存在,是因爲它們以特定的方式解決了問題,而人們則想當然地認爲它們是解決這個問題的唯一方法。但我認爲這不對。

瀏覽器

假設發佈網站的方法並不是登錄到Google服務器並將網站上傳到服務器上。如果有人想訪問網站,他們需要使用一個命令行工具,該工具會在本地計算機的browser.json文件中添加一個條目,然後把整個網站以及任何該網站鏈接到的網站都下載到本地的websites目錄中,然後再啓動瀏覽器顯示網站。感覺很不可思議,是吧?那麼爲什麼運行代碼需要採用這種模型呢?

Deno CLI的工作方式與瀏覽器類似,只不過它操作的是代碼。只需要導入一個代碼的URL,Deno就會獲取這些代碼,並緩存在本地,就像瀏覽器一樣。此外,與瀏覽器一樣,你的代碼在沙盒中運行,而沙盒對正在運行的代碼的信任度爲零,無論這些代碼來自何處。你(調用代碼的人)從外部告訴代碼可以做些什麼以及不能做什麼。而且,就像瀏覽器一樣,代碼會要求執行操作的權限,而你可以選擇授權或拒絕。

HTTP協議足夠提供有關代碼的信息,而且Deno會設法利用該協議,因此不必創建新協議。

發現代碼

首先要考慮的是,我們希望Deno CLI像瀏覽器一樣對你運行的代碼不持任何意見。它只給出代碼應當怎樣獲取,以及怎樣將代碼放在沙盒中運行。我認爲,運行時應當持有的意見僅此兩點。

在Node.js/npm生態環境中,代碼管理位於本地,再加上一個中心化的代碼倉庫來輔助代碼發現。我認爲這兩者都有很嚴重的缺陷。

在互聯網的早期,我們曾嘗試過npm這種發現機制。那時,你需要把網站添加到雅虎正確的分類下供人們查找,也許會使用搜索功能,但這都是根據內容提供者的意見時構建的,而且不是按照罪有利於消費者的方式構建的。最後終於Google出現了。爲什麼Google能取得勝利?因爲Google對很好用。它會按照簡單的搜索關鍵字對網站進行索引,同時考慮多個因素,包括內容提供商的元數據等。

雖然Deno對於代碼的模型與此不太一樣,但也很好用。此外,我們使用Google的原因是Google能解決我們的問題,而不是別人要求我們“必須使用Google”或者任何Google的替代品。

我在推特上與Laurie Voss討論過一次,他非常瞭解npm生態系統。他認爲Deno需要包管理器,而這篇文章更詳盡地介紹了我的想法,但是Laurie提出的一個觀點很有道理。

  • 我:“git並不會要求遠端代碼位於何處,就像瀏覽器和Deno一樣。無論是Gitlab、BitBucket還是GitHub,git都能使用。”
  • Laurie Voss:“但我們的代碼還是存放在同一個地方,也就是GitHub。因爲維護軟件生態系統是一項社會工作,需要影響力,這必然導致中心化的系統,這與技術要求無關。”
GitHub已成爲開源的源頭,因爲它很好用且能夠解決問題,而且是建立在廣爲接受的代碼管理工具git上的。從Deno CLI的角度來說,代碼來自何處並沒有技術限制,因此生態系統完全決定了怎樣才能讓Deno更易於發現,也許最後的方式是CLI的創建者從來沒有想到過的。

可重複的構建
在npm生態系統中這是一個問題。由於npm嚴重依賴語義版本控制,依賴於node.js/npm生態系統帶來的複雜的依賴關係圖,因此很難保證可重複的構建。Yarn引入了鎖定文件的概念,而npm緊隨其後。
我感覺這就像一條搖着尾巴討好主人的狗一樣,生態環境中的開發者們造成了問題,然後就弄出一個不完善的解決方案來解決。長期使用這個生態系統的人都知道,許多問題的解決方法就是rm -rf node_modules package-lock.json && npm install。
話雖如此,Deno爲此提供了兩種解決方案。首先是Deno緩存模塊。可以將緩存提交到源代碼控制中,同時使用--cached-only標誌確保不嘗試檢索遠程模塊。DENO_DIR環境變量可用於指定緩存的位置,以提供更大的靈活性。
其次,Deno支持鎖定文件。--lock lock.json --lock-write可以輸出一個鎖定文件,記錄所有依賴項的哈希值。之後可以使用--lock lock.json對依賴進行驗證。
還有一些其他命令支持可重複的構建。deno cache將解決給定模塊的所有依賴關係,並填充Deno緩存。deno bundle可用於生成工作負載的單個文件的“構建”,所有依賴關係都已解決幷包含在該文件中,以後可以使用deno run命令運行該文件。

信任代碼
我認爲這是另一個我們的思維比較奇怪的地方。出於某種原因,我們信任來自中心式倉庫的代碼。對於這些代碼我們根本不會考慮信任問題。不僅如此,我們還相信這些代碼的所有依賴項都是可以信賴的。我們快速搜索一下然後輸入npm install some-random-package,然後就認爲一切沒問題。我認爲,豐富的npm軟件包生態系統讓人們感到沾沾自喜。
爲了彌補這種鬆懈和自滿,我們在工具鏈中實現了安全監視軟件,分析依賴項以及成千上萬行代碼,以找出那些可能有問題的代碼。企業會設置私有倉庫,對於信任的要求要比公共倉庫高一些。
人們對此熟視無睹。其實最好的策略是我們不應該信任任何代碼。做到這一點之後,再去信任某些代碼就會容易一些。但是如果我們認爲程序包管理器和中心化倉庫可以解決此問題,甚至可以實質性地解決此問題,那就是在會自欺欺人。實際上,我認爲它們的存在讓我們放鬆了警惕。“因爲是npm上的,如果出現有問題的包,那麼肯定有人會把它取下來。”
Deno在這方面的工作還不盡如人意,但它有一個好的開始。它在開始時採用零信任,並提供相當精細的權限管理。我個人不喜歡的一件事是-A標誌,它基本上等於“允許一切”。對於沮喪的開發人員而言,允許一切要比弄清楚真正的需求要容易得多。
這些權限也再細分,比如說“這段代碼可以做這個,但是其他代碼不能做”,或者根據代碼的來源來決定是否允許提權,這些代碼是從哪裏來的。希望我們能找到一種易於使用的機制,並結合一些在運行時有效且高效的方法來嘗試解決這些難題。
不過,最近有一個變化我認爲很好,那就是Deno不再允許導入來源的降級。如果某個東西是從https://導入的,那麼只能從其他https://的地方導入。這跟瀏覽器不能降級傳輸協議是一樣的。我認爲,從長期來看,禁止一切非https://的導入是必要的,就像Service Workers要求HTTPS一樣。我們將拭目以待。

依賴管理
我認爲我們需要坦率地談談npm生態系統中的依賴關係。老實說,它很有問題。一個生態系統讓這五行代碼(https://github.com/juliangruber/isarray/blob/master/index.js)在過去9年中被下載了3000萬次,而這段代碼的功能早已存在於每個瀏覽器中,Node.js也從來不用,那這個生態系統就就有問題。在這個例子中,實際代碼僅有132個字節,但包大小爲3.4kb。可運行代碼僅佔程序包大小的3.8%。“不用擔心!”
我認爲這涉及好幾個因素。其主要原因是,這個模型是反的,這一點我說過Deno是代碼的瀏覽器。問題是,反向的模型影響了我們創建網站的方式。如果沒有中央倉庫,那麼創建網站時,我們會下載所有依賴的代碼,然後上傳到服務器上,然後每個用戶將一堆代碼下載到本地計算機上。一些證據表明,所下載的代碼中只有大約10%是該站點或Web應用程序所獨有的,其餘的是我們正在下載到開發工作站並捆綁在一起的所有代碼。這種有問題的模型正是Snowpack等解決方案試圖解決的問題。
另一個重要的問題是我們的依賴項沒有與代碼耦合。我們將依賴關係放入package.json中,但它與代碼是否實際使用了這些依賴完全無關。雖然我們的代碼表示了其他代碼中正在使用的內容,但它與該代碼的版本非常鬆散地耦合在一起。這種耦合關係包含在package.json中,它對我們編寫的代碼影響最大,因爲只有它纔是實際上消費依賴項的代碼。
因此我們提出了Deno模型,我喜歡稱之爲Deps-in-JS,因爲所有酷的代碼都叫* -in-JS。Deno將外部依賴明確地表示爲URL意,這意味着依賴關係簡潔明瞭,並且代碼和依賴關係緊密地耦合在一起。如果要查看依賴關係圖,只需執行deno info並指定本地或遠程模塊:
$ deno info https://deno.land/x/oak/examples/server.tslocal: $deno/deps/https/deno.land/d355242ae8430f3116c34165bdae5c156dca21aeef521e45acb51fcd21c9f724type: TypeScriptcompiled: $deno/gen/https/deno.land/x/oak/examples/server.ts.jsmap: $deno/gen/https/deno.land/x/oak/examples/server.ts.js.mapdeps:├── https://deno.land/[email protected]/fmt/colors.ts└─┬ https://deno.land/x/oak/mod.ts├─┬ https://deno.land/x/oak/application.ts│ ├─┬ https://deno.land/x/oak/context.ts│ │ ├── https://deno.land/x/oak/cookies.ts│ │ ├─┬ https://deno.land/x/oak/httpError.ts│ │ │ └─┬ https://deno.land/x/oak/deps.ts│ │ │ ├── https://deno.land/[email protected]/hash/sha256.ts│ │ │ ├─┬ https://deno.land/[email protected]/http/server.ts│ │ │ │ ├── https://deno.land/[email protected]/encoding/utf8.ts│ │ │ │ ├─┬ https://deno.land/[email protected]/io/bufio.ts│ │ │ │ │ ├─┬ https://deno.land/[email protected]/io/util.ts--snip--
Deno對代碼的“版本”沒有意見。URL就是URL。雖然Deno需要適當的媒體類型以瞭解如何處理代碼,但有關代碼本身如何提供的“意見”都交給Web服務器處理。服務器可以對核心內容採取語義版本控制,或者可以將URL映射到任何資源上。Deno不關心這些。例如,https://deno.land/x/實際上只是一個URL重定向服務器,它在URL重定向服務器中重寫URL,重定向到一個git commit風格的地址。所以https://deno.land/x/[email protected]/mod.ts變成https://raw.githubusercontent.com/oakserver/oak/v4.0.0/mod.ts,這個URL是GitHub上帶有版本的模塊。
當然,將“版本化”的遠程URL分散在整個代碼中並不合適,所以不要這樣做。把依賴關係當做代碼的優勢在於,您可以按照自己想要的任何方式來構造它們。常見的約定是使用deps.ts,它將重新導出您可能需要的所有依賴項。看一看oak服務器的例子:
// Copyright 2018-2020 the oak authors. All rights reserved. MIT license.// This file contains the external dependencies that oak depends upon// `std` dependenciesexport { HmacSha256 } from "https://deno.land/[email protected]/hash/sha256.ts";export {Response,serve,Server,ServerRequest,serveTLS,} from "https://deno.land/[email protected]/http/server.ts";export {Status,STATUS_TEXT,} from "https://deno.land/[email protected]/http/http_status.ts";export {Cookies,Cookie,
setCookie,getCookies,delCookie,} from "https://deno.land/[email protected]/http/cookie.ts";export {basename,extname,join,isAbsolute,normalize,parse,resolve,sep,} from "https://deno.land/[email protected]/path/mod.ts";export { assert } from "https://deno.land/[email protected]/testing/asserts.ts";// 3rd party dependenciesexport {contentType,lookup,} from "https://deno.land/x/[email protected]/mod.ts";
我創建了Oak服務器,並維護了18個月,經歷了Deno和Deno std庫的40個版本發佈,包括將media_types從內部移動到oak,再移到std庫中,目的是爲了將其從std庫中“彈出”,成爲自己的一部分。我從來沒有感到我需要一個程序包管理器來管理這一切。
TypeScript的好處之一是以全面驗證代碼與其他代碼的兼容性。如果依賴項是爲Deno編寫的“原始” TypeScript,那就太好了,但是如果你希望將TypeScript當做JavaScript的預處理來使用,同時還想保持能夠安全地依賴遠程代碼的便利。Deno支持幾種不同的方法來實現這一點,但是最無縫的是對X-TypeScript-Types頭部的支持。此頭部告訴Deno類型文件所在的位置,對依賴的JavaScript文件進行類型檢查時可以使用。Pika CDN支持此功能。任何CDN上擁有類型說明的文件都會提供該頭部,而Deno也將獲取這些類型並在對文件進行類型檢查時使用。
說了這麼多,我們可能依然需要將某個遠程(或本地)依賴項“映射”成代碼中的表示。這時可以使用尚不穩定的import-maps功能。這是一個提案規範,是W3C孵化器的一部分。它允許提供一個映射,該映射會將代碼中的特定依賴項映射到另一個資源,無論是本地文件還是遠程模塊。
我們曾在Deno中實現了很長一段時間,因爲我們真的希望它會被廣泛採用。遺憾的是,這只是來自Chrome的一項實驗,並沒有得到更廣泛的採用。因此我們不得不將其置於Deno 1.0的--unstable標誌後面。我個人認爲這個功能很可能會無疾而終,所以應該避免使用。
但是,但是,但是...
我知道很多人仍然對Deno的模型感到懷疑。我認爲Deno嘗試採取的策略(我非常贊同)是,在出現實際問題時進行處理。我聽到的很多反對意見來自Deno的新手,他們從接觸過Deno,也沒有試圖瞭解可能會有不同的方式。
話雖如此,如果我們共同遇到一個問題,並且迫切需要在Deno CLI中進行某些更改,我敢肯定這個問題一定會解決,但是很多問題根本就不存在,或者還有其他解決方式,這些解決方式並不需要運行時有強烈的意見,也不會需要與外部程序耦合來管理代碼。
因此,我希望你能嘗試一下沒有軟件包管理器或中心式倉庫的情況,看看它好不好用。您可能永遠不會回頭!
作者簡介:
Kitson Kelly,首席技術工程師@ThoughtWorks,澳大利亞墨爾本。
原文鏈接:
https://kitsonkelly.com/posts/deno-is-a-browser-for-code/
你點的每個“在看”,我都認真當成了喜歡
相關文章