deno.com
本页内容

Node 和 npm 兼容性

  • Deno 与 Node 兼容。大多数 Node 项目几乎无需修改即可在 Deno 中运行!
  • Deno 支持 npm 包。只需在导入中使用 npm: 说明符,Deno 就会处理其余部分。

例如,以下是如何在 Deno 项目中从 npm 导入 Hono 的方法

import { Hono } from "npm:hono";

这些就是您开始所需了解的全部内容!然而,这两种运行时之间存在一些关键差异,您可以在将 Node.js 项目迁移到 Deno 时利用这些差异,使您的代码更简单、更小巧。

使用 Node 的内置模块 Jump to heading

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

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

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

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

为了更轻松地更新现有代码,Deno 将为不使用 node: 前缀的导入提供有用的提示

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 在您的编辑器中也提供了相同的提示和额外的快速修复。

探索内置的 Node API

使用 npm 包 Jump to heading

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:[@][/]

这也允许使用 npx 命令中可能熟悉的功能。

# npx allows remote execution of a package from npm or a URL
$ npx create-next-app@latest

# deno run allows remote execution of a package from various locations,
# and can scoped to npm via the `npm:` specifier.
$ deno run -A npm:create-next-app@latest

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

CommonJS 支持 Jump to heading

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 的权限系统仍然有效。Deno 会探测文件系统中的 package.json 文件和 node_modules 目录以正确解析 CommonJS 模块,因此可能需要至少提供 --allow-read 权限。

使用 .cjs 扩展名 Jump to heading

如果文件扩展名为 .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:express@5.0.0

$ deno run -R -E main.cjs
[Function: createApplication] {
  application: {
    init: [Function: init],
    defaultConfiguration: [Function: defaultConfiguration],
    ...
  }
}

-R-E 标志用于允许读取文件和环境变量的权限。

package.json 类型选项 Jump to heading

如果文件中存在带有 "type": "commonjs" 选项的 package.json 文件,或者在包含 package.json 文件的项目中向上追溯目录树时,Deno 会尝试将 .js.jsx.ts.tsx 文件作为 CommonJS 加载。

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

Next.js 的打包工具等工具会自动生成这样的 package.json 文件。

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

始终检测文件是否可能是 CommonJS Jump to heading

在 Deno >= 2.1.2 中,通过使用 --unstable-detect-cjs 运行,可以告诉 Deno 将模块分析为可能是 CommonJS。这将生效,除非存在一个带有 { "type": "module" }package.json 文件。

在文件系统中查找 package.json 文件并分析模块以检测其是否为 CommonJS 比不这样做花费更长时间。因此,为了不鼓励使用 CommonJS,Deno 默认不执行此行为。

手动创建 require() Jump to heading

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

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

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

require(ESM) Jump to heading

Deno 的 require() 实现支持 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

导入 CommonJS 模块 Jump to heading

您也可以在 ES 模块中导入 CommonJS 文件。

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

提示和建议

Deno 将提供有用的提示和建议,以指导您在使用 CommonJS 模块时编写可工作的代码。

例如,如果您尝试运行一个没有 .cjs 扩展名或没有带有 { "type": "commonjs" }package.json 的 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 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

条件导出 Jump to heading

包导出可以根据解析模式设置条件。从 Deno ESM 模块导入时满足的条件如下

["deno", "node", "import", "default"]

这意味着包导出中第一个与这些字符串中的任何一个键相等的条件将被匹配。您可以使用 --unstable-node-conditions CLI 标志扩展此列表

deno run --unstable-node-conditions development,react-server main.ts
["development", "react-server", "deno", "node", "import", "default"]

导入类型 Jump to heading

许多 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 属性下可用),那么很可能是该包在 node16 模块解析下为从 ESM 导入提供错误类型。您可以通过检查在使用 tsc --module node16package.json 中的 "type": "module" 时是否也发生此错误,或查阅“类型错误吗?”网站(特别是“node16 from ESM”行)来验证这一点。

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

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

包含 Node 类型 Jump to heading

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

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

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

可执行的 npm 脚本 Jump to heading

带有 bin 入口的 npm 包可以通过以下格式的说明符从命令行执行,无需 npm install

npm:[@][/]

例如

$ deno run --allow-read npm:cowsay@1.5.0 "Hello there!"
 ______________
< Hello there! >
 --------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

$ deno run --allow-read npm:cowsay@1.5.0/cowthink "What to eat?"
 ______________
( What to eat? )
 --------------
        o   ^__^
         o  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

node_modules Jump to heading

