跳转到主要内容
在本页上

Node 和 npm 支持

现代 Node.js 项目几乎无需修改即可在 Deno 中运行。 然而,这两个运行时之间存在一些关键差异,您可以利用这些差异来简化代码并减小代码大小,从而将 Node.js 项目迁移到 Deno。

探索内置 Node API

使用 Node 的内置模块 跳转到标题

Deno 提供了一个兼容层,允许在 Deno 程序中使用 Node.js 内置 API。但是,为了使用它们,您需要在任何使用它们的导入语句中添加 node: 说明符。

import * as os from "node:os";
console.log(os.cpus());

使用 deno run main.mjs 运行它 - 您会注意到您得到的输出与在 Node.js 中运行程序的输出相同。

将应用程序中的任何导入更新为使用 node: 说明符,应该可以让任何使用 Node.js 内置模块的代码像在 Node.js 中一样运行。

为了更容易地更新现有代码,对于不使用 node: 前缀的导入,Deno 会提供有用的提示。

main.mjs
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 包。例如:

main.js
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:<package-name>[@<version-requirement>][/<sub-path>]

有关流行库的示例,请参阅教程部分

CommonJS 支持 跳转到标题

CommonJS 是一个早于 ES 模块 的模块系统。虽然我们坚信 ES 模块是 JavaScript 的未来,但有数百万个 npm 库是用 CommonJS 编写的,Deno 为它们提供了全面支持。Deno 会自动确定一个包是否使用 CommonJS,并在导入时使其无缝工作。

main.js
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。

main.cjs
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 选项 跳转到标题

如果在文件旁边或目录树上有一个包含 "type": "commonjs" 选项的 package.json 文件,并且在 CLI 上传递了 --unstable-detect-cjs 标志,Deno 将把文件视为 CommonJS。

package.json
{
  "type": "commonjs"
}
main.js
const express = require("express");

像 Next.js 的打包器和其他工具会自动生成这样的 package.json 文件。

如果您有一个使用 CommonJS 模块的现有项目,您可以通过向 package.json 文件添加 "type": "commonjs" 选项,使其与 Node.js 和 Deno 都兼容。

手动创建 require() 跳转到标题

另一种选择是手动创建 require() 函数的实例。

main.js
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const express = require("express");

在这种情况下,与运行 .cjs 文件时适用相同的规则 - 需要手动安装依赖项并提供适当的权限标志。

require(ESM) 跳转到标题

Deno 的 require() 实现支持加载 ES 模块。

这与在 Node.js 中的工作方式相同,您只能 require() 模块图中没有顶层 await 的 ES 模块 - 换句话说,您只能 require() “同步”的 ES 模块。

greet.js
export function greet(name) {
  return `Hello ${name}`;
}
esm.js
import { greet } from "./greet.js";

export { greet };
main.cjs
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

导入 index.cjs 跳转到标题

您也可以在 ES 模块中导入 CommonJS 文件,前提是这些文件使用 .cjs 扩展名。

Deno 不会查找 package.json 文件和 type 选项来确定文件是 CommonJS 还是 ESM。

greet.cjs
module.exports = {
  hello: "world",
};
main.js
import greet from "./greet.js";
console.log(greet);
$ deno run main.js
{
  "hello": "world"
}

请注意,在此示例中未指定任何权限标志 - 从 ES 模块导入 CJS 时,Deno 可以静态分析并找到相关的模块,而无需在运行时探测文件系统。

提示和建议

在使用 CommonJS 模块时,Deno 会提供有用的提示和建议来指导您编写有效的代码。

例如,如果您尝试运行没有 .cjs 扩展名的 CommonJS 模块,您可能会看到以下内容

main.js
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 does not support CommonJS modules without `.cjs` extension.
    hint: Rewrite this module to ESM or change the file extension to `.cjs`.

导入类型 跳转到标题

许多 npm 包都附带类型,您可以直接导入这些类型并在类型中使用它们

import chalk from "npm:chalk@5";

有些包不附带类型,但您可以使用 @deno-types 指令指定它们的类型。例如,使用 @types

// @deno-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 属性下可用),则很可能是该包在 node16 模块解析下为来自 ESM 的导入提供了错误的类型。您可以通过检查错误是否也发生在 tsc --module node16package.json 中的 "type": "module" 或查阅 Are the types wrong? 网站(特别是“node16 from ESM”行)来验证这一点。

