乐趣区

关于前端:聊聊Deno的那些事

这是第 99 篇不掺水的原创,想获取更多原创好文,请搜寻公众号关注咱们吧~ 本文首发于政采云前端博客:聊聊 Deno 的那些事

Deno 是什么

Deno 是一个简略、古代、平安的 JavaScript、TypeScript、Webassembly 运行时环境。

Deno 是 Node 的变位词,其发音是恐龙(dinosaur)的缩写读音 ” 蒂诺 ”。

它是建设在:

  • Rust(Deno 的底层是用 Rust 开发,而 Node 是用 C++)
  • Tokio(Deno 的事件机制是基于 Tokio,而 Node 是基于 libuv)
  • TypeScript
  • V8

Deno 的背景

Deno 起源于 Node 的创建者 Ryan Dahl,这也是大家对 Deno 我的项目充斥期待的起因之一。在 JSConfEu 上,Dahl 在他的的演讲中说出了本人对 Node 中存在的一些缺点,并解释了如何围绕 Node 的架构做出更好的决定,在演讲的最初,发表了 Deno 的第一个原型,并承诺构建一个更好、更平安的运行时环境。

Node 的缺点

原生 API 短少 Promise

Node 最大的亮点在于事件驱动,非阻塞 I/O 模型,这使得 Node 具备很强的并发解决能力,非常适合编写网络应用。在 Node 中大部分的 I/O 操作简直都是异步的,于是乎 Callback Hell 产生了:

// fs.js
const fs = require('fs');
const myFile = '/tmp/test';

fs.readFile(myFile, 'utf8', (err, txt) => {if (!err) {fs.writeFile(myFile);
  }
});

若要实现链式调用,你须要应用 Promise 从新包装下原生 API,如下所示:

const fs = require("fs");
const myFile = '/tmp/test';

function readFile_promise(path) {return new Promise((resolve, reject) => {fs.readfile(path, "utf-8", (err, data) => {if (err) {reject(err);
      } else {resolve(data);
      }
    })
  });
}

readFile_promise(myFile)
  .then((res) => {fs.writeFile(myFile, res);
  })

短少安全性

在 Node 中,能够调用 fs.chmod 来批改文件或目录的读写权限。阐明 Node 运行时的权限是很高的。如果你在 Node 中导入一份不受信赖的软件包,那么很可能它将删除你计算机上的所有文件,所以说 Node 短少平安模块化运行时。除非手动提供一个沙箱环境,诸如 Docker 这类的容器环境来解决安全性问题。

const fs = require('fs');
// 删除 hello.txt
fs.unlinkSync('./hello.txt');
// 删除 css 文件夹
fs.rmdirSync('./css');

构建零碎与 Chrome 存在差别

首先咱们须要理解构建零碎是啥?

写惯前端的童鞋可能不是很明确这个货色是干啥用的?然而其实平时你都会接触到,只是概念不同而已。前端咱们个别称其为打包构建,相似工具诸如 webpack、rollup、parcel 做的事件。它们最初的指标其实都是想得到一些目标性的文件,这里咱们的指标是编译 V8 代码。

Node 的 V8 构建零碎是 GYP(Generate Your Projects),而 Chrome 的 V8 已降级为 GN(Generate Ninja)。咱们晓得 V8 是由 Google 开发的,这也证实 Node 和 Google 的亲儿子 Chrome 渐行渐远,而且 GN 的构建速度比 GYP 快 20 倍,因为 GN 是用 C++ 编写,比起用 python 写的 GYP 快了很多。然而 Node 底层架构已无法挽回。

简单的包管理模式

Node 自带的 NPM 生态系统中,因为重大依赖语义版本控制和简单的依赖关系图,少不了要与 package.json、node_modules 打交道。node_modules 的设计尽管能满足大部分的场景,然而其依然存在着种种缺点,尤其在前端工程化畛域,造成了不少的问题。特地是不同包依赖版本不统一时,各种问题接踵而来,于是乎 yarn lock、npm lock 闪亮退场。

然而还是有很多场景是 lock 无奈笼罩的,比方当咱们第一次装置某个依赖的时候,此时即便第三方库里含有 lock 文件,然而 npm install|、yarn install 也不会去读取第三方依赖的 lock,这导致第一次创立我的项目的时候,还是会可能会触发 bug。而且因为穿插依赖,node_modules 里充斥了各种反复版本的包,造成了极大的空间节约,也导致 install 依赖包很慢,以及 require 读取文件的算法越来越复杂化。

读取文件复杂化

