Deno 入門手冊:附大量 TypeScript 代碼實例

  • 原文地址:在 freeCodeCamp 中文社區(qū)閱讀原文 The Deno Handbook
  • 原文作者:Flavio Copes
  • 原文發(fā)布時間:2020-05-12
  • 譯者:hylerrix(韓亦樂), Yunkou(寇云)
  • 校對者:hylerrix(韓亦樂)
  • 備注:本文遵循 freeCodeCamp 翻譯規(guī)范,同時本文會收錄在《Deno 鉆研之術》的翻譯篇中。
  • 備注:非營利組織 freeCodeCamp.org 自 2014 年成立以來,以“幫助人們免費學習編程”為使命,創(chuàng)建了大量免費的編程教程,包括交互式課程、視頻課程、文章等。線下開發(fā)者社區(qū)遍布 160 多個國家、2000 多個城市。我們正在幫助全球數(shù)百萬人學習編程,希望讓世界上每個人都有機會獲得免費的優(yōu)質(zhì)的編程教育資源,成為開發(fā)者或者運用編程去解決問題。搜索關注微信公眾號 “freeCodeCamp”,可了解更多信息。

我每周都在探索新的項目,很少會有一個像 Deno 這樣的項目讓我如此著迷。

在本手冊中我想要讓你快速入手 Deno。我會將其與 Node.js 進行對比,然后助力你在 Deno 上搭建第一個 REST API Demo。

