原味地址: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.0终于将于2020年5月13日发布。
我们将看看Deno是如何工作的,以及它与Node的区别,实现一个简单的聊天应用。
本文将涵盖以下内容。
安装Deno
简单的 "Hello World"
Serve an index.html
使用WebSockets
第三方依赖约定
测试
调试
结论
源码
参考资料
安装 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
如我们所见,这里没有npm
。Npm
开始是Node生态系统中不可缺少的...... 而且它是一个集中化的(甚至是私人控制的)模块存储库。现在随着Deno的出现,这种情况正在改变。后面我们会看到如何在没有package.json
和node_modules
的情况下安装包。
要升级到最新的版本,我们需要进行升级 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
如果您使用的是Visual Studio Code,我建议安装此插件,以方便使用Deno工作。
同时 推荐下载更多的 https://marketplace.visualstudio.com/items?itemName=justjavac.vscode-deno
Hello World
在Deno中做一个简单的 "Hello world",我们只需要创建一个文件.js或.ts,然后用 deno run [file]
执行即可。
如果是.ts
,它将编译+执行,而对于.js
,文件将直接执行。
// example.ts file
console.log('Hello from Deno 🖐')
命令行执行
➜ deno run example.ts
Compile file:///Users/aralroca/example.ts
Hello from Deno 🖐
tsconfig.json
文件是可选的,因为在Deno中,有一些TypeScript的默认值。要应用 tsconfig.json
,我们应该使用 deno run -c tsconfig.json [file]
。
顺便说一下,Deno尽可能使用web标准。可以使用window、fetch、Worker.....我们的代码应该同时兼容Deno和浏览器。
Serve an index.html
Deno有自己的标准库 https://deno.land/std/, 所以要使用他们的模块,我们可以直接从URL中导入。它的目标之一就是只发一个可执行文件,而且链接最少。这样一来,我们只需要将URL导入到他们的项目中,或者直接用deno运行https://...........在CLI的情况下,直接执行。
为了创建一个http服务器并服务于index.html,我们将使用这个模块:https://deno.land/std/http/。
我们将创建两个文件:server.ts
和 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 served correctly
</body>
</html>
server.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')
默认情况下,我们可以使用ESmodules代替Common.js,这表明文件扩展名始终位于末尾。此外,它还支持最新功能 async-await。
另外,我们不必担心格式化。代替使用工具作为Prettier,我们可以使用deno fmtcommand 格式化文件。
首次deno run server.ts运行时,我们将看到与“ Hello World”示例有关的两个区别:
它从http模块下载所有依赖项。代替使用 yarn or npm install
,它应该在运行项目之前安装所有必需的依赖项。由于已缓存,因此仅在第一次时才发生。要清除缓存,可以使用--reload命令。
它抛出一个错误 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
使用WebSockets
Node中的WebSocket,UUID和其他基本要素不是核心部分。这意味着我们需要使用第三方库来使用它。但是,通过使用Deno标准库,你可以在许多其他应用程序中使用WebSockets和UUID。换句话说,您无需担心维护,因为现在它将始终得到维护。
要继续实施我们的简单聊天应用程序,请使用以下命令创建一个新文件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()
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
}
}
}
现在,注册一个端点/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'),
})
}
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使其能够直接使用模块,而无需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() {
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 = ''
}
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的工作方式。
第三方和deps.ts约定
通过直接导入模块的URL,我们可以像使用Deno标准库一样使用第三方库。
但是,https//deno.land/x/ 中的生态系统还很小。但是,对您来说,我有个好消息,我们可以使用 https://www.pika.dev 中的软件包。借助Parcel
或Minibundle
之类的工具,我们可以将Node库编译为模块,以在Deno项目中重复使用它们。
我们将使用camel-case程序包将每个聊天消息转换为camelCase!
让我们将此导入添加到chat.ts文件中:
import { camelCase } from 'https://cdn.pika.dev/camel-case@^4.1.1'
const message = camelCase(typeof event === 'string' ? event : '')
而已。再次运行,server.ts将下载该camel-case软件包。现在您可以看到它有效:
但是,如果我要camelCase在多个文件中使用此帮助程序,则在所有位置添加完整导入很麻烦。URL指示我们必须使用哪个版本的软件包。这意味着,如果要升级依赖项,则需要搜索并替换所有导入。这可能会给我们带来问题,但请不要担心,对于依赖项有一个Deno约定可以解决此问题。创建一个deps.ts文件以导出所有项目依赖项。
export { camelCase } from 'https://cdn.pika.dev/camel-case@^4.1.1'
和
import { camelCase } from './deps.ts'
const message = camelCase(typeof event === 'string' ? event : '')
测试
我们将构建一个无用的camilize.ts实用程序来返回camelCase中的文本,并附带一个额外的好处,即每个大写字母包含一个🐪。为什么?看看如何用Deno进行测试。
* Return the text in camelCase + how many 🐪
*
* @example "this is an example" -> "thisIsAnExample 🐪🐪🐪"
* @param text
* @returns {string}
*/
export function camelize(text: string) {
}
顺便说一下,我们可以使用来可视化文件的JSdocs deno doc [file]
:
➜ deno doc camelize.ts
function camelize(text: string)
Return the text in camelCase + how many 🐪
让我们创建一个文件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 🐪🐪🐪')
})
要运行所有测试,我们只需要执行即可deno test。
➜ deno 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)
当然,它失败是因为我们尚未实现实用程序,但是仍然可以看到错误在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}`
}
现在所有测试通过:
➜ 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)
如果您不想使用观察程序来执行所有测试,则可以基于nodemon 使用 https://deno.land/x/denon/,然后运行denon test。
现在我们准备在上使用我们的助手chat.ts。
调试
为了用Deno进行调试:
在代码中的某处添加debugger;
一行代码。
带--inspect-brk标志运行。deno run --inspect-brk ...
或deno test --inspect-brk ...
调试测试。
chrome://inspect在Chrome上打开页面。
在“远程目标”部分,按“检查”。
按“继续”脚本执行按钮,代码将在您的断点处暂停。
结论
通过在TypeScript中创建一个简单的聊天应用程序,我们了解了Deno的工作原理。我们在没有npm,package.json,node_modules,webpack,babel,jest,pettertier的情况下完成了此操作……因为我们不需要它们,因此Deno简化了这一过程。
我们探索了一个从Deno项目开始的重要事项:权限,deno命令,如何使用deno内部,如何使用第三方依赖项,服务文件,websocket,格式化文件,测试,调试等。
我希望本文对在2020年5月13日发布的项目中开始使用Deno 1.0.0有所帮助
本文代码
https://github.com/aralroca/chat-with-deno-and-preact
参考文献
评论