一起学Deno - 开发前准备

ES6

在下来的教程前,你必须先掌握一些ES6的基本技能。

ES6是当前前端的标配,具体就不讲了。

推荐阮一峰老师的《ES6 入门教程》,最好全文都能阅读一遍,能上手敲一遍代码就更好了。

其中,Generator简单了解下就行,日常工作中通常是用不到的。

Promise和async非常重要,相较之下,async优先级更高些,Promise务必掌握Promise.all、Promise.race等常用的API。

TypeScript

Deno集成了TypeScript,开箱即用,所以你也需要一些TypeScript的基础知识。

TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。其设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。

所以你要理解一点,Deno并不是真正的TypeScript运行时,它只是在内部将TypeScript编译成了JavaScript,运行的还是JavaScript。

TypeScript 是一种给 JavaScript 添加特性的语言扩展。增加的功能包括:

  • 类型批注和编译时类型检查
  • 类型推断
  • 类型擦除
  • 接口
  • 枚举
  • Mixin
  • 泛型编程
  • 名字空间
  • 元组

你可以看看官网教程,又或者菜鸟教程。阮一峰大神也写了一本教程

重点掌握类型、接口、枚举、泛型,可以讲,普通的接口和类型足以应付日常80%以上的开发工作了。深入的话就需要研究类型体操了,如果你做底层框架开发多半能用上。

Deno简介

Deno(/diːnoʊ/, pronounced dee-no)简单说是Node.js的替代品,是Node.js之父Ryan Dahl 为挽回Node.js的错误而开发的。

Node.js存在的问题有:

  1. npm包管理(npm_modules)复杂。
  2. 历史原因导致的api维护,比如早期变态的callback设置。
  3. 没有安全措施,用户只要下载了外部模块,就只好听任别人的代码在本地运行,进行各种读写操作。
  4. 功能不完善,导致各种工具层出不穷,比如webpack、babel等。

由于上面这些原因,Ryan Dahl 决定放弃 Node.js,从头写一个替代品,彻底解决这些问题。

Deno 这个名字就是来自 Node 的字母重新组合(Node = no + de),表示"拆除 Node.js"(de = destroy, no = Node.js)。

跟 Node.js一样,Deno 也是一个服务器运行时,但是支持多种语言,可以直接运行 JavaScript、TypeScript 和 WebAssembly 程序。

  • 它内置了 V8 引擎,用来解释 JavaScript。同时,也内置了 tsc 引擎,解释 TypeScript。
  • 它使用 Rust 语言开发,由于 Rust 原生支持 WebAssembly,所以它也能直接运行 WebAssembly。
  • 它的异步操作不使用 libuv 这个库,而是使用 Rust 语言的 Tokio 库,来实现事件循环(event loop)。

它的架构如下图所示:

说明:

1、Rust 是由 Mozilla 主导开发的通用、编译型编程语言。设计准则为 "安全、并发、实用",支持函数式、并发式、过程式以及面向对象的编程风格。Deno 使用 Rust 语言来封装 V8 引擎,通过 libdeno 绑定,我们就可以在 JavaScript 中调用隔离的功能。

2、Tokio 是 Rust 编程语言的异步运行时,提供异步事件驱动平台,构建快速,可靠和轻量级网络应用。利用 Rust 的所有权和并发模型确保线程安全。Tokio 构建于 Rust 之上,提供极快的性能,使其成为高性能服务器应用程序的理想选择。在 Deno 中 Tokio 用于并行执行所有的异步 IO 任务。

3、V8 是一个由 Google 开发的开源 JavaScript 引擎,用于 Google Chrome 及 Chromium 中。V8 在运行之前将JavaScript 编译成了机器代码,而非字节码或是解释执行它,以此提升性能。更进一步,使用了如内联缓存(inline caching)等方法来提高性能。有了这些功能,JavaScript 程序与 V8 引擎的速度媲美二进制编译。在 Deno 中,V8 引擎用于执行 JavaScript 代码。

4、Deno与Node.js的核心模型是一致的,都是异步非阻塞I/O。所以Deno与Node.js在使用场景上基本一致,适合I/O密集型,不适用于CPU密集型。开发中要避免使用同步I/O和复杂计算场景。

优势

Deno凭什么来吸引开发者改换门庭,转投它的怀抱呢?于我而言,主要有几下几点:

