在本页

从 Node.js 迁移到 Deno

要将现有的 Node.js 程序迁移到 Deno,需要考虑 Node 和 Deno 运行时之间的一些差异。本指南将尝试指出其中的一些差异,并描述如何开始将您的 Node.js 项目迁移到 Deno 上运行。

关于 Node.js 兼容性

Node.js 兼容性是 Deno 中正在进行的项目 - 您可能会遇到 npm 上的一些模块或包无法按预期工作。如果您遇到 Node.js 兼容性问题,请通过 在 GitHub 上打开一个问题 告知我们。

模块导入和导出 跳转到标题

Deno 仅支持 ECMAScript 模块,而不是像 Node 中那样同时支持 ESM 和 CommonJS。如果您的 Node.js 代码使用 require,您应该将其更新为使用 import 语句。如果您的内部代码使用 CommonJS 样式的导出,则也需要更改它们。

考虑以下位于同一目录中的 Node.js 程序中的两个文件

index.js
const addNumbers = require("./add_numbers");
console.log(addNumbers(2, 2));
add_numbers.js
module.exports = function addNumbers(num1, num2) {
  return num1 + num2;
};

在 Node.js 20 及更早版本中,使用上述文件运行 node index.js 可以正常工作。但是,如果您尝试使用 deno run index.js 运行此代码,则它不会在未更改的情况下运行。您需要更改使用模块的代码以及从 add_numbers 模块导出功能的方式。

import 替换 require 跳转到标题

import 替换 require 语句,如下所示

import addNumbers from "./add_numbers.js";

此语句使用 ES6 模块标准,但执行的操作基本相同。此外,请注意,我们在导入模块时包含完整的扩展名,就像您在浏览器中一样。对名为 index.js 的文件也没有特殊处理。

export default 替换 module.exports 跳转到标题

在导出函数的 add_numbers.js 文件中,我们将使用 ES6 模块的默认导出,而不是 CommonJS 提供的 module.exports

add_numbers.js
export default function addNumbers(num1, num2) {
  return num1 + num2;
}

完成这两个更改后,此代码将使用 deno run index.js 成功运行。了解有关 Deno 中的 ES 模块的更多信息

Node.js 内置模块 跳转到标题

在 Node.js 20 及更早版本中,Node.js 标准库中的内置模块可以使用“裸规范”导入。考虑以下具有 .mjs 扩展名的 Node 程序

index.mjs
import * as os from "os";
console.log(os.cpus());

os 模块 内置于 Node.js 运行时,可以使用上述裸规范导入。

.mjs 扩展名在 Deno 中不是必需的

Deno 支持 .mjs 文件扩展名,但不是必需的。因为 Node 默认不支持 ESM,所以它要求您将使用 ESM 的任何文件命名为 .mjs 文件扩展名。

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

例如 - 如果你将上面的代码更新为这样

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

并使用 deno run index.mjs 运行它 - 你会注意到你得到了与在 Node.js 中运行程序相同的输出。将应用程序中的任何导入更新为使用 node: 指定符应该使使用 Node 内置的任何代码像在 Node.js 中一样工作。

Deno 中的运行时权限 跳转到标题

Deno 默认具有 运行时安全性,这意味着你作为开发者必须选择让你的代码访问文件系统、网络、系统环境等等。这样做可以防止供应链攻击以及代码中的其他潜在漏洞。相比之下,Node.js 没有运行时安全性的概念,所有代码都以与运行代码的用户相同的权限级别执行。

仅使用必要的标志运行你的代码 跳转到标题

当你第一次运行移植到 Deno 的 Node.js 项目时,运行时可能会提示你访问执行代码所需的权限。考虑以下简单的 express 服务器

import express from "npm:express@4";

const app = express();

app.get("/", function (_req, res) {
  res.send("hello");
});

app.listen(3000, () => {
  console.log("Express listening on :3000");
});

如果你使用 deno run server.js 运行它,它会提示你提供执行代码及其依赖项所需的许多权限。这些提示可以向你展示需要传递哪些运行时权限标志来授予你所需的访问权限。使用提供的必要权限运行上面的代码将如下所示

deno run --allow-net --allow-read --allow-env server.js

使用 deno task 重用运行时标志配置 跳转到标题

配置一组运行时标志的常见模式是设置使用 deno task 运行的脚本。以下 deno.json 文件有一个名为 dev 的任务,它将使用所有必要的标志运行上面的 express 服务器。

{
  "tasks": {
    "dev": "deno run --allow-net --allow-read --allow-env server.js"
  }
}

然后你可以使用 deno task dev 运行该任务。

使用所有权限启用运行 跳转到标题

在生产环境或敏感环境中,不建议这样做,但可以启用所有运行时权限来运行你的程序。这将是 Node 的默认行为,它缺乏权限系统。要使用所有权限启用运行程序,你可以使用

deno run -A server.js

package.json 运行脚本 跳转到标题

许多 Node.js 项目使用 npm 脚本 来驱动本地开发。在 Deno 中,你可以继续使用现有的 npm 脚本,同时随着时间的推移迁移到 deno task

在 Deno 中运行 npm 脚本 跳转到标题

Deno 支持 现有 package.json 文件 的方法之一是使用 deno task 执行其中配置的任何脚本。考虑以下具有 package.json 和其中配置的脚本的 Node.js 项目。

bin/my_task.mjs
console.log("running my task...");
package.json
{
  "name": "test",
  "scripts": {
    "start": "node bin/my_task.mjs"
  }
}

你可以通过运行 deno task start 来使用 Deno 执行此脚本。

使用和管理 npm 依赖项 跳转到标题

Deno 支持 通过 package.json 文件管理 npm 依赖项。请注意,与在命令行中使用 npm 不同,你可以简单地使用 deno run 运行你的项目,并且你的脚本第一次运行时,Deno 将缓存应用程序所需的所有依赖项。

展望未来,我们建议你通过 deno.json 管理依赖项,它也支持其他类型的导入。

导入 npm 包时,你将使用 npm: 指定符,就像你对任何内置 Node 模块使用 node: 指定符一样。

import express from "npm:express@4";

const app = express();

app.get("/", function (_req, res) {
  res.send("hello");
});

app.listen(3000, () => {
  console.log("Express listening on :3000");
});

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

在 Node.js 中,有一些 全局对象 在所有程序的范围内可用,例如 process 对象或 __dirname__filename

Deno 除了 Deno 命名空间 外,不会向全局范围添加其他对象和变量。任何不存在于 Web 标准浏览器 API 中的 API 都可以在此命名空间中找到。

每个 Node.js 内置全局对象的等效 Deno 表达式会有所不同,但应该可以使用 Deno 中略微不同的方法完成在 Node 中可以完成的所有操作。例如,Node.js 中的 process.cwd() 函数在 Deno 中存在为 Deno.cwd()

__filename__dirname 跳转到标题

CommonJS 模块中两个非常常见的 Node.js 全局变量是 __filename__dirname。这些全局变量在 Deno 或 Node.js 中的 ECMAScript 模块中 不受支持,但仍然可以通过 Deno 运行时 API 获取相同的信息。

const __filename = import.meta.filename;
const __dirname = import.meta.dirname;