DenoRyan Dahl2017年创建的,此外
Ryan Dahl也是Node.js的创始人,从2007年始终到2012年,他起初把 Node.js 移交给了其余开发者,不再过问了,转而钻研人工智能。
他始终不是很喜爱 Python 语言,长此以往,就想搞一个 JavaScript 语言的人工智能开发框架。等到他再回过头捡起 Node.js,发现这个我的项目曾经背离了他的初衷,有一些无奈漠视的问题,从此deno从此应运而生。
上面咱们来看看Deno的根本应用,并用它构建一个简略的聊天网站。

装置Deno

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

brew install denodeno 1.4.6v8 8.7.220.3typescript 4.0.3

能够看到的是deno依赖中不存在npmnpmnode的包管理工具。deno舍弃了npm应用了另外的形式进行包的治理。

Hello world

理解一门语言,国际惯例,先上Hello World
首先创立一个文件,这个文件能够是js文件也能够是ts文件,Deno内置对ts的反对,不须要应用typescriptts文件在进行编译一遍(因为作者对ts不怎么相熟,所以文章应用js进行编写)。运行这个文件调用deno run [file]
上面是实例代码

// example.jsconsole.log("Hello World")

执行deno run example.js,上面是打印后果

deno run example.jsHello 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的内容

<!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</body></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.jsServer 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配置实用十分的不便。

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Example using Demo</title></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>Send</button>        </form>      `;    }    render(html`<${Chat} />`, document.getElementById('app'));  </script></body></html>

第三方库的治理

下面例子中次要实用了deno的规范库,上面是deno的第三方库的应用,引入第三方库也是通过url的形式来引入。官网次要蕴含了规范库和第三方库,上面是具体的地址

  • 规范库: https://deno.land/std/
  • 第三方库: https://deno.land/x/

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

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

从新运行一次,能够看到更改的内容曾经失效了。

然而下面这种形式存在一个问题,如果多个文件都依赖了camelCase,每个文件须要申明一个url。当降级camel-case的时候,须要把所有依赖以后库的版本号都更改一下,很可能漏掉呈现一些问题。所以举荐对于引入的第三方库能够在一个文件中进行治理,如下:

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

编写测试

Deno绝对于node内置了十分多的性能,例如主动生成文档,代码格式化,自动测试等等。
例如当初创立一个函数用于统计字符串中一个有多少个大写字母

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

执行上面命令deno doc camelize,就能够生成以后函数的文档

deno doc camelizefunction 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 teststest camelize works ... FAILED (2ms)failures:camelize worksAssertionError: 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 (<anonymous>)    at Object.runTests (deno:cli/rt/40_testing.js:317:22)failures:        camelize workstest 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 teststest 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,一起交流学习