1、天然支持TypeScript。如果用Node.js,需要我们手动将TS编译成JS,或者使用ts-node这种第三方工具。Deno不需要配置,开箱即用。

2、内存安全。相较于Node.js,它的性能优势并不明显,因为就是JS换了个运行环境而已,Rust并不见得比C++更高效,不过Rust会天然让它的内存安全和核心代码的健壮性更有保障些。

3、安全机制。除非明确启用,否则没有文件,网络或环境访问权限。对于运行权限的划分,能清晰知道你的程序需要拥有哪些权限,对网络资源没有信任的同学可以放心了。

4、没有历史包袱。不像Node.js一开始自定义了commonjs规范,Deno直接支持ES Modules。没有回调地狱,代码结构更清晰,利于tree shaking;也可以直接使用顶级await。

5、去中心化。不用每个工程都安装一大堆node_modules,有个线上的url就能获取代码。

6、内置浏览器API。Deno实现了fetch、FormData、WebSocket等浏览器的API,虽然被某些开发者诟病,但对前端开发者而言是真的友好,比如进行接口调用,与在浏览器里使用几乎没有区别。

7、全家桶似的服务,比如打包、格式化、代码校验,可以让你更专注业务,而不是成为某工具的配置工程师。

缺点

Deno的缺点也很明显:

1、没有针对Node.js压倒性的性能优势,所以对以上优势不感兴趣的开发者而言,就没有吸引力了。

Deno 是一个合适的异步服务器,每秒 25k 请求足以满足大多数目的,此外,由于普遍使用 Promise,Deno 有更好的尾部延迟。目前 Deno HTTP 服务器每秒处理约 25 000 个请求,最大延迟为 1.3 毫秒,与之相比,Node 程序每秒处理 34 000 个请求,最大延迟介于 2 到 300 毫秒之间。

可以看出,Deno的优势在于延迟低,但并没有压倒性优势。

2、无法完全继承Node.js的生态。虽然Deno提供了几款CDN服务,可以将旧的npm包转换为Deno可使用的格式,但并非所有npm包都能转换成功。因为Node.js仍有部分API官方没有实现,如果某个偏底层的包转换失败,所有依赖它的包都无法成功。所以,你很有可能在开发的某个环节需要造轮子。当然,这对于喜欢钻研技术的同学而言反而是个利好。(截止到2023年3月,百分之九十以上的Node.js API都已经实现了,所以大部分npm包已经可以直接使用了)

3、去中心化后,国内就没有办法像npm做镜像(其实可以配置代理,但目前国内并没有相关的服务)。官方搭建deno.land来存储github上各种资源,但目前只支持github上私有资源。想要引用私有文件,就必须自己搭建服务器,在CICD中构建的话,又有额外的技术难点需要解决。

官网

首页:deno.land/

官方教程:deno.land/manual

官方API:doc.deno.land/deno/stable

官方标准库地址:deno.land/std

第三方库地址:deno.land/x

建议先把官方教程过一遍。

安装Deno

命令安装

shell 复制代码
# Shell (Mac, Linux):
$ curl -fsSL https://deno.land/install.sh | sh

# PowerShell (Windows):
$ iwr https://deno.land/install.ps1 -useb | iex

# Homebrew (Mac):
$ brew install deno

# Chocolatey (Windows):
$ choco install deno

# Scoop (Windows):
$ scoop install deno

# Build and install from source using Cargo:
$ cargo install deno --locked

下载资源安装

也可以直接在Github上下载对应的发行版本文件,如果你是windows用户,推荐使用这种。

当然,Mac与Linux也可以这样安装,但最新的Mac系统可能会因为安全权限而禁止程序使用,需要参考这里配置下。

设置环境变量

关键要设置两个环境变量,一是Deno二进制文件的目录,二是使用Deno安装的全局命令的目录。

Mac/Linux命令行操作

如果你是使用Mac/Linux命令行直接安装的,会有提示信息,让你设置:

bash 复制代码
 export DENO_INSTALL="$HOME/.deno"

也可以直接用下面命令:

ini 复制代码
echo 'export DENO_INSTALL="$HOME/.deno"

windows版本

我的电脑 -> 属性 -> 高级系统设置 -> 高级 -> 环境变量设置,上面用户变量和系统变量都可以配置。

比如把下载的deno.exe放在c:/bin目录下:

那就需要设置在path的环境变量里添加一条:

