学习 Deno:聊天应用

2025-05-24

学习 Deno:聊天应用

原文:https ://aralroca.com/blog/learn-deno-chat-app

Node.js 最初由Ryan Dahl于 2009 年用 C++编写。Ryan 于 2012 年离开了 Node.js,因为他觉得自己的目标已经基本实现。

现在,他的目标变了。在意识到 Node.js 中存在一些无法修复的设计错误后,他决定创建另一个基于 V8 构建的 JavaScript(也包括 TypeScript)运行时:Deno(Rust 语言)。Deno 1.0.0 最终将于 2020 年 5 月 13 日发布。

Deno 徽标

我们将了解 Deno 的工作原理及其与 Node 的区别,并实现一个简单的聊天应用程序。

我们将介绍以下内容:

安装 Deno

安装 Deno 的方法有很多种:使用 curl、iwr、Homebrew、Chocolatey…… 请参阅此处的安装方法。Deno 是一个单一的二进制可执行文件,它没有外部依赖项。

就我而言,我将使用 Homebrew:

➜  ~ brew install deno
➜  ~ deno --version
deno 1.0.0-rc1
v8 8.2.308
typescript 3.8.3
Enter fullscreen mode Exit fullscreen mode

正如我们所见,这里没有npm。Npm 最初在 Node 生态系统中至关重要……它是一个集中式(甚至是私有控制的)模块存储库。现在 Deno 改变了这种情况。稍后我们将看到如何在没有package.jsonnode_moduleseither 的情况下安装软件包。

要升级到最新版本我们需要这样做deno upgrade

我建议执行deno help以查看所有可能的用法:

USAGE:
    deno [OPTIONS] [SUBCOMMAND]

OPTIONS:
    -h, --help                     Prints help information
    -L, --log-level <log-level>    Set log level [possible values: debug, info]
    -q, --quiet                    Suppress diagnostic output
    -V, --version                  Prints version information

SUBCOMMANDS:
    bundle         Bundle module and dependencies into single file
    cache          Cache the dependencies
    completions    Generate shell completions
    doc            Show documentation for a module
    eval           Eval script
    fmt            Format source files
    help           Prints this message or the help of the given subcommand(s)
    info           Show info about cache or info related to source file
    install        Install script as an executable
    repl           Read Eval Print Loop
    run            Run a program given a filename or url to the module
    test           Run tests
    types          Print runtime TypeScript declarations
    upgrade        Upgrade deno executable to newest version

ENVIRONMENT VARIABLES:
    DENO_DIR             Set deno's base directory (defaults to $HOME/.deno)
    DENO_INSTALL_ROOT    Set deno install's output directory
                         (defaults to $HOME/.deno/bin)
    NO_COLOR             Set to disable color
    HTTP_PROXY           Proxy address for HTTP requests
                         (module downloads, fetch)
    HTTPS_PROXY          Same but for HTTPS
Enter fullscreen mode Exit fullscreen mode

如果您正在使用 Visual Studio Code,我建议安装此插件以方便使用 Deno:

简单的“Hello World”

对于 Deno 中的简单“Hello world”,我们只需要创建一个文件.js.ts,然后使用执行它deno run [file]

对于.ts,它将编译+执行;对于.js,文件将直接执行:

// example.ts file
console.log('Hello from Deno 🖐')
Enter fullscreen mode Exit fullscreen mode

在 shell 中:

➜  deno run example.ts
Compile file:///Users/aralroca/example.ts
Hello from Deno 🖐
Enter fullscreen mode Exit fullscreen mode

tsconfig.json文件是可选的,因为 Deno 中有一些 TypeScript 默认设置。要应用,tsconfig.json我们应该使用deno run -c tsconfig.json [file]

顺便说一句,Deno 尽可能地使用 Web 标准。可以使用window……fetch我们Worker的代码应该与 Deno 和浏览器兼容。

提供 index.html

Deno 有自己的标准库https://deno.land/std/,因此我们可以直接从URL导入来使用其模块。其目标之一是仅交付单个可执行文件,并尽量减少链接。这样,只需将 URL 导入到项目中,或者deno run https://...在使用 CLI 的情况下直接执行即可。

