忘掉 NodeJS!用 Deno 构建原生 TypeScript 应用 🦖 安装 Deno 功能 Deno 应用实战 结论

2025-05-24

忘掉 NodeJS!用 Deno 构建原生 TypeScript 应用

安装 Deno

特征

Deno 应用程序的实际运行

结论

最初发表于deepu.tech

你听说过Deno吗?如果没有,你应该了解一下。Deno 是一个现代的 JavaScript/TypeScript 运行时和脚本环境。根据 NodeJS 创始人 Ryan Dahl 的说法,Deno 正是 NodeJS 应有的样子。Deno 也是由 Ryan Dahl 于 2018 年创建的,它基于V8RustTokio构建,专注于安全性、性能和易用性。Deno 从 Go 和 Rust 中汲取了许多灵感。

在本文中,我们将了解 Deno 的功能以及它与 NodeJS 的比较。您也可以观看我为乌克兰 Devoxx 举办的讲座。

在我们继续之前,让我们安装 Deno。

安装 Deno

有多种方法可以安装 Deno。如果您使用的是 Mac 或 Linux,则可以通过Homebrew安装。在 Windows 上,您可以使用Chocolatey

# Mac/Linux
brew install deno

# windows
choco install deno
Enter fullscreen mode Exit fullscreen mode

其他安装方法请参考官方文档

请注意,Deno 仍处于积极开发阶段,因此可能尚未准备好投入生产使用

现在我们已经安装了 Deno,让我们看看它的功能。

特征

  • 开箱即用,无需任何转译设置
  • 可以执行远程脚本
  • 默认安全。除非明确启用,否则默认不访问文件、网络或环境
  • 提供精选的标准模块
  • 仅支持 ES 模块。模块全局缓存,且不可变
  • 内置工具(格式、lint、测试、捆绑等)
  • Deno 应用程序可以兼容浏览器
  • 基于 Promise 的 API(async/await支持)且无回调地狱
  • 顶级await支持
  • 使用 Web Worker 的子流程
  • WebAssembly 支持
  • 轻量级多平台可执行文件(~10MB)

Deno 不使用 NPM 进行依赖管理,因此不存在node_modules麻烦,在我看来这是一个巨大的卖点

闭嘴,拿我的钱

TypeScript 支持

Deno 原生支持 TypeScript 和 JavaScript。您可以直接用 TypeScript 编写 Deno 应用程序,Deno 无需您进行任何转译步骤即可执行它们。让我们来试试吧!

function hello(person: string) {
  return "Hello, " + person;
}

console.log(hello("John"));
Enter fullscreen mode Exit fullscreen mode

将其保存到hello.ts文件并执行deno hello.ts。你将看到 Deno 编译并执行该文件。

Deno 支持最新版本的 TypeScript,并保持支持最新。

远程脚本执行

使用 Deno,你可以轻松地运行本地或远程脚本。只需指向脚本的文件或 HTTP URL,Deno 就会下载并执行它。

deno https://deno.land/std/examples/welcome.ts
Enter fullscreen mode Exit fullscreen mode

这意味着您只需指向原始的 GitHub URL 即可执行脚本,无需进行任何安装。Deno 的默认安全模型也适用于远程脚本。

默认安全

默认情况下,使用 Deno 运行的脚本无法访问文件系统、网络、子进程或环境。这会为脚本创建一个沙盒,用户必须明确提供权限。这将控制权交到了最终用户手中。

  • 细粒度权限
  • 权限可以被撤销
  • 权限白名单支持

可以在执行期间通过命令行标志提供权限,或者在使用子进程时以编程方式提供权限。

可用的标志有:

--allow-all | -A
--allow-env
--allow-hrtime
--allow-read=<whitelist>
--allow-write=<whitelist>
--allow-net=<whitelist>
--allow-plugin
--allow-run
Enter fullscreen mode Exit fullscreen mode

请注意,标志必须在文件名之前传递,例如deno -A file.tsdeno run -A file.ts。文件名之后传递的任何内容都将被视为程序参数。

让我们看一个创建本地 HTTP 服务器的示例:

console.info("Hello there!");

import { serve } from "https://deno.land/std/http/server.ts";

const server = serve(":8000");

console.info("Server created!");
Enter fullscreen mode Exit fullscreen mode

该代码片段尝试使用网络,因此当您使用 Deno 运行该程序时,它将失败并出现错误

为了避免错误,我们需要在运行程序时传递--allow-net--allow-all标志。您也可以使用白名单授予对特定端口和域的访问权限。例如deno --allow-net=:8000 security.ts

deno 安全无提示

标准模块

