通俗易懂的 Deno 入門教程

阿寶哥發表於2020-07-01

Deno 感興趣,想嚐嚐鮮或快速入門 Deno 的小夥伴看過來,本文將從七個方面入手,帶你一步步學習 Deno 的相關知識,詳細的內容大綱如下圖所示:

自從 Deno 1.0 正式釋出之後,Deno 的熱度漸漸褪去。但近期 Deno 團隊計劃刪除所有內部程式碼構建時的 TS 型別檢查與捆綁,又把 Deno 再一次帶入大家的視野。

下面我們來通過知乎上 justjavac(迷渡大大) 一個高讚的回答來大致瞭解一下其主要原因。

來自 天豬 的提問:如何評價 deno 計劃把一些內部模組從 ts 改回 js ?

來自 justjavac 的高贊回答:

先說原因:根本原因是 ts 無法為 Deno runtime 生成高效能的 js 程式碼

首先需要澄清一個誤區:Deno 並沒有放棄 TypeScript,Deno 依然是一個安全的 TS/JS runtime。

編譯速度慢是一個方面,但不是根本原因。rustc 的編譯速度也很慢,但是生成的程式碼質量很高。tsc 確實也慢,如果 tsc 能夠生成高效能的 js 程式碼,那麼這點構建的時間成本是可以接受的。但是目前來看 ts 無法為 Deno runtime 生成高效能的 js 程式碼。或者說,Deno 團隊沒有找到 ts 生成高效能 js 程式碼的方式。

Deno 使用最新版 ts 和 v8,而 ts 和 v8 的實現目標都是 stage3,理論上 ts 只要簡單的做型別擦除就可以直接執行在 v8 上。最初 Deno 團隊也是這麼想的。但是實際使用 ts 的情況卻不是這麼理想。

此處省略好多個字,這裡就不繼續 “搬運” 了,感興趣的小夥伴可以仔細閱讀一下原文,文中 justjavac 詳細介紹了來龍去脈。

原文地址:https://www.zhihu.com/questio...

一、Deno 簡介

Deno 是一個 JavaScript/TypeScript 的執行時,預設使用安全環境執行程式碼,有著卓越的開發體驗。

