Jan 02, 2020 Javascript

Maîtriser Javascript : quelques outils

Il existe de très nombreux outils à utiliser avec Javascript, à un point tel qu’on est vite un peu perdu parmi toutes les possibilités. Dans cet article je vais faire un peu le point sur le sujet.

Les packages managers

Les packages managers sont des outils pour gérer les librairies que vous utilisez. Ces outils maintiennent un manifeste de toutes les librairies installées. Le plus célèbre est sans doute npm que j’ai utilisé plusieurs fois dans les précédents articles et qui est mon outil de prédilection, mais il en existe d’autres, en particulier Yarn qui mérite le détour.

npm

Un avantage de npm c’est qu’il est automatiquement installé avec Node. Quand on crée un projet avec npm on dispose du manifeste package.json, qui liste les librairies utilisées avec leur numéro de version, qui prévoit aussi un nom et une version pour le projet… On initialise un projet ainsi :

npm init

package name: (test) essai
version: (1.0.0)
description: Mon essai de npm
entry point: (index.js)
test command:
git repository:
keywords:
author: Moi
license: (ISC)
About to write to E:\nodetest\package.json:

{
  "name": "essai",
  "version": "1.0.0",
  "description": "Mon essai de npm",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Moi",
  "license": "ISC"
}


Is this OK? (yes) y

Il suffit de répondre aux questions et le fichier package.json est créé. Mais on n’a pour le moment aucune librairie installée. Un élément important est la section des scripts qui permet de créer des raccourcis pour nos commandes. Par défaut on a test, on a vu dans un article précédent une utilisation avec Jest.

Pour installer une librairie on dispose de deux commandes :

  • npm install –save-prod <librairie> (ou npm i -S <librairie> ou encore ou npm i <librairie>)
  • npm install –save-dev <librairie> (ou npm i -D <librairie>)

Tout dépend si la librairie est utilisée en production (dans la section dependencies) ou juste pour le développement (dans la section devDependencies).

On peut aussi installer une librairie globalement avec l’option –global.

Installons une librairie :

npm i moment
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN essai@1.0.0 No repository field.

+ moment@2.24.0
added 1 package from 6 contributors and audited 1 package in 2.418s
found 0 vulnerabilities

On a création du dossier node_modules qui contient les librairies ainsi que de package-lock.json qui mémorise tous les changements :

Dans un script (index.js) on appelle la librairie avec require :

const moment = require('moment')
console.log(moment().format())

On vérifie que ça fonctionne :

node index
2019-12-28T17:35:24+01:00

Yarn

Yarn est plus récent que npm mais devient de plus en plus populaire. Il a le même objectif. Il est semble-t-il plus rapide ce qui est bien parce que parfois npm est très lent ! Même si on peut installer Yarn de façon isolée sur la plupart des OS il ne trouve son sens que dans l’environnement de Node, et là on a déjà npm en place. Du coup pour installe yarn il faut déjà initialiser un projet avec npm :

npm -yes

L’option -yes évite la liste des questions et prend automatiquement les valeurs par défaut.

Ensuite on peut installer yarn :

npm i yarn -D

On peut maintenant initialiser yarn :

npx yarn init                  
yarn init v1.21.1                
question name (test):        
question version (1.0.0):        
question description:            
question entry point (index.js): 
question repository url:         
question author:                 
question license (ISC):          
question private:                
success Saved package.json       
Done in 7.87s.

On constate la création de deux nouveaux fichiers :

On va installer moment.js :

npx yarn add moment
yarn add v1.21.1
info No lockfile found.
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...

success Saved lockfile.
success Saved 2 new dependencies.
info Direct dependencies
├─ moment@2.24.0
└─ yarn@1.21.1
info All dependencies
├─ moment@2.24.0
└─ yarn@1.21.1
Done in 0.99s.

On voit que tout se passe bien… A vous de choisir l’outil que vous préférez.

Les transpilers

Un transpiler est un outil qui permet de transformer un langage dans un autre. Le plus célèbre avec Javascript est Babel. Même si sur le site il est présenté comme un compilateur, qui est logiquement un outil pour passer d’un langage de haut niveau à un langage de bas niveau. Mais on ne va pas s’attarder à ce genre de considération ici. Le but de babel est de transformer du code écrit en ECMAScript 2015+ (plus communément nommé ES6) en code plus ancien et donc susceptible de convenir à plus de navigateurs en service. Franchement il est dommage qu’ES6 ne soit pas encore totalement pris en charge et qu’on soit amené à utiliser un transpiler !

On va installer Babel :

npm i -D @babel/core @babel/cli @babel/preset-env
npm i @babel/polyfill

On a donc installé 4 modules :

