Un environnement d'éxecution Javascript

{firstname:'Stéphane' ,
 lastname : 'Michel',
 job: 'Software Craftsman'}

© Stéphane Michel

Sommaire / Concepts clefs

npm

SOMMAIRE

Résumé des épisodes précédents

Client serveur

GET  http://server:8080/index.html

client

serveur

SOAP

POST /DateCreation HTTP/1.0
Host: www.xyz.org
Content-Type: text/xml; charset = utf-8
Content-Length: nnn

<?xml version = "1.0"?>
<SOAP-ENV:Envelope
   xmlns:SOAP-ENV = "http://www.w3.org/2001/12/soap-envelope"
   SOAP-ENV:encodingStyle = "http://www.w3.org/2001/12/soap-encoding">

   <SOAP-ENV:Body xmlns:m = "http://www.xyz.org/datecreation">
      <m:GetDateCreation>
         <m:SchoolName>ESIR</m:SchoolName>
      </m:GetDateCreation>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
HTTP/1.0 200 OK
Content-Type: text/xml; charset = utf-8
Content-Length: nnn

<?xml version = "1.0"?>
<SOAP-ENV:Envelope
   xmlns:SOAP-ENV = "http://www.w3.org/2001/12/soap-envelope"
   SOAP-ENV:encodingStyle = "http://www.w3.org/2001/12/soap-encoding">
   <SOAP-ENV:Body xmlns:m = "http://www.xyz.org/datecreation">
      <m:GetDateCreationResponse>
         <m:DateCreation>mars 2010</m:DateCreation>
      </m:GetDateCreationResponse>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

mais ça reste très verbeux...

C'est super structuré, 

client

serveur

REST

GET /DateCreation/ESIR HTTP/1.0
Host: www.xyz.org
Content-Type: text/xml; charset = utf-8
Content-Length: nnn
HTTP/1.0 200 OK
Content-Type: application/json; charset = utf-8
Content-Length: nnn

{"creationDate":"mars 2010"}

A ben voilà !

et REST c'est...

Des méthodes HTTP

Des codes de retour HTTP

De bonnes pratiques

Client serveur

Client serveur

GET http://serv1/

GET http://serv1/

GET http://serv2/

GET http://serv2/

GET http://serv3/

GET http://serv3/

Client serveur

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

ROUTER / PROXY / GATEWAY

Stateful / session

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

ROUTER / PROXY / GATEWAY

Stateful / session

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

ROUTER / PROXY / GATEWAY

Stateful

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

ROUTER / PROXY / GATEWAY

Stateless / JWT

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

GET http://serv/

ROUTER / PROXY / GATEWAY

JWT

JWT

JWT

JWT

JWT

JWT

JWT

JWT

JWT

JWT

JWT

JWT

Node.js en quelques mots

Créé par Ryan Dahl en 2009

Voir ici pour plus de détails

En quelques mots

Moteur JavaScript Chrome V8

Modèle d'E/S non bloquant

Enorme ecosystème de modules (npm ou yarn)

Près de 350 000 modules

Plus de 35 000 màj par semaine

102 000 publishers

Lifecycle

Version paires : versions maintenues

Release

Maintenance : LTS (Long Term Support)

Statistiques

Source Stackify

Statistiques

Source GitHut

Statistiques

Premier programme

// HelloWorld.js
const os = require('os')

console.log('Hello World!')
console.log(`Running on ${os.hostname} computer and ${os.platform} operating system.`)

 

[16:07] sogitec@MACPRO8:firstApp $ node --version
v8.4.0
[16:08] sogitec@MACPRO8:firstApp $ node HelloWorld.js
Hello World!
Running on MACPRO8.local computer and darwin operating system.

HelloWorld.js

Exécution du programme

Installation

Si vous n'y trouvez pas votre bonheur, sous Linux : binaires pour de multiples distributions

https://github.com/nodesource/distributions

Si vous souhaitez disposer de plusieurs version de node/npm, la solution est
Node Version Manager

http://nvm.sh

Event Loop

et "reactor pattern"

Temps d'accès aux données

RAM : nanoseconds 

DD / réseau : milliseconds 

 

Vitesse de transfert

RAM : GB/s

Réseau : MB/s -> GB/s

I/O is slow

Qu'elles soient réseau ou disque les E/S sont dramatiquement lentes.

10^{-9} seconds
10^{-3} seconds

Blocking I/O

De plus elles sont généralement bloquantes.

 