(圖片來源:https://deno.land/artwork

DenoNode.js 之父 Ryan Dahl 的另一個大作,它跟 Node.js 的關係是這樣的:

"node".split("").sort().join(""); // Output: deno

那麼實際上 Deno 與 Node.js 之間有什麼區別呢?這裡阿寶哥就不展開介紹了,感興趣的小夥伴可以閱讀 “ Deno 正式釋出,徹底弄明白和 node 的區別” 這篇文章。

下面我們步入正題,開始進入 Deno 的世界,Deno 含有以下功能特性:

  • 預設安全,外部程式碼沒有檔案系統、網路、環境的訪問許可權,除非顯式開啟。
  • 支援開箱即用的 TypeScript 的環境。
  • 只分發一個獨立的可執行檔案(deno)。
  • 有著內建的工具箱,比如一個依賴資訊檢視器(deno info)和一個程式碼格式化工具(deno fmt)。
  • 有一組經過審計的標準模組,保證能在 Deno 上工作。
  • 指令碼程式碼能被打包為一個單獨的 JavaScript 檔案。

Deno 是一個跨平臺的執行時,即基於 Google V8 引擎的執行時環境,該執行時環境是使用 Rust 語言開發的,並使用 Tokio 庫來構建事件迴圈系統。Deno 建立在 V8RustTokio 的基礎上,它的架構如下:

(圖片來源:https://deno.land/manual/cont...

1.1 Rust

Rust 是由 Mozilla 主導開發的通用、編譯型程式語言。設計準則為 “安全、併發、實用”,支援函式式、併發式、過程式以及物件導向的程式設計風格。Deno 使用 Rust 語言來封裝 V8 引擎,通過 libdeno 繫結,我們就可以在 JavaScript 中呼叫隔離的功能。

1.2 Tokio

Tokio 是 Rust 程式語言的非同步執行時,提供非同步事件驅動平臺,構建快速,可靠和輕量級網路應用。利用 Rust 的所有權和併發模型確保執行緒安全。Tokio 構建於 Rust 之上,提供極快的效能,使其成為高效能伺服器應用程式的理想選擇。在 Deno 中 Tokio 用於並行執行所有的非同步 IO 任務。

1.3 V8

V8 是一個由 Google 開發的開源 JavaScript 引擎,用於 Google Chrome 及 Chromium 中。V8 在執行之前將JavaScript 編譯成了機器程式碼,而非位元組碼或是解釋執行它,以此提升效能。更進一步,使用瞭如內聯快取(inline caching)等方法來提高效能。有了這些功能,JavaScript 程式與 V8 引擎的速度媲美二進位制編譯。在 Deno 中,V8 引擎用於執行 JavaScript 程式碼。

二、安裝 Deno

Deno 能夠在 Mac、Linux 和 Windows 上執行。Deno 是一個單獨的可執行檔案,它沒有額外的依賴。你可以通過以下方式來安裝它:

  • 使用 Shell(Mac 和 Linux):
$ curl -fsSL https://deno.land/x/install/install.sh | sh
  • 使用 PowerShell(Windows):
iwr https://deno.land/x/install/install.ps1 -useb | iex
  • 使用 Scoop (Windows):
scoop install deno
choco install deno
brew install deno
  • 使用 Cargo (Windows,Mac,Linux):
cargo install deno

Deno 也可以手動安裝,只需從 github.com/denoland/deno/releases 下載一個 zip 檔案。它僅包含一個單獨的可執行檔案。在 Mac 和 Linux 上,你需要為它設定執行許可權。當你成功安裝之後,可以通過執行 deno --version 命令來檢視已安裝的 Deno 版本:

$ deno --version
deno 1.1.2
v8 8.5.216
typescript 3.9.2

2.1 deno_install

在安裝過程中,如果遇到問題的話,大家可以試試 justjavac(迷渡)大神提供的安裝指令碼 —— deno_install。該指令碼通過單行命令將 Deno 安裝到系統中(國內加速)。

2.1.1 安裝最新版

使用 Shell:

$ curl -fsSL https://x.deno.js.cn/install.sh | sh

使用 PowerShell:

$ iwr https://x.deno.js.cn/install.ps1 -useb -outf install.ps1; .\install.ps1
# iwr https://x.deno.js.cn/install.ps1 -useb | iex  
2.1.2 安裝某個特定版本

使用 Shell:

$ curl -fsSL https://x.deno.js.cn/install.sh | sh -s v0.41.0

使用 PowerShell:

$ iwr https://x.deno.js.cn/install.ps1 -useb -outf install.ps1; .\install.ps1 v0.41.0
更多詳細的資訊可以瀏覽 x.deno.js.cn 站點。

2.2 deno-cli

deno-cli 命令列介面提供了一組整合功能,讓你可以沉浸在 Deno 的專有開發環境中。以下是 Deno 1.1.2 版本支援的所有子命令:

SUBCOMMANDS:
    bundle         Bundle module and dependencies into single file
    cache          Cache the dependencies
    completions    Generate shell completions
    doc            Show documentation for a module
    eval           Eval script
    fmt            Format source files
    help           Prints this message or the help of the given subcommand(s)
    info           Show info about cache or info related to source file
    install        Install script as an executable
    lint           Lint source files
    repl           Read Eval Print Loop
    run            Run a program given a filename or url to the module. Use '-' as a filename 
                   to read from stdin.
    test           Run tests
    types          Print runtime TypeScript declarations
    upgrade        Upgrade deno executable to given version

2.3 REPL

在命令中輸入 deno 命令,你就會啟動一個 REPL(Read-Execute-Print-Loop):

$ deno
Deno 1.1.2
exit using ctrl+d or close()
> 0.1 + 0.2
0.30000000000000004
> const name = "阿寶哥";
undefined
> console.log(name);
阿寶哥
undefined
“讀取-求值-輸出”迴圈(英語:Read-Eval-Print Loop,簡稱 REPL),也被稱做互動式頂層構件(英語:interactive toplevel),是一個簡單的,互動式的程式設計環境。這個詞常常用於指代一個 Lisp 的互動式開發環境,也能指代命令列的模式。

2.4 VSCode Deno extension

相信很多小夥伴都在使用 VSCode IDE 進行 Web 開發,對於 Deno 的開發者來說,一定不能錯過 Deno 官方開發的 Visual Studio Code Deno extension 擴充套件。

2.4.1 未安裝 Deno extension

如果我們寫 from "./hello.ts" 這樣的語句,在 VSCode 中將會出現波浪號的錯誤資訊。因為預設情況下,TypeScript 專案不需要新增 .ts 副檔名。

ts(2691): An import path cannot end with a '.ts' extension. Consider importing './hello' instead.

Deno 允許從 URL 中匯入模組,但是 TypeScript 並不支援從 URL 中匯入模組。

ts(2307): Cannot find module 'https://deno.land/x/std/log/mod'.

2.4.2 已安裝 Deno extension

Deno 將遠端匯入(imports)快取在 $DENO_DIR 環境變數指定的特殊目錄中。如果未指定 $DENO_DIR,則預設為系統的快取目錄。

該外掛可以將遠端匯入(remote imports)解析為本地路徑。

(本章節圖片來源:https://marketplace.visualstu...

瞭解 VSCode Deno extension 更多的詳細資訊,可以閱讀 justjavac(迷渡) 大佬 我為 VS Code 開發了一個 Deno 外掛 這篇文章。

三、Deno 初體驗

3.1 Welcome 示例

相信一些讀者安裝完 Deno 已經迫不及待了,現在我們立馬來體驗一下 Deno 應用程式。首先開啟你熟悉的命令列,然後在命令列輸入以下命令:

$ deno run https://deno.land/std/examples/welcome.ts
Download https://deno.land/std/examples/welcome.ts
Warning Implicitly using master branch https://deno.land/std/examples/welcome.ts
Compile https://deno.land/std/examples/welcome.ts
Welcome to Deno ?

通過觀察以上輸出,我們可以知道當執行 deno run https://deno.land/std/examples/welcome.ts 命令之後,Deno 會先從 https://deno.land/std/examples/welcome.ts URL 地址下載 welcome.ts 檔案,該檔案的內容是:

console.log("Welcome to Deno ?");

當檔案下載成功後,Deno 會對 welcome.ts 檔案進行編譯,即編譯成 welcome.ts.js 檔案,然後再通過 V8 引擎來執行編譯生成的 JavaScript 檔案。需要注意的是,如果你在命令列重新執行上述命令,則會執行快取中已生成的檔案,並不會再次從網上下載 welcome.ts 檔案。

$ deno run https://deno.land/std/examples/welcome.ts
Welcome to Deno ?

那如何證明再次執行上述命令時, Deno 會優先執行快取中編譯生成的 JavaScript 檔案呢?這裡我們要先介紹一下 deno info 命令,該命令用於顯示有關快取或原始檔相關的資訊:

$ deno info
DENO_DIR location: "/Users/fer/Library/Caches/deno"
Remote modules cache: "/Users/fer/Library/Caches/deno/deps"
TypeScript compiler cache: "/Users/fer/Library/Caches/deno/gen"

在上述的輸出資訊中,我們看到了 TypeScript compiler cache 這行記錄,很明顯這是 TypeScript 編譯器快取的目錄,進入該目錄後,通過一層層的查詢,我們最終在 examples 目錄下找到了 welcome.ts.js 檔案:

➜  examples ls
welcome.ts.buildinfo welcome.ts.js.map
welcome.ts.js        welcome.ts.meta

開啟目錄中 welcome.ts.js 檔案,我們可以看到以下內容:

"use strict";                                                                                                      
console.log("Welcome to Deno ?");
//#sourceMappingURL=data:application/json;base64,eyJ2Z...

下面我們來修改該檔案,在檔案中新增一行輸出資訊 console.log("Hello Semlinker, from Cache");,具體如下:

"use strict";
console.log("Hello Semlinker, from Cache");
console.log("Welcome to Deno ?");
//#sourceMappingURL=data:application/json;base64,eyJ2Z...

接著我們在命令列中重新執行以下命令:

$ deno run https://deno.land/std/examples/welcome.ts
Hello Semlinker, from Cache
Welcome to Deno ?

那麼現在問題又來了,如何強制重新整理快取,即重新編譯 TypeScript 程式碼呢?針對這個問題,在執行 deno run 命令時,我們需要新增 --reload 標誌,來告訴 Deno 需要重新重新整理指定檔案:

$ deno run --reload https://deno.land/std/examples/welcome.ts
Download https://deno.land/std/examples/welcome.ts
Warning Implicitly using master branch https://deno.land/std/examples/welcome.ts
Compile https://deno.land/std/examples/welcome.ts
Welcome to Deno ?

除了 --reload 標誌之外,Deno run 命令還支援很多其他的標誌,感興趣的讀者可以執行 deno run --help 命令來檢視更多的資訊。

3.2 TCP Echo Server

前面我們已經介紹瞭如何執行官方的 welcome 示例,下面我們來介紹如何使用 Deno 建立一個簡單的 TCP echo 伺服器。首先我們建立一個 learn-deno 專案,然後在該專案下新建一個 quickstart 目錄,接著新建一個 echo_server.ts 檔案並輸入以下程式碼:

const listener = Deno.listen({ port: 8080 });

console.log("listening on 0.0.0.0:8080");
for await (const conn of listener) {
  Deno.copy(conn, conn);
}

for await...of 語句會在非同步或者同步可迭代物件上建立一個迭代迴圈,包括 String,Array,Array-like 物件(比如 arguments 或者 NodeList),TypedArray,Map, Set 和自定義的非同步或者同步可迭代物件。

for await...of 的語法如下:

for await (variable of iterable) {
statement
}

輸入完以上程式碼之後,相信很多讀者會跟我一樣,直接在命令列執行以下命令:

➜  quickstart deno run ./echo_server.ts
Compile file:///Users/fer/LearnProjects/learn-deno/quickstart/echo_server.ts
error: Uncaught PermissionDenied: network access to "0.0.0.0:8080", run again with the --allow-net flag
    at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
    at Object.sendSync ($deno$/ops/dispatch_json.ts:72:10)
    at Object.listen ($deno$/ops/net.ts:51:10)
    at Object.listen ($deno$/net.ts:152:22)
    at file:///Users/fer/LearnProjects/learn-deno/quickstart/echo_server.ts:1:23

很明顯是許可權錯誤,從錯誤資訊中,Deno 告訴我們需要設定 --allow-net 標誌,以允許網路訪問。為什麼會這樣呢?這是因為 Deno 是一個 JavaScript/TypeScript 的執行時,預設使用安全環境執行程式碼。下面我們新增 --allow-net 標誌,然後再次執行 echo_server.ts 檔案:

➜  quickstart deno run --allow-net ./echo_server.ts
listening on 0.0.0.0:8080

當伺服器成功執行之後,我們使用 nc 命令來測試一下伺服器的功能:

➜  ~ nc localhost 8080
hell semlinker
hell semlinker

介紹完如何使用 Deno 建立一個簡單的 TCP echo 伺服器,我們再來介紹一下如何使用 Deno 建立一個簡單的 HTTP 伺服器。

3.3 HTTP Server

與 TCP Server 一樣,在 quickstart 目錄下,我們新建一個 http_server.ts 檔案並輸入以下內容:

import { serve } from "https://deno.land/std@v0.50.0/http/server.ts";

const PORT = 8080;
const s = serve({ port: PORT });

console.log(` Listening on <http://localhost>:${PORT}/`);

for await (const req of s) {
  req.respond({ body: "Hello Semlinker\\n" });
}
友情提示:在實際開發過程中,你可以從 https://deno.land/std 地址獲取所需的標準庫版本。示例中我們顯式指定了版本,當然你也可以不指定版本,比如這樣:https://deno.land/std/http/se...

在上述程式碼中,我們匯入了 Deno 標準庫 http 模組中 serve 函式,然後使用該函式快速建立 HTTP 伺服器,該函式的定義如下:

// https://github.com/denoland/deno/blob/master/std/http/server.ts
export function serve(addr: string | HTTPOptions): Server {
  if (typeof addr === "string") {
    const [hostname, port] = addr.split(":");
    addr = { hostname, port: Number(port) };
  }

  const listener = Deno.listen(addr);
  return new Server(listener);
}

serve 函式接收一個引數,其型別是 string | HTTPOptions,其中 HTTPOptions 介面的定義如下:

/** Options for creating an HTTP server. */
export type HTTPOptions = Omit<Deno.ListenOptions, "transport">;

export interface ListenOptions {
  /** The port to listen on. */
  port: number;
  /** A literal IP address or host name that can be resolved to an IP address.
   * If not specified, defaults to `0.0.0.0`. */
  hostname?: string;
}

當輸入的引數型別是字串時,serve 函式會使用 : 冒號對字串進行切割,獲取 hostnameport,然後包裝成物件賦值給 addr 引數,接著使用 addr 引數繼續呼叫 listen 函式進一步建立 listener 物件,最終呼叫 new Server(listener) 建立 HTTP 伺服器。

建立完 HTTP 伺服器,我們來啟動該伺服器,開啟命令列輸入以下命令:

➜  quickstart deno run --allow-net ./http_server.ts 
Compile file:///Users/fer/LearnProjects/learn-deno/quickstart/http_server.ts
 Listening on <http://localhost>:8080/

接著開啟瀏覽器,在位址列上輸入 http://localhost:8080/ 地址,之後在當前頁面中會看到以下內容:

Hello Semlinker\n

3.4 Cat 命令

介紹完如何使用 Deno 搭建一個 HTTP 伺服器,接下來我們再來介紹如何使用 Deno 開發一個簡單的 cat 命令。在 Linux 系統中,cat 命令用於連線檔案並列印到標準輸出裝置上。 現在我們在 quickstart 目錄下,新建一個 cat.ts 檔案並輸入以下內容:

for (let i = 0; i < Deno.args.length; i++) {
  let filename = Deno.args[i];
  let file = await Deno.open(filename);
  await Deno.copy(file, Deno.stdout);
  file.close();
}

在以上程式碼中,我們使用了 Deno 名稱空間下的 3 個 API,它們的作用如下:

  • Deno.args:獲取指令碼接收的引數;
  • Deno.open:用於開啟檔案,返回一個 Promise 物件,resolve 後返回一個 Deno.File 物件;
  • Deno.copy:用於執行拷貝操作,實現從源到目標的拷貝。當遇到 EOF 或發生錯誤時,會停止拷貝操作。呼叫該方法後,也會返回一個 Promise 物件,resolve 後會返回已複製的位元組數。

由於 Deno 是一個 JavaScript/TypeScript 的執行時,預設使用安全環境執行程式碼。因此在執行 cat.ts 指令碼時,我們要設定 --allow-read 標誌 ,已允許檔案系統讀訪問。即具體的指令碼如下所示:

➜  quickstart deno run --allow-read cat.ts echo_server.ts

以上命令成功執行後,控制檯會輸出以下結果:

const listener = Deno.listen({ port: 8080 });

console.log("listening on 0.0.0.0:8080");
for await (const conn of listener) {
  Deno.copy(conn, conn);
}

快速體驗完 Deno,要進行實際專案開發,我們還得了解一下如何除錯 Deno 應用程式,所以下面我們將介紹如何使用 Chrome Devtools 和 VSCode 除錯 Deno 應用程式。

四、除錯 Deno

Deno 支援 V8 Inspector Protocol。使用 Chrome Devtools 或其他支援該協議的客戶端(比如 VSCode)能夠除錯 Deno 程式。要啟用除錯功能,用 --inspect--inspect-brk 選項執行 Deno,對應的選項描述如下:

--inspect=<HOST:PORT>
  activate inspector on host:port (default: 127.0.0.1:9229)

--inspect-brk=<HOST:PORT>
  activate inspector on host:port and break at start of user script

--inspect 選項允許在任何時間點連線偵錯程式,而 --inspect-brk 選項會等待偵錯程式連線,在第一行程式碼處暫停執行。

4.1 Chrome Devtools

讓我們用 Chrome 開發者工具來除錯一個簡單的程式,我們將使用來自 stdfile_server.ts,這是一個簡單的靜態檔案服務。

使用 --inspect-brk 選項,在第一行程式碼處暫停執行。

$ deno run --inspect-brk --allow-read --allow-net https://deno.land/std@v0.50.0/http/file_server.ts
Debugger listening on ws://127.0.0.1:9229/ws/1e82c406-85a9-44ab-86b6-7341583480b1
Download https://deno.land/std@v0.50.0/http/file_server.ts
Compile https://deno.land/std@v0.50.0/http/file_server.ts
...

開啟 chrome://inspect,點選 Target 旁邊的 Inspect

進一步瞭解更詳細的除錯說明,可訪問 https://deno.land/manual/tool... URL 地址。

4.2 VSCode

Deno 可以在 VSCode 中除錯。外掛的官方支援正在開發中 https://github.com/denoland/v...,當然我們也可以通過手動提供 launch.json 配置,來連線偵錯程式:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Deno",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "runtimeExecutable": "deno",
      "runtimeArgs": ["run", "--inspect-brk", "-A", "<entry_point>"],
      "port": 9229
    }
  ]
}

