Deno VS Node

e la sfida infinita dei runtime JavaScript

Davide D'Antonio
weBeetle
12 min readSep 21, 2020

--

Ryan Dahl, il creatore di Node.js, ha trascorso gli ultimi anni a lavorare su Deno, il nuovo runtime Javascript che, a detta dello stesso Ryan, dovrebbe risolvere tutti i problemi che affliggono Node.js.

Non fraintendetemi, Node.js è un ottimo runtime, principalmente grazie all’enorme ecosistema di moduli e all’utilizzo di JavaScript; tuttavia lo stesso Dahl ha ammesso di non aver pensato ad alcuni aspetti fondamentali come la sicurezza, i moduli e le dipendenze così solo per citarne alcune.

C’è da dire in difesa di Ryan, che a suo tempo non si sarebbe mai immaginato che la piattaforma sarebbe cresciuta così tanto, in un periodo così breve.

C’è da dire poi, che nel 2009, JavaScript era ancora quel linguaggio che tutti prendevano in giro e molte delle funzionalità che sono presenti oggi non c’erano.

Ryan Dahl

Cos’è Deno?

Deno è un runtime TypeScript basato su V8, il runtime JavaScript di Google; se avete familiarità con Node.js, il popolare ecosistema JavaScript lato server, capirete che Deno è esattamente la stessa cosa. Tranne per il fatto che è stato concepito con profondi miglioramenti:

  • Si basa sulle moderne funzionalità del linguaggio JavaScript;
  • Ha una vasta libreria Standard;
  • Supporta TypeScript in modo nativo;
  • Supporta i moduli EcmaScript;
  • Non ha un gestore di pacchetti centralizzato come npm;
  • Ha diverse utility integrate come un inspector delle dipendenze e un code formatter;
  • Mira ad essere il più possibile compatibile con i browsers;
  • La sicurezza è la sua caratteristica fondamentale.

Quali sono le differenze con Node.js?

Diciamocelo apertamente, Deno ha come obbiettivo principale quello di sostituire Node.js. Tuttavia esistono alcune caratteristiche che li legano in modo significativo. Eccole elencate:

  • Entrambi sono stati ideati e creati da Ryan Dahl;
  • Entrambi sono stati sviluppati basandosi sull’engine V8 di Google;
  • Entrambi sono stati sviluppati per eseguire codice JavaScript lato server.

Mentre le differenze sono:

  • Rust e TypeScript. A differenza di Node.js che è scritto in C++ e JavaScript, Deno è scritto in Rust e TypeScript.
  • Tokio. Introdotto al posto di libuv come piattaforma asincrona guidata dagli eventi.
  • Package Manager. A differenza di Node.js che possiede il suo package manager ufficiale npm, Deno non lo possiede, quindi è possibile importare qualsiasi modulo ES da un url.
  • ECMAScript. Deno utilizza le moderne funzionalità ECMAScript in tutte le sue API, mentre Node.js utilizza una libreria standard basata sulle callback.
  • Sicurezza. A differenza di un programma Node.js che, di default ,eredita i permessi dall’utente che sta eseguendo lo script, un programma Deno gira in una sandbox. Per esempio l’accesso al file system, alle risorse di rete, etc., dev’essere esplicitamente autorizzato con un flag in fase di esecuzione del programma stesso.

Installazione

Deno viene fornito come un singolo eseguibile senza dipendenze. Puoi installarlo usando i seguenti programmi di installazione o scaricare una versione del binario dalla seguente pagina.

Shell (Mac, Linux)

PowerShell (Windows)

Homebrew (Mac)

Uno sguardo alla sicurezza

Tra le caratteristiche principali di Deno c’è la sicurezza. A differenza di Node.js, Deno esegue il proprio codice in una sandbox, questo vuol dire che il runtime non ha accesso a risorse come:

  • Il file system;
  • La rete;
  • Non può eseguire altri script;
  • Le variabili d’ambiente.

Facciamo un esempio molto semplice. Diamo un’occhiata al seguente script:

Lo script è molto semplice. Non fa altro che creare un file di testo hello.txt che conterrà il il testo Hello Deno 🦕 al suo interno.