另一个环境变量就是 DENO_INSTALL了,与Linux一样,通常是$HOME/.deno,比如我的是C:\Users\Administrator.deno,把它的bin目录添加到path中。

验证

打开命令窗口:

css 复制代码
deno --version

成功了后能看到:

如果失败,极可能是环境变量没有生效,windows用户建议重启电脑。

升级

复制代码
deno upgrade

Deno基础

下面这些Deno基础需要了解下。

安全机制

Deno 具有安全控制,默认情况下脚本不具有读写权限。如果脚本未授权,就读写文件系统或网络,会报错。

必须使用参数,显式打开权限才可以。

css 复制代码
--allow-read:打开读权限,可以指定可读的目录,比如--allow-read=/temp。
--allow-write:打开写权限。
--allow-net=google.com:允许网络通信,可以指定可请求的域,比如--allow-net=google.com。
--allow-env:允许读取环境变量。
--allow-run:允许运行子进程。

要使用不稳定的新特性,需要这样:

css 复制代码
--unstable

还有新加的--allow-ffi--allow-hrtime ,一般用不到,这里不再赘述,请情参考官方文档。

运行

使用deno run来运行程序,当然,一般需要加上上面的安全机制。

  1. 如果你的程序什么权限都没使用,只是下面这种:
ts 复制代码
console.log('app start');

那么直接用deno run main.ts就可以了。注意,我们推荐使用ts,但也可以运行js。

  1. 如果需要读取文件,比如:
ts 复制代码
const decoder = new TextDecoder("utf-8");
const data = await Deno.readFile("hello.txt");
console.log(decoder.decode(data));

自然得这样:deno run --allow-read main.ts

  1. 如果需要网络,比如开启一个tcp:
ts 复制代码
/**
 * echo_server.ts
 */
import { copy } from "https://deno.land/std@0.106.0/io/util.ts";
const listener = Deno.listen({ port: 8080 });
console.log("listening on 0.0.0.0:8080");
for await (const conn of listener) {
  copy(conn, conn).finally(() => conn.close());
}

你必须:deno run --allow-net main.ts

  1. 如果你用了不稳定的API,那么也需要显示指定--unstable
  2. 如果懒得管理权限,则使用-Adeno run -A main.ts),那就后果自负了。

更新模块

默认代码中引用中的ts或js文件,会缓存到本地。如果你引用的文件url中没有带版本号,那么就是最新版的代码。这时,你想要更新代码,那么你需要这样操作:

css 复制代码
deno cache --reload my_module.ts

更新线上与本地文件:

ini 复制代码
deno cache --reload=https://deno.land/std@0.106.0 my_module.ts

更新线上某个文件与某文件夹:

ini 复制代码
deno cache --reload=https://deno.land/std@0.106.0/fs/copy.ts,https://deno.land/std@0.106

锁定文件校验

如果你在开发一个底层库或框架供他人使用,那么推荐创建一个deps.ts文件,来管理外部引用的文件。其实就是起到类似package-lock.json的作用,管理引用的文件与版本。

ts 复制代码
// src/deps.ts
export { xyz } from "https://unpkg.com/xyz-lib@v0.9.0/lib.ts";

然后用deno cache 生成一个lock.json文件:

ts 复制代码
# Create/update the lock file "lock.json".
deno cache --lock=lock.json --lock-write src/deps.ts

另一台机器克隆下代码,运行后:

ts 复制代码
# Download the project's dependencies into the machine's cache, integrity
# checking each resource.
deno cache --reload --lock=lock.json src/deps.ts

# Done! You can proceed safely.
deno test --allow-read src

目前发现的问题,如果引用了第三方CDN的文件,同样的文件可能生成不同的lock.json。而新版本Deno现在默认会生成deno.lock文件。

导入地图

可以使用带有--import-map=<FILE>CLI 标志的导入映射,这样开发时使用体验类似于Node.js了。

不过如果是发布给别人使用的模块,不建议用它,因为这需要别人也把你的map文件复制过来。

例:import_map.json

json 复制代码
{   
  "imports": {      
    "fmt/": "https://deno.land/std@0.106.0/fmt/"   
 }
}

main.ts:

ts 复制代码
import { red } from "fmt/colors.ts"; 

然后:

arduino 复制代码
deno run --import-map=import_map.json main.ts

