iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 24
0
Modern Web

前端工程師一起來種一棵後端技能樹吧!系列 第 24

[Day 24] 初探 Deno — 與 Node.js 的淺比較

為什麼要寫這個主題呢?原因是我想如果前端開發者要接觸後端領域,「通常」會選擇 Node.js ,畢竟不用學另外的語言,npm 開發生態系也是相通的,不過最近出了 Deno 這個東西,「有可能」在未來改變 Node.js 的開發生態,因此我認為蠻適合前端開發者了解一下的,接下來進入正文吧!


Deno 1.0 終於在前陣子發佈了,Deno 會不會取代 Node 的疑問也不斷出現在社群討論之中,今天藉由這篇文章想簡單介紹 Deno 的特性,也將它與 Node.js 做一個簡單的比較。

What is Deno ?

Deno 是由人稱 Node.js 之父的 Ryan Dahl 在 2018年的演講 「10 Things I Regret About Node.js」中提出的專案。主要是想修正當初在開發 Node.js 時的缺點,詳細資訊可以看這裡。Deno 與 Node.js 一樣是基於 V8 引擎,並且是由 Rust 語言(Node.js 使用 C++)建構的 JavaScript 與 TypeScript 執行環境,是的,你沒有看錯, Deno 內建了 TypeScript 編譯器,還不趕快起立鼓掌嗎?

淺嚐了 Deno 後,可以歸納出幾個與 Node.js 不同的 Features:

  • 支援 TypeScript
  • 支援 ES6 import 與其他新的語法
  • URL import (不再需要 npm 與 node_modules 了)
  • Security

至於 Deno 的安裝方式就不提囉,請自行 google !

支援 TypeScript 與 ECMAScript 新語法

安裝好 Deno 後,我們可以直接在編譯器中新增檔案來給 Deno 執行囉,執得注意的是,有了 Deno ,我們可以直接執行 TypeScript 檔案,而不需要安裝額外的套件,因為 Deno 內建了 TypeScript 編譯器!以簡單範例試試看吧:

let message: string;

message = 'test deno';

console.log(message);

然後用 Deno 執行這個檔案

$ deno run deno-test.ts

則會順利印出 test deno ,也不會像以往使用 tsc 的方式一樣產生出編譯後的 js 檔。

而許多較新的 ECMAScript 語法,如 import 、export、promise ,Deno 也都支援了,開發者不再需要花時間在設置 babel 等語法轉換上,可以更專注在開發環節。有一個蠻值得一提的新語法是 Deno 支援了 top-level await,以往為了使用 await 必須把有使用到該語法的片段都用 async function 包起來,如今也能避免掉這樣繁瑣的問題啦!

// simple web server with deno
import { Application } from "https://deno.land/x/oak/mod.ts";
const app = new Application();
app.use((ctx) => {   ctx.response.body = "Hello Kyle!"; });
await app.listen({ port: 5000 });
// top-level await

URL import

眼尖又熟悉 Node.js 的讀者可能會發現上面的程式有點怪怪的

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

不對啊!為什麼是從一段 URL 引入套件啊!

URL import 是 Deno 不同於 Node.js 一個很大的特色,Deno 會將專案要使用到的套件從遠端 server 抓取後 cache 到本機中,因此第一次啟動專案往往需要較久的時間,而之後重新啟動則會抓取 cache 在本機的版本,因此能夠迅速啟動。

這樣的特性帶來一個重大的改變:在 Deno 的世界中不再需要如 npm 的套件管理工具,Deno 本身除了執行環境外,也扮演了套件管理系統的角色,不需要再將他們拆開來,如果要指定特定版本的套件也可以在 URL 中指定:

import { Application } from "https://deno.land/x/abc@v1.0.0-rc10/mod.ts"; // 抓取第 1.0.0 版本的 abc module

如此一來使用 Deno 後也就不會像 Node.js 一樣有一包肥大的 node_modules 藏在專案裡了,真是可喜可賀!

Security

上面的程式碼是使用 Deno 的內建功能 writeFile 將一段文字 encode 後存到 txt file 裡面,看似平凡的程式應該沒什麼問題吧!?來執行一下

$ deno run app.ts

竟然噴出錯誤了?

這是因為 Deno 相較於 Node.js 多了 security 的特性,Deno 的程式預設是執行在 sandbox 裡的,在未經允許的狀況下 ,Deno 是不能去 access disk、不能 access network 、不能 spawn sunprocesses 的,因此上面寫入檔案的程式若要正常執行需要額外給一些 flag

$ deno run --allow-write app.ts

給予 Deno 寫入的權限,程式才能正常執行 。

其他常見的權限有以下幾種

-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.

幸好在忘記加權限的狀況下,Deno 也會很貼心的提醒應該要加上哪些 flag 才能讓程式正常執行,比起 Node.js 需要使用者完全信任程式碼的安全性,Deno 在 security 方面的確有所改善。

Deno + MongoDB 實作簡易 Todo List Restful API

