云社区 博客 博客详情

Deno从入门到实践

SegmentFault思否 发表于 2020-11-07 23:38:37 2020-11-07
0
0

【摘要】 Deno是Ryan Dahl在2017年创立的,此外Ryan Dahl也是Node.js的创始人,从2007年一直到2012年,他后来把 Node.js 移交给了其他开发者,不再过问了,转而研究人工智能。 他始终不是很喜欢 Python 语言,久而久之,就想搞一个 JavaScript 语言的人工智能开发框架。等到他再回过头捡起 Node.js,发现这个项目已经背离了他的初...

DenoRyan Dahl2017年创立的,此外
Ryan Dahl也是Node.js的创始人,从2007年一直到2012年,他后来把 Node.js 移交给了其他开发者,不再过问了,转而研究人工智能。
他始终不是很喜欢 Python 语言,久而久之,就想搞一个 JavaScript 语言的人工智能开发框架。等到他再回过头捡起 Node.js,发现这个项目已经背离了他的初衷,有一些无法忽视的问题,从此deno从此应运而生。
下面我们来看看Deno的基本使用,并用它构建一个简单的聊天网站。

安装Deno

有很多种方式安装Deno,具体如何安装可参考这篇文章deno安装
我使用Homebrew安装Deno(感觉这种方式要快一点)

brew install deno
deno 1.4.6
v8 8.7.220.3
typescript 4.0.3

可以看到的是deno依赖中不存在npmnpmnode的包管理工具。deno舍弃了npm使用了另外的方式进行包的管理。

Hello world

了解一门语言,国际惯例,先上Hello World
首先创建一个文件,这个文件可以是js文件也可以是ts文件,Deno内置对ts的支持,不需要使用typescriptts文件在进行编译一遍(因为作者对ts不怎么熟悉,所以文章使用js进行编写)。运行这个文件调用deno run [file]
下面是实例代码

// example.js
console.log("Hello World")

执行deno run example.js,下面是打印结果

deno run example.js
Hello world

如果我们使用ts格式,可以自己定义tsconfig.json文件,然后使用deno run -c tsconfig.json example.ts来覆盖deno内置的ts配置,deno内部的默认配置可参考Using Typescript

创建http服务

node中使用import path from 'path'的方式引入库或者工具,deno因为没有npm的概念,如果需要引入某个库,直接从url中获取需要的工具就可以,这样做的是为了尽可能的减少包文件的体积。
例如创建一个http server我们需要使用denohttp服务: https://deno.land/std/http/deno所有的标准库都在https://deno.land/std
首先创建两个文件,一个用服务端代码,一个客户端代码: server.jsstatic/index.html
下面是index.html的内容

html>
<html lang="en">
  <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta charset="utf-8" /> <title>Example using Denotitle>
  head>
  <body>index.htmlbody>
html>

server.js

import { listenAndServe } from 'https://deno.land/std/http/server.ts';
const file_url = fromFileUrl(new URL('../static/index.html', import.meta.url));
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.readTextFile(file_url), }); }
  }
);

console.log('Server running on localhost:3000');

deno中可以使用ESModules的模块方式代替Common.js。需要注意的是在引入文件的时候必须要加上文件后缀名,例如引入文件a,必须写a.js。此外,相对于node`deno默认支持async-await`。
当我们运行deno run server.js,可以看到和先前的Hello World例子有两个地方差异.

  1. 使用http的方式引入依赖,而不是npm或者yarn的方式。在执行文件之前,deno会首先下载所有的依赖放入缓存,当我们不清空缓存的时候可以尝试--reload命令
  2. 执行的时候会抛出两个错误,一个是没有权限接入网络Uncaught PermissionDenied: network access to "0.0.0.0:3000", 这个可以添加--allow-net表示运行网络访问,在访问localhost:3000deno抛出错误Uncaught PermissionDenied: read access to无法读取文件。这里也是和node有差异的地方,大多数情况下node可以不需要用户的同意获取网络文件等权限。deno基于安全考虑限制了大量的权限,如果需要读取某个文件夹的内容需要使用deno --allow-read=文件目录。更多命令可参考deno run -h

执行下面命令就可以启动http服务,并且访问到index.html的内容

deno run --allow-net --allow-read server.js
Server running on localhost:3000

创建聊天应用

下面来创建一个简单的聊天应用,主要实现的功能:

  1. 客户端1发送消息
  2. 服务端收到消息后,主动推送消息给客户端2
  3. 客户端2立刻收到消息并显示

下面是服务端代码的具体实现,首先创建一个chat.js, 这个文件主要是用于存储websocket实例,接受客户端发送的消息,主动向客户端发送消息,下面是具体实现:

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

const users = new Map();

function broadcast(message, senderId) {
  if (!message) { return false;
  }
  users.forEach(user => { user.send(senderId ? `[${senderId}]: ${message}` : message);
  });
}

