這是第 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