建议在上层应用开发时,所有的第三方引用都在import_map.json里维护,这样好管理依赖的版本。

来自CDN的包

目前,推荐将共享的代码发布在deno.land/x/上,它的发布也很简单,在你的GitHub仓库里配置一个hook,就可以了。

详见这篇文章《手把手教你发布一个Deno模块

esm.sh

esm.sh是个转换npm包为Deno使用的线上地址,它使用esbuild进行转换,速度很快。比如我们要使用React,可以这样直接使用:

javascript 复制代码
import React from "https://esm.sh/react";

当然也支持版本号:

javascript 复制代码
import React from "https://esm.sh/react@17.0.2";

Skypack

Skypack也是类似:

javascript 复制代码
import React from "https://cdn.skypack.dev/react";

直接使用npm包

Deno从1.28版本起,可以这样直接利用npm的包:

ts 复制代码
import chalk from "npm:chalk@5";

引用规则:

lua 复制代码
npm:<package-name>[@<version-requirement>][/<sub-path>]

以下是另一个样例:

ts 复制代码
// main.ts
// @deno-types="npm:@types/express@^4.17"
import express from "npm:express@^4.17";
const app = express();

app.get("/", (req, res) => {
  res.send("Hello World");
});

app.listen(3000);
console.log("listening on http://localhost:3000/");

如果在国内下载npm的网速较慢,可以使用环境变量NPM_CONFIG_REGISTRY来设置npm的镜像:

ini 复制代码
NPM_CONFIG_REGISTRY=https://registry.npmmirror.com/ deno run -A main.ts

安装脚本

使用deno install可以方便地安装一个脚本,类似于npm i -g xxx模块。

bash 复制代码
$ deno install --allow-net --allow-read https://deno.land/std@0.106.0/http/file_server.ts
[1/1] Compiling https://deno.land/std@0.106.0/http/file_server.ts

✅ Successfully installed file_server.
/Users/deno/.deno/bin/file_server

之后就能使用file_server命令了。当然,需要将打印信息里这个bin目录添加到环境变量里,前文提过,这里不再赘述。

代码风格

格式化

使用deno fmt。它会将代码格式化为官方推荐的样子,其实起到的类似eslint --fix的效果。vscode中安装了Deno插件后,可以很容易开启自动格式化。

bash 复制代码
# format all JS/TS files in the current directory and subdirectories
deno fmt
# format specific files
deno fmt myfile1.ts myfile2.ts
# check if all the JS/TS files in the current directory and subdirectories are formatted
deno fmt --check
# format stdin and write to stdout
cat file.ts | deno fmt -

代码校验

使用deno lint。有了它,就不必再使用eslint了。

shell 复制代码
# lint all JS/TS files in the current directory and subdirectories
$ deno lint
# lint specific files
$ deno lint myfile1.ts myfile2.ts
# print result as JSON
$ deno lint --json
# read from stdin
$ cat file.ts | deno lint -

编码风格指南

参见官方指南,举几条对我们有用的:

  • 使用 TypeScript 而不是 JavaScript
  • 在文件名中使用下划线,而不是破折号
  • 不要使用文件名index.ts/ index.js。入口文件使用mod.ts/mod.js
  • 导出的函数:最多 2 个参数,将其余的放入选项对象中
  • 顶级函数不应使用箭头语法。顶级函数应该使用function关键字。箭头语法应该仅限于闭包

调试

像Node.js一样,Deno程序也可以在chrome浏览器中调试,已经是很古老的调试方案了,具体就不介绍了。

这里主要说下vscode中调试。

需要在项目根目录下配置.vscode/launch.json文件,内容大概如下:

kotlin 复制代码
{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "deno",
            "request": "launch",
            "type": "node",
            "program": "src/main.ts", // 具体文件路径
            "cwd": "${workspaceFolder}",
            "runtimeExecutable": "deno",
            "runtimeArgs": [
                "run",
                "--unstable",
                "--inspect",
                "--allow-all"
            ],
            "attachSimplePort": 9229
        }
    ]
}

详情可以参考这里6

在文件中打好断点,以调试方式运行就可以了。

deno info

详见这篇:Deno编译问题

开发环境

软件推荐

推荐使用vscode,有钱任性的用webstorm也可以。

git安装

代码存储当然要使用git。

对git不熟悉的同学,参看廖雪峰老师的《Git 教程》。

