在Ry在2018年5月使原型可见后不久,我就开始为Deno做贡献。人们最常问的问题是“包装管理员在哪里?”很多时候甚至不是以问题的形式出现。它是这样的声明:“我认为迪诺认真对待安全问题,仅仅从互联网上下载资源是不安全的。”或者“我怎么可能管理我的依赖关系呢?”
在我看来,我们需要改变我们的思维模式。很多人认为包管理器和集中代码注册中心无处不在,这是拥有包管理器和集中代码注册中心的要求。因为它们存在并不意味着它们是必需的。它们之所以存在,是因为它们以特定的方式解决问题,而我们刚刚接受它们是解决这个问题的唯一途径。我认为这不是真的。
为了发布网站,我们不会登录到Google中央服务器,然后将我们的网站上传到注册表。然后,如果有人想要查看我们的网站,他们使用命令行工具,该工具会在我们本地机器上的browser.json文件中添加一个条目,并获取整个网站,以及该网站链接到我们本地网站目录的任何其他网站,然后在我们启动浏览器以实际查看该网站之前。那太疯狂了,对吧?那么,为什么要接受运行代码的模型呢?
Deno CLI的工作方式与浏览器类似,但适用于代码。您在代码中导入URL,Deno将获取该代码并将其缓存到本地,就像浏览器一样。此外,与浏览器一样,您的代码运行在沙箱中,沙箱对您运行的代码没有任何信任,无论其来源如何。您,调用代码的人,在外部告诉代码它能做什么和不能做什么。此外,与abrowser类似,代码可以请求您执行某些操作的权限,您可以选择授予者拒绝。
HTTP协议提供了提供有关代码的信息所需的一切,Deno尝试充分利用该协议,而不必创建新协议。
首先要考虑的是,就像浏览器一样,Deno CLI不想对您运行的代码有任何意见。它列出了如何获取代码的规则,以及它如何将自身与其运行的机器进行沙箱。在我看来,这就是一个运行时应该有的观点。
在Node.js/npm生态系统中,我们将本地机器上的代码管理与集中的代码注册表混为一谈,以帮助发现,在我看来,两者都有非常糟糕的缺陷。
早在互联网的早期,我们就试验了NPM类型的可发现性。您可以将您的网站添加到Yahoo!在正确的分类下,人们可能会出现,可能会使用搜索功能,但它的结构都是基于提供内容的人的意见,而不是基于对消费者需求的优化。最终,谷歌(Google)出现了。为什么谷歌会赢?因为它很有用。它将简单的需求表达(搜索词)与满足需求的最相关的网页相匹配的网站编入索引,考察了多个因素,包括将内容提供商提供的元数据作为其中一个因素提供给内容提供商。
虽然我们还没有用于Deno的代码的模型,但它是一个可以工作的模型。此外,我们使用谷歌是因为它为我们解决了问题,而不是被告知“你必须使用谷歌”,而且谷歌还有其他可行的替代方案。
我在Twitter上与劳里·沃斯(Laurie Voss)进行了一点辩论,我想说的是,他对NPM生态系统有公平的了解。他认为Deno需要背包经理,而这篇博客文章是我想表达的想法的一个更长的长篇大论版本,但Laurie提出了一个非常有道理的观点。
然而,我们所有的代码都在同一个地方,在GitHub上,因为维护软件生态系统的问题是导致集中化系统的社会和声誉问题,尽管没有这样的技术要求。
-Laurie Voss(@seldo)2020年5月15日。
GitHub之所以成为开源代码的发源地,是因为它非常有用,可以解决问题,并且构建在事实上的源代码版本控制工具git之上。从Deno CLI的角度来看,不应该对您的源代码来源有任何技术限制,而是由更广泛的生态系统来创建和下放使Deno的代码可被发现的方法,很可能是以我们这些创建CLI的人从未想过的创新方式。
在NPM生态系统中,这成了一个问题。由于对语义版本控制的严重依赖,以及往往来自Node.js/npm生态系统的复杂依赖图,拥有可重复的构建成为一个真正的问题。Yarn引入了锁文件的概念,npm紧随其后。
我个人的感觉是,这有点像是尾巴在摇狗,因为生态系统中开发者的行为造成了一个问题,然后需要一个不完美的解决方案来解决它。我们中任何一个长期使用该生态系统的人都知道,解决许多问题的方法是rm-rf node_module package-lock.json&;&;npm install(rm-rf节点模块包-lock.json&;&;npm install)。
话虽如此,德诺对此有两种解决方案。首先,是迪诺缓存模块。该缓存可以签入到您的源代码管理中,并且--cached-only标志将确保不会尝试检索远程模块。DENO_DIR环境变量可用于指定缓存的位置,以提供更大的灵活性。
其次,Deno支持锁定文件。--lock lock.json--lock-write将写出一个锁文件,其中包含给定工作负载的所有依赖项的散列。这将用于在使用--lock lock.json时验证将来的运行。
还有几个其他命令可以使管理可重复构建。Deno缓存将解析所提供模块的所有依赖项,并填充Deno缓存。Deno捆绑包可用于生成工作负载的单个文件“构建”,所有依赖项都已解析并包含在该文件中,因此未来的Deno运行命令只需要该单个文件。
这是我认为我们有一个扭曲的思维模式的另一个领域。无论出于什么原因,我们都信任集中注册表中的代码。我们甚至想都不想。不仅如此,我们相信该代码已经完全审查了它的所有依赖项,并且这些依赖项是值得信任的。我们快速搜索并键入npm,安装一些随机包,然后想“这很好!”我认为,NPM一揽子生态系统已经让人产生了一种自满的感觉。
为了弥补这种松懈和自满,我们在我们的工具链中实现了安全监控软件,以分析我们的依赖关系和数千行代码,让我们知道其中一些代码可能是可利用的。公司设置私有注册中心来托管可能比单个公共注册中心稍微多审查的包。
感觉这里的房间里有一头大象。最好的策略是我们不能相信任何代码。一旦我们建立了这一点,那么打开它的备份就变得更容易了。但是,如果我们认为包管理器和中央注册表解决了这个问题,甚至对这个问题有实质性的帮助,那我们就是在欺骗自己。事实上,我认为他们是在利用让我们的警卫失望。“嗯,它是在NPM上播出的,如果它对我不好,肯定会有人把它撤下。”
迪诺在这方面没有我想的那么好,但它是从一个很好的位置开始的。它在启动时没有信任,并且提供相当细粒度的权限。我个人不喜欢的一件事是有-A标志,它基本上是在说“哦,耶,允许一切”,这对于沮丧的开发人员来说是一件非常容易做的事情,而不是弄清楚他们真正需要的是什么。
也很难打破这些权限,说“这段代码可以做到这一点,但这里的另一段代码不能”,或者当代码提示提升权限时,这些代码是从哪里来的。希望我们能找出一种易于使用的机制,再加上一些在运行时将是有效和高性能的东西,试图解决这些挑战。
不过,在我看来,最近的一个变化是,Deno不再允许你降低进口商品的等级,这是一个好的变化。如果某个内容是从https://导入的,则它只能从其他https://位置导入。这遵循了不能降级传输的浏览器模型。我仍然认为从长远来看,扼杀任何不像服务工作者需要https://,那样的远程进口是件好事,所以我们将拭目以待。
我认为我们需要坦率地谈论NPM生态系统中的依赖关系。老实说,它坏了。一个支持每周下载和安装5行代码3000万次的生态系统,这些代码在过去9年里一直存在于每个浏览器中,而Node.jis是一个支离破碎的生态系统。在这个例子中,实际代码是132字节,但是包大小是3.4KB。可运行代码是包大小的3.8%。“这很好!”
我的观点是,这其中涉及到几个因素。很大一部分原因是我们颠倒了模型,我说过Deno是代码的浏览器。问题是,这种向后的模式已经影响了我们创建网站的方式。虽然我们没有中央注册表,但当我们建立一个网站时,我们下载我们依赖的所有代码,并将其烘焙成我们加载到服务器上的东西,然后每个用户将一堆代码下载到他们的本地机器上。一些证据表明,下载代码中只有10%左右是该站点或Web应用程序独有的,其余的都是我们下载到开发工作站并捆绑在一起的所有代码。这一模式正在被打破,这是积雪等解决方案试图解决的一些问题。
另一个重要问题是我们的依赖项没有与我们的代码耦合。我们将依赖项放在Package.json中,但是如果我们的代码实际上使用了Json,那么无论该代码是否使用,都是完全解耦的。虽然我们的代码表达了我们在其他代码中使用的内容,但它与该代码的版本是非常松散耦合的。它包含在Package.json中,尽管它对我们编写的代码有最大的影响,因为它是实际使用依赖代码的代码。
这就把我们带到了Deno模式,我喜欢称之为Dep-in-JS,因为所有酷的孩子都在做*-in-JS的事情。显式地将我们的外部依赖项声明为URL意味着代码依赖于其他代码是简洁和清晰的,并且我们的代码和依赖项紧密耦合在一起。如果要查看依赖关系图,只需对本地或远程模块使用Deno信息:
$DENO INFO https://deno.land/x/oak/examples/server.tslocal:$deno/deps/https/deno.land/d355242ae8430f3116c34165bdae5c156dca21aeef521e45acb51fcd21c9f724type:类型脚本编译:$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/x/oak/examples/server.ts 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└─┬├─┬│├─┬││├──││├─┬│└─┬.ts。/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/std@0│├──│├─┬│├──│├─┬Oak。.53.0/io/bufio.ts https://deno.land/[email protected]/io/util.ts--snip--│├─┬。
Deno对代码的“版本”没有强烈的看法。URL就是URL,虽然Deno需要适当的媒体类型才能理解如何处理代码,但是所有关于提供什么代码的“意见”都留给Web服务器。服务器可以实现对其核心内容的语义版本控制,或者执行URL到它想要的资源的任何类型的“神奇”映射。德诺不在乎。例如,https://deno.land/x/实际上只是一个URL重定向服务器,它重写URL以在重定向的URL中包含GIT提交式的引用。因此,https://deno.land/x/[email protected]/mod.ts变成了https://raw.githubusercontent.com/oakserver/oak/v4.0.0/mod.ts,,GitHub提供了一个很好的版本化模块。
当然,在代码库中传播“版本化”的远程URL没有多大意义,所以不要这么做。不过,依赖项只是作为代码编写的一大优点是,您可以按照您想要的任何方式来构建它们。一个常见的约定是使用des.ts,它可以重新导出您可能需要的所有依赖项。看看Oak服务器的版本:
//版权所有2018-2020橡树网作者。版权所有。麻省理工学院许可证。//此文件包含Oak所依赖的外部依赖项//`std`依赖项从";https://deno.land/[email protected]/hash/sha256.ts";;export{Response,Serve,Server,ServerRequest,ServeTLS}从";https://deno.land/[email protected]/http/server.ts";;export{STATUS,STATUS_TEXT,}导出{HmacSha256},来自";https://deno.land/[email protected]/http/http_status.ts";;export{Cookie,Cookie,setCookie,getCookie,DelCookie,}来自";https://deno.land/[email protected]/http/cookie.ts";;export{basename,extname,Join,isAbsolute,Normize,Parse,Resolve,Sep}来自";https://deno.land/[email protected]/path/mod.ts";;的{BaseName,ExtName,Join,isAbsolute,Normize,Parse,Resolve,Sep从";导出{assert}。https://deno.land/[email protected]/testing/asserts.ts";;//第三方依赖项从";https://deno.land/x/[email protected]/m导出{Content Type,Lookup,}
我创建了Oak服务器,并通过大约40个版本的Deno和Deno STD库维护了18个月,包括将media_type从内部移动到OAK,再移到STD库,结果只是将其从标准库“弹出”成为自己的东西。我从来没有想过“嘿,我需要背包经理帮我管理这件事”。
TypeScript的好处之一是您可以全面验证您的代码与其他代码的兼容性。如果您的依赖项是为Deno编写的“原始”打字脚本,这很好,但是假设您希望利用将打字脚本预处理为JavaScript的优势,但仍然能够安全地使用远程代码。Deno支持两种不同的方式来实现这一点,但最无缝的是对X-TypeScript-Types头文件的支持。此标头向Deno指示类型文件所在的位置,在对您所依赖的JavaScript文件进行类型检查时可以使用该标头。Pika CDN支持这一点。CDN上可用的、具有关联类型的任何包都将提供该标头,Deno也将获取这些类型并使用该WHENTYPE检查文件。
尽管如此,可能仍然需要将远程(或本地)依赖项“重新映射”到代码中表达的内容。在这种情况下,可以使用不稳定的导入映射实现。这是一个提案规范,是W3C孵化器的一部分,浏览器标准就出自该孵化器。它允许提供一个映射,该映射将代码中的特定依赖项映射到另一个资源,无论是本地文件还是远程模块。
我们在德诺实施了很长一段时间,因为我们真的希望它能被广泛采用。遗憾的是,这只是Chrome的原创试验,还没有得到更广泛的采用。这导致我们把它放在Deno 1.0的--不稳定标志后面。我个人认为,这仍然是一个很大的死胡同的风险,应该避免。
我怀疑很多人仍然带着对Deno的模型的反对清单而来。我认为德诺试图采取的策略,也是我非常赞同的,是在真正的问题出现时处理它们。我听到的许多反对意见都来自那些刚接触Deno的人,他们没有使用过Deno,也没有试图理解可能会有不同的方式。
话虽如此,如果我们集体遇到问题,并且Deno CLI中有迫切需要更改的东西,我相信它会发生,但是很多问题根本不存在,或者有其他方法来解决它们,而不需要您的运行时有强烈的意见或耦合到外部程序来管理您的代码。
因此,我对您的挑战是,稍微考虑一下没有包管理器或集中包存储库,看看进展如何。你可能再也回不去了!