Pas facile de faire des accès concurrents dans ses conditions.

 

Une solutions classiques des serveurs web est d'utiliser du multi thread ou du multi process.

Source : node.js Design Patterns

Multi-process c'est lourd !

C'est facile à faire (fork) mais...

 

La mémoire complète (code, données, tas et pile) du process est dupliquée...

 

Communication inter-process lentes : on passe par l'OS

 

C'est ce qui a donné l'idée du multi-thread

Multi-thread c'est moins lourd, mais c'est lourd quand même !

Les threads partagent la même mémoires et les mêmes ressources.

 

La mise au point d'un programme multi-thread est complexe.

 

Communication inter-thread est rapide.

 

Mais le CPU ne dispose pas d'autant de coeurs qu'il y a de threads...

 

L'orchestration des threads sur les différents coeurs du processeurs est complexe et coûteuse en ressources (commutation de contexte).

Voici ici ou pour plus de détails sur les threads

Du coup node est...

Voir ici pour plus de détails

...par nature "mono-thread"

Source : node.js Design Patterns

En fait pas tout à fait...

1 thread principal pour le code JavaScript

 

1 pool de threads (workers-pool basé sur libuv) pour les traitements coûteux en I/O (file, DNS, etc.) ou en CPU (crypto, zip, etc.), 

Voir ici pour plus de détails

Le tout orchestré par une architecture orientée événement très efficace (Event-driven Architecture)

node.js est mono thread...

Event Loop

Compléments ici, ici ou ici

Event Loop... c'est pas la fête du slip non plus...

En conséquence, pour un maximum de fluidité (on dit "pour que ça scale bien"), les traitements dans un callback doivent être les plus cours possibles.