目錄

  1. 什么是 Deno?
  2. 為什么是 Deno?為什么是現(xiàn)在?
  3. 你應該學習 Deno 嗎?
  4. Deno 將取代 Node.js 嗎?
  5. 一流的 TypeScript 支持
  6. 與 Node.js 的異同
  7. 不再有包管理器
  8. 安裝 Deno
  9. Deno 命令
  10. 你的第一個 Deno 應用
  11. Deno 代碼實例
  12. 你的第一個 Deno 應用(深入版)
  13. Deno 安全沙箱(Sandbox)
  14. 格式化代碼
  15. 標準庫
  16. 另一個 Deno 示例
  17. Deno 是否有 Express/Hapi/Koa/*?
  18. 示例:使用 Oak 構建 REST API
  19. 更多內(nèi)容
  20. 花絮
  21. 結語

此外,你可以在此處獲取此 Deno 手冊的 PDF / ePub / Mobi 版本。

什么是 Deno?

如果你熟悉流行的服務器端 JavaScript 運行時 Node.js,那么 Deno 就像 Node.js 一樣,但卻在很多方面都得到了深刻改善的全新 JavaScript / TypeScript 運行時。

讓我們從 Deno 的功能列表快速了解:

  • Deno 基于最新的 JavaScript 語言;
  • Deno 具有覆蓋面廣泛的標準庫;
  • Deno 以 TypeScript 為核心,配以更多獨特的方式從而帶來了巨大的優(yōu)勢,其中包括一流的 TypeScript 支持(Deno 自動編譯 TypeScript 而無需你單獨編譯);
  • Deno 大力擁抱 ES 模塊標準;
  • Deno 沒有包管理器;
  • Deno 具有一流的 await 語法支持;
  • Deno 內(nèi)置測試工具;
  • Deno 旨在盡可能地與瀏覽器兼容,例如通過提供內(nèi)置對象 fetch 和全局 window 對象。

我們將在本手冊中展開探索所有上述功能。

在你實戰(zhàn)完 Deno 并了解它獨特的功能魅力后,Node.js 或許會看起來有些過時。

特別是因為 Node.js 的 API 是基于回調(diào)機制的,因為 Node.js 是在 Promise 和 Async / Await 定義在標準之前編寫的。Node.js 中無法對此機制進行全新的更改,因為此類更改將產(chǎn)生“毀滅性”的影響。因此,在 Node.js 中我們陷入了回調(diào)大量 API 的困境。

Node.js 的確很棒,并在可見的未來將繼續(xù)成為 JavaScript 世界中事實上的標準。但我認為我們將逐漸看到 Deno 會因其一流的 TypeScript 支持和其內(nèi)置的、覆蓋面廣泛的現(xiàn)代標準庫而越來越被重視和采用。

由于沒有向后兼容性的歷史原因,Deno 將可以承擔起所有使用現(xiàn)代 Web 技術編寫的工程建設。但目前的現(xiàn)實是,我們也無法保證十年之內(nèi) Deno 不會發(fā)生像 Node.js 同樣的事情,并且不會出現(xiàn)一項新技術代替 Deno。

為什么是 Deno?為什么是現(xiàn)在?

大約 2 年前,Node.js 的創(chuàng)建者 Ryan Dahl 在 JSConf EU 上首次介紹了 Deno。觀看當時的演講視頻會非常有趣。如果你平時在大量接觸 Node.js 和 JavaScript,這個視頻請不要錯過。

每個項目經(jīng)理都必須下發(fā)決定。Ryan 回看 Node.js 中的一些早期設計依然感覺十分遺憾。此外,在 ES6/2016/2017 等持續(xù)發(fā)展中的標準加持下,如今的 JavaScript 與 2009 年 Node.js 創(chuàng)立時的 JavaScript 已經(jīng)大不相同。

因此,他開啟了一個全新項目,從而創(chuàng)建出服務器端的第二代 JavaScript 運行時。

新生的技術需要大量時間才能成熟,這正是我現(xiàn)在撰寫本手冊而不是兩年前就開始撰寫的原因。如今,第一個正式穩(wěn)定的 Deno v1.0 版本終于指日可待(不出意外的話,v1.0 會在 2020 年 5 月 13 日發(fā)布)。

譯者注:翻譯本手冊時 Deno 1.0 已經(jīng)發(fā)布。

1.0 看起來僅僅是個數(shù)字,但在社區(qū)約定下,意味著直到 Deno 2.0 前 Deno 都不會有太多重大的破壞性改變——這很重要,因為你終于可以安心學習 Deno 當前的穩(wěn)定版本了。

你應該學習 Deno 嗎?

這并不那么容易回答。

學習像 Deno 這樣全新的知識需要不少的前期技術沉淀。我的建議是:如果你現(xiàn)在才開始在服務器端使用 JavaScript 編程,并且你還不了解 Node.js,更沒有任何 TypeScript 應用開發(fā)經(jīng)驗——那么請從 Node.js 學起。

畢竟用通俗觀點來說,沒有人會在如今因為選擇學習 Node.js 而被解雇。

但如果你喜歡 TypeScript、也不想讓項目中依賴無比龐大的 NPM 軟件包、還想要隨時隨地使用 await 等語法,那么你可能真的需要 Deno。

Deno 將取代 Node.js 嗎?

不能。Node.js 的生態(tài)已經(jīng)十分龐大和完善,獲得了數(shù)以萬計的優(yōu)秀技術支持,將能再戰(zhàn)數(shù)十年。

一流的 TypeScript 支持

Deno 基于 Rust 和 TypeScript 這兩種今天正在迅速發(fā)展的語言編寫。

這意味著,即使我們可能選擇編寫純 JavaScript 代碼來運行在基于 TypeScript 語言編寫的 Deno 上,我們也可以獲得 TypeScript 的很多好處。

使用 Deno 運行 TypeScript 代碼無需任何手動編譯——Deno 會自動為你執(zhí)行此步驟。

你不必非得在 Deno 上編寫 TypeScript 代碼,但是 Deno 因其核心由 TypeScript 語言編寫的如下相關背景是不容忽視的:

首先,越來越多的 JavaScript 程序員開始喜歡上了 TypeScript 語言。

其次,你使用的工具可以方便地推斷出許多有關用 TypeScript 語言編寫的軟件的信息,例如 Deno。

因此,當我們在 VS Code(緊密集成 TypeScript 的編輯器)上的編碼環(huán)節(jié)就能及時地體會到類型檢查和高級智能感知(IntelliSense)功能帶來的好處。換句話說,編輯器可以以非常有用的方式來幫助我們了解 TypeScript 項目。

與 Node.js 的異同

由于 Deno 從某種角度來講是 Node.js 的替代品,因此直接比較兩者的異同對我們的理解會很有幫助。

相似之處:

  • 兩者都是基于 V8 引擎開發(fā)的;
  • 兩者都非常適合在服務器端上編寫 JavaScript 應用。

差異之處:

  • Node.js 用 C++ 和 JavaScript 語言編寫。Deno 用 Rust 和 TypeScript 語言編寫。
  • Node.js 有一個官方的軟件包管理器,稱為 NPM。Deno 不會有,而會允許你從 URL 導入任何 ES 模塊。
  • Node.js 使用 CommonJS 模塊語法導入軟件包。Deno 使用 ES 標準模塊導入。
  • Deno 在其所有 API 和標準庫中都使用現(xiàn)代 ECMAScript 功能,而 Node.js 使用基于回調(diào)的標準庫,并且沒有計劃對其進行升級。
  • Deno 通過權限控制提供了一個安全的沙箱環(huán)境,程序只能訪問由用戶設置為可執(zhí)行標志的文件。Node.js 程序可以直接訪問用戶足以訪問的任何內(nèi)容。
  • Deno 長期以來一直在探索將程序編譯成單個可執(zhí)行文件的可能性,從而使得該可執(zhí)行文件可以在沒有外部依賴項(例如 Go)的情況下運行,但這并不是一件容易的事,如果做得到,將會成為更有話語權的游戲規(guī)則改變者。

沒有包依賴管理器

沒有像 NPM 一樣的程序包管理器并且大量依靠 URL 來承載和導入程序包是有利有弊的。但我真的很喜歡這個特性:它將會非常靈活,我們可以直接創(chuàng)建軟件包而無需在 NPM 這樣的存儲庫中發(fā)布它們。

雖然還沒有官方的消息,但我認為 Deno 下的某種軟件包管理器將會出現(xiàn)。

與此同時,Deno 網(wǎng)站為第三方軟件包提供代碼托管服務(并幫助其通過 URL 分發(fā)):詳見 https://deno.land/x/

安裝 Deno

就閑聊到這里吧!讓我們開始著手安裝 Deno。

最簡單的方法是使用 Homebrew

brew install deno

輸出如上命令后,你將可以訪問 deno 命令。幫助是deno --help

譯者注:如果 HomeBrew 安裝太慢可以嘗試輸入如下命令手動關閉 HomeBrew 的自動更新檢測: export HOMEBREW_NO_AUTO_UPDATE=true

flavio@mbp~> deno --help
deno 0.42.0
A secure JavaScript and TypeScript runtime

Docs: https://deno.land/std/manual.md
Modules: https://deno.land/std/ https://deno.land/x/
Bugs: https://github.com/denoland/deno/issues

To start the REPL, supply no arguments:
  deno

To execute a script:
  deno run https://deno.land/std/examples/welcome.ts
  deno https://deno.land/std/examples/welcome.ts

To evaluate code in the shell:
  deno eval "console.log(30933 + 404)"

Run 'deno help run' for 'run'-specific flags.

USAGE:
    deno [OPTIONS] [SUBCOMMAND]

OPTIONS:
    -h, --help
            Prints help information

    -L, --log-level <log-level>
            Set log level [possible values: debug, info]

    -q, --quiet
            Suppress diagnostic output
            By default, subcommands print human-readable diagnostic messages to stderr.
            If the flag is set, restrict these messages to errors.
    -V, --version
            Prints version information


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
    repl           Read Eval Print Loop
    run            Run a program given a filename or url to the module
    test           Run tests
    types          Print runtime TypeScript declarations
    upgrade        Upgrade deno executable to newest version

ENVIRONMENT VARIABLES:
    DENO_DIR             Set deno's base directory (defaults to $HOME/.deno)
    DENO_INSTALL_ROOT    Set deno install's output directory
                         (defaults to $HOME/.deno/bin)
    NO_COLOR             Set to disable color
    HTTP_PROXY           Proxy address for HTTP requests
                         (module downloads, fetch)
    HTTPS_PROXY          Same but for HTTPS

Deno 命令

請注意上節(jié)中 deno --helpSUBCOMMANDS 中的部分,其中列出了我們在當前版本(0.42.0)中可以運行的所有命令,如下:

  • bundle :將項目的模塊和依賴項捆綁到單個文件中;
  • cache :緩存依賴項;
  • completions :generate shell completions;
  • doc :顯示某模塊的文檔;
  • eval :運行一段代碼,例如 deno eval "console.log(1 + 2) ;
  • fmt :內(nèi)置的代碼格式化程序(類似于 Go 語言中的 gofmt);
  • help :打印某消息或某給定子命令的幫助信息;
  • info :顯示有關緩存的信息或與源文件有關的信息;
  • install :將腳本安裝為可執(zhí)行文件;
  • repl :開啟 REPL 環(huán)境(默認子命令);
  • run :運行給定文件名或 URL 的程序;
  • test :運行測試;
  • types :打印運行時的 TypeScript 聲明;
  • upgrade :升級 Deno 到最新版本。

你可以運行 deno <subcommand> help 以獲取該子命令的特定文檔,例如 deno run --help。

如下所示,我們可以直接輸入 deno 命令命令來默認啟動 REPL(Read-Execute-Print-Loop)環(huán)境直接調(diào)試功能,這與運行 deno repl 效果是相同的。

一個更常見的直接使用 deno 命令的場景是執(zhí)行在 TypeScript 文件中寫的 Deno 應用程序。

譯者注:現(xiàn)在需要使用 deno run 命令而非 deno 命令來執(zhí)行 TypeScript 文件。

你可以同時運行 TypeScript(.ts)文件或 JavaScript(.js)文件。

如果你不熟悉 TypeScript,請不要擔心——Deno 是用 TypeScript 編寫的,并且你可以使用純 JavaScript 編寫“客戶端”應用程序。

如果你想快速上手的 TypeScript 話,可以閱讀我的 TypeScript 教程。

你的第一個 Deno 應用

讓我們來運行第一個 Deno 應用程序。

Deno 讓我感到非常驚奇的特性是:你甚至不必寫一行代碼,便可以直接運行任何 URL 上的 Deno 應用程序。

此時 Deno 會將 URL 上的程序下載到本地并進行編譯,然后運行:

當然,我一般不建議從 Internet 運行無法保障安全性的代碼。在這種情況下,我們先運行 Deno 官方網(wǎng)站上提供的 Demo;另外 Deno 還有一個沙箱,可以阻止程序執(zhí)行你不希望做的事情。稍后再詳細介紹。

這個程序很簡單,只需要一個console.log()調(diào)用:

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

如果使用瀏覽器打開直接打開 https://deno.land/std/examples/welcome.ts 這個 URL,則會看到以下頁面:

奇怪吧?你可能期待著打開 URL 后出現(xiàn)一個純 TypeScript 文件以供下載,但是我們卻看到了一個網(wǎng)頁。原因是 Deno 網(wǎng)站的 Web 服務器知道你正在使用瀏覽器,并為你提供了對用戶更加友好的頁面。

為了驗證這個功能,我們可以使用 wget 命令來測試這個 URL, wget 使用 text/plain 下載文本而不是 text/html

如果你想再運行這個程序,現(xiàn)在已經(jīng)被 Deno 緩存了,不需要再下載和編譯了。

你可以用 --reload 參數(shù)強制重新下載和編譯原始源碼。

在當前版本(0.42.0)中,deno --run 有許多未在 deno --help 清單中列出的功能。你需要運行 deno run --help 以顯示更多。

flavio@mbp~> deno run --help
deno-run
Run a program given a filename or url to the module.

By default all programs are run in sandbox without access to disk, network or
ability to spawn subprocesses.
  deno run https://deno.land/std/examples/welcome.ts

Grant all permissions:
  deno run -A https://deno.land/std/http/file_server.ts

Grant permission to read from disk and listen to network:
  deno run --allow-read --allow-net https://deno.land/std/http/file_server.ts

Grant permission to read whitelisted files from disk:
  deno run --allow-read=/etc https://deno.land/std/http/file_server.ts

USAGE:
    deno run [OPTIONS] <SCRIPT_ARG>...

OPTIONS:
    -A, --allow-all
            Allow all permissions

        --allow-env
            Allow environment access

        --allow-hrtime
            Allow high resolution time measurement

        --allow-net=<allow-net>
            Allow network access

        --allow-plugin
            Allow loading plugins

        --allow-read=<allow-read>
            Allow file system read access

        --allow-run
            Allow running subprocesses

        --allow-write=<allow-write>
            Allow file system write access

        --cached-only
            Require that remote dependencies are already cached

        --cert <FILE>
            Load certificate authority from PEM encoded file

    -c, --config <FILE>
            Load tsconfig.json configuration file

    -h, --help
            Prints help information

        --importmap <FILE>
            UNSTABLE:
            Load import map file
            Docs: https://deno.land/std/manual.md#import-maps
            Specification: https://wicg.github.io/import-maps/
            Examples: https://github.com/WICG/import-maps#the-import-map
        --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

        --lock <FILE>
            Check the specified lock file

        --lock-write
            Write lock file. Use with --lock.

    -L, --log-level <log-level>
            Set log level [possible values: debug, info]

        --no-remote
            Do not resolve remote modules

    -q, --quiet
            Suppress diagnostic output
            By default, subcommands print human-readable diagnostic messages to stderr.
            If the flag is set, restrict these messages to errors.
    -r, --reload=<CACHE_BLACKLIST>
            Reload source code cache (recompile TypeScript)
            --reload
              Reload everything
            --reload=https://deno.land/std
              Reload only standard modules
            --reload=https://deno.land/std/fs/utils.ts,https://deno.land/std/fmt/colors.ts
              Reloads specific modules
        --seed <NUMBER>
            Seed Math.random()

        --unstable
            Enable unstable APIs

        --v8-flags=<v8-flags>
            Set V8 command line options. For help: --v8-flags=--help


ARGS:
    <SCRIPT_ARG>...
            script args

Deno 代碼實例

除了前文我們運行的 Demo 外,Deno 官網(wǎng)還提供了一些其他的例子,可以在這里查看:https://deno.land/std/examples/。

譯者注:你可能需要配置代理來更好地訪問 DenoLand。

在撰寫本手冊時,我們可以找到:

  • cat.ts :打印的內(nèi)容是作為參數(shù)提供的文件列表;
  • catj.ts :打印的內(nèi)容是作為參數(shù)提供的文件列表;
  • chat/ :聊天的一種實現(xiàn);
  • colors.ts :打印一個彩色版本的 Hello world!;
  • curl.ts :一個簡單的實現(xiàn),curl 它打印指定為參數(shù)的 URL 的內(nèi)容;
  • echo_server.ts :TCP 回顯服務器;
  • gist.ts :一個將文件發(fā)布到 gist.github.com 的程序;
  • test.ts :樣本測試套件;
  • welcome.ts :一個簡單的 console.log 語句(我們在上面運行的第一個程序);
  • xeval.ts :允許你為收到的任何標準輸入行運行任何 TypeScript 代碼。曾經(jīng)被設計為 deno xeval 子命令但現(xiàn)在從官方命令中刪除。

你的第一個 Deno 應用(深入版)

我們來寫一些代碼吧。

前文執(zhí)行的 deno run [https://deno.land/std/examples/welcome.ts](https://deno.land/std/examples/welcome.ts) 命令執(zhí)行的是官網(wǎng)提供的一個 Deno 應用,所以我們沒有看到任何關于 Deno 代碼具體的樣子。

接下來讓我們從 Deno 官方網(wǎng)站上列出的默認示例應用開始。

import { serve } from "https://deno.land/std/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" });
}

這段代碼從 http/server 模塊中導入服務函數(shù)??梢娢覀儾恍枰劝惭b這些模塊,而且也不會像 Node.js 那樣將這些模塊大量存儲在本地機器上。這也是 Deno 安裝速度快的原因之一。

[https://deno.land/std/http/server.ts](https://deno.land/std/http/server.ts) 中導入會導入最新版本的模塊。你可以使用@VERSION導入特定的版本,如下所示。

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

該 serve 函數(shù)在此文件中的定義如下:

/**
 * Create a HTTP server
 *
 *     import { serve } from "https://deno.land/std/http/server.ts";
 *     const body = "Hello World\n";
 *     const s = serve({ port: 8000 });
 *     for await (const req of s) {
 *       req.respond({ body });
 *     }
 */