既然特性介紹完了,就來用 Deno 搭配 mongoDB 實作簡易的 Todo List Restful API 吧!
(以下假設讀者都熟悉使用 Node.js 開發 API)
首先將 db 連線的功能拆成一個檔案

import { MongoClient, Database } from 'https://deno.land/x/mongo@v0.8.0/mod.ts';

let db: Database;

export function connect() {
  const client = new MongoClient();
  client.connectWithUri(
    '你的 mongo url'
  );

  db = client.database('todo-app'); // 資料庫名稱
}

export function getDb() {
  return db;
}

除了 MongoClient、Database (資料庫的 type) 是從 URL 載入外,其他的操作方式都幾乎跟 Node.js 一樣。
接下來就是 web server 的部分

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

import todosRoutes from './routes/todos.ts';
import { connect } from '.db_client.ts';

connect();

const app = new Application();

app.use(async (ctx, next) => {
  console.log('Middleware!');
  await next();
});

app.use(async (ctx, next) => {
  ctx.response.headers.set('Access-Control-Allow-Origin', '*');
  ctx.response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  ctx.response.headers.set('Access-Control-Allow-Headers', 'Content-Type');
  await next();
});

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

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

目前 Deno 最受歡迎的 web framework 是 oak framework ,它是受到 Node.js Koa framework 啟發所創造出來的(從名字是重新排列就看得出來),因此運作方式也非常相似,都是 middleware based 的框架,使用方式也大同小異。
最後來實作 router 的部分,CRUD 全都給它試一次

import { Router } from 'https://deno.land/x/oak/mod.ts';
import { ObjectId } from 'https://deno.land/x/mongo@v0.8.0/mod.ts';

import { getDb } from '.db_client.ts';

const router = new Router();

interface Todo {
  id?: string;
  text: string;
}

router.get('/todos', async (ctx) => {
  const todos = await getDb().collection('todos').find(); // { _id: ObjectId(), text: '...' }[]
  const transformedTodos = todos.map(
    (todo: { _id: ObjectId; text: string }) => {
      return { id: todo._id.$oid, text: todo.text };
    }
  );
  ctx.response.body = { todos: transformedTodos };
});

router.post('/todos', async (ctx) => {
  const data = await ctx.request.body();
  const newTodo: Todo = {
    // id: new Date().toISOString(),
    text: data.value.text,
  };

  const id = await getDb().collection('todos').insertOne(newTodo);

  newTodo.id = id.$oid;

  ctx.response.body = { message: 'Created todo!', todo: newTodo };
});

router.put('/todos/:todoId', async (ctx) => {
  const tid = ctx.params.todoId!;
  const data = await ctx.request.body();

  await getDb()
    .collection('todos')
    .updateOne({ _id: ObjectId(tid) }, { $set: { text: data.value.text } });

  ctx.response.body = { message: 'Updated todo' };
});

router.delete('/todos/:todoId', async (ctx) => {
  const tid = ctx.params.todoId!;

  await getDb().collection('todos').deleteOne({ _id: ObjectId(tid) });

  ctx.response.body = { message: 'Deleted todo' };
});

export default router;

oak 也內建了 router 的機制,實作方法真的跟 Node.js 有 87% 像了,所以就留給讀者自行閱讀了。
最後將 API server 跑起來

deno run --allow-net --allow-write --allow-read --unstable --allow-plugin app.ts

因為 web server 除了會進行讀寫外,也會進行 network access,所以也必須提供 — allow-net 的 flag, — unstable 與 — allow-plugin 則是因為使用不穩定的 mongo module 所需要加上的 flag。

結論

關於 Deno 會不會取代 Node.js ,我認為至少 5 年內這個答案都是否定的,畢竟 Deno 還太新了,很多 features 都還處在 unstable 的狀態,反觀 Node.js 已經十分成熟,許多大公司都採用 Node.js 開發,生態系也十分完整,有些 Deno 補足的缺點也沒有花高成本解決的必要,因此我認為 Node.js 在五年以內絕對不會有被取代的問題存在。不過我認為學習 Deno 是沒有損失的,畢竟它的確補足了 Node.js 的一些缺點,而且它與 Node.js 其實都是程式的執行環境,因此並不需要去學習一個新的語言,如果熟悉 Node.js ,照理來說可以 “微痛” 上手 Deno 才對,有人說 Deno 是未來,你覺得呢?Let’s wait and see !

Medium 版本

https://medium.com/@oldmo860617/%E5%88%9D%E6%8E%A2-deno-%E8%88%87-node-js-%E7%9A%84%E6%B7%BA%E6%AF%94%E8%BC%83-19e8c6cbb249


上一篇
[Day 23] 淺淺認識 Database Sharding
下一篇
[Day 25] 菜鳥工程師的初次 Open Source Contribution - (1)
系列文
前端工程師一起來種一棵後端技能樹吧!30

尚未有邦友留言

立即登入留言