Come già anticipato in precedenza, il codice viene eseguito in una sandbox e di conseguenza non avrà l’accesso al filesystem.

Se eseguiamo il programma dalla linea di comando:

Verrà stampato a video qualcosa del tipo:

Come possiamo notare, l’errore è abbastanza chiaro. I file non sono stati scritti sul filesystem in quanto il programma non ha il permesso di scrittura sul filesystem ma, aggiungendo il flag -- allow-write nel seguente modo:

lo script terminerà senza errori e ci ritroveremo il file hello.txt nella stessa directory dove abbiamo eseguito lo script.

Ovviamente oltre ai flag --allow-write per l’accesso al filesystem, esistono anche --allow-net per l’accesso alle risorse di rete, oppure -- allow-env per accedere alle variabili d’ambiente e -- allow-run per consentire l’esecuzione di sottoprocessi. Potete trovare la lista completa dei flag di permesso al seguente url https://deno.land/manual/getting_started/permissions.

Un semplice server

Questo è un esempio molto semplice di un server che accetta connessioni sulla porta 8000 e restituisce al client la stringa Hello Deno.

Ovviamente dovete eseguire lo script con il flag --allow-net , quindi:

Sul terminale dovrebbe apparire qualcosa tipo:

Ora se dal vostro browser preferito, o se preferite utilizzare il comando curl , è possibile effettuare un test all’indirizzo http://localhost:8000. Il risultato che otterrete sarà il seguente

I moduli

Proprio come i browser, Deno carica i suoi moduli via URL. Molti sono confusi inizialmente da questo approccio, ma in realtà ha senso. Ecco un semplice esempio:

Non avere un gestore di pacchetti e dover fare affidamento agli URL per ospitare e importare i pacchetti ha vantaggi e svantaggi. I pro di questo approccio sono diversi:

  • è molto flessibile;
  • possiamo creare pacchetti senza pubblicarli su un repository come npm.

Suppongo che in futuro potrà emergere una sorta di gestore di pacchetti, ma non è ancora uscito nulla di ufficiale.

Il sito Web di Deno fornisce hosting di codice (e quindi distribuzione tramite URL) a pacchetti di terze parti: https://deno.land/x/.

Importando il codice tramite URL, rendiamo possibile ai creatori di pacchetti ospitare il loro codice ovunque ritengano opportuno: il decentramento nella migliore delle ipotesi. Quindi non abbiamo più la necessità del famoso package.json e di conseguenza della directory node_modules.

All’avvio dell’applicazione, Deno scarica tutti i moduli importati, li compila e li memorizza nella cache. Una volta memorizzati, Deno non effettuerà nuovamente il download fino a quando non lo richiediamo esplicitamente con il flag--reload.

Devo scrivere ogni volta l’URL?

Deno supporta nativamente import maps. Infatti è possibile utilizzarlo specificando dalla linea di comando il flag --importmap=<FILE>. Vediamo un esempio di utilizzo.

Iniziamo creando il file import_map.json con il seguente contenuto:

Il file spefica che a ftm/ corrisponde l’URL https://deno.land/sdt@0.65.0/ftm/ , di conseguenza sarà possibile utilizzarlo nel seguente modo:

Siccome questa feature risulta ancora instabile dobbiamo eseguire lo script colors.ts utilizzando il flag --unstable , quindi:

Ora sul terminale dovrebbe apparire qualcosa tipo:

E per quanto riguarda il versioning?

Il versioning deve essere supportato dal fornitore del pacchetto, lato client si tratta semplicemente di impostare il numero di versione nell’URL in questo modo:

Funzionalità integrate

Diciamocelo con sincerità: lo stato attuale degli strumenti che uno sviluppatore JavaScript ha a disposizione è un vero CAOS! Quando si aggiungono poi quelli TypeScript il caos aumenta ulteriormente.

Una delle caratteristiche migliori di JavaScript è che il codice non viene compilato, di conseguenza può essere eseguito immediatamente all’interno di un browser. Questo facilita la vita allo sviluppatore in quanto è molto facile ottenere feedback immediati sul codice scritto. Sfortunatamente però, questa semplicità nell’ultimo periodo è stata minata da quello che possiamo descrivere come “il culto degli strumenti eccessivi”. Questi strumenti hanno trasformato lo sviluppo JavaScript in un vero incubo di complessità. Addirittura esistono online interi corsi per la guida alla configurazione di Webpack! Si avete capito bene…un intero corso!