注意:將 <entry_point> 替換為實際的指令碼名稱。

下面讓我們來嘗試一下除錯本地原始檔,建立 server.ts

import { serve } from "https://deno.land/std@v0.50.0/http/server.ts";
const s = serve({ port: 8000 });
console.log("http://localhost:8000/");

for await (const req of s) {
  req.respond({ body: "Hello World\n" });
}

<entry_point> 改為 server.ts,然後執行。

(圖片來源:https://deno.land/manual/tool...

(圖片來源:https://deno.land/manual/tool...

五、Deno Web API

Deno 還支援 W3C 標準規範,因此像 fetchsetTimeoutBlobWorker 等 API 都可以被直接使用。目前 Deno 已實現了以下 Web APIs:

出於篇幅考慮考慮,阿寶哥不打算介紹上面所有 Web APIs,只打算簡單介紹一下 Blob API 和 Worker API。

5.1 Blob API

Blob(Binary Large Object)表示二進位制型別的大物件。在資料庫管理系統中,將二進位制資料儲存為一個單一個體的集合。Blob 通常是影像、聲音或多媒體檔案。在 JavaScript 中 Blob 型別的物件表示不可變的類似檔案物件的原始資料。

Blob 由一個可選的字串 type(通常是 MIME 型別)和 blobParts 組成:

MIME(Multipurpose Internet Mail Extensions)多用途網際網路郵件擴充套件型別,是設定某種副檔名的檔案用一種應用程式來開啟的方式型別,當該副檔名檔案被訪問的時候,瀏覽器會自動使用指定應用程式來開啟。多用於指定一些客戶端自定義的檔名,以及一些媒體檔案開啟方式。

常見的 MIME 型別有:超文字標記語言文字 .html text/html、PNG影像 .png image/png、普通文字 .txt text/plain 等。

5.1.1 建構函式

Blob 建構函式的語法為:

var aBlob = new Blob(blobParts, options);

相關的引數說明如下:

  • blobParts:它是一個由 ArrayBuffer,ArrayBufferView,Blob,DOMString 等物件構成的陣列。DOMStrings 會被編碼為 UTF-8。
  • options:一個可選的物件,包含以下兩個屬性:

    • type —— 預設值為 "",它代表了將會被放入到 blob 中的陣列內容的 MIME 型別。
    • endings —— 預設值為 "transparent",用於指定包含行結束符 \n 的字串如何被寫入。 它是以下兩個值中的一個: "native",代表行結束符會被更改為適合宿主作業系統檔案系統的換行符,或者 "transparent",代表會保持 blob 中儲存的結束符不變。
5.1.2 屬性

前面我們已經知道 Blob 物件包含兩個屬性:

  • size(只讀):表示 Blob 物件中所包含資料的大小(以位元組為單位)。
  • type(只讀):一個字串,表明該 Blob 物件所包含資料的 MIME 型別。如果型別未知,則該值為空字串。
5.1.3 方法
  • slice([start[, end[, contentType]]]):返回一個新的 Blob 物件,包含了源 Blob 物件中指定範圍內的資料。
  • stream():返回一個能讀取 blob 內容的 ReadableStream
  • text():返回一個 Promise 物件且包含 blob 所有內容的 UTF-8 格式的 USVString
  • arrayBuffer():返回一個 Promise 物件且包含 blob 所有內容的二進位制格式的 ArrayBuffer

這裡我們需要注意的是,Blob 物件是不可改變的。我們不能直接在一個 Blob 中更改資料,但是我們可以對一個 Blob 進行分割,從其中建立新的 Blob 物件,將它們混合到一個新的 Blob 中。這種行為類似於 JavaScript 字串:我們無法更改字串中的字元,但可以建立新的更正後的字串。

5.1.4 使用示例
let myBlobParts = ["<html><h2>Hello Semlinker</h2></html>"]; 
let myBlob = new Blob(myBlobParts, {
  type: "text/html",
  ending: "transparent",
}); 

console.log(myBlob.size + " bytes size");
console.log(myBlob.type + " is the type");

以上程式碼使用 deno run deno-blob.ts 命令執行後,命令列會輸出以下結果:

37 bytes size
text/html is the type

5.2 Worker API

Web Worker 是 HTML5 標準的一部分,這一規範定義了一套 API,它允許一段 JavaScript 程式執行在主執行緒之外的另外一個執行緒中。Web Worker 的作用,就是為 JavaScript 創造多執行緒環境,允許主執行緒建立 Worker 執行緒,將一些任務分配給後者執行。

在主執行緒執行的同時,Worker 執行緒在後臺執行,兩者互不干擾。等到 Worker 執行緒完成計算任務,再把結果返回給主執行緒。這樣的好處是,可以在獨立執行緒中處理一些計算密集型或高延遲的任務,從而允許主執行緒(通常是 UI 執行緒)不會因此被阻塞或拖慢。

(圖片來源:https://viblo.asia/p/simple-w...

Worker() 建構函式建立一個 Worker 物件,該物件執行指定的URL指令碼。這個指令碼必須遵守同源策略 。如果違反同源策略,則會丟擲一個 SECURITY_ERR 型別的 DOMException。

5.2.1 Worker 建構函式

Worker 建構函式的語法為:

const myWorker = new Worker(aURL, options);

相關的引數說明如下:

  • aURL:是一個 DOMString 表示 worker 將執行的指令碼的 URL。它必須遵守同源策略。
  • options(可選):包含可在建立物件例項時設定的選項屬性的物件。可用屬性如下:

    • type:用以指定 Worker 型別的 DOMString 值. 該值可以是 classic 或 module。如果未指定,將使用預設值 classic。
    • credentials:用以指定 worker 憑證的 DOMString 值。該值可以是 omit,same-origin 或 include。如果未指定,或者 type 是 classic,將使用預設值 omit (不要求憑證)。
    • name:在 DedicatedWorkerGlobalScope 的情況下,用來表示 Worker 的 scope 的一個 DOMString 值,主要用於除錯目的。

需要注意的是,在建立 Web Worker 的時候,可能會出現以下異常:

  • 當 document 不被允許啟動 worker 的時候,將丟擲一個 SecurityError 異常。比如:如果提供的 aURL 有語法錯誤,或者與同源策略相沖突(跨域訪問)。
  • 如果 worker 的 MIME 型別不正確,將丟擲一個 NetworkError 異常。worker 的 MIME 型別必須是 text/javascript
  • 如果 aURL 無法被解析(格式錯誤),將丟擲一個 SyntaxError 異常。
5.1.2 使用示例

main.ts

const worker = new Worker(new URL("worker.ts", import.meta.url).href, {
  type: "module",
});

worker.onmessage = (e: MessageEvent) => {
  console.log(`Main: Received msg from deno worker - ${e.data}`);
};

worker.postMessage("Hello Deno");

worker.ts

self.onmessage = (e: MessageEvent) => {
  console.log(`Worker: Received from main - ${e.data}`);
  self.postMessage("Hello Semlinker");
};

以上程式碼使用 deno run --allow-read main.ts 命令執行後,命令列會輸出以下結果:

Worker: Received from main - Hello Deno
Main: Received msg from deno worker - Hello Semlinker

六、Deno Web 開發

相信接觸過 Node.js 的讀者對 Express、Hapi、Koa 這些 Web 應用開發框架都不會陌生,在 Deno 平臺中如果你也想做 Web 應用開發,可以考慮直接使用以下現成的框架:

  • abc:A better Deno framework to create web application。
  • deno-drash:A REST microframework for Deno with zero dependencies。
  • deno-express:Node Express way for Deno。
  • oak:A middleware framework for Deno's net server ? 。
  • pogo:Server framework for Deno。
  • servest:?A progressive http server for Deno?。

寫作本文時,目前 Star 數最高的專案是 Oak,下面我們來簡單介紹一下 Oak

A middleware framework for Deno's http server, including a router middleware.

This middleware framework is inspired by Koa and middleware router inspired by koa-router.

很顯然 Oak 的的靈感來自於 Koa,而路由中介軟體的靈感來源於 koa-router 這個庫。如果你以前使用過 Koa 的話,相信你會很容易上手 Oak。不信的話,我們來看個示例:

import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();

app.use((ctx) => {
  ctx.response.body = "Hello Semlinker!";
});

await app.listen({ port: 8000 });

以上示例對於每個 HTTP 請求,都會響應 "Hello Semlinker!"。只有一箇中介軟體是不是感覺太 easy 了,下面我們來看一個更復雜的示例(使用多箇中介軟體):

import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();

// Logger
app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.headers.get("X-Response-Time");
  console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});

// Timing
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});

