这是第 99 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注咱们吧~ 本文首发于政采云前端博客: 聊聊Deno的那些事
Deno 是一个简单、现代、安全的 JavaScript、TypeScript、Webassembly 运行时环境。javascript
Deno 是 Node 的变位词,其发音是恐龙(dinosaur)的缩写读音"蒂诺"。
它是创建在:css
Deno 起源于 Node 的建立者 Ryan Dahl,这也是你们对 Deno 项目充满期待的缘由之一。在 JSConfEu 上,Dahl 在他的的演讲中说出了本身对 Node 中存在的一些缺陷,并解释了如何围绕 Node 的架构作出更好的决定,在演讲的最后,宣布了 Deno 的第一个原型,并承诺构建一个更好、更安全的运行时环境。html
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,以下所示:java
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 这类的容器环境来解决安全性问题。node
const fs = require('fs'); //删除hello.txt fs.unlinkSync('./hello.txt'); // 删除css文件夹 fs.rmdirSync('./css');
首先咱们须要了解构建系统是啥?python
写惯前端的童鞋可能不是很明白这个东西是干啥用的?可是其实平时你都会接触到,只是概念不一样而已。前端咱们通常称其为打包构建,相似工具诸如 webpack、rollup、parcel 作的事情。它们最后的目标其实都是想获得一些目标性的文件,这里咱们的目标是编译 V8 代码。react
Node 的 V8 构建系统是 GYP(Generate Your Projects),而 Chrome 的 V8 已升级为 GN(Generate Ninja)。咱们知道 V8 是由 Google 开发的,这也证实 Node 和 Google 的亲儿子 Chrome 渐行渐远,并且 GN 的构建速度比 GYP 快20倍,由于 GN 是用 C++ 编写,比起用 python 写的 GYP 快了不少。可是 Node 底层架构已没法挽回。webpack
Node 自带的 NPM 生态系统中,因为严重依赖语义版本控制和复杂的依赖关系图,少不了要与 package.json、node_modules 打交道。node_modules 的设计虽然能知足大部分的场景,可是其仍然存在着种种缺陷,尤为在前端工程化领域,形成了很多的问题。特别是不一样包依赖版本不一致时,各类问题接踵而来,因而乎 yarn lock、npm lock 闪亮登场。git
然而仍是有不少场景是 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 其实和 RN、Flutter 这些框架很相似,由于它本质上也是跑了个 JS 引擎,只是这个 JS 引擎是 V8,不负责 UI 的 binding 而已。因此说架构的本质就是思路复刻、模块重组。
与 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 运行 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
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"; // 没有后缀
Deno 经过与浏览器 API 保持一致,来减小你们的认知。
console.log(window === this, window === self, window === globalThis); // true true true
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!
咱们看到执行时会有 Download
和 Compile
两个步骤,因而乎咱们会产生几个疑问:
一、每次执行都要下载吗?
答:不须要每次下载,有缓存机制。
> deno run index.jshello world!
二、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)
三、依赖代码更新了怎么办?
答:当依赖模块更新时,咱们能够经过 --reload
进行更新缓存,例如:
> deno run --reload index.js// 经过白名单的方式更新部分依赖> deno run --reload=https://deno.land index.js
四、多版本怎么处理?
答:暂时没有好的解决方案,只能经过 git tag 的方式区分版本。
Deno 是经过 URL 导入代码,能够在互联网上的任何地方托管模块。而且相比 Node 的 require 读取文件,它显得更加轻巧玲珑,而且无需集中注册表便可分发 Deno 软件包。不须要 package.json 文件和依赖项列表,由于全部模块都是在应用程序运行时下载,编译和缓存的。
使用 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
本地建立一个 index.ts 文件,内容以下所示:
// index.tsconsole.log("Welcome to Deno 🦕");
打开终端,输入如下命令行:
> deno run index.ts
以上输出 "Welcome to Deno 🦕"。
本地建立一个 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 对象。
从远程模块导入 add 和 multiply 方法:
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.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.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 所没有的,你们能够多多试玩。
政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在平常的业务对接以外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推进并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。
若是你想改变一直被事折腾,但愿开始能折腾事;若是你想改变一直被告诫须要多些想法,却无从破局;若是你想改变你有能力去作成那个结果,却不须要你;若是你想改变你想作成的事须要一个团队去支撑,但没你带人的位置;若是你想改变既定的节奏,将会是“5 年工做时间 3 年工做经验”;若是你想改变原本悟性不错,但老是有那一层窗户纸的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但愿参与到随着业务腾飞的过程,亲手推进一个有着深刻的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我以为咱们该聊聊。任什么时候间,等着你写点什么,发给 ZooTeam@cai-inc.com