聊聊Deno的那些事

這是第 99 篇不摻水的原創,想獲取更多原創好文,請搜索公衆號關注咱們吧~ 本文首發於政採雲前端博客: 聊聊Deno的那些事

Deno 是什麼

deno1

Deno 是一個簡單、現代、安全的 JavaScriptTypeScriptWebassembly 運行時環境。javascript

Deno 是 Node 的變位詞,其發音是恐龍(dinosaur)的縮寫讀音"蒂諾"。

它是創建在:css

  • Rust(Deno 的底層是用 Rust 開發,而 Node 是用 C++)
  • Tokio(Deno 的事件機制是基於 Tokio,而 Node 是基於 libuv)
  • TypeScript
  • V8

Deno 的背景

deno演講.png

Deno 起源於 Node 的建立者 Ryan Dahl,這也是你們對 Deno 項目充滿期待的緣由之一。在 JSConfEu 上,Dahl 在他的的演講中說出了本身對 Node 中存在的一些缺陷,並解釋瞭如何圍繞 Node 的架構作出更好的決定,在演講的最後,宣佈了 Deno 的第一個原型,並承諾構建一個更好、更安全的運行時環境。html

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,以下所示: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');

構建系統與 Chrome 存在差別

v8編譯.png

首先咱們須要瞭解構建系統是啥?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

複雜的包管理模式

deno模塊太陽.png

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 的架構

deno源碼.png

  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 的特色

安全

deno-sec.png

與 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恐龍標誌

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

tsbanner.jpeg

使用 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

chromebanner.png

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

promisebanner.png

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 兩個步驟,因而乎咱們會產生幾個疑問:

一、每次執行都要下載嗎?

答:不須要每次下載,有緩存機制。

> 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 文件和依賴項列表,由於全部模塊都是在應用程序運行時下載,編譯和緩存的。

上手 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

實戰體驗

deno-getting-started.jpeg

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

相關文章
相關標籤/搜索