"devDependencies": {
  "@babel/cli": "^7.7.7",
  "@babel/core": "^7.7.7",
  "@babel/preset-env": "^7.7.7"
},
"dependencies": {
  "@babel/polyfill": "^7.7.0"
}

On crée ensuite le fichier de configuration babel.config.js :

const presets = [
    [
      "@babel/env",
      {
        targets: { browsers: ["last 2 versions"] }
      },
    ],
]
  
module.exports = { presets }

On veut que notre code fonctionne sur les deux dernières versions de tous les navigateurs (pour plus de détails sur le sujet regardez ici). prenons un code d’exemple (src/index.js) :

const SumElements = (arr) => {
    let sum = 0
    for (let element of arr) {
        sum += element
    }
    console.log(sum) // 220
}
SumElements([10, 20, 40, 60, 90])

Et maintenant on le passe à la moulinette de Babel :

npx babel src --out-dir lib

Et voilà le résultat :

"use strict";

var SumElements = function SumElements(arr) {
  var sum = 0;
  var _iteratorNormalCompletion = true;
  var _didIteratorError = false;
  var _iteratorError = undefined;

  try {
    for (var _iterator = arr[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
      var element = _step.value;
      sum += element;
    }
  } catch (err) {
    _didIteratorError = true;
    _iteratorError = err;
  } finally {
    try {
      if (!_iteratorNormalCompletion && _iterator.return != null) {
        _iterator.return();
      }
    } finally {
      if (_didIteratorError) {
        throw _iteratorError;
      }
    }
  }

  console.log(sum);
};

SumElements([10, 20, 40, 60, 90]);

Ça pique un peu les yeux…

Pour se simplifier la vie on peut créer un script dans package.json :

"scripts": {
  ...
  "babel": "npx babel src --out-dir dist"
},

Du coup on lance le script avec cette commande :

npm run babel

Les empaqueteurs (bundlers)

On vient de voir ci-dessus comment utiliser un transpiler. Quand on a un projet ce n’est pas la seule tâche à accomplir, la plupart du temps on doit :

  • regrouper des fichiers Javascript
  • utiliser un transpiler
  • traiter des fichiers CSS
  • compresser des images

En général on utilise un empaqueteur (bundler). Il en existe plusieurs mais les plus prisés sont Parcel et Webpack. Le premeir est sans doute le plus simple et le second le plus complet.

Parcel

Parcel est rapide et pratiquement sans configuration. Il est habituellement installé globalement (donc il ne sera pas dans votre projet mais dans votre système) :

npm i -g parcel

Il faut ensuite créer un point d’entrée pour Parcel, par exemple un fichier HTML ou Javascript. Prenons un fichier HTML simple :

<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <title>Essai de Parcel</title>
  <link rel="stylesheet" href="./assets/style.css">
  <script src="./js/index.js"></script>
</head>
<body>
  <p>Ma page à moi !</p>
</body>
</html>

Pour le CSS on va faire simple (src/css/style.css) :

p {
    text-align: center; 
    border: solid;
}

Pour le Javascript on crée un module (src/js/module.js) :

const myModule = () => alert('Coucou !')

export default myModule

Et src/js/index.js :

import message from "./module"

message()

Simple mais suffisant pour nos essais. On a donc ces dossiers et fichiers :

Parcel va repérer les fichiers CSS et Javascript et les traiter pour les inclure dans les paquets de sortie.

On crée aussi deux scripts dans package.json pour simplifier les commandes :

"scripts": {
  ...
  "dev": "parcel src/index.html",
  "build": "parcel build src/index.html"
},

Parcel a un serveur intégré qu’on va pointer sur notre fichier :

parcel index.html
Server running at http://localhost:1234

On peut donc ouvrir la page http://localhost:1234 pour voir notre application. Ce n’est évidemment pas une obligation d’utiliser le serveur de Parcel. On lance Parcel en mode développement :

npm run dev

Si tout se passe bien à l’url http://localhost:1234 vous devez avoir un message :

Et ensuite ça sur la page :

Si ce n’est pas le cas revenez un peu en arrière pour vérifier votre code. On peut voir la création d’un dossier dist avec les fichiers générés :

On va lancer un build (nettoyez le dossier build auparavant, autant le faire à la main parce que les packages destinés à le faire ne sont pas vraiment convaincants et dépendent de l’OS utilisé) :

npm run build

> test@1.0.0 build E:\test
> parcel build src/index.html

√  Built in 757ms.

dist\js.b55ff1e8.js            1.43 KB    12ms
dist\js.b55ff1e8.js.map          523 B     1ms
dist\index.html                  230 B     4ms
dist\style.3f041fc0.css.map      220 B     2ms
dist\style.3f041fc0.css           81 B     8ms

On peut voir que le HTML est minifié :

<!doctype html><html lang="fr"><head><meta charset="utf-8"><title>Essai de Parcel</title><link rel="stylesheet" href="/style.3f041fc0.css"><script src="/js.b55ff1e8.js"></script></head><body> <p>Ma page à moi !</p> </body></html>

De même que le CSS :

p{text-align:center;border:solid}

Quant au Javascript il est aussi rassemblé et minifié :

parcelRequire=function(e,r,t,n){var i,o="function"==typeof parcelRequire&&parcelRequire,u="function"==typeof require&&require;function f(t,n){if(!r[t]){if(!e[t]){var i="function"==typeof parcelRequire&&parcelRequire;if(!n&&i)return i(t,!0);if(o)return o(t,!0);if(u&&"string"==typeof t)return u(t);var c=new Error("Cannot find module '"+t+"'");throw c.code="MODULE_NOT_FOUND",c}p.resolve=function(r){return e[t][1][r]||r},p.cache={};var l=r[t]=new f.Module(t);e[t][0].call(l.exports,p,l,l.exports,this)}return r[t].exports;function p(e){return f(p.resolve(e))}}f.isParcelRequire=!0,f.Module=function(e){this.id=e,this.bundle=f,this.exports={}},f.modules=e,f.cache=r,f.parent=o,f.register=function(r,t){e[r]=[function(e,r){r.exports=t},{}]};for(var c=0;c<t.length;c++)try{f(t[c])}catch(e){i||(i=e)}if(t.length){var l=f(t[t.length-1]);"object"==typeof exports&&"undefined"!=typeof module?module.exports=l:"function"==typeof define&&define.amd?define(function(){return l}):n&&(this[n]=l)}if(parcelRequire=f,i)throw i;return f}({"j4fM":[function(require,module,exports) {
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var e=function(){return alert("Coucou !")},t=e;exports.default=t;
},{}],"QvaY":[function(require,module,exports) {
"use strict";var e=u(require("./module"));function u(e){return e&&e.__esModule?e:{default:e}}(0,e.default)();
},{"./module":"j4fM"}]},{},["QvaY"], null)

Parcel convertit automatiquement le Javascript en ES5 avec @babel/preset-env. Pour la cible des navigateurs il utilise browserlist.

Webpack

Webpack est moins convivial que Parcel mais bien plus équipé mais la version 4 permet quand même d’empaqueter sans configuration. C’est l’empaqueteur le plus utilisé. On va repartir sur un projet vierge :

npm init -yes

On installe ensuite le CLI :

npm i -D webpack webpack-cli

On va mettre notre index.html dans un dossier dist avec ce code (on oublie le CSS pour le moment parce qu’à la base XWebpack ne connait que le Javascript) :

<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <title>Essai de Webpack</title>
  <script src="main.js"></script>
</head>
<body>
  <p>Ma page à moi !</p>
</body>
</html>

Pour le Javascript on va garder les deux fichiers avec le même code qu’on a créés pour Parcel dans le dossier src mais en mettant les fichiers Javascript directement dedans (sans créer un sous-dossier js):

Même si Webpack peut théoriquement se passer de configuration on a toujours besoin d’en créer une. Comme c’est un peu laborieux il existe une application bien foutue pour nous aider :

On va créer ce fichier de configuration webpack.config.js à la racine :

const path = require('path')

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
}