export async function chat(ws) {
  const userId = v4.generate(); users.set(userId, ws);
  broadcast(`> User with the id ${userId} is connected`);
  for await (const event of ws) { const message = typeof event === 'string' ? event : ''; broadcast(message, userId); if (!message && isWebSocketCloseEvent(event)) { users.delete(userId); broadcast(`> User with the id ${userId} is disconnected`); break; }
  }
}

然后在server.js中定义路由,用于处理websocket请求

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.js";

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");

下面是客户端的代码,这里为了简单实用preact,他不需要额外的babelwebpack配置实用非常的方便。

html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Example using Demotitle>
head>
<body>
  <div id="app">div>
  <script type="module"> import { html, render, useEffect, useState } from 'https://unpkg.com/htm/preact/standalone.module.js'; let ws; function Chat() { const [messages, setMessages] = useState([]); const onReceiveMessage = ({ data }) => setMessages(m => [...m, data]); const onSendMessage = e => { const msg = e.target[0].value; e.preventDefault(); console.log(msg); ws.send(msg); e.target[0].value = ''; }; 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>Sendbutton> form> `; } render(html`<${Chat} />`, document.getElementById('app')); script>
body>
html>

第三方库的管理

上面例子中主要实用了deno的标准库,下面是deno的第三方库的使用,引入第三方库也是通过url的方式来引入。官网主要包含了标准库和第三方库,下面是具体的地址

但是,官网上的第三放库实在是太少了不能满足我们的需求。好消息是我们可以使用https://www.pika.dev上面的库,此外可以通过打包工具如Parcelnode包转换成deno能够使用的包。
下面借助camel-case将用户输入的输入内容转为驼峰式,添加以下代码到chat.js

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

重新运行一次,可以看到更改的内容已经生效了。

但是上面这种方式存在一个问题,如果多个文件都依赖了camelCase,每个文件需要声明一个url。当升级camel-case的时候,需要把所有依赖当前库的版本号都更改一下,很可能漏掉出现一些问题。所以推荐对于引入的第三方库可以在一个文件中进行管理,如下:

//deps.js
export { camelCase } from 'https://cdn.pika.dev/camel-case@^4.1.1';
// chat.js
import { camelCase } from './deps.ts';

编写测试

Deno相对于node内置了非常多的功能,例如自动生成文档,代码格式化,自动测试等等。
例如现在创建一个函数用于统计字符串中一个有多少个大写字母

/**
 * 统计字符串的大写字母的个数
 */
export function camelize(text) {
  // todo:
}

执行下面命令deno doc camelize,就可以生成当前函数的文档

deno doc camelize
function camelize(text)
  统计字符串的大写字母的个数

然后创建一个测试文件test.js,用于测试当前函数是否符合要求。
Deno内置了一些测试的工具如Deno.test,断言等等,下面是test.js的内容

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

Deno.test("camelize works", async () => {
  assertStrictEq(camelize("AAbbCC"), 4);
});

然后执行deno test发现有以下报错

running 1 tests
test camelize works ... FAILED (2ms)

failures:

camelize works
AssertionError: Values are not strictly equal: [Diff] Actual / Expected


-   undefined
+   4 at assertStrictEquals (asserts.ts:298:9) at file:///Users/bytedance/cornelius/deno-chart/test.js:5:3 at asyncOpSanitizer (deno:cli/rt/40_testing.js:34:13) at Object.resourceSanitizer [as fn] (deno:cli/rt/40_testing.js:68:13) at TestRunner.[Symbol.asyncIterator] (deno:cli/rt/40_testing.js:240:24) at AsyncGenerator.next () at Object.runTests (deno:cli/rt/40_testing.js:317:22)

failures: camelize works

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (2ms)

可以看到咱们的测试是没有通过的,下面更改camelize中的代码

export function camelize(text) {
  return (text.match(/[A-Z]/g) || []).length;
}

再次执行deno test,可以看到通过了测试

running 1 tests
test camelize works ... ok (4ms)

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

Debugging

除了console,借助chrome还能够进行断点调试。

  1. 首先在启动命令中添加--inspect-brk例如deno run --inspect-brk camelize.js执行后会在9229端口启动一个websocket
  2. chrome://inspect中添加一个端口如: localhost:9229
  3. 点击inspect就可以调试deno代码了
  4. 调试代码就和正常的调试一样就可以了
欢迎关注「前端好好学」,前端学习不迷路或加微信 ssdwbobo,一起交流学习

文章来源: segmentfault.com,作者:云中歌,版权归原作者所有,如需转载,请联系作者。

原文链接:segmentfault.com/a/1190000037751598

登录后可下载附件,请登录或者注册

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:huaweicloud.bbs@huawei.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
评论文章 //点赞 收藏 0
点赞
分享文章到微博
分享文章到朋友圈

上一篇:从底层深入Go的基础模型 - interface

下一篇:2020年11月编程语言排行榜:20年来 Java 首次跌落至第三

评论 (0)


登录后可评论,请 登录注册

评论