<3 Deno
Deno是一个相对较新的JavaScript运行时。我发现非常有趣和美观,符合最近支配软件进化的“越差即好”规律的趋势。这篇文章解释了原因。
在我看来,Deno的主要目标是简化相对于现状的软件开发。简化意味着消除意外的复杂性。对我来说,当今软件意外复杂性的一个重要来源是隐式依赖关系。软件是由许多组件构建的,虽然有些组件定义得相对明确(Linux syscall interface,amd64 ISA),但其他组件则不那么明确。示例:将 Rust 项目的 OpenSSL 从 1.1.1 升级到 3.0.0 可以在您的机器上运行,但在 CI 上会中断,因为 3.0.0 现在需要一些新的 perl 模块,该模块通常 预期 与 perl 安装一起存在,但并非普遍如此。解决这类问题的一种方法是在它们周围放置一个 docker 容器。但另一种方法是非常小心地避免产生问题。从一般意义上讲,Deno选择了第二条高尚且艰难的道路。
该领域的第一个问题是引导。通常,您可以通过编写一些自定义脚本来完成所有繁重的工作来消除相当多的复杂性。但是你如何运行它呢?
一个答案是使用 shell 脚本,因为 shell 已经安装。哪个 Shell?砰,嘘,PowerShell?POSIX sh可能是一个理智的选择,Windows用户可以在他们的子系统中运行 Linux。您还需要安装 shell check 以确保您不会意外使用 bash。在某些时候,你的脚本变得太大,你用Python重写它。你现在必须安装Python,我听说现在这些在 Windows 上变得很要容易了。当然,您要在虚拟环境中运行它 docker container。而且你要小心使用“python3 -m pip”而不是“pip3”来确保你使用正确的东西。
虽然脚本和管道应该是对抗复杂性的一种方式,但要达到软件的每个贡献者都可以运行脚本的程度,就需要 docker container 对环境进行大量的处理!
Deno并没有解决每一台可以想象的机器上都存在的问题。但是,一旦您将“deno”二进制文件放到机器上,它就会非常努力地避免产生其他问题。一些表现形式:
Deno带有开箱即用的代码格式化程序('deno fmt')和LSP服务器('deno lsp')。这里的优势并不是这些是提高生产力的高价值功能(尽管确实如此),而是您不需要拉额外的 deps 来获得这些功能。类似地,Deno 是一个 TypeScript 运行时---不涉及转译步骤,你只需要“deno main.ts”。
Deno不依赖于系统的shell。大多数脚本环境,包括node,python和ruby,都犯了一个严重的错误,即添加一个API来生成一个由shell中介的进程。这是缓慢的,不安全的,而且很脆弱(又是哪个 Shell?我有一个关于这个问题的更长的帖子。Deno 没有这个易受攻击的 API。并不是说“没有API”是一项特别具有挑战性的技术成就,但它 比当前的选项更好。
Deno有一个正确设计的任务系统。每当你做一个不平凡的软件项目时,不可避免地会出现一个点,你需要编写一些软件来编排你的软件。意外的复杂性以“Makefile”或“./scripts/*.sh”目录的形式蔓延。Node(据我所知)开创了一个好主意,通过在“package.json”中包含“scripts”字段,将这些视为项目的头等关注点。然后,它通过系统的 shell 运行脚本来拙劣地执行,这会将其降级到具有更多间接性的“./scripts”目录。相比之下,Deno 在 'deno_task_shell' 中运行脚本---一个专门构建的小型跨平台 shell。您不再需要担心“rm”的行为可能会因“哪个 rm”而异,因为它现在是 shell 的内置功能。
这些都是工程上的可有可无。它们不一定孤立地那么重要,但它们共同指向与我的价值观非常一致的项目价值。但也有一些创新的、更大的功能。
第一个大功能是权限系统。运行 Deno 程序时,需要明确指定它可以访问的操作系统资源。ping “google.com” 需要明确的加入。您可以安全地运行
$ deno run https://shady.website.eu/caesar-cipher.ts <input.txt >output.txt
并确保这不会窃取您的秘密。当然,它仍然可以无限期地烧毁 CPU 或用垃圾填充 out.txt,但它将无法读取显式传递的输入之外的任何内容。对于许多(如果不是大多数)脚本任务来说,这是针对供应链攻击的一个很好的额外保护。
第二大特点是Deno的有趣,很小,但仍然实用,承担依赖管理。首先,不言而喻,没有全局依赖关系。所有内容的范围都限定为当前项目。当然,也有带有校验和的 Lock 文件。
但是,没有包注册表,甚至没有单独的包管理器。在 Deno 中,依赖项始终是一个 URL。运行时本身可以理解URL,下载其内容并加载生成的TypeScript或JavaScript。令人惊讶的是,感觉这足以表达各种依赖模式。例如,如果您需要一个集中式注册表,例如https://deno.land/x,则可以使用指向该注册表的URL!URL也可以表达semver,“foo@1”重定向到“foo@1.2.3”。Import maps是重新映射依赖关系的标准、灵活的方法,适用于需要调整树中的深层内容时。至关重要的是,除了锁定文件之外,Deno 还附带了一个内置的“deno vendor”命令,该命令获取当前项目的所有依赖项并将它们放入子文件夹中,使生产部署不受依赖项托管故障的影响。
Deno的内置API方法很好地从其基于URL的依赖管理中引导。首先,Deno 提供了一组运行时 API。这些 API 是绝对稳定的,遵循现有标准(例如,用于进行网络的“fetch”),并扮演着为底层操作系统提供跨平台接口的角色。然后是标准库。有一个雄心壮志,即提供一个全面的标准库,该库由核心开发人员审查。同时,巨大 stdlib 需要多年的大量工作。因此,作为稳定的 1.30.3 运行时 API 的伴侣,它是“deno”二进制文件的一部分,有 0.177.0 版本的 stdlib,它就像任何其他依赖项一样下载。我相当肯定,随着时间的推移,这将最终产生真正稳定、全面和高质量的 stdlib。
所有这些共同意味着你可以确定,如果你让“deno --version”工作,那么'deno run your-script.ts'将永远工作,因为由于环境差异而出错的表面积被大幅削减。
Deno唯一的大缺点是语言---所有这些运行时的出色之处都与TypeScript有关。JavaScript 在 ES6 之后是一个奇怪的野兽---,它实际上使用起来很愉快,并且有一些非常好的部分,比如防注入模板文字语义。但是所有旧的WATs都喜欢
["10", "10", "10"].map(parseInt)
同时。TypeScript 在 JavaScript 类型方面做得令人钦佩,但生成的类型系统并不简单。从语言学上讲,理论上似乎有可能比 TypeScript 好得多的东西。但在实际存在的语言中,TypeScript 似乎是一个不错的选择。
总而言之,从历史上看,“脚本”和“粘合代码”领域一直受到意外将自己强粘到手头的特定 UNIX 风格的问题的困扰。Deno 最终似乎是一种技术,它试图通过没有上述依赖项来解决隐式依赖项的问题 而不是将所有内容放在 docker 容器中。