Il caos degli strumenti è cresciuto al punto che molti sviluppatori non vedono l’ora di tornare a scrivere effettivamente codice piuttosto che giocare con i file di configurazione. Un progetto emergente che affronta questo problema è la Rome di Facebook.

Deno è un intero ecosistema in sé, completo di runtime e di un proprio sistema di gestione di moduli/pacchetti. Ciò offre una possibilità molto più ampia di avere tutti i propri strumenti integrati. Esaminiamo quali strumenti sono disponibili nella versione 1.0 e come è possibile utilizzarli per ridurre la dipendenza da librerie di terze parti e semplificare lo sviluppo.

Non è ancora possibile sostituire un’intera pipeline di build in Deno, ma non credo passerà molto tempo prima di averne a disposizione una. Di seguito la lista delle funzionalità integrate:

  • bundler: scrive in un singolo file JavaScript il modulo specificato in input e tutte le sue di pendenze;
  • debugger: consente di eseguire il debug dei programmi Deno con Chrome Devtools, VS Code e altri strumenti;
  • dependecy inspector: eseguendolo su un modulo ES vengono elencate tutte le dipendenze in un albero;
  • doc generator: analizza le annotazioni JSDoc in un dato file e produce la documentazione.
  • formatter: formatta automaticamente sia il codice JavaScript che TypeScript;
  • test runner: utility che consente di testare il codice JavaScript e TypeScript in combinazione con il modulo assertions della libreria standard;
  • linter: utile ad individuare potenziali bug nei programmi.

Bundler

Deno può creare un semplice bundle dalla riga di comando utilizzando deno bundle, ma espone anche un’API interna in modo che l’utente possa creare il proprio output, qualcosa che può essere personalizzato per l’uso frontend. Questa API è attualmente instabile, quindi è necessario utilizzare il flag--unstable. Riprendiamo l’esempio fatto in precedenza, modificandolo nel seguente modo:

Ora se lanciamo il seguente comando dalla console:

verrà creato un file colors.bundle.js che conterrà tutto il codice necessario per eseguire lo script. Infatti se proviamo ad eseguire lo script con il comando

noteremo che non verrà scaricato alcun modulo dal repository di Deno in quanto tutto il codice necessario per l’esecuzione è contenuto nel file colors.bundle.js . Il risultato che otteniamo a video è lo stesso dell’esempio precedente:

Debugger

Deno possiede un debugger integrato. Per lanciare un programma in modalità debug manualmente basta utilizzare il flag dalla linea di comando --inspect-brk

Ora se apriamo chrome://inspect in Chrome o Chromium ci ritroviamo una pagina simile a questa

Cliccando su inspect potremo iniziare a debuggare il nostro codice.

Dependecy inspector

Utilizzare questo tool è veramente semplice! Basta usare il subcommand info con a seguire un’URL (o il path) di un modulo e verrà stampato tutto l’albero delle dipendenze di quel modulo. Se per esempio lanciamo il comando sul file server.ts utilizzato nell’esempio precedente verrà stampato a video

Inoltre il comando deno info può essere utilizzato per mostrare informazioni sulla cache:

Doc generator

Questa è una fantastica utility che vi permette di generare automaticamente la documentazione JSDoc. Per utilizzarla basta usare il comando deno doc, seguito da un elenco di uno o più file sorgente, e automaticamente verrà stampata a video la documentazione per ciascuno dei membri esportati dal modulo. Vediamo come funziona con un semplice esempio. Immaginiamo di avere il file add.ts con il seguente contenuto

Eseguendo il comando deno doc add.ts verrà stampato sullo standard output il documento JSDoc:

È possibile utilizzare il flag--jsonper produrre la documentazione in formato JSON. Questo formato JSON viene utilizzato dal sito Web deno per generare la documentazione del modulo in modo automatico.

Formatter

La formattazione è fornita da dprint, un’alternativa velocissima a Prettier che clona tutte le regole stabilite di Prettier 2.0. Per formattare uno o più file, usa deno ftm <files>o l’estensione Visual Studio Code.

