Node.js終結者?青出於藍的Deno(一)
有編程經驗的人,都一定會聽聞過Node.js
,Node.js
基于Chrome
的V8
引擎開發,本身能夠運行JavaScript
,在前端開發(Frontend Development)、後端開發(Backend Development)、Android及iOS開發(Android & iOS Development),都有Node.js
的蹤影,更帶起全JS開發
的潮流,也就是大家常常在Youtube
上看到的MEAN Stack
(Mongodb
,Express
,Angular
,Node.js
),也是以Node.js
為中心發展起來的。
然而,Node.js的作者Ryan Dahl
卻在2018年六月JsConf EU
之上,發表了一個題目為10個Node.js的設計錯誤的短講。出乎意料的是: 原來Ryan
2012年後已離開Node.js
開發工作,原因是他的興趣主要在伺服器編程(Server Programming)之上,所以轉投了Golang
的懷抱,所以已一段長時間沒有用Node.js
作開發工作。而更重要的是,他2012年離開時,認為Node.js
作為一個後端運行JavaScript
程式,已是大功告成。他萬萬沒有想過Node.js
會變成今天般無所不在(ubiquitous),因此很多設計上沒有周詳考慮,現在已經是Too late to change, Too big to fail
。
螢幕截圖自影片10 things I regret about Node.js - Ryan Dahl
綜觀全片,有趣的是Ryan Dahl
其實只講了七個問題。提出問題,而不提出答案沒有多大意義,Ryan
亦提出了他心目中的解答,也就是本文要介紹的deno。
Deno
是一個能夠原生運行JavaScript
及TypeScript
的程式庫,名字由來是將Node
的字母重新排列而得來(no-de
到de-no
),標誌是一隻可愛的恐龍,原因也許是因為deno
跟恐龍英文dinosaur
聽起來很像?筆者也不太清楚。
Ryan
在2018年時就開始了Deno
的開發,事隔兩年,在2020年5月,正好推出了1.0,標誌著Deno
技術開始成熟,可以挑戰Node.js
了!
現在我們就在以下七個方面,將Node.js
及deno
大比拼一番!
Node.js官方API 使用Callback VS Deno原生使用Promise
Node.js
開發者都知道著名的Callback Hell
(回調地獄),因為Node.js
的官方API全是以Callback寫成,即使是讀取檔案如此簡單之事,都需要用到Callback Function(回調函數)。
const fs = require('fs');
fs.readFile('./my-file.txt', function (err, data) {
//<-- 這個就是回調函數
//數據在回調函數之內才能使用
if (err) {
console.log(err);
return;
}
console.log(data.toString());
});
相反,如果使用新的Promise
API,程式碼就會大大簡化:
const fs = require('fs');
async function main() {
const data = await fs.readFile('./my-file.txt');
console.log(data.toString());
}
Node.js
開發初期早就支援Promise
,但在2010
年作者覺得不太有用,又移走了Promise
,結果兜兜轉轉到了版本0.11
才重新加入Promise
,浪費了不少開發者的青春在回調地獄之上..... 而由於一開始的官方API 就是使用Callback
,因此為了向下兼容(Backward Compatibility),因此即使到了版本14,官方API依然是Callback
為主,Promise
為輔,變相任何初學者都要同時學習Callback
及Promise
,變相學習量加倍。
Deno
呢?當然不會重蹈覆轍,一開始就完整支援Promise
與Async/Await
。
const fileContent = await Deno.readTextFile('./my-file.txt');
console.log(fileContent);
Deno
不僅支援Promise
及Async/Await
,也完整支援TypeScript
,連帶Top Level await
這樣的新功能也一併支援了。
簡潔程度直迫其他Dynamic Languages
(動態語言)啊。
要將字串寫入檔案也很簡單。也是一句完成。
await Deno.writeTextFile('./my-file.txt', 'Hello World!', { append: true });
Node.js無掩雞籠 VS Deno安全沙盒
非香港讀者注:無掩雞籠乃粵語俗語,意指自出自入,無任何保安可言
Node.js
的安全性(Security)很有問題嗎?其實當你運行一個Node.js
檔案時,如以下這句command
,本身已是危機四伏。
node index.js
index.js
擁有你現有使用者(Current User)的所有權限(All Permissions)。假如這個js
檔是由黑客所寫之病毒檔案,每當你運行時,就會將你所有個人檔案加密(Encryption),然後再向你勒索bitcoin
才會解密(典型CryptoLocker旳攻擊方法)。除非你逐行細心閱讀該檔案內容,否則這樣的攻擊是無法避免的。此問題不只是Node.js
專有,不論是Python
、PHP
、C#
、Java
,這個問題一樣存在。所以,傳統程式語言其實是「無掩雞籠」的!
Deno
則不然,由於Node.js
及Deno
都是基於V8引擎所開發的,而V8又是Google Chrome
的核心套件,瀏覽器的安全性要求非常高,這樣的安全問題,在瀏覽器世界其實早已解決,只是作者在之前編寫Node.js
時,沒有加入這些防禦措施。
上面readFile
的例子,如果你用deno
直接運行,寫法是deno run read-file.ts
。
$ deno run read-file.ts
Check file:///your/path/to/deno-test.ts
error: Uncaught PermissionDenied: read access to "./my-file.txt", run again with the --allow-read flag
at unwrapResponse (rt/10_dispatch_json.js:25:13)
at sendAsync (rt/10_dispatch_json.js:76:12)
at async open (rt/30_files.js:52:17)
at async Object.readTextFile (rt/40_read_file.js:30:18)
at async file:///your/path/to/deno-test.ts:5:21
出現了PermissionDenied
錯誤,因為deno
預設是沒有讀取檔案的權限。因此deno run read-file.ts
無法讀取檔案。
加上-allow-read
的選項,才是正確用法:
deno run --allow-read read-file.ts
同理,要寫入檔案,我們就需要選項--allow-write
,否則就會得到以下錯誤。
error: Uncaught PermissionDenied: write access to "./my-file.txt", run again with the --allow-write flag
為何要開發者親自輸入呢?因為Deno
建立了一個沙盒(Sandbox),將程式在其中運行:沙盒內之程式碼除非獲得授權,否則無法與電腦其他任何資料、任何硬件互動。因此保證了運行deno run deno-test.ts
這個動作百分百分安全。Deno
除了--allow-read
及--allow-write
控制檔案讀寫之外,還有其他不同權限控制: 包括--allow-net
控制網絡存取、--allow-run
控制子進程存取等等。以沙盒保證程式運行的安全性,在芸芸程式語言中是首創,將運行程式的安全性放在第一位。
Node.js組建系統GYP VS Deno FFI(開發中)
如果大家安裝過一些Node.js
的著名套件,例如Tensorflow
、Bcrypt
的話。都一定會見過以下這個惱人的錯誤。
GYP
全名是Generate Your Project
,用作生成其他組建系統的元組建系統(Meta-build system for build system
),最主要用途在生成一些不是原生JavaScript
的程式庫時用到,由於Tensorflow
、Bcrypt
都會用到native code
,因此這個煩人的問題,就代表你編譯native code
時出現問題!又不知要耗費多少除錯的時間了...
那為何Node.js
最終會使用了GYP
呢?其實是由於原本V8
引擎一開始是使用GYP
的,所以Node.js
亦仿隨。但後來V8
用了另一個元組建系統GN
,Node.js
就陰差陽錯下成了唯一使用GYP
的主流程式庫。而GYP
的格式很古怪,你說是JSON
,但其 實又有Python
的語法,就像兩者的合體。
{
'target_name': 'hello',
'sources': [
'kitty.cc',
],
'include_dirs': [
'shared_stuff/public', # Merged, list item prepended due to include_dirs+
'headers',
],
'link_settings': {
'libraries': [
'-lm',
'-lshared_stuff', # Merged, list item appended
],
'library_dirs': [
'/usr/lib',
],
},
'test': 1, # Merged, int value replaced
}
GYP
這個怪胎,就導致了許多編譯(Compilation)上的問題,燃燒了多少的青春...
筆者在開發時,也會盡量避免需要gyp
的套件,因為通常問題多多。
Deno
則採取更「正常」的做法,也就是使用FFI(Foreign Function interface)
,方便編程者直接將其他語言的程式與Deno
一齊使用。不過這個功能正在開發之中,所以大家請拭目以待。
稍息一下
篇幅所限,這次就先理解這三點,下篇我們會繼續漫談Deno
這個後起之秀!