git的提交信息要遵循 Angular 团队提议的《AngularJS Git Commit Message Conventions》:

<type>(<scope>): <subject> 必须填写

空一行

<body> 选填

空一行

<footer> 选填

  • type 类型只能从以下列表选择:

    • build:影响生成系统或外部依赖性的更改
    • ci: 更改 CI 配置文件和脚本
    • feat: 新功能(feature)
    • fix: 修补 bug
    • perf: 提高性能的代码更改
    • docs: 文档(documentation)
    • style: 不影响代码含义的更改(不影响代码运行的变动)
    • refactor: 代码修改既不修复错误,也不添加特征(即不是新增功能,也不是修改 bug 的代码变动)
    • test: 添加缺失测试或纠正现有测试
    • revert: 撤回
  • scope 影响范围
  • subject 是 commit 目的的简短描述,不超过 72 个字符
  • body Body 部分是对本次 commit 的详细描述,可以分成多行
  • footer 不兼容变动主要及一些额外说明

扩展安装

在扩展商店中查找deno,安装,然后视情况选择是全局启用,还是工作区使用。

配置文件

当前工程目录下,新建.vscode文件夹,编辑settings.json:

json 复制代码
{
  "deno.enable": true,
  "deno.suggest.imports.hosts": {
    "https://deno.land": true
  },
  "deno.lint": true,
  "deno.suggest.autoImports": true,
}

开启了deno.lint之后,如果代码校验问题,会出现下方的问题里:

如果用到了importMap和tsconfig.json(官方推荐改用deno.json或deno.jsonc),也要对应配置:

json 复制代码
{
 "deno.importMap": "import_map.json",
 "deno.config": "deno.json"
}

还有其它配置,视情况自行启用。

自动格式化

打开一个ts文件,比如内容是这样:

ts 复制代码
console.log(11)

从deno默认的代码规范上,它是不合格的,因为丢失了最后的分号(;)。

右键菜单,找到使用...格式化文档:

配置默认为Deno。

回到页面时,保存代码,会看到分号就自动加上了。

这对保障我们代码统一的格式、风格是有益的。通常情况下,我们用默认的风格就足够了,不需要一个团队为是用双引号还是单引号,结尾要不要加分号而纠结。

安装脚本

git hook

在工程根目录下执行:

arduino 复制代码
deno run --allow-write https://deno.land/x/jw_cli@v0.5.0/cli/git_hook.ts

这个脚本其实就是让你每次git commit时触发deno lint命令。

版本升级推送

全局安装:

arduino 复制代码
deno install --allow-read --allow-write --allow-run --unstable -n tag -f https://deno.land/x/jw_cli@v0.5.1/cli/tag/mod.ts
版本号更新

在项目根目录下执行patch/minor/major进行版本号的变更,逻辑与Node.js的npm version命令一致,它会更新deno.json[c]文件中版本号。

复制代码
tag patch
tag minor
tag major

它会同时更新根目录下的deno.json[c]文件和README.md,如果后者有使用deno.json[c]中配置的name,将会对应替换。

比如原来REAME.md中有段是这样的:

ts 复制代码
import { Body } from "https://deno.land/x/oak_nest@v1.13.19/mod.ts";

deno.json[c]中name为oak_nest,那么这里的oak_nest@v1.13.19都会对应替换为新的版本。

版本号不以v开头

假设你推送的tag版本号不想以v开头,那么可以添加一个参数-L或者--local:

复制代码
tag patch -L
添加自定义信息

打标签时默认提交信息是版本号,如果想自定义信息,可以使用-M或者--msg:

arduino 复制代码
tag minor -M "feat: change some"
更新所有目录的README.md文件

如果想要更新所有目录的README.md文件,可以使用-D或者--deep:

复制代码
tag -D
相关推荐
嘉伟桑1 小时前
Python调用电价API返回JSON示例:分时电价、现货电价和字段解析
后端
武子康1 小时前
Shippy:确定性工具、会话级 Sandbox 与 Live-Data Eval(4 类收敛 + 7 步实现方案 + 6 类评测指标)
前端·人工智能·后端
我叫黑大帅1 小时前
我为什么单一消费者的场景下,要用 Redis List 当消息队列?
redis·后端·面试
AskHarries2 小时前
文件上传系统
后端
止语Lab3 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活3 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困3 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn3 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530143 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908403 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端