Deno 提供NodeJS、Go 或 Rust 等标准模块。随着新版本的发布,该列表也在不断扩展。目前可用的模块包括:

  • archive- TAR 档案处理
  • colors- 控制台上的 ANSI 颜色
  • datetime- 日期时间解析实用程序
  • encoding- 编码/解码 CSV、YAML、HEX、Base32 和 TOML
  • flags- CLI 参数解析器
  • fs- 文件系统 API
  • http- HTTP 服务器框架
  • log- 日志框架
  • media_types- 解析媒体类型
  • prettier- 更漂亮的格式化 API
  • strings- 字符串实用程序
  • testing- 测试实用程序
  • uuid- UUID支持
  • ws- Websocket 客户端/服务器

标准模块在命名空间下可用https://deno.land/std,并根据 Deno 版本进行标记。

import { green } from "https://deno.land/std/fmt/colors.ts";
Enter fullscreen mode Exit fullscreen mode

ES 模块

Deno 仅支持使用远程或本地 URL 的ES 模块。这使得依赖管理变得简单且轻量。与 NodeJS 不同,Deno 在这方面并不会显得过于智能,这意味着:

  • require()不受支持,因此不会与导入语法混淆
  • 没有“神奇”的模块解析
  • 通过URL导入第三方模块(本地和远程)
  • 远程代码仅获取一次并全局缓存以供以后使用
  • 远程代码被视为不可变的,除非--reload使用标志,否则永远不会更新
  • 支持动态导入
  • 支持导入地图
  • 第三方模块可在https://deno.land/x/获取
  • 如果需要,可以使用 NPM 模块作为简单的本地文件 URL,或者从jspm.iopika.dev使用

因此,我们可以导入任何通过 URL 访问的库。让我们基于 HTTP 服务器示例进行构建

import { serve } from "https://deno.land/std/http/server.ts";
import { green } from "https://raw.githubusercontent.com/denoland/deno/master/std/fmt/colors.ts";
import capitalize from "https://unpkg.com/lodash-es@4.17.15/capitalize.js";

const server = serve(":8000");

console.info(green(capitalize("server created!")));

const body = new TextEncoder().encode("Hello there\n");

(async () => {
  console.log(green("Listening on http://localhost:8000/"));
  for await (const req of server) {
    req.respond({ body });
  }
})();
Enter fullscreen mode Exit fullscreen mode

通过使用下面的导入映射可以使导入路径变得更好

{
  "imports": {
    "http/": "https://deno.land/std/http/",
    "fmt/": "https://raw.githubusercontent.com/denoland/deno/master/std/fmt/",
    "lodash/": "https://unpkg.com/lodash-es@4.17.15/"
  }
}
Enter fullscreen mode Exit fullscreen mode

现在我们可以简化路径如下

import { serve } from "http/server.ts";
import { green } from "fmt/colors.ts";
import capitalize from "lodash/capitalize.js";

const server = serve(":8000");

console.info(green(capitalize("server created!")));

const body = new TextEncoder().encode("Hello there\n");

(async () => {
  console.log(green("Listening on http://localhost:8000/"));
  for await (const req of server) {
    req.respond({ body });
  }
})();
Enter fullscreen mode Exit fullscreen mode

--importmap使用标志运行此程序deno --allow-net=:8000 --importmap import-map.json server.ts。请注意,标志应位于文件名之前。现在您可以访问http://localhost:8000并验证这一点。

内置工具

Deno 借鉴了 Rust 和 Golang 的灵感,提供了内置工具。在我看来,这非常棒,因为它可以帮助你轻松上手,无需费心设置测试、linting 和打包框架。以下是目前可用/计划中的工具

  • 依赖项检查器(deno info):提供有关缓存和源文件的信息
  • Bundler(deno bundle):将模块和依赖项捆绑到单个 JavaScript 文件中
  • 安装程序(deno install):全局安装一个 Deno 模块,相当于npm install
  • 测试运行器deno test):使用 Deno 内置测试框架运行测试
  • 类型信息(deno types):获取 Deno TypeScript API 参考
  • 代码格式化程序(deno fmt):使用 Prettier 格式化源代码
  • Linter(计划中)(deno lint):源代码的 Linting 支持
  • 调试器(计划中)(--debug):Chrome Dev 工具的调试支持

例如,使用 Deno,您可以使用提供的实用程序轻松编写测试用例

假设我们有factorial.ts

export function factorial(n: number): number {
  return n == 0 ? 1 : n * factorial(n - 1);
}
Enter fullscreen mode Exit fullscreen mode

我们可以为此编写一个测试,如下所示