Avec node.js tous les clients (cas d'un serveur web) partagent le même thread.

 

La prise en compte des requêtes clientes en attente ne se fait que si le callback en cours est terminé. 

Source : node.js Design Patterns

Au final node.js c'est...

V8 : moteur Javascript rapide et efficace au niveau mémoire

Des wrappers pour exposer la lib libuv de gestion des E/S non bloquantes

Une API Javascript (node-code) de haut niveau

Blocking

vs non-blocking

 

Blocking vs non-blocking

Voir détail ici

Blocking methods execute synchronously and non-blocking methods execute asynchronously.

Exemples d'appels potentiellement bloquants :

  • I/O (file ou network) de la librairie libuv,
  • certains modules natifs (BDD sqlite3 par exemple) 

 

Blocking vs non-blocking

Appel synchrone bloquant

Appel asynchrone non-bloquant

Voir détail ici

Blocking vs non-blocking

Les appels asynchrones sont à privilégier puisqu'ils sont non bloquants. Ils rendent la main à l'EventLoop qui se charge de gérer les exécutions concurrentes.

 

Ce mécanisme est très différent de ce que l'on trouve dans d'autres langages où c'est plutôt l'ajout de nouveaux thread qui permet de gérer les traitements concurrents.

 

Remarque : la gestion des thread est très gourmande en ressource (mémoire, cpu (changement de contexte)).

Voir détail ici

Principal (et premier) gestionnaire de modules de node.js

 

Alternative possible : yarn

A faillit remplacer npm en proposant des performances bien supérieures en 2016, mais npm a rattrapé son retard depuis  

yarn vs npm ici

Rechercher un module

p : popularité

q : qualité

m : maintenance

Critères de choix

Nb téléchargement, évolution dans le temps

Test unitaire,
couverture de code

Anomalies,

délais de correction

Autres sites de recherche

Si vous hésitez entre plusieurs modules similaires

Etat des lieux 2017 (statistiques d'utilisatrion)

(Exemple pour le back-end)

Etat des lieux 2018

Commandes de base

npm init

  • initialisation d'un nouveau projet
  • création d'un fichier package.json

npm install --save <module>

npm i --save <module>

  • télécharge et installe un module dans le répertoire node_modules
  • ajout d'une dépendance dans package.json
  • Attention sans  --save la dépendance n'est pas ajoutée dans package.json -> le projet devient difficile à partager
  • --save-dev au lieu de --save : si le module n'est utile qu'en mode développement (exemple : outillages tests unitaires)

Documentation officielle ici

Installer un module globallement

npm i -g <module>

npm install -g <module>

  • télécharge et installe un module globalement pour le compte utilisateur
  • le module devient accessible de partout
  • pour les modules transversaux et les outils (nodemon)

A éviter dans la mesure du possible car ses modules ne sont pas associés aux projets.

Intéressant spour les modules très lourd (gain de place)

Désinstaller un module 

npm uninstall --save <module>

npm uninstall --save-dev <module>

  • Supprime le module du répertoire node_module ainsi que du package.json

 

npm uninstall -g <module>

  • Supprime le module globalement

Réinitialiser un projet

npm ci

  • supprime le répertoire node_modules et retélécharge l'ensemble des modules 
  • Se base sur les dépendances du package.json
  • Permet de s'assurer que toutes les dépendances sont bien définies dans package.json

npm i

  • se contente de télécharger et d'installer les packages manquants 

Mettre à jour npm avec npm !

npm i npm@latest -g

Anatomie de package.json

Structure

{
  "name": "firstproject",
  "version": "0.0.1",
  "description": "My first project",
  "main": "server.js",
  "scripts": {
    "start": "nodemon server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "sm",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.3",
    "winston": "^3.1.0"
  },
  "devDependencies": {
    "mocka": "0.0.1"
  }
}
npm i --save express

npm i --save winston

 

npm i --save-dev mocka

 

Plus de détails ici

Versioning

Quelques modules du noyau

console

console.log('hello %s', 'world')

 

console.error(new Error('Whoops, something bad happened')) 

console.assert(false, 'Whoops %s work', 'didn\'t')

 

console.count('abc')
 abc: 1

 

console.time('100-elements')
for (let i = 0; i < 100; i++) {}
console.timeEnd('100-elements')
// prints 100-elements: 225.438ms

 

console.table([{ a: 1, b: 'Y' }, { a: 'Z', b: 2 }])
// ┌─────────┬─────┬─────┐
// │ (index) │  a  │  b  │
// ├─────────┼─────┼─────┤
// │    0    │  1  │ 'Y' │
// │    1    │ 'Z' │  2  │
// └─────────┴─────┴─────┘

util

const util = require('util')

 

util.format('hello %s', 'world')
  hello world

util.log('sortie avec timestamp')
  28 Oct 17:55:52 - sortie avec timestamp

console.log(
 util.inspect({ value: 'foo', items: [ 'a', 'b' ], fonc: hello })
)
  { value: 'foo',
    items: [ 'a', 'b' ],
    fonc: [Function: hello] }

process

// Accès aux variables d'environnement
console.log(process.env.PATH)

 

// Version de Node et libs sous-jacentes
console.log(process.version, process.versions) 

// OS d'exécution
console.log(process.platform, process.arch) 

// Paramètres de l'appli
console.log(process.argv)

 

// Events sur le process
process.on('exit', (code) => {
  console.log(`About to exit with code: ${code}`)
})
process.on('uncaughtException', 
   (err) => console.error('Kesessa ?', err))

 

os

const os = require('os')

// Caractère de fin de ligne sur la plateforme courante
console.log(os.EOL)

// Info sur l'OS, idem process.arch et process.platform
console.log(os.platform(), os.release(), os.arch(), os.type())

// Détails processeurs et interfaces réseau,
// similaires à cat /proc/cpuinfo et ifconfig
console.log(os.cpus(), os.networkInterfaces())

// Répertoire home de l'utilisateur courant
console.log(os.homedir())

// Utilisation mémoire (niveau OS, ne 
// pas confondre avec process.memoryUsage()
console.log(os.totalmem(), os.freemem())

fs

const fs = require('fs')

// Lecture d'un fichier (par défaut, méthode asynchrone)
fs.open('/open/some/file.txt', 'r', (err, fd) => {
  if (err) throw err
  fs.close(fd, (err) => {
    if (err) throw err
  })
})

// Observer les modifications dans un répertoire
fs.watch('./tmp', { encoding: 'buffer' }, (eventType, filename) => {
  if (filename) {
    console.log(filename)
  }
})

// lecture de répertoire et informations sur un fichier
fs.readdir('.', (err, files) => {                 // ici il faudrait tester err
  files.forEach((file) => {
    fs.stat(path.join(__dirname, file), (err, stat) => {   // idem
      console.log('%s : %d octets, dernière modif. %j',file,stat.size,stat.mtime)
    })
  })
})

crypto

Ensemble de wrapper autour de OpenSSL

 

Certificate : Génération et manipulation de certificats

Sign, Verify : signature et vérification de signature

Hash : génération de hashcode

Cipher, Decipher : cryptage, décryptage

Crypto : Point d'entrée du module, génération de clefs asymétriques, etc.

 

et de nombreux autres

Assert : Test d'assertions

Child Process : gestion des multi-process

Cluster : multi-thread

Errors : Gestion des erreurs (try, catch, finally)

Events : Gestion des événements (détaillé plus loin)

http, https : gestion de la pile Http(s)

Stream : Gestion des flux (détaillé plus loin)

TTY : Interaction avec la console

UDP/Datagram : réseau bas niveau, sockets

URL : Manipulation d'URL

Zlib : compactage / décompactage de fichiers

Best of - modules

Extensions du langage

Underscore et Lo-Dash

Underscore

Outillage sur les tableaux, map, etc.

each, map, reduce, filter, etc.

 

Intéressant si on ne dispose pas de ES6 (voir ici pour plus de détail).

lo-Dash

Similaire à underscore en plus riche et plus populaire (voir ici pour un comparatif) :
outillage sur les chaînes de caractères, tableaux, map, etc.

each, map, reduce, filter, etc.

 

Comme underscore, perd de son intérêt si on dispose d'ES6 (voir ici pour plus de détail).

Gestion des dates

moment.js, date-fsn

moment.js

Outil de manipulation de dates.

A peut prêt toutes les opérations que l'on peut imaginer faire sur des dates : parsing, validation, comparaison, formattage.

date-fns

Outil de manipulation de dates.

Plus de 140 opérations sur les dates.

Peut être un peu plus facile d'utilisation que moment, mais c'est question de goût...

Il est à moment.js ce que lo-dash est à underscore... une alternative qui a dépassée sur certains points l'original...

Logging avancé

winston et bunyan

Winston

Gestion des log dans l'application (console, fichier, etc.)

const logger = winston.createLogger({
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'combined.log' })
  ]
})

