Node.js終結者?青出於藍的Deno(二)
2020-08-21
上回講到三個Deno
比Node.js
優勝的地方,今次我們將會繼續介紹Deno
這個初生之犢的優勢!
Node.js 使用NPM VS Deno 無中央資料庫
Node.js
的模組(Module),主要是存在NPM
之上,也許不少開發者不知道,其實NPM
是一間公司,專為開發者提供存放私人模組(private module)的服務。而2020年3月時Github
也收購了NPM
,也就是代表現行大多數的Node.js
模組,都在Microsoft
的控制之下。這無疑代表NPM
本質也是一個中央資料庫,所以當間中NPM
因技術問題而無法供開發者下載模組的話,Node.js
開發者就會哀鴻遍野,因為NPM
就是一個Single point of failure(單點故障)。
相較之下,Deno
則完全沒有中央資料庫的概念,在Deno
的設計之中。要安裝任何模組,有兩個不同的方法:
1. 使用URLimport
模組,就像瀏覽器一樣,以下是一個由Opine
程式庫的一段例子:
import {opine} from 'https://deno.land/x/opine@0.21.2/mod.ts'; const app = opine(); app.use((req,res)=>{ res.send("Hello World"); }); app.listen(3000);
由這個例子可見,我們只需寫好URL
,Deno
自動會從URL
下載所需的程式碼,也會自動將程式碼存在快取(Cache),省卻下載時間。
2. 使用相對路徑(Relative Path)讀取其他本機檔案
import { sum } from './sum.ts' sum(1,2);
因此,Deno
的套件(Package)運作方式與瀏覽器很相似,沒有限制像必須使用NPM
那樣的中央資料庫。
Node.js 使用node_modules VS Deno自動下載
Node_modules
的問題很有名,每個Node.js
開發者都有這樣的經驗: 電腦磁碟空間快要耗盡了,原來是node_modules
佔用了成GB的空間...網上還有這樣的meme
:
Node_modules
的質量比黑洞更甚:)
!
Deno
由於摒棄了中央資料庫的做法,變相只需要下載所需的模組,無須每次下載新專案,都要運行npm install
。
那在何時才會開始自動下載呢?答案是在第一次運行的時候。
gordon➜ ~ deno run --allow-net --allow-read opine.ts Download https://deno.land/x/opine@0.21.2/mod.ts ... Download https://deno.land/x/evt@v1.8.7/tools/Deferred.ts Download https://deno.land/x/evt@v1.8.7/lib/Evt.loosenType.ts Download https://deno.land/x/evt@v1.8.7/tools/safeSetTimeout.ts Download https://raw.githubusercontent.com/garronej/run_exclusive/v2.2.13/deno_dist/lib/runExclusive.ts Check file:///home/gordon/opine.ts
以後,再運行這個檔案,就會直接使用已在快取之檔案,因此既有瀏覽器的簡便,也不會每次重新下載。
gordon➜ ~ deno run --allow-net --allow-read opine.ts Check file:///home/gordon/opine.ts
剩餘兩個其實算是小問題,不過既然Ryan
提到了,就一併解釋吧。
Node.js require無須副檔名 VS Deno import 必須副檔名
Node.js
中的require
是不須副檔名的,因此如果你想require
一個名為myfile.js
的檔案,你只需寫:
const myfile = require('./myfile');
這為Node.js
平添了不必要的複雜性,因為myfile
是甚麼呢?是myfile.js
? 還是myfile.ts
? 隨著Node.js
的用途增多,這樣的「方便」,反而模糊了背後的意思。
index.js有JavaScript 入口的概念
這是完全無用的功能,因為在package.json
就可以控制main
是那個檔案了,因此這個功能沒有實際作用。
{ ... "main":"index.js", ... }
其他未提及的優勢
不過,筆者在用Deno
小試牛刀之後,感到Deno
還有其他明顯優勢,而Ryan
並沒有提及。大概是因為2018年時發展尚早,未有這樣想法。
Deno能夠獨立執行
首先其中一個Deno
的最大優勢,就是安裝Deno
只是安裝了一個獨立的執行檔,也就是如果你去Deno
的網站,運行以下的command
:
# 如果你是用Mac/Linux curl -fsSL https://deno.land/x/install/install.sh | sh # 如果你是用 Windows PowerShell iwr https://deno.land/x/install/install.ps1 -useb | iex
其實只是下載了一個二進位檔案(Binary file),也就是一個執行檔。在筆者的電腦,只是一個在~/.deno
資料夾內的檔案。
gordon➜ ~ tree ~/.deno /home/gordon/.deno └── bin └── deno 1 directory, 1 file
這個做法承襲自Golang(大概因為Ryan Dahl曾長時間運用Golang工作)而來,好處是使用deno
,只有一個binary file
的依賴,不會因為你有其他依賴未安裝,就無法使用deno
,而且整個流程完全無須系統使用者權限(Administrator Right),實在令安裝過程簡單不少。
要運行Deno
的command
,只需打deno XXX
。以下是deno help
的輸出,
deno 1.3.0 A secure JavaScript and TypeScript runtime ... 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 -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 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 ENVIRONMENT VARIABLES: ...
由這裏可見,Deno
有不少有用的subcommand
,就等筆者介紹幾個特別令人感興趣的!
自帶測試工具
在寫Node.js
的時候,開發者常常需要寫自動測試(Automated Tests),然而Node.js
中有很多不同的測試程式庫: Jest
、Jasmine
、Mocha
、Chai
都是其中的例子。因此要閱讀他人之Node.js
專案時,如果別人正好用了你不常用的測試程式庫(Testing library),就變相要於不同專案使用不同之testing library
,實在是不太方便。
Deno
由於是後起之秀,今時今日自動測試早已是編程必要部份,因此原生支援testing
,無需再思前想後要用那個library
了。
以下是一個Deno
測試的例子:
import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; Deno.test("1 + 1 should be 2", () => { assertEquals(1 + 1 , 2); });
用deno test
直接運行,就會得到ok
的結果。
gordon➜ ~ deno test test.ts Download https://deno.land/std/testing/asserts.ts Warning Implicitly using latest version (0.65.0) for https://deno.land/std/testing/asserts.ts Download https://deno.land/std@0.65.0/testing/asserts.ts Download https://deno.land/std@0.65.0/fmt/colors.ts Download https://deno.land/std@0.65.0/testing/diff.ts Check file:///home/gordon/.deno.test.ts running 1 tests test 1 + 1 should be 2 ... ok (2ms) test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (3ms)
自帶Linter及formatter
Node.js
的Linter
及Formatter
都是需要額外安裝及設置,著名的prettier
就是其中的好例子。Deno
則早已包含在執行檔之內。
直接使用Linter
:
gordon➜ ~ deno lint test.ts
直接使用Formatter
:
gordon➜ ~ deno fmt test.ts /home/gordon/test.ts
將這些本身是外部套件的功能加至原生支援(Native support),最大的好處在於開發者無須為選擇程式庫煩惱,而且程式碼的樣式也因而非常統一(Consistent), 非常方便維護。
自帶Bundler
還有一個筆者認為Deno
非常強大的功能,就是deno
的封裝(bundle)功能。現時要開發React
應用時,Webpack
是必不可少的一環,因為React
當中大量使用了瀏覽器不支援的格式如JSX
、TypeScript
、SCSS
等。因此React
開發一個必備的過程就是Build
的步驟,也就是將程式碼由Node.js
的世界﹐「封裝」(Bundle)成Browser
的世界。
Webpack 網站banner
很準確地概括了Bundler
這個概念。
Screen capture from webpack.js.org
Deno
對此的解決方法如上面雷同,就是將封裝功能一併放到執行檔之中。
如果用deno bundle
將剛才的test.ts
封裝
gordon➜ ~ deno bundle test.ts test-bundle.js Bundle file:///home/gordon/test.ts Emit "test-bundle.js" (25.27 KB)
test-bundle.js
的開頭大概是這樣:
/ Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // This is a specialised implementation of a System module loader. "use strict"; // @ts-nocheck /* eslint-disable */ let System, __instantiate; (() => { const r = new Map(); System = { register(id, d, f) { r.set(id, { d, f, exp: {} }); }, }; async function dI(mid, src) { let id = mid.replace(/\.\w+$/i, ""); if (id.includes("./")) { const [o, ...ia] = id.split("/").reverse(), [, ...sa] = src.split("/").reverse(), oa = [o]; let s = 0, i; ... // 以下省略
這個檔案可以直接用deno.run
運行,裏面有齊了整個test.ts
的依賴,不過由於有Deno
的依賴,這個檔案不能在瀏覽器內運行。
Deno的弱點
談了那麼多Deno
的好處,那Deno
相比起Node.js
,有明顯的弱點嗎?
筆者想到兩點比較明顯的弱點,都與Deno
的兼容性有關的。
與現有Node.js專案不兼容
Deno
完全摒棄package.json
與node_modules
的做法,因此你不可能直接使用deno
去運行現有的Node.js
專案,因為連import
的運作模式,也不盡相同。
因此除非你是重新開始一個新專案,也是所謂的Green Field Project
,否則你不能在一個專案中漸進使用Deno
。
筆者認為這是Deno
成為主流的最大障礙,因為對大多數公司而言,大量由Node.js
所寫成的程式碼有很大的商業價值,因此要由零開始乃是不切實際。我們可以由Kotlin
及Scala
的發展觀察到這個現象,Scala
及Kotlin
都是以Better Java
的姿態面世,縱然兩者都是JVM Languages
,Scala
基本上與Java
不相容,Java
專案要變成Scala
專案也很不容易,因此Scala
一直都只是在Big Data
方面得到長足發展。Kotlin
則相反,設計之初就以漸進使用為賣點,更可以在一個Java
專案只是少數幾個檔案使用Kotlin
,結果今日的發展大家都知道,Kotlin
因其兼容性,真正達到了Better Java
的特點。 所以筆者認為Deno
要得到更大成功,能夠漸進在Node.js
中使用,是至為必要的。
Deno無法得益於Node.js龐大的套件庫
另外,Deno
與NPM
本身不相容,因此Deno
開發者無法在NPM
上直接使用已存在的NPM
套件。這也是對Deno
的發展的另一障礙。因為NPM
packages坐擁世上最多的套件數目,遠遠拋離其他程式語言的套件管理員,無法從此得益,實屬可惜。
Screen capture from modulecounts.com
由網站Module Counts,NPM
有超過125萬個套件,如果Deno
能夠運用套件,絕大多數的功能就已告齊備。
結語
返回文章的題目,Deno
能否取代Node.js
成為JavaScript
的主要開發環境呢?筆者認為短期兩至三年內可能性都非常低,但如果未來Deno
能夠解決筆者所言的兩個局限性,筆者也會躍躍欲試,將手頭上的Node.js
專案加入Deno
了。
Comments
Read More
到底React Hooks 有何特別?
2018-11-27
新近推出的React 16.7包括一個很有趣的功能,名字叫做React Hooks。看到這個名字,很多人會下意識認為是在講componentDidMount, componentDidUpdate等方法。但其實這些方法的正名是 React Lifecycle Method, 推出React Hooks是為了方便開發者多用functional component,但仍然能夠使用state及 props等重要功能。
Dart vs JavaScript vs TypeScript
2019-02-17
隨著Flutter受到開發者的重視,Google於2011年推出的Dart又重新進入大家關注的視野之內,不過除了Flutter以外,其實Google的開發者早在2016年也推出過Angular Dart,讓開發者以Dart開發網站應用,不過由於Angular Dart對比TypeScript版Angular文本長期不足,因此沒有引起太多關注。Google推出Flutter,可以說為大家對Dart的信心注入了一劑强心針,大家又重新開始關注這個已有8年歷史的程式語言。 本文想介紹的是,就是到底Dart有何特色?與JavaScript比較,又有何優劣?由於TypeScript開始於前端日漸盛行,我們亦可以趁機比較一下三種語言的異同。
SQL首部曲:NoSQL? No! SQL!
2019-10-08
由本篇開始,接連四篇都是與SQL有關的文章,會想寫SQL的原因,是因為SQL在現今軟件開發及數據科學佔有舉足輕重之地位,卻總是在背後默默無名,從未見得到像其他新興技術之關注,有見及此,筆者決定介紹SQL之特點,順便破除一些對使用SQL上常有的誤解。
Node.js終結者?青出於藍的Deno(一)
2020-08-06
有編程經驗的人,都一定會聽聞過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為中心發展起來的。