import { test } from "https://deno.land/std/testing/mod.ts";
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
import { factorial } from "./factorial.ts";

test(function testFactorial(): void {
  assertEquals(factorial(5), 120);
});

test(function t2(): void {
  assertEquals("world", "worlds");
});
Enter fullscreen mode Exit fullscreen mode

浏览器兼容性

如果满足以下条件,Deno 程序或模块也可以在浏览器上运行

  • 程序必须完全用 JavaScript 编写,并且不应使用全局 Deno API
  • 如果程序是用 Typescript 编写的,则必须将其捆绑为 JavaScript,deno bundle并且不应使用全局 Deno API

为了兼容浏览器,Deno 还支持window.loadwindow.unload事件。load并且unload事件也可以一起使用window.addEventListener

让我们看下面的示例,可以使用它运行,deno run也可以将其打包并在浏览器中执行

import capitalize from "https://unpkg.com/lodash-es@4.17.15/capitalize.js";

export function main() {
  console.log(capitalize("hello from the web browser"));
}

window.onload = () => {
  console.info(capitalize("module loaded!"));
};
Enter fullscreen mode Exit fullscreen mode

我们可以打包它deno bundle example.ts browser_compatibility.js,并在 HTML 文件中使用它browser_compatibility.js,然后在浏览器中加载它。尝试一下,看看浏览器控制台。

承诺 API

Deno 的另一个优点是它的所有 API 都基于Promise,这意味着与 NodeJS 不同,我们无需处理回调地狱。此外,该 API 在各个标准模块之间保持高度一致。让我们来看一个例子:

const filePromise: Promise<Deno.File> = Deno.open("dummyFile.txt");

filePromise.then((file: Deno.File) => {
  Deno.copy(Deno.stdout, file).then(() => {
    file.close();
  });
});
Enter fullscreen mode Exit fullscreen mode

但是我们说没有回调,Promise API 的好处是我们可以使用async/await语法,因此,我们可以重写上面的代码

const filePromise: Promise<Deno.File> = Deno.open("dummyFile.txt");

filePromise.then(async (file: Deno.File) => {
  await Deno.copy(Deno.stdout, file);
  file.close();
});
Enter fullscreen mode Exit fullscreen mode

运行deno -A example.ts看看它的实际效果,别忘了创建dummyFile.txt一些内容

顶级await

上面的代码仍然使用了回调函数,如果我们await也可以使用它呢?幸运的是,Deno 支持顶级await提案(TypeScript 尚不支持)。有了它,我们可以重写上面的代码

const fileName = Deno.args[0];

const file: Deno.File = await Deno.open(fileName);

await Deno.copy(Deno.stdout, file);

file.close();
Enter fullscreen mode Exit fullscreen mode

是不是很棒?运行如下命令deno -A example.ts dummyFile.txt

使用 Web Worker 的子进程

由于 Deno 使用的是单线程的 V8 引擎,因此我们必须使用类似 NodeJS 的子进程来创建新线程(V8 实例)。这可以通过 Deno 中的服务工作线程 (Service Worker) 来实现。以下是示例,我们将顶层await示例中使用的代码导入到此处的子进程中。

const p = Deno.run({
  args: ["deno", "run", "--allow-read", "top_level_await.ts", "dummyFile.txt"],
  stdout: "piped",
  stderr: "piped",
});

const { code } = await p.status();

if (code === 0) {
  const rawOutput = await p.output();
  await Deno.stdout.write(rawOutput);
} else {
  const rawError = await p.stderrOutput();
  const errorString = new TextDecoder().decode(rawError);
  console.log(errorString);
}

Deno.exit(code);
Enter fullscreen mode Exit fullscreen mode

您可以像在 NodeJS 中一样以子进程的形式运行任何 CMD/Unix 命令

WebAssembly 支持

WebAssembly是 JavaScript 领域最具创新性的功能之一。它允许我们使用任何兼容语言编写的程序在 JS 引擎中执行。Deno 原生支持 WebAssembly。让我们来看一个例子。

首先,我们需要一个 WebAssembly (WASM) 二进制文件。由于我们这里主要讨论 Deno,所以我们使用一个简单的 C 程序。你也可以使用 Rust、Go 或任何其他支持的语言。最后,你只需要提供一个编译好的.wasm二进制文件。

int factorial(int n) {
    return n == 0 ? 1 : n * factorial(n - 1);
}
Enter fullscreen mode Exit fullscreen mode

我们可以使用此处的在线转换器将其转换为 WASM 二进制文件,并将其导入到下面的 TypeScript 程序中

const mod = new WebAssembly.Module(await Deno.readFile("fact_c.wasm"));
const {
  exports: { factorial },
} = new WebAssembly.Instance(mod);

