朋友们,美好的一天!
我提请您注意Flavio Copes撰写的文章“ Deno手册:带有代码示例的TypeScript运行时教程”的翻译。
在本文中,我们将学习如何与Deno合作。我们将其与Node.js进行比较,并使用它构建一个简单的REST API。
什么是迪诺?
如果您熟悉流行的服务器端JavaScript生态系统Node.js,则Deno几乎是同一回事。差不多,但是不完全是。
让我们从我最喜欢的Deno功能列表开始:
- 它基于现代JavaScript
- 它具有可扩展的标准库
- 它具有标准的TypeScript支持(这意味着您不必手动编译TypeScript,Deno会自动进行编译)
- 支持ES模块
- 它没有包管理器
- 它具有全球性
await
- 它具有内置的测试设施
- 其目标是最大程度地提高浏览器兼容性。为此,它提供了一个内联
fetch
对象和一个全局对象window
在本教程中,我们将探讨所有这些可能性。
熟悉Deno及其功能之后,Node.js似乎有点过时了。
特别是因为Node.js基于回调函数(它是在promise和async / await之前编写的)。它们不太可能出现在此处,因为这意味着需要进行根本的更改。
Node.js非常棒,并且将仍然是JavaScript世界中的事实上的标准。但是,我相信Deno将由于其TypeScript支持和现代标准库而迅速普及。
Deno可以负担得起现代代码,因为它不需要向后兼容。当然,不能保证此代码在下一个十年内会保持最新,但是今天仍然如此。
为什么是德诺?为什么现在?
Deno是大约2年前由JSConf EU的Node.js创建者Ryan Dahl宣布的。观看YouTube视频,它非常有趣,并且必须查看您是否正在使用Node.js和JavaScript。
每个项目经理(创建者)都必须做出决定。 Ryan对Node中的一些早期决定感到遗憾。此外,技术日新月异,如今的JavaScript与2009年的Node完全不同。回想一下ES6 / 2016/2017等。
因此,他决定启动一个新项目,这是服务器端JavaScript应用程序的第二次浪潮。
我现在只写这篇文章的原因是因为这项技术要花很长时间才能成熟。最终,我们获得了Deno 1.0(它于2020年5月13日发布),这是第一个稳定的版本。
这似乎是一个普通数字,但1.0表示直到Deno 2.0之前都不会有任何大的变化。学习新技术时,您不希望它变化太快。
您应该学习Deno吗?
好问题。
学习像Deno这样的新东西需要付出很多努力。我的建议:如果您刚开始使用服务器端JS并且还不了解Node.js,并且以前从未编写过TypeScript,请从Node开始。
从来没有人因为选择Node而被解雇(用著名的说法解释)。
但是,如果您喜欢不依赖于大量npm软件包的TypeScript,并且想在各处使用它
await
,那么Deno可能就是您想要的。
它将取代Node.js吗?
没有。Node.js是一个巨大的权威,这是一种获得了令人难以置信的良好支持的技术,该技术在未来十年内不会出现。
一流的TypeScript支持
Deno用Rust和TypeScript编写,这两种语言在当今世界都很流行。
这意味着即使我们正在编写JavaScript,我们也可以从TypeScript中获得很多好处。
使用Deno运行TypeScript代码不需要编译-Deno会自动进行编译。
您不必编写TypeScript代码,但是Deno的核心是用TypeScript编写的事实带来了巨大的变化。
首先,很大一部分JavaScript开发人员喜欢TypeScript。
其次,您使用的工具可以获得有关TypeScript软件(如Deno)的大量信息。
这意味着,例如,当我们使用VS Code编写代码(自诞生以来就与TypeScript紧密集成)时,我们将获得诸如编写代码时进行类型检查或高级IntelliSense功能的好处。换句话说,代码编辑器的帮助变得更加高效。
与Node.js的区别
由于Deno本质上是Node.js的替代品,因此比较两者是有意义的。
一般:
- 两者均基于V8引擎
- 两者都非常适合服务器端JavaScript开发
差异:
- Node用C ++和JavaScript编写。Deno用Rust和TypeScript编写。
- Node有一个正式的软件包管理器
npm
。Deno没有这样的管理器,而是让您使用URL导入任何模块。 - Node使用CommonJS语法导入软件包。Deno使用官方方式-ES模块。
- Deno ECMAScript , Node.js .
- Deno () . . Node.js , .
- Deno , .. , , Go, . .
没有程序包管理器并使用URL来获取和导入程序包有其优点和缺点。主要优点之一是创建包而无需将其发布到npm之类的存储库所具有的极大灵活性。
我认为Deno软件包管理器的某些替代方案迟早会出现。
Deno官方网站托管第三方软件包:https : //deno.land/x/
安装Deno
够说话了!让我们安装Deno。
最简单的方法是使用Homebrew:
brew install deno
其他安装方法在此处列出。
安装后,该命令变为可用
deno
。输入以下内容即可获得帮助deno --help
:
flavio@mbp~> deno --help
deno 0.42.0
A secure JavaScript and TypeScript runtime
Docs: https://deno.land/std/manual.md
Modules: https://deno.land/std/ https://deno.land/x/
Bugs: https://github.com/denoland/deno/issues
To start the REPL, supply no arguments:
deno
To execute a script:
deno run https://deno.land/std/examples/welcome.ts
deno https://deno.land/std/examples/welcome.ts
To evaluate code in the shell:
deno eval "console.log(30933 + 404)"
Run 'deno help run' for 'run'-specific flags.
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
By default, subcommands print human-readable diagnostic messages to stderr.
If the flag is set, restrict these messages to errors.
-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
迪诺队
您是否注意到该部分
SUBCOMMANDS
?这是我们可以运行的所有命令的列表。我们有哪些团队?
bundle
-将模块和项目依赖项收集到一个文件中cache
-缓存依赖项completions
-生成贝壳笔芯doc
-显示模块的文档eval
-用于计算代码块,例如deno eval "console.log(1 + 2)"
fmt
-内置代码格式化程序(如goFmt
Go中的代码)help
-显示辅助命令列表info
-显示有关缓存或文件的信息install
-将脚本设置为可执行repl
-读取-计算-输出周期(默认)run
-使用模块的给定名称或URL运行程序test
-运行测试types
-显示TypeScript功能列表upgrade
-将Deno更新到最新版本
您可以运行
deno <subcommand> help
以获取有关特定命令的信息,例如deno run --help
。
我们可以使用命令
deno
来启动read-evaluate-output循环:
这与starting相同
deno repl
。
通常
deno
用于启动TypeScript文件中包含的Deno应用程序。
您可以同时运行TypeScript文件(.ts)和JavaScript文件(.js)。
如果您不熟悉TypeScript,请不要担心:Deno是用TypeScript编写的,但是您可以使用JavaScript编写客户端应用程序。
Deno上的第一个应用
让我们创建第一个应用程序。
为此,我们甚至不必编写代码,我们将使用URL在终端中运行它。
Deno下载程序,对其进行编译并运行:
当然,我不建议从Internet运行随机代码。在这种情况下,我们将从Deno的官方站点启动它,另外Deno还有一个沙箱,可以阻止程序执行我们明确不允许他们执行的操作。
这个程序非常简单,可以调用
console.log()
:
console.log('Welcome to Deno ') // ,
如果在浏览器中打开https://deno.land/std/examples/welcome.ts,则会看到以下内容:很
奇怪,不是吗?您可能希望看到TypeScript文件,但得到了一个网页。关键是Deno网站服务器知道您正在使用浏览器,并为您提供了更加用户友好的页面。
加载使用相同的URL
wget
,例如,和得到text/plain
代替text/html
:
当你重新启动程序,这要归功于缓存,不需要重新启动:一个强制重新启动可以进行
使用标志
--reload
:它
deno run
有没有通过显示许多不同的功能deno --help
。为了查看它们,您应该运行deno run --help
:
flavio@mbp~> deno run --help
deno-run
Run a program given a filename or url to the module.
By default all programs are run in sandbox without access to disk, network or
ability to spawn subprocesses.
deno run https://deno.land/std/examples/welcome.ts
Grant all permissions:
deno run -A https://deno.land/std/http/file_server.ts
Grant permission to read from disk and listen to network:
deno run --allow-read --allow-net https://deno.land/std/http/file_server.ts
Grant permission to read whitelisted files from disk:
deno run --allow-read=/etc https://deno.land/std/http/file_server.ts
USAGE:
deno run [OPTIONS] <SCRIPT_ARG>...
OPTIONS:
-A, --allow-all
Allow all permissions
--allow-env
Allow environment access
--allow-hrtime
Allow high resolution time measurement
--allow-net=<allow-net>
Allow network access
--allow-plugin
Allow loading plugins
--allow-read=<allow-read>
Allow file system read access
--allow-run
Allow running subprocesses
--allow-write=<allow-write>
Allow file system write access
--cached-only
Require that remote dependencies are already cached
--cert <FILE>
Load certificate authority from PEM encoded file
-c, --config <FILE>
Load tsconfig.json configuration file
-h, --help
Prints help information
--importmap <FILE>
UNSTABLE:
Load import map file
Docs: https://deno.land/std/manual.md#import-maps
Specification: https://wicg.github.io/import-maps/
Examples: https://github.com/WICG/import-maps#the-import-map
--inspect=<HOST:PORT>
activate inspector on host:port (default: 127.0.0.1:9229)
--inspect-brk=<HOST:PORT>
activate inspector on host:port and break at start of user script
--lock <FILE>
Check the specified lock file
--lock-write
Write lock file. Use with --lock.
-L, --log-level <log-level>
Set log level [possible values: debug, info]
--no-remote
Do not resolve remote modules
-q, --quiet
Suppress diagnostic output
By default, subcommands print human-readable diagnostic messages to stderr.
If the flag is set, restrict these messages to errors.
-r, --reload=<CACHE_BLACKLIST>
Reload source code cache (recompile TypeScript)
--reload
Reload everything
--reload=https://deno.land/std
Reload only standard modules
--reload=https://deno.land/std/fs/utils.ts,https://deno.land/std/fmt/colors.ts
Reloads specific modules
--seed <NUMBER>
Seed Math.random()
--unstable
Enable unstable APIs
--v8-flags=<v8-flags>
Set V8 command line options. For help: --v8-flags=--help
ARGS:
<SCRIPT_ARG>...
script args
代码示例
在Deno网站上还有其他示例,可以在这里找到。
在撰写本文时,可以在指定的存储库中找到以下内容:
cat.ts
-显示作为参数传递的文件的内容catj.ts
-做同样的事情,cat.ts
但是首先对文件的内容执行一些操作chat/
-聊天实现colors.ts
-使用模块样式化文本的示例curl.ts
-curl
输出作为参数传递的URL内容的简单实现echo_server.ts
-TCP回显服务器gist.ts
-在gist.github.com中放置文件的程序test.ts
-测试程序welcome.ts
-我们启动的程序xeval.ts
-允许您运行从任何标准数据源获得的TypeScript。deno xeval
已从官方队伍名单中删除
Deno上的第一个真实应用
让我们写一些代码。
我们启动的第一个应用程序
deno run https://deno.land/std/examples/welcome.ts
已经编写,因此您对Deno并没有学到任何新知识。
让我们从Deno网站上发布的标准示例开始:
import { serve } from 'https://deno.land/std/http/server.ts'
const s = serve({ port: 8000 })
console.log('http://localhost:8000/')
for await (const req of s) {
req.respond({ body: 'Hello World\n' })
}
在这里,我们
serve
从模块导入函数http/server
。看到?我们不必安装它,它也不会像Node模块一样存储在我们的计算机上。这是快速安装Deno的原因之一。
在帮助下,
https://deno.land/std/http/server.ts
我们导入了模块的最新版本。可以使用@VERSION
以下命令导入特定版本:
import { serve } from 'https://deno.land/std@v0.42.0/http/server.ts'
这是什么功能
serve
:
/**
* Create a HTTP server
*
* import { serve } from "https://deno.land/std/http/server.ts";
* const body = "Hello World\n";
* const s = serve({ port: 8000 });
* for await (const req of s) {
* req.respond({ body });
* }
*/
export function serve(addr: string | HTTPOptions): Server {
if (typeof addr === 'string') {
const [hostname, port] = addr.split(':')
addr = { hostname, port: Number(port) }
}
const listener = listen(addr)
return new Server(listener)
}
接下来,我们调用该函数
serve()
并将其传递给带有property的对象port
。
然后,我们运行一个循环以响应服务器的每个请求:
for await (const req of s) {
req.respond({ body: 'Hello World\n' })
}
请注意,我们使用关键字时
await
并未将代码包装在async
函数中。
让我们在本地运行程序。我正在使用VS Code,但是您可以使用任何编辑器。
我建议从justjavac安装扩展(存在另一个具有类似名称的扩展,但已弃用,并且将来可能会消失):该
扩展提供了多个实用程序来帮助您编写Deno应用程序。
让我们创建一个文件
app.ts
并将其代码粘贴到其中:使用以下命令
运行
deno run app.ts
:
Deno将加载程序所需的所有依赖项,但首先加载文件中导入的依赖项。https://deno.land/std/http/server.ts
文件具有多个依存关系:
import { encode } from '../encoding/utf8.ts'
import { BufReader, BufWriter } from '../io/bufio.ts'
import { assert } from '../testing/asserts.ts'
import { deferred, Deferred, MuxAsyncIterator } from '../async/mod.ts'
import {
bodyReader,
chunkedBodyReader,
emptyReader,
writeResponse,
readRequest,
} from './_io.ts'
import Listener = Deno.Listener
import Conn = Deno.Conn
import Reader = Deno.Reader
这些依赖项将自动导入。
最后,我们有一个问题:
怎么回事?我们收到一个权限被拒绝的错误。
让我们谈谈沙盒。
沙盒
如前所述,Deno具有一个沙箱,可以防止程序执行未经其许可的程序。
这是什么意思?
正如Ryan在演讲中所说,有时候您想在浏览器之外运行JavaScript程序,而又不想让程序访问系统中的所有内容。或使用网络访问外界。
没有什么可以阻止Node.js应用程序从系统中获取SSH密钥或其他信息并将其发送到服务器的。这就是为什么我们通常只从受信任的源安装Node软件包。但是,我们如何知道我们正在使用的项目之一是否遭到黑客入侵?
Deno模仿浏览器使用的权限系统。在浏览器中运行的JavaScript代码无法对您的系统执行任何操作,除非您明确允许这样做。
回到Deno,如果程序需要网络访问,我们必须授予它访问权限。
这是使用标志完成的
--allow-net
:
deno run --allow-net app.ts
服务器现在在端口8000上运行:
其他标志:
--allow-env
-允许访问环境变量--allow-hrtime
-允许高分辨率测量--allow-net=<allow-net>
-允许访问网络--allow-plugin
-允许加载插件--allow-read=<allow-read>
-允许读取文件--allow-run
-允许启动子流程--allow-write=<allow-write>
-允许写入文件--allow-all
-授予所有权限(类似-A
)
的许可
net
,read
并且write
可能是部分许可。例如,我们可以允许位于某个目录中的只读文件:--allow-read=/dev
。
代码格式
我喜欢Go的一件事就是命令
gofmt
。所有的Go代码看起来都一样。每个人都在使用它gofmt
。
JavaScript开发人员通常使用Prettier,
deno fmt
实际上也可以使用Prettier 。
假设您有一个格式错误的文件:
您开始
deno fmt app.ts
并且在缺少分号的情况下进行自动格式化:
标准库
尽管该项目年代久远,但Deno的标准库仍然相当广泛。
它包括以下内容:
archieve
-归档实用程序async
-用于处理异步代码的实用程序bytes
-分割字节的辅助函数datetime
-解析日期/时间encoding
-不同格式的编码/解码flags
-解析命令行标志fmt
-形成和展示fs
-用于文件系统的应用程序界面hash
-加密库http
-HTTP服务器io
-输入/输出操作库log
-记录实用程序mime
-混合数据支持node
-具有Node的向后兼容层path
-使用路径ws
-网络插座
再举一个例子
让我们看看另一个官方示例-
cat.ts
:
const filenames = Deno.args
for (const filename of filenames) {
const file = await Deno.open(filename)
await Deno.copy(file, Deno.stdout)
file.close()
}
我们将
filenames
内容分配给一个变量Deno.args
,该变量是一个包含使用命令行传递的参数的变量。
我们遍历它们,对于每一个,我们首先使用它们
Deno.open()
打开文件,然后Deno.copy()
将内容复制到Deno.stdout
。最后,我们关闭文件。
如果您运行:
deno run https://deno.land/std/examples/cat.ts
该程序将加载并编译,但是没有任何反应,因为我们没有传递任何参数。
现在让我们尝试一下:
deno run https://deno.land/std/examples/cat.ts app.ts
我们
收到权限错误:默认情况下,Deno无权访问系统。让我们通过以下方式授予他这种许可
--allow-read
:
deno run --allow-read=./ https://deno.land/std/examples/cat.ts app.ts
Deno是否存在Express / Hapi / Koa / *?
哦没问题。看一下以下项目:
示例:使用Oak创建REST API
我将使用Oak创建一个REST API。Oak之所以有趣,是因为它受到Kode(NOde.js流行的中间件)的启发,并具有类似的语法。
我们的API非常简单。
我们的服务器将在内存中存储狗的列表,它们的名称和年龄。
我们想要获得以下功能:
- 将新狗添加到列表中
- 获取所有狗的清单
- 获取有关特定狗的信息
- 从名单上删除一只狗
- 更新狗的年龄
我们将使用Typescript编写代码,但是没有什么可以阻止您使用JavaScript编写代码-只是不指定数据类型。
我们创建一个文件
app.ts
。
首先
Application
,Router
从和导入对象Oak
:
import { Application, Router } from 'https://deno.land/x/oak/mod.ts'
我们得到环境变量PORT和HOST:
const env = Deno.env.toObject()
const PORT = env.PORT || 4000
const HOST = env.HOST || '127.0.0.1'
默认情况下,我们的应用程序将在localhost:4000上运行。
创建一个Oak应用程序并启动它:
const router = new Router()
const app = new Application()
app.use(router.routes())
app.use(router.allowedMethods())
console.log(`Listening on port ${PORT}...`)
await app.listen(`${HOST}:${PORT}`)
该应用程序现在应该可以工作了。
我们检查:
deno run --allow-env --allow-net app.ts
Deno下载依赖项:
并开始侦听端口4000。
重新启动后,由于进行了缓存,将跳过安装步骤:
定义狗的接口,然后定义
dog
包含对象的数组Dog
:
interface Dog {
name: string
age: number
}
let dogs: Array<Dog> = [
{
name: 'Roger',
age: 8,
},
{
name: 'Syd',
age: 7,
},
]
让我们开始实现API。
一切就绪。让我们为访问指定端点的路由器添加一些功能:
const router = new Router()
router
.get('/dogs', getDogs)
.get('/dogs/:name', getDog)
.post('/dogs', addDog)
.put('/dogs/:name', updateDog)
.delete('/dogs/:name', removeDog)
我们定义了以下内容:
GET /dogs
GET /dogs/:name
POST /dogs
PUT /dogs/:name
DELETE /dogs/:name
让我们一一实现这些路由。
让我们从开始
GET /dogs
,它返回所有狗的列表:
export const getDogs = ({ response }: { response: any }) => {
response.body = dogs
}
以下是通过名称获取特定狗的方法:
export const getDog = ({
params,
response,
}: {
params: {
name: string
},
response: any
}) => {
const dog = dogs.filter(dog => dog.name === params.name)
if (dog.length) {
response.status = 200
response.body = dog[0]
return
}
response.status = 400
response.body = { msg: `Cannot find dog ${params.name}` }
}
这是将新狗添加到列表的方法:
export const addDog = async ({
request,
response,
}: {
request: any
response: any
}) => {
const body = await request.body()
const dog: Dog = await body.value
dogs.push(dog)
response.body = { msg: 'OK' }
response.status = 200
}
这是更新狗的年龄的方法:
export const updateDog = async ({
params,
request,
response,
}: {
params: {
name: string
},
request: any
response: any
}) => {
const temp = dogs.filter((existingDog) => existingDog.name === params.name)
const body = await request.body()
const { age }: { age: number } = await body.value
if (temp.length) {
temp[0].age = age
response.status = 200
response.body = { msg: 'OK' }
return
}
response.status = 400
response.body = { msg: `Cannot find dog ${params.name}` }
}
以下是从列表中删除狗的方法:
export const removeDog = ({
params,
response,
}: {
params: {
name: string
},
response: any
}) => {
const lengthBefore = dogs.length
dogs = dogs.filter((dog) => dog.name !== params.name)
if (dogs.length === lengthBefore) {
response.status = 400
response.body = { msg: `Cannot find dog ${params.name}` }
return
}
response.body = { msg: 'OK' }
response.status = 200
}
完整的应用程序代码:
import { Application, Router } from 'https://deno.land/x/oak/mod.ts'
const env = Deno.env.toObject()
const PORT = env.PORT || 4000
const HOST = env.HOST || '127.0.0.1'
interface Dog {
name: string
age: number
}
let dogs: Array<Dog> = [
{
name: 'Roger',
age: 8,
},
{
name: 'Syd',
age: 7,
},
]
export const getDogs = ({ response }: { response: any }) => {
response.body = dogs
}
export const getDog = ({
params,
response,
}: {
params: {
name: string
},
response: any
}) => {
const dog = dogs.filter(dog => dog.name === params.name)
if (dog.length) {
response.status = 200
response.body = dog[0]
return
}
response.status = 400
response.body = { msg: `Cannot find dog ${params.name}` }
}
export const addDog = async ({
request,
response,
}: {
request: any
response: any
}) => {
const body = await request.body()
const { name, age }: { name: string; age: number } = await body.value
dogs.push({
name: name,
age: age,
})
response.body = { msg: 'OK' }
response.status = 200
}
export const updateDog = async ({
params,
request,
response,
}: {
params: {
name: string
},
request: any
response: any
}) => {
const temp = dogs.filter((existingDog) => existingDog.name === params.name)
const body = await request.body()
const { age }: { age: number } = await body.value
if (temp.length) {
temp[0].age = age
response.status = 200
response.body = { msg: 'OK' }
return
}
response.status = 400
response.body = { msg: `Cannot find dog ${params.name}` }
}
export const removeDog = ({
params,
response,
}: {
params: {
name: string
},
response: any
}) => {
const lengthBefore = dogs.length
dogs = dogs.filter(dog => dog.name !== params.name)
if (dogs.length === lengthBefore) {
response.status = 400
response.body = { msg: `Cannot find dog ${params.name}` }
return
}
response.body = { msg: 'OK' }
response.status = 200
}
const router = new Router()
router
.get('/dogs', getDogs)
.get('/dogs/:name', getDog)
.post('/dogs', addDog)
.put('/dogs/:name', updateDog)
.delete('/dogs/:name', removeDog)
const app = new Application()
app.use(router.routes())
app.use(router.allowedMethods())
console.log(`Listening on port ${PORT}...`)
await app.listen(`${HOST}:${PORT}`)
感谢您的关注。