Node 应用 require 援用其余脚本文件,其外部逻辑如下:

 当 Node 遇到 require(X) 时,按上面的程序解决。(1)如果 X 是内置模块(比方 require('http'))
  a. 返回该模块。b. 不再继续执行。(2)如果 X 以 "./" 或者 "/" 或者 "../" 结尾
  a. 依据 X 所在的父模块,确定 X 的绝对路径。b. 将 X 当成文件,顺次查找上面文件,只有其中有一个存在,就返回该文件,不再继续执行。X
      X.js
      X.json
      X.node
  c. 将 X 当成目录,顺次查找上面文件,只有其中有一个存在,就返回该文件,不再继续执行。X/package.json(main 字段)X/index.js
      X/index.json
      X/index.node(3)如果 X 不带门路
  a. 依据 X 所在的父模块,确定 X 可能的装置目录。b. 顺次在每个目录中,将 X 当成文件名或目录名加载。(4)抛出 "not found"

能够看得出来,require 的读取逻辑是很简单的,尽管用起来很可恶,然而没必要。

Deno 的架构

  1. Deno 以 Rust 作为启动入口,通过 Rust FFI 去执行 C++ 代码,而后在 C++ 中引入 V8 实例。
  2. 初始化 V8 对象以及注入内部 C++ 办法,例如 send、recv 等办法。
  3. 向 V8 全局作用域下注入 Deno 对象,裸露 Deno 的一些根本 API 给 JavaScript。
  4. 通过绑定在 V8 上的 C++ 办法,调用对应的 Rust 办法,去执行底层逻辑。

不难发现 Deno 其实和 RN、Flutter 这些框架很相似,因为它实质上也是跑了个 JS 引擎,只是这个 JS 引擎是 V8,不负责 UI 的 binding 而已。所以说架构的实质就是思路复刻、模块重组。

Deno 的特点

平安

与 Node 相同,Deno 默认在沙箱中执行代码,这意味着运行时无法访问以下权限:

  • 文件系统
  • 网络
  • 环境变量

你能够通过命令行参数模式来开启默认敞开的权限,相似上面这样:

// 授予从磁盘读取和侦听网络的权限
deno run --allow-read --allow-net https://deno.land/std/http/file_server.ts

// 授予从磁盘 filepath 读取白名单文件的权限
deno run --allow-read=/etc https://deno.land/std/http/file_server.ts

// 授予所有权限
deno run --allow-all https://deno.land/std/http/file_server.ts

或者通过编程模式管制权限,相似上面这样:

// 检测是否有读取权限
const status = await Deno.permissions.query({name: "write"});
if (status.state !== "granted") {throw new Error("need write permission");
}

// 读取 log 文件
const log = await Deno.open("request.log", "a+");

// 敞开读写权限
await Deno.permissions.revoke({name: "read"});
await Deno.permissions.revoke({name: "write"});

// 打印 log 内容
const encoder = new TextEncoder();
await log.write(encoder.encode("hello\n"));

内置工具

Deno 目前提供了以下内置工具,在应用 JavaScript 和 TypeScript 时十分有用,只须要执行以下命令即可:

  • deno bundler (自带打包和 tree shaking 性能,能够将咱们的代码打包成单文件)
  • deno compile (将 Deno 我的项目构建为齐全独立的可执行文件)
  • deno installe (能够将咱们的代码生成可执行文件进行间接应用)
  • deno info (查看所有模块的依赖关系树)
  • deno doc (将源代码中的正文生成文档)
  • deno fmt (递归地格式化每个子目录中的每个文件)
  • deno repl (启动一个 read-eval-print-loop,它容许您在全局上下文中交互式地构建程序状态)
  • deno test (对名为 .test 的文件进行单元测试)
  • deno lint (代码检测器)

反对 TyprScript

应用 Deno 运行 TypeScript 代码不须要编译步骤以及繁琐的配置文件—— Deno 会主动为你执行这一步骤。

源码中咱们发现,Deno 其实是集成了一个 TypeScript 编译器和一个用于运行时快照的小型编译器主机。转换的外围代码如下:

// globalThis.exec 这个函数在 /cli/tsc/99_main_compiler.js 中
// 其次要作用就是把 TypeScript 转换成 JavaScript
let exec_source = format!("globalThis.exec({})", request_str);

  runtime
    .execute("[native code]", startup_source)
    .context("Could not properly start the compiler runtime.")?;
  runtime.execute("[native_code]", &exec_source)?;