如果您想使用不支持 TypeScript 的 node16 模块解析的包,您可以

  1. 在该包的问题跟踪器上打开一个关于此问题的问题。(也许还可以贡献一个修复 😃(尽管,不幸的是,缺少用于支持 ESM 和 CJS 的包的工具,因为默认导出需要不同的语法。另请参见 microsoft/TypeScript#54593
  2. 使用 CDN(为 Deno 支持重建包)而不是 npm: 标识符。
  3. 使用 // @ts-expect-error// @ts-ignore 忽略代码库中出现的类型错误。

包含 Node 类型 跳转到标题

Node 附带了许多内置类型,例如 Buffer,这些类型可能在 npm 包的类型中被引用。要加载这些类型,您必须向 @types/node 包添加类型引用指令

/// <reference types="npm:@types/node" />

请注意,在大多数情况下不指定版本是可以的,因为 Deno 会尝试使其与其内部 Node 代码保持同步,但如果需要,您始终可以覆盖使用的版本。

可执行 npm 脚本 跳转到标题

可以使用以下格式的说明符从命令行执行带有 bin 条目的 npm 包,而无需 npm install

npm:<package-name>[@<version-requirement>][/<binary-name>]

例如

$ 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

或者使用配置文件

deno.json
{
  "nodeModulesDir": "auto"
}

自动模式会自动将依赖项安装到全局缓存中,并在项目根目录中创建一个本地 node_modules 目录。这推荐用于具有依赖于 node_modules 目录的 npm 依赖项的项目 - 主要是使用打包器或具有 postinstall 脚本的 npm 依赖项的项目。

手动创建 node_modules 跳转到标题

如果你的项目有一个 package.json 文件,你可以使用手动模式,这需要一个安装步骤来创建你的 node_modules 目录。

deno install
deno run --node-modules-dir=manual main.ts

或者使用配置文件

deno.json
{ "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 目录中缺少包的错误时回退到 automanual 模式。

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 标志运行上述命令,将在当前目录中创建一个与 npm 类似的文件夹结构的 node_modules 文件夹。

Node.js 全局对象 跳转到标题

在 Node.js 中,所有程序的作用域中都有许多全局对象,这些对象是 Node.js 特有的,例如 process 对象。

以下是一些你可能会遇到的全局变量,以及如何在 Deno 中使用它们。

  • process - Deno 提供了 process 全局变量,这是迄今为止流行的 npm 包中最常用的全局变量。所有代码都可以使用它。但是,Deno 会通过提供 lint 警告和快速修复来引导你从 node:process 模块显式导入它。
process.js
console.log(process.versions.deno);
$ deno run process.js
2.0.0
$ deno lint process.js
error[no-process-globals]: 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://lint.deno.land/rules/no-process-globals


Found 1 problem (1 fixable via --fix)
Checked 1 file
  • require() - 参见CommonJS 支持

  • Buffer - 要使用 Buffer API,需要从 node:buffer 模块显式导入。

buffer.js
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 包使用,例如 esbuildnpm:sqlite3npm: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 模块编写的,在大多数情况下,您几乎不需要进行任何更改。

需要注意的要点包括:

  1. 导入 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 中导入它们的推荐方法。

  1. 一些 Node.js 中可用的全局变量 需要显式导入,例如 Buffer
import { Buffer } from "node:buffer";
  1. require() 仅在扩展名为 .cjs 的文件中可用,在其他文件中,需要 手动创建 require() 的实例。npm 依赖项可以使用 require(),而不管文件扩展名如何。

运行脚本 跳转到标题

Deno 原生支持使用 deno task 子命令运行 npm 脚本(如果您是从 Node.js 迁移过来,这类似于 npm run script 命令)。考虑以下 Node.js 项目,其 package.json 中包含一个名为 start 的脚本。

package.json
{
  "name": "my-project",
  "scripts": {
    "start": "eslint"
  }
}

您可以通过运行以下命令使用 Deno 执行此脚本:

deno task start

可选改进 跳转到标题

Deno 的核心优势之一是统一的工具链,它开箱即用地支持 TypeScript,以及诸如 linter、formatter 和测试运行器之类的工具。切换到 Deno 可以简化您的工具链,并减少项目中活动组件的数量。

配置

Deno 有自己的配置文件 deno.jsondeno.jsonc,可用于 配置您的项目

您可以使用它通过 imports 选项 定义依赖项 - 您可以将依赖项从 package.json 中逐个迁移,或者选择根本不在配置文件中定义它们,而是在代码中内联使用 npm: 说明符。

除了指定依赖项之外,您还可以使用 deno.json 来定义任务、代码检查和格式化选项、路径映射和其他运行时配置。

代码检查(Linting)

Deno 内置了一个注重性能的代码检查器。它类似于 ESlint,但规则数量有限。如果您不依赖 ESLint 插件,您可以从 package.jsondevDependencies 部分删除 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://lint.deno.land/rules/no-constant-condition


Found 1 problem
Checked 4 files

许多代码检查问题可以通过传递 --fix 标志来自动修复:

deno lint --fix

所有受支持的代码检查规则的完整列表可以在 https://lint.deno.land/ 上找到。要了解有关如何配置代码检查器的更多信息,请查看 deno lint 子命令

格式化(Formatting)

Deno 内置了一个 格式化工具,可以根据 Deno 样式指南选择性地格式化您的代码。您可以使用 Deno 内置的零配置代码格式化工具 deno fmt,而不是将 prettier 添加到您的 devDependencies 中。

您可以通过运行以下命令来格式化您的项目:

deno fmt

如果在 CI 中使用 deno fmt,您可以传递 --check 参数,以便在格式化工具检测到格式不正确的代码时以错误退出。

deno fmt --check

格式化规则可以在您的 deno.json 文件中配置。要了解有关如何配置格式化工具的更多信息,请查看 deno fmt 子命令

测试

Deno 鼓励为您的代码编写测试,并提供了一个内置的测试运行器,以便于编写和运行测试。测试运行器与 Deno 紧密集成,因此您无需进行任何额外配置即可使 TypeScript 或其他功能正常工作。

my_test.ts
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.jsonpackage.json 文件以指定私有包的导入路径。例如:

deno.json
{
  "imports": {
    "@mycompany/package": "npm:@mycompany/[email protected]"
  }
}

或者,如果您使用的是 package.json 文件:

package.json
{
  "dependencies": {
    "@mycompany/package": "1.0.0"
  }
}

现在,您可以在 Deno 代码中导入您的私有包:

main.ts
import { hello } from "@mycompany/package";

console.log(hello());

并使用 deno run 命令运行它:

deno run main.ts

Node.js 到 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.jsonpackage.json
tsc deno check ¹
typedoc deno doc
jest / ava / mocha / tap / 等 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 二进制文件中。