export function serve(addr: string | HTTPOptions): Server {
  if (typeof addr === "string") {
    const [hostname, port] = addr.split(":");
    addr = { hostname, port: Number(port) };
  }

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

我們接下來實例化一個服務器,調(diào)用 server() 函數(shù)傳遞一個帶有端口屬性的對象。

然后我們運行如下循環(huán)來響應來自服務器的每一個請求。

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

請注意,我們在這里使用 await 關鍵字而不需要將其封裝到異步函數(shù)中,因為 Deno 在其內(nèi)部實現(xiàn)了頂層的 await 支持。

讓我們在本地運行這個程序。假設你使用的是 VS Code(你可以使用任何你喜歡的編輯器),我建議從 justjavac 開發(fā)的 Deno VS Code 擴展入手(當我嘗試的時候還有一個同名的擴展,但是已經(jīng)被淘汰了,可能將來會消失)。

譯者注:justjavac 的 Deno VS Code 拓展將被官方收錄,以后可以直接使用官方的拓展。

該擴展將為 VS Code 提供幾個實用工具和不錯的東西來幫助你編寫應用程序。

現(xiàn)在在一個文件夾中創(chuàng)建一個 app.ts 文件,然后粘貼上面的代碼。

現(xiàn)在用 deno run app.ts 命令運行它。

Deno 會先下載、編譯我們導入的那個依賴及其所有需要的依賴項。

這是由于我們導入的 [https://deno.land/std/http/server.ts](https://deno.land/std/http/server.ts) 文件本身就有數(shù)個其它依賴:

import { encode } from "../encoding/utf8.ts";
import { BufReader, BufWriter } from "../io/bufio.ts";
import { assert } from "../testing/asserts.ts";
import { deferred, Deferred, MuxAsyncIterator } from "../async/mod.ts";
import {
  bodyReader,
  chunkedBodyReader,
  emptyReader,
  writeResponse,
  readRequest,
} from "./_io.ts";
import Listener = Deno.Listener;
import Conn = Deno.Conn;
import Reader = Deno.Reader;

但 Deno 都會幫我們自動導入。

在最后,我們還有一個問題。

這是怎么回事?我們?yōu)槭裁磿盏綀?zhí)行權限被拒絕的提示?

