Deno是Ryan Dahl在2017年创立的,此外
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依赖中不存在npm,npm是node的包管理工具。deno舍弃了npm使用了另外的方式进行包的管理。
Hello world
了解一门语言,国际惯例,先上Hello World。
首先创建一个文件,这个文件可以是js文件也可以是ts文件,Deno内置对ts的支持,不需要使用typescript对ts文件在进行编译一遍(因为作者对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我们需要使用deno的http服务: https://deno.land/std/http/,deno所有的标准库都在https://deno.land/std 。
首先创建两个文件,一个用服务端代码,一个客户端代码: server.js和static/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例子有两个地方差异.
- 使用
http的方式引入依赖,而不是npm或者yarn的方式。在执行文件之前,deno会首先下载所有的依赖放入缓存,当我们不清空缓存的时候可以尝试--reload命令 - 执行的时候会抛出两个错误,一个是没有权限接入网络
Uncaught PermissionDenied: network access to "0.0.0.0:3000", 这个可以添加--allow-net表示运行网络访问,在访问localhost:3000,deno抛出错误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发送消息
- 服务端收到消息后,主动推送消息给客户端2
- 客户端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,他不需要额外的babel、webpack配置实用非常的方便。
<!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的方式来引入。官网主要包含了标准库和第三方库,下面是具体的地址
- 标准库: deno.land/std/
- 第三方库: deno.land/x/
但是,官网上的第三放库实在是太少了不能满足我们的需求。好消息是我们可以使用
https://www.pika.dev上面的库,此外可以通过打包工具如Parcel把node包转换成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 (<anonymous>)
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还能够进行断点调试。
- 首先在启动命令中添加
--inspect-brk例如deno run --inspect-brk camelize.js执行后会在9229端口启动一个websocket - 在
chrome://inspect中添加一个端口如:localhost:9229 - 点击
inspect就可以调试deno代码了 - 调试代码就和正常的调试一样就可以了
欢迎关注「前端好好学」,前端学习不迷路或加微信 ssdwbobo,一起交流学习