// Hello World!
app.use((ctx) => {
  ctx.response.body = "Hello World!";
});

await app.listen({ port: 8000 });

為了更好地理解 Oak 中介軟體流程控制,我們來一起回顧一下 Koa 大名鼎鼎的 “洋蔥模型”

從 “洋蔥模型” 示例圖中我們可以很清晰的看到一個請求從外到裡一層一層的經過中介軟體,響應時從裡到外一層一層的經過中介軟體。上述程式碼使用 deno run --allow-net oak/oak-middlewares-demo.ts 成功執行後,我們開啟瀏覽器,然後訪問 http://localhost:8000/ URL 地址,之後在命令列會輸出以下結果:

GET http://localhost:8000/ - 0ms
GET http://localhost:8000/favicon.ico - 0ms

這裡我們只是簡單介紹了 Oak,如果對 Oak 如何開發 REST API 感興趣的話,可以閱讀阿寶哥 了不起的 Deno 實戰教程 這篇文章。

七、阿寶哥有話說

7.1 Deno 如何使用第三方模組

截止目前(2020-06-30)為止,Deno 支援的第三方庫的數目為 707

(圖片來源:https://deno.land/x

下面我們以大家熟悉的 lodash 為例,來看一下如何使用第三方模組。首先在搜尋框中輸入 lodash,對應的搜尋結果如下:

(圖片來源:https://deno.land/x

接著選擇你想要安裝的模組,這裡阿寶哥選擇 deno_lodash 這個模組,點選該列表項會進入該模組的詳情頁。然後你就可以根據模組的說明文件來安裝使用模組了,以下是文件中該模組的使用示例:

import _ from "https://deno.land/x/deno_lodash/mod.ts";

console.log(_.defaults({ 'a': 1 }, { 'a': 3, 'b': 2 }));
// → { 'a': 1, 'b': 2 }

console.log(_.partition([1, 2, 3, 4], (n:number) => n % 2));
// → [[1, 3], [2, 4]]

7.2 Deno 如何統一管理專案依賴

在 Deno 專案中可以使用一個 deps.ts 檔案來統一管理所有依賴,別的地方直接從 deps.ts 統一引入,例如 oak 專案的 deps.ts 如下所示:

// https://github.com/oakserver/oak/blob/main/deps.ts
export { copyBytes, equal } from "https://deno.land/std@0.59.0/bytes/mod.ts";
export { Sha1 } from "https://deno.land/std@0.59.0/hash/sha1.ts";
export { HmacSha256 } from "https://deno.land/std@0.59.0/hash/sha256.ts";

// 已省略部分程式碼
// 3rd party dependencies
export {
  contentType,
  extension,
  lookup,
} from "https://deno.land/x/media_types@v2.3.8/mod.ts";
export {
  compile,
  parse as pathParse,
  pathToRegexp,
} from "https://raw.githubusercontent.com/pillarjs/path-to-regexp/v6.1.0/src/index.ts";

那麼如何查詢這些模組呢?除了在前面提到的 Deno 官網 第三方模組 頁面查詢之外,你也可以在 pika 中尋找指定的模組,如果搜尋出來的結果能直接使用,不會報錯,表明該模組可以正常在 deno 中使用。

7.3 Deno 如何使用 npm 包

在 Deno 中如果要使用 npm 包,可以利用 Deno 標準庫中提供的 createRequire 方法,具體使用方式如下:

import { createRequire } from "https://deno.land/std/node/module.ts";

const require = createRequire(import.meta.url);
// Loads native module polyfill.
const path = require("path");
// Loads extensionless module.
const cjsModule = require("./my_mod");
// Visits node_modules.
const leftPad = require("left-pad");

除此之外,Deno 還可以藉助 jspm.io 的能力來呼叫 npm 的模組,感興趣的小夥伴可以閱讀 Deno藉助jspm使用npm 這篇文章。

7.4 如何用一個程式碼庫來維護同時支援 Deno 和 npm 的模組

儘管將模組移植到 Deno 非常容易,但是你要維護兩個程式碼庫卻很麻煩。所以如果你想只使用一個程式碼庫來維護同時支援 Deno 和 npm 的模組,這時你可以考慮使用 denoify 這個工具。

denoify 是一個構建工具,該構建工具將旨在以 node 或 web 為目標的 TypeScript 程式碼庫作為輸入,並將原始檔的修改版本作為 Deno模組部署。

(圖片來源:https://github.com/garronej/d...

以下是使用 denoify 開發的模組:

感興趣的小夥伴,可以自行了解一下上述的模組。另外,如果你也想要在專案中使用 denoify ,那麼可以參考這個倉庫,該倉庫會一步步教你如何在專案中配置 denoify

八、總結

本文從七個方面入手,介紹了 Deno 入門相關的基礎知識,希望對 Deno 初學者能有一些幫助,當然有寫得不好的地方歡迎小夥伴們指出。Deno 現在還在高速地發展,標準庫還未正式釋出穩定的版本,寫作本文時,最新版本是 0.59.0。相比成熟的 Node.js 社群,Deno 社群還有很長一段路要走,感興趣的小夥伴們可以持續關注它。

阿寶哥是在 Deno 1.0.0 釋出後才正式學習 Deno,目前也還在不斷學習中。對阿寶哥個人來說,近期比較感興趣的是 Deno Web API,因此近期也花了蠻多時間學習 Deno Web API 的實現。在學習過程中,也遇到挺多問題的,這裡要感謝 justjavac(迷渡大大) 的耐心解答。

能看到這裡的小夥伴都是 “真愛”,最後感謝大家閱讀本教程,歡迎對 Deno 感興趣的小夥伴跟阿寶哥一起交流和學習 Deno。

九、參考資源

十、推薦閱讀