這就涉及到了 Deno 的 Sandbox 問題,我們一起來看看。

Deno 安全沙箱(Sandbox)

我之前提到過,Deno 有一個安全沙箱,可以防止程序做一些你不允許的事情。

這意味著什么呢?

Ryan 曾在 Deno 的介紹講座中提到的一件事是:有時候你想在 Web 瀏覽器之外運行一個 JavaScript 程序,卻不想讓它肆意在你的系統(tǒng)中訪問任何它想要的東西,比如使用網(wǎng)絡與外部世界對話。

為什么我們通常只安裝來自可信來源的 Node.js 包?這是因為沒有什么可以阻止 Node.js 程序獲取你系統(tǒng)上的 SSH 密鑰或其他任何東西,并將其發(fā)送到服務器上。但是,我們該怎么知道自己或其他人使用的一個項目是否被黑客入侵了?

Deno 的解決方案是試圖大量借鑒瀏覽器實現(xiàn)相同的權限模型——除非你明確允許,否則在瀏覽器中運行的任何 JavaScript 都不能在你的系統(tǒng)上做不正當?shù)氖虑椤?/p>

回到 Deno,如果一個程序想要像前面的例子一樣訪問網(wǎng)絡,那么我們需要給它權限。

我們可以通過在運行命令時傳遞一個標志來實現(xiàn),本例中是 --allow-net。

