Deno模块系统:入门指南
了解Deno模块系统——如果你来自Node.js,你会遇到的最大的工作流程变化。了解它的工作原理和使用方法,如何在Deno中使用Node.js包,等等。
Node.js是一个基于Chrome的V8引擎的JavaScript运行时,由Ryan Dahl开发,于2009年发布。
Deno是一个基于Chrome的V8引擎的JavaScript运行时,由Ryan Dahl开发,2022年发布。
它是在十年后才创建的。这并不一定使它成为Node.js的续作或优于Node.js,但它偏离了这条道路。
最大的差异:Deno原生支持TypeScript、安全、测试和浏览器API。模块处理受到的关注较少,但它可能是对你如何创建JavaScript应用程序的最大改变。在讨论Deno之前,让我带你回到一个更简单的时代... ...
Node.js Modules
2009年,JavaScript还没有标准的模块系统。这在一定程度上是因为其浏览器的传统,而ES6 / ES2022还有几年的时间。
Node.js不提供模块是不可想象的,因此它从社区变通方法的选择中采用了CommonJS。这导致了Node包管理器(Node Package Manager,简称npm)的发展,它允许开发人员轻松搜索、使用和发布自己的JavaScript模块。
npm的使用呈指数增长。它已成为有史以来最受欢迎的软件包管理器,到2022年中,将托管近150万个模块,每天发布800多个新模块(来源:modulecounts)。
Deno Modules
Deno选择从绝对或相对URL导入的ES2022模块:
import { something } from 'somewhere/somehow.js';
该URL上的脚本必须相应地导出函数或其他值,例如
export function something() {
console.log('something was executed');
}
Deno使用与现代Web浏览器中实现的模块系统相同的模块系统。
Node.js也支持ES2022模块......但它很复杂,仍然是实验性的。CommonJS和ES2022模块看起来相似,但工作方式不同:
CommonJS在执行代码时按需从文件系统加载依赖项。ES模块是从URL预解析的,以便在执行代码之前解决进一步的导入问题。
Node.js必须继续支持CommonJS以及处理混合的ES模块。因此,它假定:
.cjs 结尾的文件使用CommonJS以 .mjs 结尾的文件使用ES模块以 .js 结尾的文件是CommonJS,除非 package.json 设置了 "type":”module” 或 node 被执行时有 --input-type=module 选项。
可以理解为什么Deno选择单一标准模块系统。但是,npm对于Node的成功至关重要,因此令人惊讶的是Deno放弃了它。
没有程序包管理器。
对npm的一个批评是每个项目的 node_modules 目录太大。当模块需要其他模块的特定版本时,它可以达到数百兆字节。
Deno首次在脚本中遇到其URL时,Deno会将模块下载并缓存在全局目录中。因此,无论有多少项目引用该特定模块版本,都只需要一个副本。
我知道你在想:“啊,但是如果……”
…但是Deno有解决模块URL所引起问题的选项。
不可靠的URLs
URL可能会暂时失败,改变,或者永远消失。这对于任何包管理器来说都是一个问题,npm在过去也遇到过这样的问题(它也允许从URL安装)。
对于关键任务的Node.js应用,建议将 node_modules 目录添加到项目的Git/其他仓库中。
Deno也支持类似的选项,您可以将 DENO_DIR 环境变量设置为当前工程中的一个目录路径。如
DENO_DIR=~/myproject/deno_modules`
在Windows cmd中使用:
> set DENO_DIR="C:\myproject\deno_modules"
或Windows Powershell:
> $env:DENO_DIR="C:\myproject\deno_modules"
当你的应用程序运行时,Deno会将模块缓存到该目录,以便可以将其添加到项目的源代码控制仓库中。
你也可以考虑将你的依赖关系捆绑到一个JavaScript或TypeScript文件中。Deno bundle命令可以在一个步骤中完成。
deno bundle myscript.js myscript.bundle.js
其中 myscript.js 是你的入口脚本,通常用 deno run 执行。由此产生的自带的 myscript.bundle.js 文件可以部署到一个实时服务器上。
Deno支持顶层 await:无需将等待调用包装在匿名异步函数中。不幸的是,顶级 await 在捆绑中失败了,所以必须添加一个包装函数。这是一个已知的问题,将在未来的版本中修复。
模块版本控制
理想情况下,模块URL应该是有版本的,这样你就可以引用一个特定的代码版本。例如,Deno标准库允许你加载HTTP服务器模块的特定版本。
import { serve } from 'deno.land/std@0.61.0/http/server.ts';
可以改为引用master分支:
import { serve } from 'deno.land/std/http/server.ts';
但这会下载最新版本,将来的发行版可能与你的应用程序不兼容。
可以使用类似的版本约定在自己的服务器上发布你的模块,但你的网站可能会收到大量的流量,因为它变得很流行。一个更稳健的方法是在GitHub等服务上使用一个仓库,并为每个版本分配一个git标签。像 denopkg 和 unpkg 这样的服务可以用来提供一个公开版本的模块 URL。
多模块提及
在整个应用程序的代码库中,你可能需要在许多文件中引用相同的模块URL。当你要更新该模块时,需要在多个位置更改URL。搜索和替换虽然可以,但是笨重,容易出错,并且增加了合并冲突的机会。
或者,你可以使用单个依赖项文件,该文件将导入项目中正在使用的每个模块。通常将其命名为 deps.js 或 deps.ts:
// deps.js: module dependencies
// 所有标准路径模块函数
export * as path from 'deno.land/std@0.61.0/path/mod.ts';
// 一些std datetime模块函数
export { parseDate, currentDayOfYear } from 'deno.land/std@0.61.0/datetime/mod.ts';
然后,你可以在任何其他项目文件中引用 deps.js 中的模块:
import { path, currentDayOfYear } from './deps.js';
console.log( path.sep );
console.log( currentDayOfYear() );
更新模块时,只需在 deps.js 中更改单个URL引用。
另一种选择是导入map。这是一个小的JSON文件,通常命名为 import_map.json,它为一个完整或部分的URL分配一个名称。
{
"imports": {
"path/": "deno.land/std@0.61.0/path/",
"datetime/": "deno.land/std@0.61.0/datetime/"
}
}
你可以在任何脚本中引用导入map名称:
import * as path from 'path/mod.ts';
import { currentDayOfYear } from 'datetime/mod.ts';
console.log( path.sep );
console.log(currentDayOfYear());
然后在使用 deno run 执行应用程序时导入JSON文件:
deno run \
--importmap=import_map.json \
--unstable \
myscript.js
导入map当前是不稳定的功能,因此需要 —unstable 标志。该功能可能会在将来的Deno版本中更改。
完整性调查
在你不知情的情况下,URL引用的代码可能会更改或被黑。高知名度的网站因为直接链接到第三方客户端代码而被入侵。想象一下,如果一个脚本能够访问服务器资源,会造成多大的破坏。
Deno具有内置的安全性,所以脚本的执行必须使用诸如 --allow-read 和 --allow-net 这样的标志来限制文件系统和网络访问。这将有助于防止一些问题的发生,但它不能代替对模块完整性的验证。
Deno提供了一个完整性检查选项。如果你使用一个单一的依赖文件,这是最简单的(如上所述)。
// deps.js: module dependencies
// 所有标准路径模块函数
export * as path from 'deno.land/std@0.61.0/path/mod.ts';
// 一些std datetime模块函数
export { parseDate, currentDayOfYear } from 'deno.land/std@0.61.0/datetime/mod.ts';
下面的deno命令会生成一个 lock.json 文件,其中包含所有导入模块的校验和。
deno cache --lock=lock.json --lock-write deps.js
当另一个开发者克隆你的项目时,他们可以重新加载每个模块,并验证每个模块的完整性,以保证它们与你的项目完全相同。
deno cache --reload --lock=lock.json deps.js
Deno不强制执行完整性检查。最好的办法是将这些过程作为自动的Git钩子或类似的东西来运行。
使用 Node.js Modules
许多Node.js的API已经被复制到Deno中——参见deno.land/std/node。这不是一个完整的列表,但你会发现常见的文件、事件、缓冲区和实用模块。
在deno.land/x上有近800个第三方模块的集合。其中有类似Express.js的框架、数据库驱动、加密功能、命令行工具等。
deno.land/std/node:deno.land/std/node
在deno.land/x上有近800个第三方模块的集合。其中有类似Express.js的框架、数据库驱动、加密功能、命令行工具等。
deno.land/x:deno.land/x
你还将发现精选的热门模块列表,例如Awesome Deno。
Awesome Deno:github/denolib/awesome-deno
然而,你可能可以导入150万个Node.js模块中的任何一个。一些CDN可以将npm/CommonJS包转换为ES2022模块URL,包括:。
Skypack.devjspmunpkg
在Deno中,你需要的模块是否可以正常工作是另一回事。
幸运的是,随着JavaScript运行时生态系统的发展,无需特殊处理即可在Node.js和Deno上工作的跨平台模块很可能会到来。
更多模块事项
引用模块URL是有争议的,对于那些来自于非常流行的npm的人来说可能会感到不安。尽管如此,Deno已经简化了JavaScript模块的使用。它解决了一些npm批评,同时减轻了ES2022模块的许多潜在副作用。
但这远非完美。
发布npm模块非常简单,搜索npmjs很简单。你的搜索词可能会返回500个结果,但是通过按流行度、质量和维护因素对软件包进行排名,可以将选择瘫痪最小化。
向Deno的第三方模块列表提交代码比较困难。模块必须通过自动测试,但质量没有保证,搜索结果是按字母顺序排列的。现有的系统一旦达到几千个模块,就不太可能持续下去。
在npm中更新包也很容易。你可以运行 npm outdated 来查看更新列表,或者当 package.json 中引用了较宽松的版本号时,直接运行 npm install。
在Deno中没有相应的更新检查选项。类似于包管理器的项目包括Trex、更新Deno依赖性和deno-check-updates,但这些项目通常依赖于导入映射,并且总是依赖于语义版本的URL。
你应该切换到Deno吗?
Node.js并没有死。它已经成熟了,在运行时背后有十年的模块、技术、文档和经验。
Deno充分利用了这些知识,但它是非常新的,并且将在未来几年迅速发展。现在就把Deno作为一个大的应用可能还为时过早,但是对于较小的项目来说风险更小。那些已经使用TypeScript或来自其他语言的开发者可能会享受到更简单的体验,但是Node.js开发者在转换到Deno和返回时不会有任何问题。
但是,Deno有一个有趣的好处:
它的模块系统与客户端JavaScript相同它实现了许多浏览器API:你可以引用 window 对象,设置事件监听器,启动Web Workers,使用Fetch()API进行远程服务器请求等等。
可在客户端或服务器上运行的同构JavaScript库的梦想已向前迈出了重要的一步。
#Deno# #Node.js#