console.log(factorial(10));
Enter fullscreen mode Exit fullscreen mode

运行deno -A example.ts并查看 C 程序的输出。


Deno 应用程序的实际运行

现在我们已经对 Deno 的功能有了大致的了解,接下来让我们构建一个 Deno CLI 应用程序

运行deno --helpdeno run --help查看运行程序时可以传递的所有选项。您可以在 Deno网站手册中了解有关 Deno 功能和 API 的更多信息。

让我们构建一个简单的代理服务器,它可以作为 CLI 工具安装。这是一个非常简单的代理,但如果你愿意,可以添加更多功能让它更智能。

console.info("Proxy server starting!");

import { serve } from "https://deno.land/std/http/server.ts";
import { green, yellow } from "https://deno.land/std/fmt/colors.ts";

const server = serve(":8000");

const url = Deno.args[0] || "https://deepu.tech";

console.info(green("proxy server created!"));

(async () => {
  console.log(green(`Proxy listening on http://localhost:8000/ for ${url}`));

  for await (const req of server) {
    let reqUrl = req.url.startsWith("http") ? req.url : `${url}${req.url}`;

    console.log(yellow(`URL requested: ${reqUrl}`));

    const res = await fetch(reqUrl);
    req.respond(res);
  }
})();
Enter fullscreen mode Exit fullscreen mode

运行deno --allow-net deno_app.ts https://google.com并访问http://localhost:8000/。现在你可以在控制台上看到所有流量了。你可以使用任何你喜欢的 URL 来代替 Google。

让我们打包并安装该应用程序。

deno install --allow-net my-proxy deno_app.ts
Enter fullscreen mode Exit fullscreen mode

如果要覆盖文件,请使用deno install -f --allow-net my-proxy deno_app.ts。您还可以将脚本发布到 HTTP URL 并从那里安装。

现在只需运行my-proxy https://google.com,我们就有了自己的代理应用。是不是简洁又好用?


结论

让我们看看 Deno 与 NodeJS 的比较,以及为什么我相信它具有巨大的潜力

为什么 Deno 比 NodeJS 更好

我认为 Deno 比 NodeJS 更好,原因如下。我猜 NodeJS 的创建者也这么认为

  • 易于安装 - 单一轻量级二进制文件,内置依赖管理
  • 默认安全 - 沙盒、细粒度权限和用户控制
  • 简单的 ES 模块解析 - 没有像 NodeJS 那样的智能(混乱)模块系统
  • 去中心化且全局缓存的第三方模块——node_modules高效
  • 不依赖包管理器或包注册表(无 NPM、无 Yarn、无node_modules
  • 原生 TypeScript 支持
  • 遵循网络标准和现代语言特性
  • 浏览器兼容性——能够在浏览器和 Deno 应用程序中重用模块
  • 远程脚本运行器 - 脚本和工具的整洁安装
  • 内置工具——无需设置工具、捆绑器等

为什么这很重要

这有什么关系?为什么我们需要另一个脚本环境?JavaScript 生态系统难道还不够臃肿吗?

  • NodeJS 生态系统已经变得过于沉重和臃肿,我们需要一些东西来打破垄断并推动建设性的改进
  • 动态语言仍然很重要,尤其是在以下领域
    • 数据科学
    • 脚本
    • 工具
    • 命令行界面
  • 许多 Python/NodeJS/Bash 用例可以使用 Deno 替换为 TypeScript
    • TypeScript 提供更好的开发人员体验
    • 一致且可记录的 API
    • 更易于构建和分发
    • 不会一直下​​载互联网
    • 更安全

挑战

这并非没有挑战,Deno 要想成功,仍然必须克服这些问题

  • 库和模块的碎片化
  • 与现有的许多 NPM 模块不兼容
  • 库作者必须发布与 Deno 兼容的版本(这并不难,但需要额外的步骤)
  • 由于 API 不兼容,迁移现有的 NodeJS 应用程序并不容易
  • 捆绑包尚未优化,因此可能需要工具或改进
  • 稳定性,因为 Deno 还比较新(NodeJS 已经经过实战检验)
  • 尚未投入生产

如果您喜欢这篇文章,请点赞或留言。

您可以在TwitterLinkedIn上关注我。

封面图片来源:来自互联网的随机图片

文章来源:https://dev.to/deepu105/forget-nodejs-build-native-typescript-applications-with-deno-kkb
PREV
面向 JavaScript 开发人员的 Golang - 第 1 部分 比较相似的事情 结论 参考文献:
NEXT
适合所有人的简单 TypeScript 函数式编程技术