deno run --allow-net app.ts

該應用程序現(xiàn)在監(jiān)聽在 8000 端口上運行著 HTTP 服務器:

其他標志允許 Deno 解鎖其他功能,如下所示:

  • --allow-env :允許訪問環(huán)境變量;
  • --allow-hrtime :允許高分辨率時間測量;
  • --allow-net=<allow-net> :允許網(wǎng)絡訪問;
  • --allow-plugin :允許加載插件;
  • --allow-read=<allow-read> :允許文件系統(tǒng)讀取權限;
  • --allow-run :允許運行子進程;
  • --allow-write=<allow-write> :允許文件系統(tǒng)寫入訪問;
  • --allow-all :允許所有權限(與-A相同)。

其中,net、readwrite 的權限可以是細化的。例如,你可以使用 --allow-read=/dev,允許從特定文件夾中讀取。

格式化代碼

Go 語言編譯器自帶的 gofmt 命令是我非常喜歡 Go 語言特性之一。所有的 Go 代碼的格式看起來都是一樣的。每位 Go 程序員都在使用 gofmt。

JavaScript 程序員都習慣于運行 Prettier 工具,而 deno fmt 實際上直接內(nèi)置相關庫到底層上運行。

假設你有一個格式化問題嚴重的文件如下圖所示。