Se il comando viene lanciato con il flag --check verrà effettuato il check della formattazione di tutti i file JS e TS della directory corrente.

Test runner

La sintassi di questa utility è molto semplice. Basta utilizzare il comando deno test e verranno eseguiti tutti i file che terminano con _test o .test con estensione .js , .ts , .jsx , .tsx . In aggiunta a questa utility è possibile utilizzare l’API standard di Deno che ci fornisce il modulo asserts che è possibile utilizzare nel seguente modo

Questo modulo ci fornisce nove asserzioni

  • assert(expr: unknown, msg = ""): asserts expr
  • assertEquals(actual: unknown, expected: unknown, msg?: string): void
  • assertNotEquals(actual: unknown, expected: unknown, msg?: string): void
  • assertStrictEquals(actual: unknown, expected: unknown, msg?: string): void
  • assertStringContains(actual: string, expected: string, msg?: string): void
  • assertArrayContains(actual: unknown[], expected: unknown[], msg?: string): void
  • assertMatch(actual: string, expected: RegExp, msg?: string): void
  • assertThrows(fn: () => void, ErrorClass?: Constructor, msgIncludes = "", msg?: string): Error
  • assertThrowsAsync(fn: () => Promise<void>, ErrorClass?: Constructor, msgIncludes = "", msg?: string): Promise<Error>

Linter

Deno viene fornito con un linter di codice incorporato per JavaScript e TypeScript. Questa è una nuova feature ed attualmente richiede il flag --unstable per essere eseguito.

Benchmark

Siamo arrivati al momento della verità! Chi è il migliore Deno o Node? Ma forse la domanda da porsi è un’altra: chi è il più veloce? Ho effettuato un benchmark molto semplice (un banalissimo hello server http) ed i risultati sono stati veramente interessanti. I test sono stati eseguiti sul mio laptop che ha le seguenti caratteristiche:

Il tool utilizzato per effettuare i benchmark è autocannon e gli script utilizzati li trovate al seguente indirizzo:

Il primo test effettuato è su 100 connessioni concorrenti autocannon http://localhost:3000 -c100 ed i risultati ottenuti sono stati i seguenti:

Sembra che Node batta Deno in quanto a velocità! Ma attenzione questo benchmark si basa su 100 connessioni simultanee che sono troppe per un server di piccole medie dimensioni. Quindi facciamo un altro test: questa volta però anziché 100 invieremo 10 connessioni simultanee.

Anche questa volta Node batte Deno:

Sembra che Node batta Deno in quanto a prestazioni 2–0. Ha prestazioni migliori in entrambi i casi analizzati, e cioè sia in una rete con un traffico elevato sia in una rete con un traffico moderato. Tuttavia la community di Deno sta lavorando sodo affinché venga presto adottato in ambiente di produzione, anche se la vedo dura a combattere contro un titano come Node!

Per concludere

Lo scopo di questo articolo non è stato quello di sostenere né NodeDeno, ma piuttosto confrontare i due. Ora dovreste avere una comprensione delle somiglianze tra i due e, forse ancora più importante, delle differenze.

Deno presenta alcuni vantaggi particolari agli sviluppatori, tra cui un robusto sistema di autorizzazioni e un supporto TypeScript in modo nativamente. Le decisioni di progettazione e gli strumenti incorporati aggiuntivi mirano a fornire un ambiente produttivo e una buona esperienza di sviluppo.

Node, d’altra parte, ha un ecosistema massiccio e ben consolidato attorno ad esso che è stato più di un decennio in sviluppo. Questo, insieme alla pletora di documentazione e tutorial là fuori, probabilmente rende Node.js una scommessa sicura per un po’ di tempo a venire. Che dire…

Ringraziamenti

Un particolare ringraziamento va a Matteo Collina per avermi fatto notare un errore sui benchmark postati in precedenza. 🙏

Per maggiori dettagli date uno sguardo a questa issue su github.

Bibliografia

--

--

Davide D'Antonio
weBeetle

👨‍🎓Degree in computer science 💑 Married with Milena 🤓 Huge Nerd! 💻 Code lover 👨🏻‍💻 Fullstack developer