本页内容
Node 和 npm 支持
现代 Node.js 项目在 Deno 中运行时,几乎不需要或只需少量修改。但是,在将 Node.js 项目迁移到 Deno 时,您可以利用两者运行时之间的一些关键差异来简化和缩小代码。
使用 Node 的内置模块 跳转到标题
Deno 提供了一个兼容层,允许在 Deno 程序中使用 Node.js 内置 API。但是,为了使用它们,您需要将 node:
说明符添加到任何使用它们的 import 语句中
import * as os from "node:os";
console.log(os.cpus());
并使用 deno run main.mjs
运行它 - 您会注意到您获得与在 Node.js 中运行程序相同的输出。
更新应用程序中的任何导入以使用 node:
说明符应使任何使用 Node 内置函数的代码像在 Node.js 中一样运行。
为了更轻松地更新现有代码,对于未使用 node:
前缀的导入,Deno 将提供有用的提示
import * as os from "os";
console.log(os.cpus());
$ deno run main.mjs
error: Relative import path "os" not prefixed with / or ./ or ../
hint: If you want to use a built-in Node module, add a "node:" prefix (ex. "node:os").
at file:///main.mjs:1:21
Deno LSP 在您的编辑器中也提供了相同的提示和其他快速修复。
使用 npm 包 跳转到标题
Deno 通过使用 npm:
说明符,原生支持导入 npm 包。例如
import * as emoji from "npm:node-emoji";
console.log(emoji.emojify(`:sauropod: :heart: npm`));
可以使用以下命令运行
$ deno run main.js
🦕 ❤️ npm
在 deno run
命令之前不需要 npm install
,也不会创建 node_modules
文件夹。这些包也受到与 Deno 中其他代码相同的权限的约束。
npm 说明符具有以下格式
npm:[@][/]
有关流行库的示例,请参阅教程部分。
CommonJS 支持 跳转到标题
CommonJS 是一种模块系统,早于 ES 模块。虽然我们坚信 ES 模块是 JavaScript 的未来,但有数百万个 npm 库是用 CommonJS 编写的,Deno 为它们提供了完全支持。Deno 将自动确定包是否正在使用 CommonJS,并在导入时使其无缝工作
import react from "npm:react";
console.log(react);
$ deno run -E main.js
18.3.1
npm:react
是一个 CommonJS 包。Deno 允许您像导入 ES 模块一样导入它。
Deno 强烈建议在您的代码中使用 ES 模块,但提供具有以下限制的 CommonJS 支持
当使用 CommonJS 模块时,Deno 的权限系统仍然有效。 可能需要至少提供 --allow-read
权限,因为 Deno 将探测文件系统以查找 package.json
文件和 node_modules
目录,以正确解析 CommonJS 模块。
使用 .cjs 扩展名 跳转到标题
如果文件扩展名是 .cjs
,Deno 会将此模块视为 CommonJS。
const express = require("express");
Deno 不会查找 package.json
文件和 type
选项来确定文件是 CommonJS 还是 ESM。
当使用 CommonJS 时,Deno 期望依赖项将手动安装,并且 node_modules
目录将存在。最好在您的 deno.json
中设置 "nodeModulesDir": "auto"
以确保这一点。
$ cat deno.json
{
"nodeModulesDir": "auto"
}
$ deno install npm:express
Add npm:[email protected]
$ deno run -R -E main.cjs
[Function: createApplication] {
application: {
init: [Function: init],
defaultConfiguration: [Function: defaultConfiguration],
...
}
}
-R
和 -E
标志用于允许读取文件和环境变量的权限。
package.json type 选项 跳转到标题
如果文件旁边或项目目录树中有 package.json
文件,并且其中包含 "type": "commonjs"
选项,则 Deno 将尝试将 .js
、.jsx
、.ts
和 .tsx
文件作为 CommonJS 加载。
{
"type": "commonjs"
}
const express = require("express");
像 Next.js 的 bundler 和其他工具会自动生成这样的 package.json
文件。
如果您有一个使用 CommonJS 模块的现有项目,您可以通过在 package.json
文件中添加 "type": "commonjs"
选项,使其同时与 Node.js 和 Deno 一起工作。
始终检测文件是否可能是 CommonJS 跳转到标题
通过在 Deno >= 2.1.2 中使用 --unstable-detect-cjs
运行,可以告诉 Deno 分析模块是否可能是 CommonJS。除非存在带有 { "type": "module" }
的 package.json 文件,否则这将生效。
在文件系统上查找 package.json 文件并分析模块以检测其是否为 CommonJS 比不这样做花费的时间更长。出于这个原因,并且为了不鼓励使用 CommonJS,Deno 默认情况下不执行此行为。
手动创建 require() 跳转到标题
另一种选择是手动创建 require()
函数的实例
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const express = require("express");
在这种情况下,与运行 .cjs
文件时适用相同的要求 - 依赖项需要手动安装并提供适当的权限标志。
require(ESM) 跳转到标题
Deno 的 require()
实现支持 require ES 模块。
这与 Node.js 中的工作方式相同,在 Node.js 中,您只能 require()
模块图中没有顶层 await 的 ES 模块 - 或者换句话说,您只能 require()
“同步”的 ES 模块。
export function greet(name) {
return `Hello ${name}`;
}
import { greet } from "./greet.js";
export { greet };
const esm = require("./esm");
console.log(esm);
console.log(esm.greet("Deno"));
$ deno run -R main.cjs
[Module: null prototype] { greet: [Function: greet] }
Hello Deno
导入 CommonJS 模块 跳转到标题
您还可以在 ES 模块中导入 CommonJS 文件。
module.exports = {
hello: "world",
};
import greet from "./greet.js";
console.log(greet);
$ deno run main.js
{
"hello": "world"
}
提示和建议
当您使用 CommonJS 模块时,Deno 将提供有用的提示和建议来指导您编写可工作的代码。
例如,如果您尝试运行没有 .cjs
扩展名或没有带有 { "type": "commonjs" }
的 package.json
的 CommonJS 模块,您可能会看到此提示
module.exports = {
hello: "world",
};
$ deno run main.js
error: Uncaught (in promise) ReferenceError: module is not defined
module.exports = {
^
at file:///main.js:1:1
info: Deno supports CommonJS modules in .cjs files, or when the closest
package.json has a "type": "commonjs" option.
hint: Rewrite this module to ESM,
or change the file extension to .cjs,
or add package.json next to the file with "type": "commonjs" option,
or pass --unstable-detect-cjs flag to detect CommonJS when loading.
docs: https://docs.deno.org.cn/go/commonjs
导入类型 跳转到标题
许多 npm 包都附带类型,您可以导入这些类型并直接使用它们
import chalk from "npm:chalk@5";
有些包不附带类型,但您可以使用 @ts-types
指令指定它们的类型。例如,使用 @types
包
// @ts-types="npm:@types/express@^4.17"
import express from "npm:express@^4.17";
模块解析
官方 TypeScript 编译器 tsc
支持不同的 moduleResolution 设置。Deno 仅支持现代 node16
解析。不幸的是,许多 npm 包未能正确提供 node16 模块解析下的类型,这可能导致 deno check
报告类型错误,而 tsc
不会报告。
如果来自 npm:
导入的默认导出似乎具有错误的类型(正确的类型似乎在 .default
属性下可用),则很可能是该包为来自 ESM 的导入提供了错误的 node16 模块解析下的类型。您可以通过检查错误是否也发生在 tsc --module node16
和 package.json
中的 "type": "module"
中,或者通过查阅 Are the types wrong? 网站(特别是“node16 from ESM”行)来验证这一点。
如果您想使用不支持 TypeScript 的 node16 模块解析的包,您可以
- 在包的问题跟踪器上打开关于此问题的 issue。(也许可以贡献一个修复程序 😃 (虽然不幸的是,由于默认导出需要不同的语法,因此缺少支持 ESM 和 CJS 的包的工具。另请参阅 microsoft/TypeScript#54593))
- 使用 CDN,它为 Deno 支持重建包,而不是
npm:
标识符。 - 使用
// @ts-expect-error
或// @ts-ignore
忽略代码库中出现的类型错误。
包含 Node 类型 跳转到标题
Node 附带许多内置类型,例如 Buffer
,npm 包的类型中可能会引用这些类型。要加载这些类型,您必须将类型引用指令添加到 @types/node
包中
/// <reference types="npm:@types/node" />
请注意,在大多数情况下,不指定版本是可以的,因为 Deno 会尝试使其与内部 Node 代码保持同步,但如果需要,您可以随时覆盖使用的版本。
可执行的 npm 脚本 跳转到标题
带有 bin
条目的 npm 包可以从命令行执行,而无需使用以下格式的说明符进行 npm install
npm:[@][/]
例如
$ deno run --allow-read npm:[email protected] "Hello there!"
______________
< Hello there! >
--------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
$ deno run --allow-read npm:[email protected]/cowthink "What to eat?"
______________
( What to eat? )
--------------
o ^__^
o (oo)\_______
(__)\ )\/\
||----w |
|| ||
node_modules 跳转到标题
当您运行 npm install
时,npm 会在您的项目中创建一个 node_modules
目录,其中包含 package.json
文件中指定的依赖项。
Deno 使用 npm 说明符将 npm 包解析为中央全局 npm 缓存,而不是在您的项目中使用 node_modules
文件夹。这是理想的,因为它使用的空间更少,并保持您的项目目录干净。
但是,在某些情况下,您可能需要在 Deno 项目中使用本地 node_modules
目录,即使您没有 package.json
(例如,当使用 Next.js 或 Svelte 等框架或依赖于使用 Node-API 的 npm 包时)。
默认 Deno 依赖行为 跳转到标题
默认情况下,当您使用 deno run
命令时,Deno 不会创建 node_modules
目录,依赖项将安装到全局缓存中。这是新 Deno 项目的推荐设置。
自动创建 node_modules 跳转到标题
如果您的项目需要 node_modules
目录,您可以使用 --node-modules-dir
标志或配置文件中的 nodeModulesDir: auto
选项来告诉 Deno 在当前工作目录中创建 node_modules
目录
deno run --node-modules-dir=auto main.ts
或使用配置文件
{
"nodeModulesDir": "auto"
}
自动模式自动将依赖项安装到全局缓存中,并在项目根目录中创建本地 node_modules 目录。这对于具有依赖于 node_modules 目录的 npm 依赖项的项目(主要是使用 bundler 的项目或具有 postinstall 脚本的 npm 依赖项的项目)推荐使用。
手动创建 node_modules 跳转到标题
如果您的项目有 package.json
文件,您可以使用手动模式,这需要一个安装步骤来创建您的 node_modules
目录
deno install
deno run --node-modules-dir=manual main.ts
或使用配置文件
{ "nodeModulesDir": "manual" }
然后,您将运行 deno install/npm install/pnpm install
或任何其他包管理器来创建 node_modules
目录。
手动模式是使用 package.json
的项目的默认模式。您可能会从 Node.js 项目中识别出此工作流程。建议将它用于使用 Next.js、Remix、Svelte、Qwik 等框架或 Vite、Parcel 或 Rollup 等工具的项目。
我们建议您使用默认的 none
模式,如果遇到关于 node_modules
目录中缺少包的错误,则回退到 auto
或 manual
模式。
Deno 1.X 中的 node_modules 跳转到标题
使用 --node-modules-dir
标志。
例如,给定 main.ts
import chalk from "npm:chalk@5";
console.log(chalk.green("Hello"));
deno run --node-modules-dir main.ts
运行上述命令,使用 --node-modules-dir
标志,将在当前目录中创建一个 node_modules
文件夹,其文件夹结构与 npm 类似。
Node.js 全局对象 跳转到标题
在 Node.js 中,有许多 全局对象在所有程序的范围内可用,这些对象特定于 Node.js,例如 process
对象。
以下是一些您可能在野外遇到的全局对象以及如何在 Deno 中使用它们
process
- Deno 提供了process
全局对象,这是迄今为止在流行的 npm 包中最常用的全局对象。它对所有代码都可用。但是,Deno 将通过提供代码检查警告和快速修复来引导您从node:process
模块显式导入它
console.log(process.versions.deno);
$ deno run process.js
2.0.0
$ deno lint process.js
error[no-process-global]: NodeJS process global is discouraged in Deno
--> /process.js:1:13
|
1 | console.log(process.versions.deno);
| ^^^^^^^
= hint: Add `import process from "node:process";`
docs: https://docs.deno.org.cn/lint/rules/no-process-global
Found 1 problem (1 fixable via --fix)
Checked 1 file
-
require()
- 请参阅 CommonJS 支持 -
Buffer
- 要使用Buffer
API,需要从node:buffer
模块显式导入它
import { Buffer } from "node:buffer";
const buf = new Buffer(5, "0");
首选使用 Uint8Array
或其他 TypedArray
子类。
-
__filename
- 请改用import.meta.filename
。 -
__dirname
- 请改用import.meta.dirname
。
Node-API 插件 跳转到标题
Deno 支持 Node-API 插件,这些插件被流行的 npm 包(如 esbuild
、npm:sqlite3
或 npm:duckdb
)使用。
您可以期望所有使用公共和文档化的 Node-API 的包都能工作。
大多数使用 Node-API 插件的包都依赖于 npm “生命周期脚本”,例如 postinstall
。
虽然 Deno 支持它们,但出于安全考虑,默认情况下不运行它们。请在 deno install
文档中阅读更多内容。
从 Deno 2.0 开始,只有当存在 node_modules/
目录时,才支持使用 Node-API 插件的 npm 包。在您的 deno.json
文件中添加 "nodeModulesDir": "auto"
或 "nodeModulesDir": "manual"
设置,或使用 --node-modules-dir=auto|manual
标志运行,以确保这些包正常工作。如果配置错误,Deno 将提供有关如何解决情况的提示。
从 Node 迁移到 Deno 跳转到标题
使用 Deno 运行您的 Node.js 项目是一个简单的过程。在大多数情况下,如果您的项目是使用 ES 模块编写的,您可以期望几乎不需要进行任何更改。
需要注意的主要点包括
- 导入 Node.js 内置模块需要
node:
说明符
// ❌
import * as fs from "fs";
import * as http from "http";
// ✅
import * as fs from "node:fs";
import * as http from "node:http";
建议无论如何都要在您现有的项目中更改这些导入说明符。这也是在 Node.js 中导入它们的推荐方法。
- 某些 Node.js 中可用的全局对象需要显式导入,例如
Buffer
import { Buffer } from "node:buffer";
require()
仅在扩展名为.cjs
的文件中可用,在其他文件中,需要手动创建require()
的实例。npm 依赖项可以使用require()
,而与文件扩展名无关。
运行脚本 跳转到标题
Deno 原生支持使用 deno task
子命令运行 npm 脚本(如果您从 Node.js 迁移,这类似于 npm run script
命令)。考虑以下 Node.js 项目,其 package.json
中有一个名为 start
的脚本
{
"name": "my-project",
"scripts": {
"start": "eslint"
}
}
您可以使用 Deno 通过运行以下命令来执行此脚本
deno task start
可选的改进 跳转到标题
Deno 的核心优势之一是统一的工具链,它开箱即用地支持 TypeScript,以及代码检查器、格式化程序和测试运行器等工具。切换到 Deno 可以简化您的工具链,并减少项目中移动组件的数量。
配置
Deno 有自己的配置文件 deno.json
或 deno.jsonc
,可用于配置您的项目
您可以使用它通过 imports
选项定义依赖项 - 您可以从 package.json
逐个迁移您的依赖项,或者选择根本不在配置文件中定义它们,而是在您的代码中内联使用 npm:
说明符。
除了指定依赖项之外,您还可以使用 deno.json
来定义任务、代码检查和格式化选项、路径映射和其他运行时配置。
代码检查
Deno 附带一个内置的代码检查器,该检查器在编写时考虑了性能。它类似于 ESLint,但规则数量有限。如果您不依赖 ESLint 插件,您可以从 package.json
的 devDependencies
部分删除 eslint
依赖项,而改用 deno lint
。
Deno 可以在几毫秒内检查大型项目。您可以通过运行以下命令在您的项目上试用它
deno lint
这将检查您项目中的所有文件。当代码检查器检测到问题时,它将在您的编辑器和终端输出中显示该行。一个可能看起来像这样的例子
error[no-constant-condition]: Use of a constant expressions as conditions is not allowed.
--> /my-project/bar.ts:1:5
|
1 | if (true) {
| ^^^^
= hint: Remove the constant expression
docs: https://docs.deno.org.cn/lint/rules/no-constant-condition
Found 1 problem
Checked 4 files
许多代码检查问题可以通过传递 --fix
标志自动修复
deno lint --fix
所有受支持的代码检查规则的完整列表可以在 https://docs.deno.org.cn/lint/ 上找到。要了解有关如何配置代码检查器的更多信息,请查看 deno lint
子命令。
格式化
Deno 附带一个 内置的格式化程序,它可以选择根据 Deno 风格指南格式化您的代码。您可以改用 Deno 的内置零配置代码格式化程序 deno fmt
,而不是将 prettier
添加到您的 devDependencies
中。
您可以通过运行以下命令在您的项目上运行格式化程序
deno fmt
如果在 CI 中使用 deno fmt
,您可以传递 --check
参数,以便在格式化程序检测到格式不正确的代码时退出并显示错误。
deno fmt --check
可以在您的 deno.json
文件中配置格式化规则。要了解有关如何配置格式化程序的更多信息,请查看 deno fmt
子命令。
测试
Deno 鼓励为您的代码编写测试,并提供内置的测试运行器,使编写和运行测试变得容易。测试运行器与 Deno 紧密集成,因此您不必进行任何额外的配置即可使 TypeScript 或其他功能正常工作。
Deno.test("my test", () => {
// Your test code here
});
deno test
当传递 --watch
标志时,当任何导入的模块发生更改时,测试运行器将自动重新加载。
要了解有关测试运行器以及如何配置它的更多信息,请查看 deno test
子命令文档。
私有仓库 跳转到标题
Deno 支持私有仓库,允许您托管和共享自己的模块。这对于想要保持代码私有的组织或想要与特定人群共享代码的个人非常有用。
什么是私有仓库? 跳转到标题
大型组织通常托管自己的私有 npm 仓库,以安全地管理内部包。这些私有仓库充当组织可以发布和存储其专有或自定义包的仓库。与公共 npm 仓库不同,私有仓库仅对组织内授权用户可访问。
如何在 Deno 中使用私有仓库 跳转到标题
首先,配置您的 .npmrc
文件以指向您的私有仓库。.npmrc
文件必须位于项目根目录或 $HOME
目录中。将以下内容添加到您的 .npmrc
文件中
@mycompany:registry=http://mycompany.com:8111/
//mycompany.com:8111/:_auth=secretToken
将 http://mycompany.com:8111/
替换为您的私有仓库的实际 URL,并将 secretToken
替换为您的身份验证令牌。
然后更新您的 deno.json
或 package.json
以指定您的私有包的导入路径。例如
{
"imports": {
"@mycompany/package": "npm:@mycompany/[email protected]"
}
}
或者,如果您使用的是 package.json
{
"dependencies": {
"@mycompany/package": "1.0.0"
}
}
现在您可以在您的 Deno 代码中导入您的私有包
import { hello } from "@mycompany/package";
console.log(hello());
并使用 deno run
命令运行它
deno run main.ts
Node 到 Deno 速查表 跳转到标题
Node.js | Deno |
---|---|
node file.js |
deno file.js |
ts-node file.ts |
deno file.ts |
nodemon |
deno run --watch |
node -e |
deno eval |
npm i / npm install |
deno install |
npm install -g |
deno install -g |
npm run |
deno task |
eslint |
deno lint |
prettier |
deno fmt |
package.json |
deno.json 或 package.json |
tsc |
deno check ¹ |
typedoc |
deno doc |
jest / ava / mocha / tap / etc |
deno test |
nexe / pkg |
deno compile |
npm explain |
deno info |
nvm / n / fnm |
deno upgrade |
tsserver |
deno lsp |
nyc / c8 / istanbul |
deno coverage |
基准测试 | deno bench |
¹ 类型检查自动发生,TypeScript 编译器内置于 deno
二进制文件中。