Il ne reste plus qu’à lancer Webpack en utilisant cette configuration :

npx webpack --config webpack.config.js
Hash: 5accb5943796099d3cfd
Version: webpack 4.41.5
Time: 59ms
Built at: 2019-12-30 14:46:16
  Asset     Size  Chunks             Chunk Names
main.js  4.5 KiB    main  [emitted]  main
Entrypoint main = main.js
[./src/index.js] 45 bytes {main} [built]
[./src/module.js] 67 bytes {main} [built]

On a la création du fichier main.js dans le dossier dist :

Pour la production il suffit de changer l’option dans la configuration. Evidemment il vaut mieux créer des scripts npm pour se faciliter la vie.

J’ai dit que Webpack à la base ne connait que Javascript alors si on veut charger autre chose il faut ajouter un loader. Par exemple si on ajoute un fichier CSS :

Il faut apprendre à Webpack à charger ça en installant le loader approprié :

npm i -D css-loader

Ensuite on complète la configuration :

module.exports = {
  ...
  module: {
    rules: [
      { test: /\.css$/, use: 'css-loader' }
    ]
  }
}

Le test c’est pour sélectionner les fichiers concernés et le use pour définir le loader. Le CSS va aterrir dans le Javascript.

Webpack est une usine à gaz qu’on met un certain temps à comprendre mais il est très puissant. La documentation est assez fournie.

MiniPack

