让你玩转Deno

1,246 阅读5分钟

Deno 的目标是为现代程序员提供一个高效、安全的脚本环境。

Node.js 类似,Deno 也是运行在服务器端的 JavaScript。 它更像是升级版的 Node.js,可以认为是 Node 2.0 版。

Why Deno?

Node.js 已经成为 JavaScript 生态系统中重要的一部分,被广泛的应用。然后 Node.js 之父 Ryan Dahl 不满意它的设计,想通过一个全新的设计来弥补Node的缺陷。这就是Deno 产生的原因。

Deno 是一个全新的 JavaScriptTypeScript 运行时,由 V8 JavaScript 引擎 RustTypeScript 实现来:

  • 1、语言优势

Deno 天生就支持 JavaScriptTypeScript。 尤其是在前端逐渐在 ts 化的路上,Deno 的优势会越来越明显。

  • 2、兼容性

Deno 设计之初就尝试同时运行在浏览器端和服务端,它会尽可能的支持最新的 ES 特性。

  • 3、安全性

Deno 默认是安全的,除非设置允许,否则不能读写文件、发起网络请求或访问环境变量。这可以防止 Deno 的恶意使用。这块是 Node 不如 Deno 的地方。

  • 4、标准库

DenoJavaScript 之上提供了许多内部实用函数。此外,Deno 还提供了几个内置的工具来改善开发体验。

下面我们从头开始一步一步实现一个微型 Deno 应用程序:

安装 Deno

限于篇幅仅介绍 mac 平台下的安装:

Shell (Mac, Linux):
curl -fsSL https://deno.land/x/install/install.sh | sh

Homebrew (Mac):
brew install deno

查看安装是否成功:

deno --version
-> deno 1.0.5
-> v8 8.4.300
-> typescript 3.9.2

想更新 Deno 版本可使用 upgrade 命令。

也可以执行以下远程脚本验证 Deno 是否安装成功:

deno run https://deno.land/std/examples/welcome.ts
-> Welcome to Deno

仅仅输出一段文本。演示了 deno 通过动态下载和编译从远程源代码执行应用。

如果安装过程中出现问题,可以给我留言,或参考官网

从 Hello Deno 开始

mkdir deno-example
cd deno-example
touch index.js

index.js 中输入:

console.log("Hello Deno");

deno run xxx.js 执行指定文件:

deno run index.js
-> Hello Deno

恭喜你,完成了第一个 Deno 应用, ^_^

deno 的权限机制

前面我提到说 Deno 默认是安全的:

const url = "https://api.github.com/users/answer518";

fetch(url)
  .then((result) => result.json())
  .then((result) => {
    console.log(result);
  });

当你像执行 hello deno 一样运行 deno run request.js:

error: Uncaught PermissionDenied: network access to "https://api.github.com/users/answer518", run again with the --allow-net flag
    at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
    at Object.sendAsync ($deno$/ops/dispatch_json.ts:98:10)
    at async fetch ($deno$/web/fetch.ts:296:27)
    at async file:///Users/guotingjie/research/deno-project/request.js:3:16

Deno 中执行以下操作:网络请求,读写文件, 这些都需要得到 Deno 的许可:

 deno-example  deno run --allow-net request.js
{
  login: "answer518",
  id: 17827860,
  node_id: "MDQ6VXNlcjE3ODI3ODYw",
  avatar_url: "https://avatars1.githubusercontent.com/u/17827860?v=4",
  gravatar_id: "",
  url: "https://api.github.com/users/answer518",
  html_url: "https://github.com/answer518",
  followers_url: "https://api.github.com/users/answer518/followers",
  following_url: "https://api.github.com/users/answer518/following{/other_user}",
  gists_url: "https://api.github.com/users/answer518/gists{/gist_id}",
  starred_url: "https://api.github.com/users/answer518/starred{/owner}{/repo}",
  subscriptions_url: "https://api.github.com/users/answer518/subscriptions",
  organizations_url: "https://api.github.com/users/answer518/orgs",
  repos_url: "https://api.github.com/users/answer518/repos",
  events_url: "https://api.github.com/users/answer518/events{/privacy}",
  received_events_url: "https://api.github.com/users/answer518/received_events",
  type: "User",
  site_admin: false,
  name: "果糖酱",
  company: "undefined",
  blog: "https://answer518.github.io/",
  location: "China Beijing",
  email: null,
  hireable: null,
  bio: null,
  twitter_username: null,
  public_repos: 53,
  public_gists: 0,
  followers: 2,
  following: 31,
  created_at: "2016-03-14T12:27:14Z",
  updated_at: "2020-03-31T12:08:12Z"
}