你運行 deno fmt app.ts,它就會執(zhí)行正確的代碼格式化,包括自動加上缺失的分號。

標準庫

盡管 Deno 還很年輕,但它的標準庫仍然很龐大。這包括:

  • archive :tar 文件歸檔的實用程序
  • async :異步工具
  • bytes :幫助器來操作字節(jié)切片
  • datetime :日期 / 時間解析
  • encoding :各種格式的編碼/解碼
  • flags :解析命令行標志
  • fmt :格式化和打印
  • fs :文件系統(tǒng) API
  • hash :加密庫
  • http :HTTP 服務器
  • io :I/O 庫
  • log :日志實用程序
  • mime :支持多類型數(shù)據(jù)
  • node :Node.js 兼容層
  • path :路徑操縱
  • ws :WebSockets

另一個 Deno 示例

我們再來看看另一個 Deno APP 的例子,以如下 cat.ts 為例。

const filenames = Deno.args;
for (const filename of filenames) {
  const file = await Deno.open(filename);
  await Deno.copy(file, Deno.stdout);
  file.close();
}

這里把 Deno.args 的值分配給了 filenames 變量,Deno.args 是一個包含所有發(fā)送到命令中的參數(shù)的變量。

我們對這些參數(shù)進行迭代:對每一個參數(shù),我們使用 Deno.open() 打開文件,并使用 Deno.copy() 將文件的內(nèi)容打印到 Deno.stdout 中,最后我們關閉該文件。

如果你使用如下命令:

deno run https://deno.land/std/examples/cat.ts