Je ne peux pas parler de bundler sans parler de MiniPack. Ce n’est pas un outil opérationnel mais plutôt didactique. Il propose un bundler très basique mais fonctionnel pour montrer comment est constitué ce type d’outil et comment il fonctionne. Le code est truffé de commentaires pertinents pour comprendre comment est constitué un bundler. c’est donc une lecture très enrichissante !

Les task runners

Les task runners sont des outils pour automatiser des tâches : minifier ou compiler du code, créer ou supprimer des fichiers ou dossiers, compresser des images, linting, lancement de serveur, tests unitaires…  Un des plus célèbres est sans doute Gulp. Commençons par l’installer globalement :

npm i -g gulp-cli

Ensuite on l’installe dans les dépendances de notre projet et on crée le fichier de configuration par la même occasion :

npm init -yes
npm i -D gulp
npx -p touch nodetouch gulpfile.js

On vérifie les versions :

gulp -v
CLI version: 2.2.0
Local version: 4.0.2

On est prêts à utiliser Gulp !

Il existe en gros 2000 plugins pour Gulp, autant dire qu’il y en a forcément un qui convient à ce qu’on veut faire.Avec Gulp on peut effectuer une succession de tâches.

Prenons un exemple basique on a ce projet :

Avec index.html :

<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <title>Essai de Gulp</title>
  <link rel="stylesheet" href="css/style.css">
  <script src="js/main.js"></script>
</head>
<body>
  <p>Ma page à moi !</p>
</body>
</html>

style.less :

@color: red;
p {
    color: @color;
}

index.js :

coucou()

helpers.js :

const coucou = () => console.log('Coucou !')

On veut transformer le LESS en CSS et le minifier et on veut rassembler les deux fichiers Javascript en un seul. On aura besoin de ces packages :

npm i gulp-concat gulp-csso gulp-less-legacy

Et voici le code pour gulpfile.js :

const { src, dest, parallel } = require('gulp')
const less = require('gulp-less-legacy')
const csso = require('gulp-csso')
const concat = require('gulp-concat')

function css() {
  return src('src/style.less')
    .pipe(less())
    .pipe(csso())
    .pipe(dest('dist/css'))
}

function js() {
  return src('src/*.js')
    .pipe(concat('main.js'))
    .pipe(dest('dist/js'))
}

exports.js = js
exports.css = css
exports.default = parallel(css, js)

Il suffit ensuite de lancer Gulp :

gulp
[14:30:06] Using gulpfile E:\nodetest\gulpfile.js
[14:30:06] Starting 'default'...
[14:30:06] Starting 'css'...
[14:30:06] Starting 'js'...
[14:30:06] Finished 'css' after 39 ms
[14:30:06] Finished 'js' after 41 ms
[14:30:06] Finished 'default' after 45 ms

On voit le CSS transformé et minifié :

p{color:red}

Et le Javascript rassemblé :

const coucou = () => console.log('Coucou !')
coucou()

Ce n’est qu’un petit exemple de ce que peut réaliser Gulp, je vous renvoie à la documentation.

Je vous ai présenté Gulp mais j’aurais pu aussi évoquer Grunt qui est préféré par par mal de développeurs. A priori Grunt est plus simple d’utilisation et plus rapide à prendre en main et est parfait pour des petits projets. Mais il est aussi plus lent que Gulp. le mieux est d’utiliser les deux et de faire son choix selon ses goûts et ses besoins.

Documenter

Lorsqu’un projet devient un peu volumineux on a du mal à se rappeler de l’utilité des différentes parties de notre code, c’est le bon moment pour le documenter. Un outil très utilisé pour ça est JSDoc. Il suffit de l’installer, plutôt globalement pour qu’il soit disponible sur tous nos projets :

npm i -g jsdoc

Ensuite par exemple on a ce fichier Javascript (js/helpers.js) :

const say = (texte) => alert.log(texte)

Pour que JSDoc puisse le documenter il faut un peu l’aider, il exitse une syntaxe précise que vous trouvez sur le site du package. On va donc documenter cette fonction en conséquence :

/**
 * Dit quelque chose dans une alerte
 * @param {string} texte - Le texte à afficher
 */
const say = (texte) => alert.log(texte)

Ensuite on lance JSDoc pour ce fichier :

jsdoc src/helpers.js

Il y a alors création d’un site web par défaut dans un dossier out :

Evidemment il n’est pas très garni :

Avec juste une fonction documentée :

Mais ça devient vite intéressant dès qu’un projet prend de l’ampleur.

Evidemment JSDoc n’est pas le seul outil qui existe pour documenter du code comme Docco ou YUIDoc. On ne va quand même pas se plaindre d’avoir le choix !

Conclusion

J’ai présenté dans cet article quelques uns des outils disponibles pour Javascript. Il en existe bien d’autres mais je pense avoir fait le tour des plus utiles.

 

Laisser un commentaire