做者:Flavio Copes翻译:疯狂的技术宅javascript
原文:https://www.freecodecamp.org/...html
未经容许严禁转载前端
我每周都会探索一些新的项目,但不多会有像 Deno 这样吸引个人。java
在本文中,我会让你快速了解 Deno,并把它与 Node.js 进行比较,以此构建你的第一个 REST API。node
Deno 就像 Node,可是在不少方面都获得了深刻的改善。先从 Deno 功能列表开始:git
await
fetch
和全局 window
对象咱们将在本指南中探索全部这些功能。程序员
在你开始使用 Deno 并了解了其功能以后,Node.js 看起来就像是“旧的”东西。github
特别是由于 Node.js API 是基于回调的,它是在 promise 和 async/await以前编写的。 Node 中没有可用于修改的余地,这种修改的代价将会是巨大的。因此咱们只能用回调或大量的 API 调用。web
Node.js 很是棒,并将继续成为 JavaScript 世界中事实上的标准。可是我想咱们会逐渐看到 Deno 因为其一流的 TypeScript 支持和现代标准库而愈来愈被普遍的采用。面试
因为没有向后兼容性的报复,因此 Deno 能够用现代技术编写全部的东西。固然咱们没法保证十年以内在 Deno 身上也会发生一样的事情,而且会出现一项新技术,但这是目前的现实。
大约2年前,Node.js 的原始建立者 Ryan Dahl 在 JSConf EU 上宣布了 Deno(油管上的演讲视频),这很是有趣,若是你常常用 Node.js 和 JavaScript,那么它是必看的。
每一个项目经理都必须作出决定。 Ryan 对 Node 中的一些早期决定感到遗憾。此外,技术也在不断发展,现在的 JavaScript 与 2009 年 Node 创立时的语言已经彻底不一样。好比现代的 ES6/2016/2017 功能等。
因此他开始了一个新项目,用来建立第二波基于 JavaScript 的服务器端程序。
我如今而不是两年前写本文的缘由是,技术须要大量时间才能成熟。咱们终于达到了 Deno 1.0(1.0 在2020年5月13日发布),这是 Deno 正式宣布稳定的初版。
这彷佛只是一个数字,但 1.0 表示直到 Deno 2.0 才会有重大突破。当你采用一种新技术时,这很重要——你不想学习某些东西,由于它改变得太快。
这是一个大问题。
学习诸如 Deno 之类的新东西须要很大的努力。个人建议是,若是你如今开始使用服务器端 JS,而且还不了解 Node,而且从未编写过任何 TypeScript 代码,那么就从 Node 开始。没有人由于选择 Node.js 而被解雇。
可是,若是你喜欢 TypeScript,想要在任何地方使用 await
,但不想依赖项目中庞大的 npm 包,那么 Deno 可能就是你想要的。
答案是否认的。Node.js 是一项庞大的、完善的、得到了良好支持的技术,它将会持续数十年。
Deno 用 Rust 和 TypeScript 编写,这两种语言今天正在迅速发展。
特别是使用 TypeScript 意味着即便咱们选择用纯 JavaScript编写代码,也能够得到 TypeScript 的不少好处。
使用 Deno 运行 TypeScript 代码不须要编译步骤——Deno 会自动为你执行这一步骤。
你不会被迫使用 TypeScript 编写代码,可是 Deno 的核心是用 TypeScript 编写的这一事实是明显的。
首先愈来愈多的 JavaScript 程序员开始喜欢 TypeScript。
其次,你使用的工具能够推断出许多有关用 TypeScript 编写的软件的信息,例如 Deno。
这意味着,当咱们用 VS Code 进行编码时(因为两者都是在 MicroSoft 上开发的,所以与 TypeScript 紧密集成),能够在编写代码时得到类型检查和高级 IntelliSense 功能。换句话说,编辑器可以以很是有用的方式帮助咱们。
因为 Deno 基本上是 Node.js 的替代品,因此直接对二者比较很是有用。
类似之处:
差别:
npm
的官方包管理器。 Deno 没有,而是让你从 URL 导入任何 ES 模块。没有包管理器而且必须依靠 URL 来承载和导入包有利有弊。我真的很喜欢 pros:它很是灵活,咱们能够建立软件包而无需将其发布到 npm 这样的存储库中。
我认为会有某种包管理器出现,可是尚未官方的消息。
Deno 网站为第三方软件包提供代码托管(并经过 URL 分发):https://deno.land/x/
聊的够多了!下面开始安装 Deno。
在 Mac 上最简单的方法是用 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
生成 shell 补全doc
显示模块的文档eval
用来评估一段代码,例如 deno eval "console.log(1 + 2)"
fmt
内置的代码格式化程序(相似于 Go 中的 gofmt
)help
打印此消息或给定子命令的帮助info
显示有关缓存的信息或与源文件有关的信息install
把脚本做为可执行文件进行安装repl
读取评估打印循环(默认)run
运行为模块指定文件名或 URL 的程序test
运行测试types
打印运行时 TypeScript 声明upgrade
upgrade deno
to the newest versionupgrade
升级到最新版本的 deno
能够运行 deno help
来获取命令的特定其余说明,例如 deno run --help
。
就像帮助所说的那样,咱们能够用这个命令使 deno 来启动REPL(Read-Execute-Print-Loop),而无需任何其余操做。
这与运行 deno repl
相同。
这个命令的一种更常见的使用方法是执行包含在 TypeScript 文件中的 Deno 程序。
你能够同时运行 TypeScript(.ts
)文件与 JavaScript(.js
)文件。
若是你不熟悉 TypeScript,请不要担忧:尽管 Deno 是用 TypeScript 编写的,可是你也能够用 JavaScript 编写“客户端”程序。
让咱们运行第一个 Deno 应用程序。
我感到很是惊奇的是,甚至不须要写一行代码代码——你能够从任何 URL 运行命令。
Deno下载程序,进行编译,而后运行:
固然,从互联网上运行任意代码不是一种建议作法。不过咱们是从 Deno 官方网站上运行它的,另外,Deno 还有一个沙箱,能够阻止程序执行你不但愿作的任何事情。稍后再详细介绍。
这个程序很是简单,只需调用 console.log()
便可:
console.log('Welcome to Deno 🦕')
若是用浏览器打开 URL https://deno.land/std/example... ,则会看到如下页面:
奇怪吧?你可能但愿拿到 TypeScript 文件,可是却获得了一个网页。缘由是 Deno 网站的 Web 服务器知道你正在使用浏览器,并为你提供了更加用户友好的页面。
例如,用 wget
下载相同的UR,它要求使用 text/plain
版本而不是 text/html
:
若是你想再次运行该程序,那么如今它已由 Deno 缓存,不须要再次下载:
你可使用 `--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 网站还提供了一些其余例子,你能够在这找到它们:https://deno.land/std/examples/。
在撰写本文时,咱们能够找到:
cat.ts
: 打印做为参数提供的文件列表的内容catj.ts
: 打印做为参数提供的文件列表的内容chat/
: 一个聊天程序的实现colors.ts
: 一个例子curl.ts
: curl
的简单实现,可打印做为参数输入的 URL 的内容echo_server.ts
: 一个 TCP 回显服务器gist.ts
:一个将文件发布到 gist.github.com 的程序test.ts
:测试的例子welcome.ts
:一个简单的 console.log 语句(咱们在上面运行的第一个程序)xeval.ts
容许你为收到的任何标准输入行运行任何 TypeScript 代码。 曾经是 deno xeval
命令,但此后已从官方命令中删除。让咱们写一些代码。
你使用 deno run https://deno.land/std/examples/welcome.ts
运行的 Deno 程序是别人写的,因此你对 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' }) }
该代码从 http/server
模块导入 serve
函数。看到没?咱们没必要事先安装它,也不会像 Node 模块那样将其存储在本地计算机上。这是 Deno 安装如此之快的缘由之一。
从 https://deno.land/std/http/se... 会导入模块的最新版本。你可使用 @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()
函数的服务器,该服务器传递带有 port
属性的对象。
而后,咱们运行这个循环来响应来自服务器的每一个请求。
for await (const req of s) { req.respond({ body: 'Hello World\n' }) }
请注意,因为 Deno 实现了 top-level await,所以无需使用 await
关键字便可将其包装到 async
函数中。
让咱们在本地运行该程序。假设你用的是 VS Code,不过你可使用任何喜欢的编辑器。
我建议从 justjavac
安装 Deno 扩展(我尝试时有另外一个名称相同,但已弃用的扩展——未来可能会消失)
该扩展将提供一些可以给 VS Code 带来好处的实用工具,帮助你编写程序。
下面在文件夹中建立一个 app.ts
文件,并粘贴上面的代码:
而后用 deno run app.ts
运行:
Deno 首先下载咱们导入的依赖项,而后再下载所需的全部依赖项。
https://deno.land/std/http/se... 文件自己有多个依赖项:
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 在 Deno 简介演讲中提到过,有时你想在 Web 浏览器以外运行 JavaScript 程序,但又不想让它访问系统上任何内容,或使用网络与外界对话。
没有什么方法可以阻止 Node.js 应用获取你系统上的 SSH 密钥或任何其余的东西,并将其发送到服务器。这就是为何咱们一般只从受信任的源安装 Node 软件包的缘由。可是,咱们怎么知道本身使用的项目是否遭到黑客入侵,而其余人是否被黑客入侵呢?
Deno 尝试复制与浏览相同的权限模型。除非你明确容许,不然在浏览器中运行的 JavaScript 不会在你的系统上作任何使人做呕的事情。
回到 Deno,若是一个程序想要像之前那样访问网络,那么咱们须要给它权限。
能够经过在运行命令时传递一个标志来实现,在本例中是 --allow-net
:
deno run --allow-net app.ts
如今该程序能够在端口 8000 上运行 HTTP 服务器了:
有不少容许 Deno 解锁其余功能的标志:
--allow-env
容许环境访问--allow-hrtime
容许高精度的时间测量--allow-net =
容许网络访问--allow-plugin
容许加载插件--allow-read =
容许文件系统读访问--allow-run
容许运行子进程--allow-write =
容许文件系统写访问--allow-all
容许全部权限(与 -A
相同)net
、 read
和 write
的权限能够是细粒度的。例如你能够用 --allow-read=/dev
来容许从特定目录中读取
我喜欢 Go 语言的一个缘由是 Go 编译器附带的 gofmt
命令。全部的 Go 代码看起来都同样。每一个人都在用 gofmt
。
JavaScript 程序员习惯于运行 Prettier,deno fmt
其实是在后台运行的。
假设你有一个格式很乱的文件,以下所示:
运行 deno fmt app.ts
,它会自动被正确的格式化,还会在缺乏分号的地方自动添加:
尽管这个项目还很年轻,但 Deno 的标准库仍然很庞大。
其中包括:
archive
: tar 存档工具async
async utiltiesasync
:异步工具bytes
:用来操做字节片断的辅助datetime
: 日期/时间解析encoding
:各类格式的编码/解码功能flags
: 解析命令行标志fmt
: 格式化和打印fs
:文件系统 APIhash
:加密库http
: HTTP服务器io
: I/O 库log
: 日志实用工具mime
:支持 multipart 数据node
: Node.js 兼容性层path
:路径操做ws
: websockets让咱们查看另外一个 Deno 应用示例:cat
:
const filenames = Deno.args for (const filename of filenames) { const file = await Deno.open(filename) await Deno.copy(file, Deno.stdout) file.close() }
这会将 Deno.args
的内容赋值给 filenames
变量,这一变量是包含发送到命令的全部参数的变量。
咱们遍历它们,对于每个文件名咱们都用 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
假设你在同一文件夹中有上一个项目的 app.ts
,会看到权限错误:
由于在默认状况下 Deno 是不容许访问文件系统的。使用 --allow-read=./
能够授予对当前文件夹的访问权限:
deno run --allow-read=./ https://deno.land/std/examples/cat.ts app.ts
固然有相似的项目:
我想举一个简单的例子,说明如何用 Oak 构建 REST API。 Oak 之因此有趣,是由于它受到了流行的 Node.js 中间件 Koa 的启发,所以,若是你之前用过,将会很是熟悉。
要构建的API很是简单。咱们的服务器将会在内存中存储带有名称和年龄的狗的列表。
咱们想:
咱们将用 TypeScript 进行这些操做,固然你也能够用 JavaScript 编写 API —— 只需去掉类型就能够了。
建立一个 app.ts
文件。
首先从 Oak 导入 Application
和 Router
对象:
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 端口。
下次运行命令时,Deno 将会跳过安装部分,由于这些软件包已经被缓存了:
在文件开始,咱们为狗定义一个接口,而后声明一个初始的 Dog 对象 dogs
数组:
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}` } }
这是咱们添加新 dog 的方法:
export const addDog = async ({ request, response, }: { request: any response: any }) => { const body = await request.body() const dog: Dog = body.value dogs.push(dog) response.body = { msg: 'OK' } response.status = 200 }
注意,我如今使用 const body = await request.body()
来获取 body 的内容,由于 name
和 age
的值是做为 JSON 传递的。
这是更新狗年龄的方法:
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 } = 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 } = 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 } = 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}`)
Deno 官方网站是 https://deno.land
可在 https://doc.deno.land 和 https://deno.land/typedoc/ind... 上获得 API 文档。
awesome-deno https://github.com/denolib/aw...
fetch
实现,与浏览器中的相匹配但愿你喜欢这个 Deno 教程!