一起学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
相关推荐
ai小鬼头7 小时前
AIStarter教你快速打包GPT-SoVITS-v2,解锁AI应用市场新玩法
前端·后端·github
paopaokaka_luck8 小时前
基于SpringBoot+Vue的汽车租赁系统(协同过滤算法、腾讯地图API、支付宝沙盒支付、WebsSocket实时聊天、ECharts图形化分析)
vue.js·spring boot·后端·websocket·算法·汽车·echarts
giao源8 小时前
Spring Boot 整合 Shiro 实现单用户与多用户认证授权指南
java·spring boot·后端·安全性测试
【本人】8 小时前
Django基础(四)———模板常用过滤器
后端·python·django
豌豆花下猫9 小时前
Python 潮流周刊#111:Django迎来 20 周年、OpenAI 前员工分享工作体验(摘要)
后端·python·ai
LaoZhangAI9 小时前
ComfyUI集成GPT-Image-1完全指南:8步实现AI图像创作革命【2025最新】
前端·后端
LaoZhangAI9 小时前
Cline + Gemini API 完整配置与使用指南【2025最新】
前端·后端
LaoZhangAI9 小时前
Cline + Claude API 完全指南:2025年智能编程最佳实践
前端·后端