为了创建一个 http 服务器并提供服务,index.html我们将使用这个模块:https://deno.land/std/http/

我们将创建两个文件:server.tsindex.html

索引.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta charset="utf-8" />
    <title>Example using Deno</title>
  </head>
  <body>index.html served correctly</body>
</html>
Enter fullscreen mode Exit fullscreen mode

服务器.ts

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

listenAndServe({ port: 3000 }, async (req) => {
  if (req.method === "GET" && req.url === "/") {
    req.respond({
      status: 200,
      headers: new Headers({
        "content-type": "text/html",
      }),
      body: await Deno.open("./index.html"),
    });
  }
});

console.log("Server running on localhost:3000");
Enter fullscreen mode Exit fullscreen mode

我们可以默认使用 ESmodules 代替 Common.js,这样文件扩展名就始终位于末尾。此外,它还支持最新的功能async-await

而且,我们再也不用担心格式化了。不用像 Prettier 那样使用工具,直接用deno fmt命令就可以格式化文件。

第一次deno run server.ts运行时,我们会看到与“Hello World”示例的两个不同之处:

  1. 它从模块下载所有依赖项http。它不应该使用yarnnpm install,而应该在运行项目之前安装所有需要的依赖项。由于缓存了缓存,因此只会在第一次安装时执行。要清除缓存,可以使用该--reload命令。

  2. 它会抛出一个错误Uncaught PermissionDenied: network access to "127.0.0.1:3000", run again with the --allow-net flag。Deno 默认是安全的。这意味着我们无法访问网络或读取文件(index.html)。这是相对于 Node 的重大改进之一。在 Node 中,任何 CLI 库都可以在未经我们同意的情况下执行许多操作。例如,使用 Deno,可以仅允许在一个文件夹中进行读取访问:deno --allow-read=/etc。要查看所有权限标志,请运行deno run -h

现在我们已准备好为您服务index.html

➜ deno run --allow-net --allow-read server.ts
Compile file:///Users/aralroca/server.ts
Server running on localhost:3000
Enter fullscreen mode Exit fullscreen mode

Deno 服务器为 index.html 提供服务

使用 WebSockets

WebSockets、UUID 以及 Node 中的其他基本功能并非核心组件。这意味着我们需要使用第三方库才能使用它们。然而,您可以使用 Deno 标准库来使用 WebSockets、UUID 以及许多其他功能。换句话说,您无需担心维护问题,因为 Deno 会持续维护。

为了继续实现我们的简单聊天应用程序,让我们创建一个新文件chat.ts

import {
  WebSocket,
  isWebSocketCloseEvent,
} from "https://deno.land/std/ws/mod.ts";
import { v4 } from "https://deno.land/std/uuid/mod.ts";

const users = new Map<string, WebSocket>();

function broadcast(message: string, senderId?: string): void {
  if(!message) return
  for (const user of users.values()) {
    user.send(senderId ? `[${senderId}]: ${message}` : message);
  }
}