程序被下載編譯后,由于我們沒有指定任何參數(shù),所以沒有發(fā)生任何事情。

現(xiàn)在試試這個:

deno run https://deno.land/std/examples/cat.ts app.ts

假設你在同一個文件夾里有之前項目中的 app.ts。

你會得到如下權限錯誤。

這是因為 Deno 默認情況下不允許訪問文件系統(tǒng)。需要使用 --allow-read=./ 命令授予對當前文件夾的訪問權限:

deno run --allow-read=./ https://deno.land/std/examples/cat.ts app.ts

Deno 是否有 Express/Hapi/Koa/*

當然有??梢钥纯聪路竭@些庫。

示例:使用 Oak 構建 REST-API

我想在這里做一個簡單的 Demo 實戰(zhàn),介紹一下如何使用 Oak 框架構建REST API。Oak 很有意思,因為它的靈感來自于 Koa,一個流行的 Node.js 中間件。正因為如此,如果你以前用過 Koa 的話,會很快熟悉 Oak。

我們要構建的 API 示例也非常簡單。

我們的服務器將在內(nèi)存中存儲一個帶有名字和年齡的旺柴的列表。

我們的需求是:

  • 添加旺柴;
  • 列出旺柴;
  • 獲取有關特定旺柴的詳細信息;
  • 從名單上刪除一只旺柴;
  • 更新旺柴的年齡。

我們將使用 TypeScript 進行此操作,但是沒有什么可以阻止你使用 JavaScript 編寫 API——你只需要刪除下方 TypeScript 文件中所有有關類型描述的代碼并將文件名后綴改為 .js。

創(chuàng)建一個 app.ts 文件。

讓我們開始從 Oak 導入 ApplicationRouter 對象:

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

然后我們得到環(huán)境變量 PORTHOST:

const env = Deno.env.toObject();
const PORT = env.PORT || 4000;
const HOST = env.HOST || "127.0.0.1";

默認情況下,我們的應用程序?qū)⒃?localhost:4000 上運行。

現(xiàn)在,我們創(chuàng)建 Oak 應用程序并啟動它:

const router = new Router();

const app = new Application();

app.use(router.routes());
app.use(router.allowedMethods());

console.log(`Listening on port ${PORT}...`);

await app.listen(`${HOST}:${PORT}`);

現(xiàn)在,應用程序應該可以正常編譯了。

deno run --allow-env --allow-net app.ts

然后 Deno 將下載依賴項:

這時程序監(jiān)聽在 4000 端口上。

下次運行該命令時,Deno 會跳過安裝部分,因為這些包已經(jīng)被緩存了。

在文件的頂部,讓我們定義一個旺柴的接口,然后我們聲明一個初始的 Dogs 數(shù)組 Dog 對象。

interface Dog {
  name: string;
  age: number;
}

let dogs: Array<Dog> = [
  {
    name: "Roger",
    age: 8,
  },
  {
    name: "Syd",
    age: 7,
  },
];

現(xiàn)在,讓我們來實現(xiàn)具體 API。

我們已經(jīng)準備好了一切。在你創(chuàng)建了路由器之后,讓我們添加一些函數(shù),這些函數(shù)將在任何時候觸發(fā)這些路由中的一個端點時被調(diào)用。

const router = new Router();

router
  .get("/dogs", getDogs)
  .get("/dogs/:name", getDog)
  .post("/dogs", addDog)
  .put("/dogs/:name", updateDog)
  .delete("/dogs/:name", removeDog);

看到了嗎?我們的 API 定義是:

  • GET /dogs
  • GET /dogs/:name
  • POST /dogs
  • PUT /dogs/:name
  • DELETE /dogs/:name

讓我們開始一一實現(xiàn)。

從開始 GET /dogs,它將返回所有旺柴的列表:

export const getDogs = ({ response }: { response: any }) => {
  response.body = dogs;
};

接下來,我們就來看看如何通過名字來檢索旺柴。

export const getDog = ({
  params,
  response,
}: {
  params: {
    name: string;
  };
  response: any;
}) => {
  const dog = dogs.filter((dog) => dog.name === params.name);
  if (dog.length) {
    response.status = 200;
    response.body = dog[0];
    return;
  }

  response.status = 400;
  response.body = { msg: `Cannot find dog ${params.name}` };
};

這是我們添加一個新的旺柴的方法:

export const addDog = async ({
  request,
  response,
}: {
  request: any;
  response: any;
}) => {
  const body = await request.body();
  const dog: Dog = body.value;
  dogs.push(dog);

  response.body = { msg: "OK" };
  response.status = 200;
};

注意,我現(xiàn)在使用 const body = await request.body() 來獲取正文的內(nèi)容,因為 nameage 值是以 JSON 的形式傳遞的。

這是我們更新旺柴的年齡的方法:

export const updateDog = async ({
  params,
  request,
  response,
}: {
  params: {
    name: string;
  };
  request: any;
  response: any;
}) => {
  const temp = dogs.filter((existingDog) => existingDog.name === params.name);
  const body = await request.body();
  const { age }: { age: number } = body.value;

  if (temp.length) {
    temp[0].age = age;
    response.status = 200;
    response.body = { msg: "OK" };
    return;
  }

  response.status = 400;
  response.body = { msg: `Cannot find dog ${params.name}` };
};

這是我們?nèi)绾螐牧斜碇袆h除旺柴的方法:

export const removeDog = ({
  params,
  response,
}: {
  params: {
    name: string;
  };
  response: any;
}) => {
  const lengthBefore = dogs.length;
  dogs = dogs.filter((dog) => dog.name !== params.name);

  if (dogs.length === lengthBefore) {
    response.status = 400;
    response.body = { msg: `Cannot find dog ${params.name}` };
    return;
  }

  response.body = { msg: "OK" };
  response.status = 200;
};

這是完整的示例代碼:

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

const env = Deno.env.toObject();
const PORT = env.PORT || 4000;
const HOST = env.HOST || "127.0.0.1";

interface Dog {
  name: string;
  age: number;
}

let dogs: Array<Dog> = [
  {
    name: "Roger",
    age: 8,
  },
  {
    name: "Syd",
    age: 7,
  },
];

export const getDogs = ({ response }: { response: any }) => {
  response.body = dogs;
};

export const getDog = ({
  params,
  response,
}: {
  params: {
    name: string;
  };
  response: any;
}) => {
  const dog = dogs.filter((dog) => dog.name === params.name);
  if (dog.length) {
    response.status = 200;
    response.body = dog[0];
    return;
  }

  response.status = 400;
  response.body = { msg: `Cannot find dog ${params.name}` };
};

export const addDog = async ({
  request,
  response,
}: {
  request: any;
  response: any;
}) => {
  const body = await request.body();
  const { name, age }: { name: string; age: number } = body.value;
  dogs.push({
    name: name,
    age: age,
  });

  response.body = { msg: "OK" };
  response.status = 200;
};

export const updateDog = async ({
  params,
  request,
  response,
}: {
  params: {
    name: string;
  };
  request: any;
  response: any;
}) => {
  const temp = dogs.filter((existingDog) => existingDog.name === params.name);
  const body = await request.body();
  const { age }: { age: number } = body.value;

  if (temp.length) {
    temp[0].age = age;
    response.status = 200;
    response.body = { msg: "OK" };
    return;
  }

  response.status = 400;
  response.body = { msg: `Cannot find dog ${params.name}` };
};

export const removeDog = ({
  params,
  response,
}: {
  params: {
    name: string;
  };
  response: any;
}) => {
  const lengthBefore = dogs.length;
  dogs = dogs.filter((dog) => dog.name !== params.name);

  if (dogs.length === lengthBefore) {
    response.status = 400;
    response.body = { msg: `Cannot find dog ${params.name}` };
    return;
  }

  response.body = { msg: "OK" };
  response.status = 200;
};

const router = new Router();
router
  .get("/dogs", getDogs)
  .get("/dogs/:name", getDog)
  .post("/dogs", addDog)
  .put("/dogs/:name", updateDog)
  .delete("/dogs/:name", removeDog);

const app = new Application();

app.use(router.routes());
app.use(router.allowedMethods());

console.log(`Listening on port ${PORT}...`);

await app.listen(`${HOST}:${PORT}`);

更多內(nèi)容

Deno 官方網(wǎng)站為 https://deno.land

API 文檔位于 https://doc.deno.landhttps://deno.land/typedoc/index.html 中。

一份 Awesome Deno 資源清單 https://github.com/denolib/awesome-deno

譯者注:中文的 Awesome Deno 清單由譯者持續(xù)維護中,可以訪問這里:Awesome Deno 資源全圖譜

花絮

  • Deno 提供了一個內(nèi)置的 fetch 實現(xiàn),該實現(xiàn)與瀏覽器中可用的匹配。
  • Deno 正在進行與 Node.js stdlib 的兼容層

結語

我希望你喜歡這個 Deno 入門手冊!

別忘了,你可以在此處獲取此 Deno 手冊的 PDF / ePub / Mobi 版本。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容