...

logger.log({level:'info',message:'Premier log de type info'})

 

// Raccourcis
logger.log('Log de type info (par défaut)')
logger.error('Une erreur')
logger.warn('Un warning')
logger.debug('Une trace de debug')

 

Bunyan

Similaire à Winston mais propose par défaut un log au format JSON pour lequel il propose un client (CLI) pour leur visualisation.

const bunyan = require('bunyan')
const log = bunyan.createLogger({
   name: 'myapp', 
   streams: [
      { stream: process.stdout },
      { path: 'logs/dev.log' }
   ]
})

log.info('hi')
log.warn({lang: 'fr'}, 'au revoir')

 

Contrôle de type

tcomb

tcomb

Contrôle de type sur les objets (sans aller jusqu'à faire du TypeScript).

Permet d'imposer une structure à un objet ou un type à un paramètre de fonction.

Remonte une exception si la structure n'est pas respectée.

Permet de réduire les effets de bord en particulier lorsque l'on a pas la main sur la source des données.

import t from 'tcomb'

const GPS = t.struct({                             
  lat: t.Number,
  lon: t.Number,
}, {strict: true})

const Bordeaux = GPS({ lat: 44.8638, lon: -0.6561 }) // OK
const Crest = GPS({ lat: 44.7311, long: 4.9861 })    // erreur : long
const Toulouse = GPS({ lat: 43.6008, lon: 'r0s3' }   // erreur : 'r0s3'
import t from 'tcomb'

function sum(a, b) {
  t.Number(a)
  t.Number(b)
  return a + b
}

sum(1, 's') // throws '[tcomb] Invalid value "s"   
            // supplied to Number'

Contrôle de code

ESLint

ESLint

Outil de vérification syntaxique pour ECMAScript.

Bonnes pratiques, etc.

 

npm install --save-dev eslint

 

// Création du fichier de config (à faire une fois)

./node_modules/.bin/eslint --init

 

// Lancer eslint sur un fichier js

./node_modules/.bin/eslint myFile.js

 

S'utilise en ligne de commande ou plus simplement directement intégré à l'IDE (VSCode par exemple via le plugin ESLint de Dirk Baeumer).

HTTP

express, helmet

Permet de réaliser un serveur HTTP

Décrit en détail dans le module Express du cours.

var express = require('express')
var app = express()

app.get('/', function (req, res) {
  res.send('Hello World')
})
app.listen(3000)

Helmet

Aglomère un ensemble de modules qui œuvrent pour la sécurisation des serveurs HTTP

const express = require('express')
const helmet = require('helmet')

const app = express()

app.use(helmet())

...

ModuleDefault?

contentSecurityPolicy for setting Content Security Policy  
crossdomain for handling Adobe products' crossdomain requests  
dnsPrefetchControl controls browser DNS prefetching
expectCt for handling Certificate Transparency  
featurePolicy to limit your site's features  
frameguard to prevent clickjacking
hidePoweredBy to remove the X-Powered-By header
hpkp for HTTP Public Key Pinning  
hsts for HTTP Strict Transport Security
ieNoOpen sets X-Download-Options for IE8+
noCache to disable client-side caching  
noSniff to keep clients from sniffing the MIME type
referrerPolicy to hide the Referer header  
xssFilter adds some small XSS protections

Anatomie des modules

Définition

Module : boîte dans laquelle on pourra embarquer une collection de méthodes ou de variables liées entre elles et qui formeront un ensemble cohérent (Framework).

 

Le module détermine l’accessibilité de chaque membre.

 

Pour le code qui la consomme, il s’agit donc d’une boîte noire.

Common.js

Syntaxe historique (avant ES6) popularisée par node.js pour créer des modules

// HelloWorld.js
const os = require('os')

exports.hello = function(){
  console.log('Hello World!')
  console.log(
    `Running on ${os.hostname} computer and ${os.platform} operating system.`
  )
}
// index.js
const hello = require('./HelloWorld')

hello.hello()
[16:40] sogitec@MACPRO8:firstApp $ node index.js
Hello World!
Running on MACPRO8.local computer and darwin operating system.

Création d'un module avec ES6

// lib.js
const PI = 3.1415926;

function sum(...args) {
  log('sum', args)
  return args.reduce((num, tot) => tot + num);
}

function mult(...args) {
  log('mult', args);
  return args.reduce((num, tot) => tot * num);
}

// private function
function log(...msg) {
  console.log(...msg)
}

export { PI, sum, mult }
export default sum

Plus de détails ici

Utilisation d'un module avec ES6

// Import complet d'un module
import * as lib from './lib.js'

console.log( lib.mult(1,2,3,4) ) // 24

 

// Import par défaut
import maFonction from './lib.js'

console.log( maFonction(1,2,3,4) ) // 10 (maFonction = sum)

// Renommage
import { PI as monPi} from './lib.js' 
// Import d'une sous partie d'un module
import { sum } from './lib.js'

console.log( sum(1,2,3,4) ) // 10

 

Plus de détails ici

Ordre de recherche des modules

( require ou import)

PAS d'extension dans le require ou l'import

... = require('monModule')
import ... as 'monModule'
  • Chemin relatif vers fichier 'monModule' avec extension .js, .json ou .node (addon binaire)

ou

  • Dossier 'monModule' avec…
    1. Fichier package.json doté d’une clé main
    2. Fichier index.js (fichier par défaut)
    3. Fichier index.node (addon binaire node par défaut)

Ordre de recherche des modules (suite)

( require ou import)

l'algorithme précis est décrit ici  https://nodejs.org/api/modules.html#modules_all_together

  1. Recherche dans les modules natifs (fs, os, etc.)
  2. Si le chemin commence par '/', '../' ou './' alors recherche du chemin dans le 'file system' (local relatif (../ et ./) ou local absolu (/)
  3.  Sinon Recherche dans le répertoire node_modules courant puis dans les éventuels répertoires node_modules parents
  4. Si pas trouvé, recherche dans le répertoire node_modules global (voir npm -g)

Plus de détails ici 

Caching

Les modules sont mis en cache au premier chargement.

 

Si un module est importé plusieurs fois dans un projet, c'est le même objet qui est partagé !

 

Cas particulier des système non case sensitive (windows) :

require('monmodule')
require('MonModule')

correspondent au même fichier mais retourneront deux objets différents

 

Bonne pratique : Toujours mettre le nom des modules en lowercase

Caching (exemple)

// lib.js
let compteur = 0
const increase = () => {
  compteur++
}
const current = () => {
  return compteur
}
export default { current, increase }
//index.js
import lib1 from './lib.js'
import lib2 from './lib.js'

console.log(`${lib1.current()}, ${lib2.current()}`)
lib1.increase()
console.log(`${lib1.current()}, ${lib2.current()}`)
lib2.increase()
console.log(`${lib1.current()}, ${lib2.current()}`)

Sortie attendue :

0, 0
1, 1
2, 2

Configuration

 avec      

git init

Initialisation du repository GIT

Initialisation nouveau projet

npm init

Création d'un fichier .gitignore avec https://www.gitignore.io/

Creation projet npm, GIT et .gitignore

Commiter .gitignore lors du premier commit

git add .

git commit -m "Initialisation du projet"

Creation arborescence des fichiers

Les sources sous /src

 

Les sources compilées par babel seront sous /build

npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill

Installation modules babel

Configuration babel pour node.js

// babel.config.js
const presets = [
['@babel/env', {
   targets: {
     node: 'current'},
   useBuiltIns: 'usage'}
]]

module.exports = { presets }

Configuration Babel  1/2

Configuration package.json pour compiler et lancer le projet

{"name": "monappli",
 "version": "1.0.0",
 "description": "ma description",
 "main": "src/index.js",
 "scripts": {
   "build": "npx babel src --out-dir build",
   "start": "npm run build && node build/index.js",
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC",
 "devDependencies": {
   "@babel/cli": "^7.1.2",
   "@babel/core": "^7.1.2",
   "@babel/preset-env": "^7.1.0"
 },
 "dependencies": {
 "@babel/polyfill": "^7.0.0"
}}

Configuration Babel  2/2

Configuration package.json pour compiler et lancer le projet

npm start

Utilisation

npm run build && node build/index.js
npx babel src --out-dir build

Execution de build/index.js

"Compilation" des fichiers de src/ dans le répertoire build/

Outillage

nodemon

npm install -g nodemon

Relance automatiquement l'application lorsqu'une modification de source est réalisée

En phase de développement

// S'utilise à la place de node pour lancer une application

nodemon server.js   // au lieu de "node server.js"

npm-run-all

npm install npm-run-all --save-dev

Permet de lancer des scripts en parallèle ou en séquence en gérant les aspects cross plateforme (le & pour lancer des process en parallèle ne fonctionne pas sous windows).

{ "...": "...", 
  "scripts": { "build": "npm-run-all -p 'build:*' -s start",    
  "build:css": "sass ...", 
  "build:js": "browserify ...", 
  "start": "node server.js" } 
}

// puis

npm run scripts

S'utilise en ligne de commande ou plus généralement avec npm en configurant package.json

husky

npm install husky --save-dev

S'intègre avec GIT et permet de lancer des traitements/contrôles avant de réaliser des "git push", des "git commit" ou des "git pull" (liste complète des hooks ici)

{ "...": "...", 
  "scripts": { 
    "precommit": "lint-staged", 
    "post-merge": "npm install" } }

// puis

git commit -m "...

S'utilise en configurant package.json

debug

npm install debug --save-dev

Permet de disposer de traces limitées à une sous-partie applicative

import { debug } from "debug"

const debugA = debug("module:a")
const debugB = debug("module:b")
const debugHTTP = debug("http")

debugA("Trace from moduleA")
debugB("Trace from moduleA")
debugHTTP("Trace from HTTP ...")

 

$ DEBUG=module:b node build/index.js 
  module:b Trace from moduleA +0ms

 

$ DEBUG=module:* node build/index.js 
  module:a Trace from moduleA +0ms
  module:b Trace from moduleA +0ms

 

$ DEBUG=module:*,http node build/index.js
  module:a Trace from moduleA +0ms
  module:b Trace from moduleA +0ms
  http Trace from HTTP ... +0ms

 

depcheck

npm install -g depcheck

Permet d'analyser les modules du projet pour détecter ceux qui ne servent à rien et ceux qui ne sont pas déclarés dans package.json

Events

Principes

Communication via des fonctions d'écoute et d'émission de messages

Les événements 'error' sont spéciaux : faute de traitement, ils agiront comme une exception (stack trace sur stderr puis arrêt du processus).

Dans Node, la majorité des objets noyau émettent des événements : flux, requête/réponse, watchers, erreurs…

L’API est toujours la même, centralisée dans une classe EventEmitter

Principes

//index.js
import EventEmitter from 'events'
const emitter = new EventEmitter()

emitter.on('alloffame', (date, name, dcd) => {
  console.log(`${name} born in ${date.getFullYear()} ${dcd ? ', DEAD' : ''}`)
})

emitter.once('alloffame', (date, name, bissextil) => {
  console.log(`First All of Fame received for ${name}`)
})

emitter.emit('alloffame', new Date('1930-05-11'), 'Edsger Dijkstra', true)
emitter.emit('alloffame', new Date('1955-06-08'), 'Tim Berners-Lee')
Edsger Dijkstra born in 1930 , DEAD
First All of Fame received for Edsger Dijkstra
Tim Berners-Lee born in 1955

Voir ici pour des compléments

Streams

Intêret

Permet de manipuler des volumes de données énormes sans utiliser beaucoup de ressources.

Le coeur I/O de node !

Buffering vs streaming

Buffering : on attend que le buffer soit plein avant d'envoyer le résultat au destinataire

Source : node.js design patterns

Buffering vs streaming

Source : node.js design patterns

const fs = require("fs")
const zlib = require("zlib")
const file = process.argv[2]

fs.readFile(file, (err, buffer) => {
  zlib.gzip(buffer, (err, buffer) => {
      fs.writeFile(file + '.gz', buffer, err => {
        console.log('File successfully compressed')
      })
  })
}

Exemple buffering  : compression d'un fichier

Buffering vs streaming

Streaming : le flux est envoyé au destinataire au fur et à mesure qu'il est lu.

Source : node.js design patterns

Avantages du streaming

Le streaming permet de réaliser des tâches qu'il n'est pas possible de faire avec le buffering comme lire un fichier de plusieurs Go car nécessiterait de mettre le contenu complet en mémoire...

De plus, le moteur Javascript V8 limite la taille des buffers à 0x3FFFFFFF octets soit un peu moins de 1Go.

 

Si le fichier est plus gros on part en 'out of memory' !

Moins gourmand en mémoire

Le destinataire du flux peut commencer à travailler dessus alors que l’émetteur du flux n'a pas encore terminé son émission.

Plus rapide

Avantages du streaming

Streaming

Source : node.js design patterns

const fs = require('fs')
const zlib = require('zlib')
const file = process.argv[2]

fs.createReadStream(file)
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream(file + '.gz')
  .on('finish', () => 
        console.log('File successfully compressed')
  )

Exemple streaming  : compression d'un fichier (à comparer avec la version buffering, 2 planches plus haut)

Anatomie des streams

Un stream en node.js implémente l'une des 4 interfaces suivantes :

stream.Readable (2 modes : flowing et non-flowing)

stream.Writable

stream.Duplex

stream.Transform

Un stream est aussi une instance de EventEmitter pour la production d'événements :

  • end
  • error

stream.Readable

const {createReadStream} = require('fs')
createReadStream(__filename, {highWaterMark: 100})     (1)
    .on('end', () => console.log('Lecture terminée'))  (2)
    .on('data', (data) => {                            (3)
        console.log('%d octets reçus', data.length) 
     })
    .on('error', (err) => console.error(err))          (4)

Lecture de flux : flowing mode (ancienne méthode)

Voir doc createReadStream pour plus de détails

(1) taille du buffer : 100 octets (par défaut, 64ko)

(2) Appelé à la fin de la lecture 

(3) Appelé pour chaque bloc (de 100 octets ici)

(3) Appelé si une erreur survient

Voir ici pour l'exemple d'origine

stream.Readable

const {createReadStream} = require('fs')
const stream = createReadStream(__filename, {highWaterMark: 100}) (1)
    .on('end', () => console.log('Lecture terminée'))             (2)
    .on('readable', () => {                                       (3)
        while (null !== (data=stream.read())) { 
            console.log('%d octets reçus', data.length) 
        })
     })                           
    .on('error', (err) => console.error(err))                     (4)

Lecture de flux : non-flowing mode (nouvelle méthode)

(1) taille du buffer : 100 octets (par défaut, 64ko)

(2) Appelé à la fin de la lecture 

(3) Appelé dès que le bloc est disponible

(3) Appelé si une erreur survient

Un peu plus de détails ici

stream.Writeable

const {createWriteStream, readFile} = require('fs')
const {join} = require('path')
const dest = join(__dirname, 'debug.txt')

const stream = createWriteStream(dest)          (1) 
stream.on('finish', () => { 
       readFile(dest, (error, data) => {        (4) 
                console.log(String(data)) 
       }) 
})
stream.on('error', (err) => console.error(err)) (5)
            
stream.write('Hell')                            (2) 
stream.write('o Worl') 
stream.end('d!')                                (3)

Ecriture de flux

Voir doc createReadStream pour plus de détails

(1) Création d'un flux vers le fichier debug.txt

(2) Ecriture dans le flux (encodage par défaut : utf8)

(3) end indique qu'il que se sont les dernières données

(4) l'event 'finish' est appelé suite au "end"

(5) Si une erreur survient

Voir ici pour l'exemple d'origine

stream.Duplex

flux bidirectionnel

Hérite à la fois de stream.Readable et de stream.Writable

Utile pour les objets qui peuvent être à la fois émettrice et réceptrice.

 

Exemple : socket réseau

Plus de détails ici

stream.Transform

flux de transformation

Cas particulier de stream.Duplex

Comme lui, hérite à la fois de stream.Readable et de stream.Writable

Permet de réaliser des filtres de transformation sur des flux

 

Exemples : zlib ou crypto

Plus de détails ici

pipe

const {createReadStream, createWriteStream} = require('fs') 
const {join} = require('path') 
const filename_copy = join(__dirname, 'copie.js') 

const source = createReadStream(__filename)            (1) 

const dest = createWriteStream(filename_copy)          (2)
source.pipe(dest)                                      (3) 
  .on('finish', () => console.log('Copie terminée !')) (4)
source.pipe(process.stdout)                            (5)

Les données lues depuis une source (Readable) sont redirigées vers une destination (Writeable) à l’aide de la fonction pipe()

(1) Création d'un flux de lecture

(2) Création d'un flux d'écriture

(3) Redirection du flux de lecture vers le flux d'écriture

(4) La redirection retourne le flux d’écriture, que l’on écoute pour savoir quand il a terminé d’écrire sur le disque

(5) Le flux de la source peut être redirigé vers plusieurs destinations

Voir ici pour l'exemple d'origine

buffer

Les flux utilisent par défaut des objets buffers.

Leur taille est définie à la création et est non modifiable.

Ils se comportent un peu comme un tableau d'integer.

Itérable avec for..of 

  • 'ascii' - For 7-bit ASCII data only. This encoding is fast and will strip the high bit if set.

  • 'utf8' - Multibyte encoded Unicode characters. Many web pages and other document formats use UTF-8.

  • 'utf16le' - 2 or 4 bytes, little-endian encoded Unicode characters. Surrogate pairs (U+10000 to U+10FFFF) are supported.

  • 'ucs2' - Alias of 'utf16le'.

  • 'base64' - Base64 encoding. When creating a Buffer from a string, this encoding will also correctly accept "URL and Filename Safe Alphabet" as specified in RFC4648, Section 5.

  • 'latin1' - A way of encoding the Buffer into a one-byte encoded string (as defined by the IANA in RFC1345, page 63, to be the Latin-1 supplement block and C0/C1 control codes).

  • 'binary' - Alias for 'latin1'.

  • 'hex' - Encode each byte as two hexadecimal characters.

Encodage possibles (c.f. doc officielle)

Voir ici pour plus de détails

Ce qui fait la force de node.js c'est son moteur de pool threads

EventLoop permet à node.js d'exécuter des opérations I/O de manière non bloquante bien qu'il soir mono-thread !

Dans node.js les opérations I/O, et javascript se partagent un thread unique.

Pour de bonnes performances, node.js implique une programmation asynchrone!

Un projet node.js est défini par la présence d'un fichier package.json

bibliographie

node.js

Sites

https://delicious-insights.com/fr/articles/libs-node-js/

26 modules nodes que j'utilises tout le temps • Delicious Insights

https://oncletom.io/node.js/appendix-a/index.html​

Node.js • Apprendre par la pratique • Appendix A Sélection de modules npm • Thomas Parisot

https://www.tutorialspoint.com/nodejs/index.htm​

tutorials points • Node.js • tutorialspoint

https://nodejs.org/dist/latest-v10.x/docs/api/

Node.js v10.15.0 Documentation • nodejs.org

https://github.com/substack/stream-handbook

Stream Handbook • substack

https://nodeschool.io/fr-fr/

Ateliers open sources Nodeschool • Nodeschool

https://oncletom.io/node.js/chapter-04/index.html​

Node.js • Apprendre par la pratique • Chapitre 4 sur node.js • Thomas Parisot

node.js

Livres en ligne gratuits

https://github.com/substack/stream-handbook​

stream handbook • Projet Github

https://github.com/workshopper/stream-adventure​

stream adventure • Projet Github

https://github.com/azat-co/practicalnode​

Practical node.js, 2nd edition • 2018 • Apress

Livres (bible)

Node.js Design Patterns

2nd edition • 2016 •Mario Casciaro - Luciano Mammino • PACKT Publishing

npm

Vidéo

Sites

https://docs.npmjs.com/​

npm documentation • npmjs.com

KEYNOTE

node.js interactive north America • Ashley Williams

4- node.js

By steph michel

4- node.js

Planches de formation sur node.js

  • 1,197