当您运行 npm install 时,npm 会在您的项目中创建一个 node_modules 目录,其中包含 package.json 文件中指定的依赖项。

Deno 使用npm 说明符将 npm 包解析到中央全局 npm 缓存,而不是在您的项目中使用 node_modules 文件夹。这非常理想,因为它占用空间更少并保持项目目录整洁。

然而,在某些情况下,即使您没有 package.json,您的 Deno 项目中也可能需要一个本地 node_modules 目录(例如,使用 Next.js 或 Svelte 等框架时,或依赖使用 Node-API 的 npm 包时)。

Deno 默认依赖项行为 Jump to heading

默认情况下,当您使用 deno run 命令时,Deno 不会创建 node_modules 目录,依赖项将安装到全局缓存中。这是新 Deno 项目的推荐设置。

自动创建 node_modules Jump to heading

如果您的项目中需要一个 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 Jump to heading

如果您的项目有 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 Jump to heading

使用 --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 全局对象 Jump to heading

在 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-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 模块显式导入它

buffer.js
import { Buffer } from "node:buffer";

const buf = new Buffer(5, "0");

对于需要 Node.js 特定类型(如 BufferEncoding)的 TypeScript 用户,这些类型在使用 @types/node 时通过 NodeJS 命名空间可用

buffer-types.ts
/// <reference types="npm:@types/node" />

// Now you can use NodeJS namespace types
function writeToBuffer(data: string, encoding: NodeJS.BufferEncoding): Buffer {
  return Buffer.from(data, encoding);
}

优先使用Uint8Array 或其他 TypedArray 子类。

  • __filename - 改用 import.meta.filename

  • __dirname - 改用 import.meta.dirname

Node-API 插件 Jump to heading

Deno 支持流行的 npm 包(如 esbuildnpm:sqlite3npm:duckdb)使用的Node-API 插件

您可以预期所有使用公共和文档化的 Node-API 的包都能正常工作。

信息

大多数使用 Node-API 插件的包都依赖于 npm 的“生命周期脚本”,例如 postinstall

虽然 Deno 支持它们,但出于安全考虑,它们默认不运行。在deno install 文档中阅读更多内容。

截至 Deno 2.0,使用 Node-API 插件的 npm 包仅在存在 node_modules/ 目录时才受支持。在您的 deno.json 文件中添加 "nodeModulesDir": "auto""nodeModulesDir": "manual" 设置,或使用 --node-modules-dir=auto|manual 标志运行,以确保这些包正常工作。如果配置错误,Deno 将提供如何解决该情况的提示。

从 Node 迁移到 Deno Jump to heading

使用 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()

运行脚本 Jump to heading

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

可选改进 Jump to heading

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

配置

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

您可以使用它通过 imports 选项定义依赖项——您可以逐一从 package.json 迁移依赖项,或者选择完全不在配置文件中定义它们,直接在代码中使用 npm: 说明符。

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

Linting(代码检查)

Deno 内置了一个以性能为先的 linter。它类似于 ESlint,但规则数量有限。如果您不依赖 ESLint 插件,可以从 package.jsondevDependencies 部分中删除 eslint 依赖项,转而使用 deno lint

Deno 可以在几毫秒内对大型项目进行 lint。您可以通过运行以下命令在您的项目上尝试它

deno lint

这将对项目中的所有文件进行 lint。当 linter 检测到问题时,它会在您的编辑器和终端输出中显示该行。这可能看起来像一个示例

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

许多 linting 问题可以通过传递 --fix 标志自动修复

deno lint --fix

所有支持的 linting 规则的完整列表可以在 https://docs.deno.org.cn/lint/ 上找到。要了解有关如何配置 linter 的更多信息,请查阅 deno lint 子命令

格式化

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

您可以通过运行以下命令在您的项目上运行格式化工具

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 子命令文档。

私有注册表 Jump to heading

注意

勿与私有仓库和模块混淆。

Deno 支持私有注册表,允许您托管和共享自己的模块。这对于希望保持代码私密性的组织或希望与特定人群共享代码的个人来说非常有用。

什么是私有注册表? Jump to heading

大型组织通常会托管自己的私有 npm 注册表,以安全地管理内部包。这些私有注册表充当组织可以发布和存储其专有或自定义包的仓库。与公共 npm 注册表不同,私有注册表仅对组织内部的授权用户可见。

如何在 Deno 中使用私有注册表 Jump to heading

首先,配置您的.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/package@1.0.0"
  }
}

或者如果您正在使用 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 到 Deno 备忘单 Jump to heading

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 二进制文件中。

您找到所需内容了吗?

隐私政策