Deno : un successeur à nodeJS ?
Je dois vous avouer que j'ai pris très récemment connaissance de ce projet.
Depuis qu'il est sorti en version 1.0.0 le 13 mai dernier, il a commencé à faire du bruit sur twitter et a fini par arriver à mes oreilles.
Je ne fais pas plus de nodeJS que ça mais j'ai récemment commencer à me mettre sur un projet en typescript, donc quand j'ai lu que deno pouvait comprendre de manière native le typescript. Je me suis que ça serait bien d'y jeter un petit coup d'oeil. Ce qui est chose faite 😀.
Le projet se prononce "Dino" pour faire le jeu de mot. Un bon nom de projet doit toujours avoir un jeu de mots 🤓.
Cet article est un peu tout à la fois: un pense-bête, un bilan de ce que j'ai pu comprendre de deno et un espace d'expérimentation.
Caractéristiques
Déjà, que promet Deno?
Deno n'est pas un langage de programmation mais un runtime. Il permet de manière native de faire tourner à la fois du typescript et du javascript.
Attention! Deno n'est pas du nodeJS amélioré, c'est une réécriture totale en Rust ❤️🦀 et utilise un nouvelle API de binding du moteur V8 sous la forme d'une lib appelée rusty_v8. Ceci implique que les modules de nodeJS comme http
n'existe pas en tout cas pas sous la même forme que sous nodeJS.
Sa principale promesse est d'offrir un runtime sécurisé à la fois pour l'exécution du javascript mais aussi du typescript.
Une autre promesse est de s'affranchir des node_modules. En téléchageant une fois pour toute les dépendances pour les stocker dans un endroit unique sur le disque dur de l'utilisateur.
Deno permet aussi d'utiliser les API standards propres aux navigateurs internet modernes que sont fetch
et l'objet window
.
Il comporte aussi (pour le moment de manière instable ⚡️) des fonctionnalité comme les WebWorkers
. Permettant de réaliser des opération en tâche de fond grâce à l'utilisation de threads séparés. Ceci étant permis grâce à l'architecture du projet se basant sur la célèbre et robuste lib rust tokio
.
Il existe encore bien d'autre chose possible à réaliser mais pour le moment installons le Dino ^^.
Installation
J'écris ces lignes depuis un MacOS, je vais donc installer deno via homebrew mais il existe un nombre assez invraisemblable d'installer dino. Je vous laisse faire votre marché en fonction de votre OS et de votre préférence.
De mon côté, je vais donc éxécuter :
brew install dino
Si tout s'est bien passé vous devriez pouvoir faire :
# deno --version
deno 1.0.0
v8 8.4.300
typescript 3.9.2
Parfait c'est installé 🎉!
Mes premiers programmes
Deno est fait pour faire tourner à la fois du js et du ts, comme je préfère les langage typé. Allons y pour du ts.
Hello World
Je créé mon fichier hello.ts
// hello.ts
"Hello from 🦕"
Que je lance via:
# deno run hello.ts
Compile file:///lab/deno/playground/hello.ts
Hello from 🦕
Vous pouvez remarquer qu'il existe une phase de compilation, celle ci disparaît si vous relancez le deno run
sans changer la source.
Le site de deno.land propose tout un tas d'études de cas et d'exemple de code. Nous allons en étudier certains.
Manipuler des fichiers
Il s'agit d'une réécriture de la commande UNIX cat
qui a pour but de concaténer le contenu d'un ensemble de fichiers.
// cat.ts
for ; i < Deno.args.length; i++
Plusieurs choses ici, d'abord le Deno.args
, Deno
est un objet globalement injecté au runtime par deno avec tout ce qui est nécessaire pour gérer les I/O du programme et bien plus encore. Ici nous utilisons la propriété args
qui nous permet de récupérer la liste des arguments passer au programme lors du run.
Ensuite nous avons le Deno.open
qui permet d'ouvrir un fichier, à ceci près que tout appel de ce genre est asynchrone, il s'agit en fait d'une promesse d'ouverture du fichier. Pour récupérer le file descriptor il faut attendre que la promesse soit résolue ou rejeté par l'OS. C'est le boulot du mot clef await
qui n'est pas spécifique à deno
. Mais par contre de ce que je comprends l'intégralité du programme est considéré comme asynchrone. Et donc le async
d'habitude nécessaire ne semble pas à avoir à être présent.
A la ligne suivante on remarque le Deno.stdout
qui est un file descriptor pointant vers la sortie standard, écrire dans ce FD revient à écrire dans la console.
De la même manière le Deno.copy
est une promesse de copy du FD du fichier dans stdout
.
4 pauvres lignes et déjà une foule d'informations sur le fonctionnement de deno 😃.
Pour tester notre programme on va avoir besoin de fichier avec du contenu dedans. Je suis pas inspiré pour les noms.
// fichier1.txt
contenu1
// fichier2.txt
contenu1
C'est parti on peut lancer tout ça !
# deno run cat.ts fichier1.txt fichier2.txt
Compile file:///lab/deno/playground/cat.ts
error: Uncaught PermissionDenied: read access to "/Users/yguern/Documents/lab/deno/playground/fichier1.txt", run again with the --allow-read flag
at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
at Object.sendAsync ($deno$/ops/dispatch_json.ts:98:10)
at async Object.open ($deno$/files.ts:37:15)
at async file:///Users/yguern/Documents/lab/deno/playground/cat.ts:4:16
Ça compile et fail 💥. Notre dino 🦕 préféré nous explique ce qui ne va pas, nous n'avons pas les permissions de lire dans le système de fichier. Quand je vous avais dit que le runtime était sécurisé je vous avais pas menti, à moins de l'exprimer explicitement, un programme n'a pas la possibilité de manipuler le FS ni le réseau. Il est même possible et de révoquer des droits en plein milieu du runtime.
Bon notre programme ne vient pas des hackers russes 🐻, on va considéré qu'il est safe. Relançons avec les bonnes permissions.
# deno run --allow-read cat.ts fichier1.txt fichier2.txt
Compile file:///lab/deno/playground/cat.ts
contenu1contenu2
Mieux ✅
Jouer avec l'API fetch
Un des intérêts de faire du deno et pas du nodeJS est de pouvoir bosser directement avec la fetch
API.
On va essayer de récupérer des Chuck Norris Jokes, pour ce nous devons faire un appel sur une API.
;
await joke
Pas grand chose à dire sur ce qui est fait c'est assez classique et ressemblant à ce qui pourrait exister sur navigateur.
Une petite nouveauté est le Deno.stdout.write
qui permet d'écrire dans le file descriptor stdout
. Celui-ci prend comme argument un Uint8Array
d'où l'utilisation de TextEncoder
.
Même combat que tout à l'heure, vous n'avez pas autorisez explicitement l'accès au réseau:
# deno run joke.ts
error: Uncaught PermissionDenied: network access to "http://api.icndb.com/jokes/random", run again with the --allow-net flag
at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
at Object.sendAsync ($deno$/ops/dispatch_json.ts:98:10)
at async fetch ($deno$/web/fetch.ts:591:27)
at async file:///Users/yguern/Documents/lab/deno/playground/joke.ts:1:13
Pour se faire le flag --allow-net
est là pour ça:
# deno run --allow-net joke.ts
Chuck Norris can remember the future.
A vous les blagues les plus hilarantes 🃏.
Pour le moment le "scheme" file://
n'est pas supporté mais une PR est à l'étude.
Echo server
C'est le cas d'étude le plus simple lorsque que l'on débute dans le TCP, il renvoie à l'expéditeur le contenu de qui est envoyé.
// echo.ts
`listen on 0.0.0.0:`
for await of listener
Rien de transcendant ligne 1, on utilise le Deno.args
s'il existe comme port sinon on utilise une valeur par défaut. Ceci grâce au nullish coalescing operator
ou ??
qui est voie d'adoption sur les navigateurs.
Le Deno.listen
démarre un serveur TCP sur le port indiqué. L'objet listener
réalise l'interface AsyncIterable<T>
ce qui lui permet de se comporter comme un générateur de promesses de connexions.
Ce que for await
peut alors utiliser pour gérer les connexions entrantes. Chaque conn
correspond au socket établi par l'utilisateur sur notre serveur.
Ensuite pour chaque connexion on copie la connexion vers elle même et le tour et joué. Pour cela on utilise la méthode déjà étudier de Deno.copy
.
L'éxécution avec les bonnes permissions nous donne:
# deno run --allow-net echo.ts 8091
listen on 0.0.0.0:8091
Notre serveur nous annonce qu'il attend une connexion entrante.
Utilisons netcat
# nc localhost 8091
Salut Deno !
Salut Deno !
Comme prévu on a un echo de ce qu'on a tapé 👻.
Lancer un programme
A l'instar de nodeJS nous pouvons aussi lancer un programme depuis notre typescript.
// run.ts
// On démarre dans un sous processus echo
;
// On attend que echo finisse
await ;
# deno run --allow-run run.ts
hello 🦕
Les Web Workers
Les Web Workers permettent d'éxécuter des scripts sur des threads séparés.
Cela se décompose en deux parties:
Le worker
// worker.ts
self.onmessage =
On utilise l'API standard des WebWorker, celle ci possède une méthode onmessage
qui est appelée dès qu'un evènement survient.
Cet évènement est provoqué du côté du main.ts
par l'appelle à la méthode postMessage
du worker.
// main.ts
"Hello 🦕"
Deux choses importantes ici. D'abord la définition du worker doit être dans un fichier séparé et ensuite il faut impérativement et explicitement indiquer le { type: "module" }
sinon ça marchera pas 😛.
# deno run --allow-read main.ts
Hello 🦕
Le flag --allow-read
est ici essentiel car on intéragie avec le système de fichier.
Le WebAssembly
Et oui on peut en faire nativement avec deno.
Je vais créér mon propre fichier .wasm, mais vous êtes libre de passer cette étape 😀.
Construire un .wasm via Rust
D'abord on se fait un petit projet en wasm via rust.
Si vous ne l'avez pas déjà installez cargo-generate
.
cargo install cargo-generate
Il va vous permettre de générer un projet boilerplate de wasm-pack.
Ensuite installez wasm-pack
lui même:
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
Un fois ceci fait vous pouvez générer votre projet. Personellement je l'ai appelé deno
.
J'ai très légèrement modifié deno/src/lib.rs
// deno/src/lib.rs
...
Puis faire un wasm-pack build
pour générer le fichier deno/pkg/deno_bg.wasm
. Vous pouvez essayer de l'ouvrir mais c'est du binaire.
Utiliser le .wasm
Donc maintenant qu'on a notre .wasm nous allons essayer d'appeller la méthode answer
.
// main.ts
;
Tout d'abord on récupère le buffer du binaire dans la variable wasm
. Puis on en fait un module via WebAssembly.Module
, puis on transforme ce module en instance par la méthode WebAssembly.Instance
pour enfin pouvoir l'utiliser.
La variable wasmInstance
expose une propriété exports
qui contient tout ce qui est exposé vers l'extérieur par notre projet Rust. Dont notre méthode answer
que l'on peut exécuter comme une fonction typescript normale.
On lit un fichier donc attention aux permissions.
# deno run --allow-read main.ts
42
L'exemple est idiot mais ça marche, la fusion deno/rust est fonctionnelle. 🦕 ❤️ 🦀.
Tester
Ouais mais non, moi j'aime bien quand je peux tester mon code 😅. Et justement deno propose une librairie de test déjà intégrée.
On va encore faire du trivial mais c'est juste pour le principe de fonctionnement.
Je déclare une fonction hello
et je veux vérifier son comportement.
// main.ts
;
"Should return rigth string",
La première ligne importe depuis la lib standard le système d'assertion.
Et ensuite il suffit de faire comme avec jest et déclarer son test.
Puis pour lancer les tests
# deno test module.ts
running 1 tests
test Should return rigth string ... ok (3ms)
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (3ms)
Et voilà ça test ✅!
Metions honnorables
Formattage du code
Si vous êtes du genre à indenter n'importe comment et à mettre les paranthèses n'importe où ou à ne pas respecter la élémentaire de ne jamais mettre de tabulations !!
Deno propose une méthode de formattage.
# deno fmt fichier_crado.ts
Ça effectue les corrections et réenregistre le fichier.
Bundler
Si une source externe est composé de nombreux fichiers vous pouvez demander à deno de créer un bundle js de l'intégralité de ces fichiers.
# deno bundle https://deno.land/std/examples/colors.ts colors.bundle.js
Bundling https://deno.land/std/examples/colors.ts
Emitting bundle to "colors.bundle.js"
8044 bytes emmited.
Vous pouvez alors faire.
deno run colors.bundle.js
Hello world!
Pour exécuter ce bundle.
Et la liste est encore longue. Mais je vais m'arrêter là cet article est déjà beacoup trop long pour une première découverte.
Conclusion
Je suis complètement séduit par deno, j'ai hâte de voir ce que ça va donner dans un proche avenir.
Pour le moment je n'ai pas fait de réel projet avec, mais je compte m'y mettre et je vous referait un article pour donner mes impressions.
Merci de m'avoir lu et à la prochaine ❤️.
Ce travail est sous licence CC BY-NC-SA 4.0.