兼容性

在上面的例子中 fetch API 用于发起网络请求,这属于 es 标准 API,而这方面 Deno 尝试与 es 最新特性保持一致。

另外像 async/awaitnode 6.x 版本开始支持, 在 Deno 中是默认支持的:

const url = "https://api.github.com/users/answer518";

const result = await fetch(url).then((result) => result.json());
console.log(result);

标准库

Deno 附带了一组实用函数,称为 Deno 的标准库。

下面演示一个利用 Deno 标准库实现一个 web server:

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

const server = serve({ port: 8080 });

for await (const req of server) {
  req.respond({ body: "Hello Deno" });
}

浏览器打开 http://localhost:8000/,就会向 Deno 应用程序发出一个 HTTP GET 请求,该请求返回一个带有 “Hello Deno” 正文的 HTTP 响应,随后将在浏览器中显示。

没问可以扩展一下上面这个案例:

import { serve } from "https://deno.land/std/http/server.ts";
const url = "https://api.github.com/users/answer518";
const server = serve({ port: 8000 });

for await (const req of server) {
  const result = await fetch(url).then((result) => result.json());
  req.respond({ body: JSON.stringify(result) });
}

前面我们是在控制台中看到结果,现在可以直接通过浏览器中直接看到 fetch 得到的结果。

单元测试

首先看一个在 Deno 中单元测试的例子:

import { mapStory } from "./stories.js";

Deno.test("maps to a smaller story with formatted date", () => {});
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";

import { mapStory } from "./stories.js";

Deno.test("maps to a smaller story with formatted date", () => {
  const stories = [
    {
      id: "1",
      title: "title1",
      url: "url1",
      created_at_i: 1476198038,
    },
  ];

  const expectedStories = [
    {
      title: "title1",
      url: "url1",
      createdAt: "2016-10-11",
    },
  ];

  assertEquals(stories.map(mapStory), expectedStories);
});

assertEquals 函数接收两个参数:第一个是我们要测试的对象,第二个是我们期望的值:

deno test

-> running 1 tests
-> test maps to a smaller story with formatted date ... ok (9ms)

-> test result: ok. (10ms)
-> 1 passed;
-> 0 failed;
-> 0 ignored;
-> 0 measured;
-> 0 filtered out

typescript

Deno 同时支持 JavaScriptTypeScript 两种语言。无论导入的是 Deno 标准库还是本地项目文件,都需要加上文件扩展名:

假设我们将 index.js 改为 index.ts , 那么我们需要调整所有引用 index 文件的地方: .js 改为 .ts

import { format } from "https://deno.land/x/date_fns/index.js";

interface Story {
  title: string;
  url: string;
  created_at_i: number;
}

interface FormattedStory {
  title: string;
  url: string;
  createdAt: string;
}

export const mapStory = (story: Story): FormattedStory => ({
  title: story.title,
  url: story.url,
  createdAt: format(new Date(story.created_at_i * 1000), "yyyy-MM-dd"),
});

想要启动你的 Deno 应用,必须指定 .ts 后缀的文件名:

deno run --allow-net index.ts

可以通过修改 .tsconfig 文件来修改默认的 typescript 配置

Deno 中的环境变量

创建一个 .env 文件

PORT = 8000;

index.ts 演示了如何从 .env 文件中读取配置变量:

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

import { mapStory } from "./stories.ts";

const url = "http://hn.algolia.com/api/v1/search?query=javascript";

const server = serve({
  port: parseInt(config()["PORT"]),
});

for await (const req of server) {
  const result = await fetch(url).then((result) => result.json());

  const stories = result.hits.map(mapStory);

  req.respond({ body: JSON.stringify(stories) });
}

.env 中属性以键值对形式存在,之所以对 port 进行类型转换,因为属性值是以字符串类型存在的。

默认读取文件需要显式允许才可以:

deno run --allow-net --allow-read index.ts

重要:.env 涉及到敏感信息,不应该被提交到 git 仓库,记得加入.gitignore 文件中

结束

这就是有关 Deno 使用的基础知识。是不是感觉和 Node 差不多,适用场景多么的相似,相比 Node,它有很多的优点,比如支持 typescript,默认更安全等等。

至于将来 Deno 会不会替代 Node, 个人感觉短期内很难,至少等到 5 年以后。不过本人很看好 Deno 的未来。