export async function chat(ws: WebSocket): Promise<void> {
  const userId = v4.generate();

  // Register user connection
  users.set(userId, ws);
  broadcast(`> User with the id ${userId} is connected`);

  // Wait for new messages
  for await (const event of ws) {
    const message = typeof event === 'string' ? event : ''

    broadcast(message, userId);

    // Unregister user conection
    if (!message && isWebSocketCloseEvent(event)) {
      users.delete(userId);
      broadcast(`> User with the id ${userId} is disconnected`);
      break;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

现在,注册一个端点/ws来公开聊天server.ts

import { listenAndServe } from "https://deno.land/std/http/server.ts";
import { acceptWebSocket, acceptable } from "https://deno.land/std/ws/mod.ts";
import { chat } from "./chat.ts";

listenAndServe({ port: 3000 }, async (req) => {
  if (req.method === "GET" && req.url === "/") {
    req.respond({
      status: 200,
      headers: new Headers({
        "content-type": "text/html",
      }),
      body: await Deno.open("./index.html"),
    });
  }

  // WebSockets Chat
  if (req.method === "GET" && req.url === "/ws") {
    if (acceptable(req)) {
      acceptWebSocket({
        conn: req.conn,
        bufReader: req.r,
        bufWriter: req.w,
        headers: req.headers,
      }).then(chat);
    }
  }
});

console.log("Server running on localhost:3000");
Enter fullscreen mode Exit fullscreen mode

为了实现我们的客户端部分,我们将选择 Preact,以便能够直接使用模块,而不需要 npm、babel 和 webpack,正如我们在上一篇文章中看到的那样。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Chat using Deno</title>
  </head>
  <body>
    <div id="app" />
    <script type="module">
      import { html, render, useEffect, useState } from 'https://unpkg.com/htm/preact/standalone.module.js'

      let ws

      function Chat() {
        // Messages
        const [messages, setMessages] = useState([])
        const onReceiveMessage = ({ data }) => setMessages(m => ([...m, data]))
        const onSendMessage = e => {
          const msg = e.target[0].value

          e.preventDefault()
          ws.send(msg)
          e.target[0].value = ''
        }

        // Websocket connection + events
        useEffect(() => {
          if (ws) ws.close()
          ws = new WebSocket(`ws://${window.location.host}/ws`)
          ws.addEventListener("message", onReceiveMessage)

          return () => {
            ws.removeEventListener("message", onReceiveMessage)
          }
        }, [])

        return html`
          ${messages.map(message => html`
              <div>${message}</div>
          `)}

          <form onSubmit=${onSendMessage}>
           <input type="text" />
           <button>Send</button>
          </form>
        `
      }

      render(html`<${Chat} />`, document.getElementById('app'))
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

结果:

使用 Deno + Preact 实现聊天

这是一个非常丑陋的聊天,没有样式,但功能齐全,因为我们的目标是了解 Deno 是如何工作的。

第三方和 deps.ts 约定

我们可以像使用 Deno 标准库一样使用第三方库,通过直接导入模块的 URL。

然而, https://deno.land/x/的生态系统还很小。不过,有个好消息要告诉你,我们可以使用https://www.pika.dev上的软件包。借助 Parcel 或 Minibundle 等工具,我们可以将 Node 库编译成模块,以便在 Deno 项目中复用。

我们将使用camel-case包将每个聊天消息转换为 camelCase!

从 pika web 导入驼峰式命名库

让我们在文件中添加这个导入chat.ts

import { camelCase } from 'https://cdn.pika.dev/camel-case@^4.1.1';
// ...before code
const message = camelCase(typeof event === 'string' ? event : '')
// ... before code
Enter fullscreen mode Exit fullscreen mode

就是这样。再次运行server.ts将会下载camel-case软件包。现在你可以看到它已经运行了:

在 deno 上使用驼峰式包

但是,如果我想camelCase在多个文件中使用这个辅助函数,那么在每个文件中都添加完整的导入会很麻烦。URL 指示了我们必须使用哪个版本的包。这意味着如果我们想要升级依赖项,就需要搜索并替换所有导入。这可能会给我们带来麻烦,但不用担心,Deno 有一个针对依赖项的约定可以解决这个问题。创建一个deps.ts文件来导出所有项目依赖项。

// deps.ts file
export { camelCase } from 'https://cdn.pika.dev/camel-case@^4.1.1';
Enter fullscreen mode Exit fullscreen mode


// chat.ts file
import { camelCase } from './deps.ts';
// ...
const message = camelCase(typeof event === 'string' ? event : '')
// ...
Enter fullscreen mode Exit fullscreen mode

测试

我们将构建一个无用camelize.ts实用程序,以驼峰命名格式返回文本,并添加一个额外的字符,每个大写字母包含一个 🐪。为什么?看看如何使用 Deno 进行测试。

/**
 * Return the text in camelCase + how many 🐪
 * 
 * @example "this is an example" -> "thisIsAnExample 🐪🐪🐪"
 * @param text 
 * @returns {string}
 */
export function camelize(text: string) {
  // @todo
}
Enter fullscreen mode Exit fullscreen mode

顺便说一下,我们可以使用以下方式可视化文件的 JSdocs deno doc [file]

➜  deno doc camelize.ts 
function camelize(text: string)
  Return the text in camelCase + how many 🐪
Enter fullscreen mode Exit fullscreen mode

让我们创建一个文件test.ts。测试运行器内置于 Deno 核心中Deno.test(),我们可以使用 STD https://deno.land/std/testing/asserts.ts来使用断言。

import { assertStrictEq } from "https://deno.land/std/testing/asserts.ts";
import { camelize } from "./camelize.ts";

Deno.test("camelize works", async () => {
  assertStrictEq(camelize("this is an example"), "thisIsAnExample 🐪🐪🐪");
});

Enter fullscreen mode Exit fullscreen mode

要运行所有测试,我们只需要执行deno test

➜  deno test
Compile file:///Users/aralroca/test.ts
running 1 tests
test camelize works ... FAILED (0ms)

failures:

camelize works
AssertionError: actual: undefined expected: thisIsAnExample 🐪🐪🐪
    at assertStrictEq (asserts.ts:224:11)
    at test.ts:5:3
    at asyncOpSanitizer ($deno$/testing.ts:36:11)
    at Object.resourceSanitizer [as fn] ($deno$/testing.ts:70:11)
    at TestApi.[Symbol.asyncIterator] ($deno$/testing.ts:264:22)
    at TestApi.next (<anonymous>)
    at Object.runTests ($deno$/testing.ts:346:20)

failures:

        camelize works

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (0ms)
Enter fullscreen mode Exit fullscreen mode

当然,它会失败,因为我们还没有实现我们的实用程序,但是,我们仍然可以看到错误是如何在 shell 中显示的。

实施该camelize实用程序后:

import { camelCase } from "./deps.ts";

/**
 * Return the text in camelCase + how many 🐪
 * 
 * @example "this is an example" -> "thisIsAnExample 🐪🐪🐪"
 * @param text 
 * @returns {string}
 */
export function camelize(text: string) {
  const camelCaseText = camelCase(text);
  const matches = camelCaseText.match(/[A-Z]/g) || [];
  const camels = Array.from({ length: matches.length })
    .map(() => "🐪")
    .join("");

  return `${camelCaseText} ${camels}`;
}
Enter fullscreen mode Exit fullscreen mode

现在所有测试都通过了:

➜  deno test
Compile file:///Users/aralroca/camelize.ts
running 1 tests
test camelize works ... ok (3ms)

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (3ms)
Enter fullscreen mode Exit fullscreen mode

如果您想使用观察者不每次都执行所有测试,您可以使用基于 nodemon 的https://deno.land/x/denon/,然后运行denon test​​。

现在我们准备使用我们的助手了chat.ts

骆驼上的信息

调试

为了使用 Deno 进行调试:

  1. 在代码中的某处添加一行debugger;代码。
  2. 使用标志运行--inspect-brkdeno run --inspect-brk ...deno test --inspect-brk ...调试测试。
  3. 在 Chrome 上打开chrome://inspect页面。
  4. 在远程目标部分按“检查”。
  5. 按下“恢复脚本执行”按钮,代码将在断点处暂停。

使用 Deno 进行调试

结论

我们通过用 TypeScript 创建一个简单的聊天应用来了解 Deno 的工作原理。我们没有使用 npm、package.json、node_modules、webpack、babel、jest、prettier……因为我们不需要它们,所以 Deno 简化了这个过程。

我们探索了开始 Deno 项目的重要事项:权限、deno 命令、如何使用 deno 内部结构、如何使用第三方依赖项、提供文件、websockets、格式化文件、测试、调试等。

我希望本文能够帮助您在 2020 年 5 月 13 日发布 Deno 1.0.0 时开始在您的项目中使用它。

本文代码

我把代码上传到我的 GitHub 上:

参考

文章来源:https://dev.to/aralroca/learn-deno-chat-app-37f0
PREV
Yarn 2 隆重推出!🧶🌟
NEXT
从 Node 到 Deno