前段时间 Deno 外部把 TS 改回 JS 的探讨很是冷落,但并不意味着 Deno 放弃了 TypeScript,它仍然是一个平安的 TS/JS Runtime。

例如:

// index.ts
const str: string = 'hello word';
console.log(str);

你能够间接在命令行运行并打印出 hello word:

deno run index.ts

反对 ES 模块规范

Deno 采纳的是 ES Module 的浏览器实现。ES Module 大家应该都是比拟相熟的,它是 JavaScript 官网的标准化模块零碎,其浏览器实现如下所示:

// 从 URL 导入 import React from "https://cdn.bootcdn.net/ajax/libs/react/17.0.1/cjs/react-jsx-dev-runtime.development.js";// 从相对路径导入 import * as Api from "./service.js";// 从绝对路径导入 import "/index.js";

须要留神的是,Deno 不反对以下写法:

import foo from "foo.js";import bar from "bar/index.js";import zoo from "./index"; // 没有后缀 

兼容浏览器 API

Deno 通过与浏览器 API 保持一致,来缩小大家的认知。

  • 模块零碎:从下面的介绍看出 Deno 是齐全遵循浏览器实现的。
  • 默认平安
  • 对于异步操作返回 Promise
  • 应用 ArrayBuffer 解决二进制
  • 存在 window 全局变量
  • 反对 fetch、webCrypto、worker 等 Web 规范,也反对 onload、onunload、addEventListener 等事件操作函数
console.log(window === this, window === self, window === globalThis); // true true true

反对 Promise

Deno 所有的异步操作,一律返回 Promise,并且全局反对 await。

// 读取异步接口数据 const response = await fetch("http://my.json.host/data.json");console.log(response.status)console.log(response.statusText);const jsonData = await response.json();// 读取文件 const decoder = new TextDecoder("utf-8");const data = await Deno.readFile("hello.txt");console.log(decoder.decode(data));

去中心化包

Deno 没有 package.json、node_modules,那么它是怎么进行包治理的呢?咱们先看上面的例子:

// index.jsimport {white, bgRed} from "https://deno.land/std/fmt/colors.ts";console.log(bgRed(white("hello world!")));// 命令行执行 > deno run index.jsDownload https://deno.land/std/fmt/colors.tsCompile https://deno.land/std/fmt/colors.tshello world!

咱们看到执行时会有 DownloadCompile 两个步骤,于是乎咱们会产生几个疑难:

1、每次执行都要下载吗?

答:不须要每次下载,有缓存机制。

> deno run index.jshello world!

2、Download 和 Compile 的文件在哪里呢?

答:咱们能够通过下面介绍的自带工具 deno info 来查看依赖关系。

> deno info index.jslocal: /Users/xxx/Desktop/index.tstype: TypeScriptemit: /Users/xxx/Library/Caches/deno/gen/file/Users/xxx/Desktop/index.ts.jsdependencies: 0 unique (total 41B)file:///Users/xxx/Desktop/index.ts (41B)

3、依赖代码更新了怎么办?

答:当依赖模块更新时,咱们能够通过 --reload 进行更新缓存,例如:

> deno run --reload index.js// 通过白名单的形式更新局部依赖 > deno run --reload=https://deno.land index.js

4、多版本怎么解决?

答:临时没有好的解决方案,只能通过 git tag 的形式辨别版本。

Deno 是通过 URL 导入代码,能够在互联网上的任何中央托管模块。并且相比 Node 的 require 读取文件,它显得更加笨重小巧,并且无需集中注册表即可散发 Deno 软件包。不须要 package.json 文件和依赖项列表,因为所有模块都是在利用程序运行时下载,编译和缓存的。

上手 Deno

装置

应用 Shell (macOS 和 Linux):

curl -fsSL https://deno.land/x/install/install.sh | sh

应用 PowerShell (Windows):

iwr https://deno.land/x/install/install.ps1 -useb | iex

运行 deno –version,如果它打印出 Deno 版本,阐明装置胜利。

> deno --versiondeno 1.8.1 (release, aarch64-apple-darwin)v8 9.0.257.3typescript 4.2.2

实战体验

Hello Word

本地创立一个 index.ts 文件,内容如下所示:

// index.tsconsole.log("Welcome to Deno 🦕");

关上终端,输出以下命令行:

> deno run index.ts

以上输入 “Welcome to Deno 🦕”。

HTTP 申请

本地创立一个 http.ts 文件,内容如下所示:

const url = Deno.args[0]; // 获得第一个命令行参数,存储到变量 url。const res = await fetch(url); // 向指定的地址发出请求,期待响应,而后存储到变量 res。const body = new Uint8Array(await res.arrayBuffer()); // 把响应体解析为一个 ArrayBuffer,期待接管结束,将其转换为 Uint8Array,最初存储到变量 body。await Deno.stdout.write(body); // 把 body 的内容写入规范输入流 stdout。

关上终端,输出以下命令行:

deno run --allow-net=api.github.com http.ts https://api.github.com/users/answer518

以上输入 json 对象。

近程导入

从近程模块导入 addmultiply 办法:

import {add,  multiply,} from "https://x.nest.land/ramda@0.27.0/source/index.js";function totalCost(outbound: number, inbound: number, tax: number): number {return multiply(add(outbound, inbound), tax);}console.log(totalCost(19, 31, 1.2)); // 60console.log(totalCost(45, 27, 1.15)); // 82.8

反对 WASM

// wasm.tsconst wasmCode = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 133, 128, 128, 128, 0, 1, 96, 0, 1, 127,  3, 130, 128, 128, 128, 0, 1, 0, 4, 132, 128, 128, 128, 0, 1, 112, 0, 0,  5, 131, 128, 128, 128, 0, 1, 0, 1, 6, 129, 128, 128, 128, 0, 0, 7, 145,  128, 128, 128, 0, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 4, 109, 97,  105, 110, 0, 0, 10, 138, 128, 128, 128, 0, 1, 132, 128, 128, 128, 0, 0,  65, 42, 11]);const wasmModule = new WebAssembly.Module(wasmCode);const wasmInstance = new WebAssembly.Instance(wasmModule);const main = wasmInstance.exports.main as CallableFunction;console.log(main().toString());

关上终端,输出以下命令行:

> deno run wasm.ts

以上输入数字 42。

RESTful 服务

// restful.tsimport {Application, Router} from "https://deno.land/x/oak/mod.ts";const books = new Map<string, any>();books.set("1", {  id: "1",  title: "平庸的世界",  author: "路遥",});const router = new Router();router  .get("/", (context) => {context.response.body = "Hello world!";})  .get("/book", (context) => {context.response.body = Array.from(books.values());  })  .get("/book/:id", (context) => {if (context.params && context.params.id && books.has(context.params.id)) {context.response.body = books.get(context.params.id);    }  });const app = new Application();app.use(router.routes());app.use(router.allowedMethods());await app.listen({hostname: '127.0.0.1', port: 8000});

终端输出以下命令:

> deno run  --allow-net restful.ts

本地拜访 http://localhost:8000/book/1 将会返回 id 为 1 的 book 数据。

动态资源服务

// static.tsimport {Application} from "https://deno.land/x/oak/mod.ts";const app = new Application();app.use(async (context) => {await context.send({    root: Deno.cwd(), // 动态资源的根门路  });});await app.listen({hostname: "127.0.0.1", port: 8000});

终端输出以下命令:

> deno run  --allow-net --allow-read static.ts

本地拜访 http://localhost:8000/static.ts 将会返回 static.ts 的源码。

结束语

Deno 是一个十分平凡的我的项目,但却不是 “下一代 Nods.js”。Ryan Dahl 本人也说:“Node.js isn’t going anywhere”。并且 Deno 还处在开发中,性能还不稳固,不倡议用于生产环境。然而,它曾经是一个可用的工具,有很多新个性都是 Node 所没有的,大家能够多多试玩。

举荐浏览

数据可视化摸索之 SpreadJS

H5 页面列表缓存计划

招贤纳士

政采云前端团队(ZooTeam),一个年老富裕激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员形成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端利用、数据分析及可视化等方向进行技术摸索和实战,推动并落地了一系列的外部技术产品,继续摸索前端技术体系的新边界。

如果你想扭转始终被事折腾,心愿开始能折腾事;如果你想扭转始终被告诫须要多些想法,却无从破局;如果你想扭转你有能力去做成那个后果,却不须要你;如果你想扭转你想做成的事须要一个团队去撑持,但没你带人的地位;如果你想扭转既定的节奏,将会是“5 年工作工夫 3 年工作教训”;如果你想扭转原本悟性不错,但总是有那一层窗户纸的含糊… 如果你置信置信的力量,置信平凡人能成就不凡事,置信能遇到更好的本人。如果你心愿参加到随着业务腾飞的过程,亲手推动一个有着深刻的业务了解、欠缺的技术体系、技术发明价值、影响力外溢的前端团队的成长历程,我感觉咱们该聊聊。任何工夫,等着你写点什么,发给 ZooTeam@cai-inc.com

退出移动版