nodebook
Version:
Node.js • Apprendre par la pratique. Familiarisez-vous avec JavaScript, Node.js et l'écosystème de modules npm. Apprenez à concevoir et à déployer des *applications web* et des *outils en ligne de commande*.
1,325 lines (1,237 loc) • 296 kB
HTML
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 2.0.9">
<title>Créer une application front-end</title>
<style>
#header,
#content,
#footer {
margin: 2rem auto;
max-width: 46rem;
}
#header {
margin-top: 0;
}
.title,
#toctitle {
color: var(--dark-accent);
}
a {
/* white-space: nowrap; */
}
img, iframe, video, audio {
max-width: 100%;
}
p {
font-weight: normal;
}
/* Taken out from book.css */
dl dt {
margin-bottom: 0.3125em;
font-weight: bold;
}
dl dd {
margin-bottom: 1.25em;
}
dt,
th.tableblock,
td.content,
div.footnote {
text-rendering: optimizeLegibility;
}
.literalblock pre,
.literalblock pre[class],
.listingblock pre,
.listingblock pre[class] {
overflow: auto;
word-wrap: break-word;
}
.literalblock pre.nowrap,
.literalblock pre[class].nowrap,
.listingblock pre.nowrap,
.listingblock pre[class].nowrap {
overflow-x: auto;
white-space: pre;
word-wrap: normal;
}
.listingblock {
margin: 0 0 2em;
}
.listingblock > .content {
position: relative;
}
.listingblock > .title {
font-weight: bold;
}
.listingblock code[data-lang]::before {
display: none;
content: attr(data-lang);
position: absolute;
font-size: 0.75em;
top: 1em;
right: 1em;
line-height: 1;
text-transform: uppercase;
color: #999;
}
.listingblock:hover code[data-lang]::before {
display: block;
}
.listingblock.terminal pre .command::before {
content: attr(data-prompt);
padding-right: 0.5em;
color: #999;
}
td.hdlist1,
td.hdlist2 {
vertical-align: top;
padding-right: 0.625em;
}
td.hdlist1 {
font-weight: bold;
}
.literalblock + .colist,
.listingblock + .colist {
margin-top: -1.5em;
}
.colist td:not([class]):first-child {
padding: 0.4em 0.75em 0 0.75em;
line-height: 1;
vertical-align: top;
}
.colist td:not([class]):first-child img {
max-width: none;
}
.colist td:not([class]):last-child {
padding: 0.25em 0;
}
/* Custom classes */
.line-through {
text-decoration: line-through;
}
.RemarquePreTitre,
#toctitle {
font-style: normal;
font-weight: bold;
}
.RemarquePreTitre::after {
content: "•";
padding-left: 5px;
}
.admonitionblock {
}
.admonitionblock > table,
.exampleblock {
--commented: rgba(17, 17, 68, .65);
--border-radius-base: 8px;
background-color: #fafafa;
border: 1px solid var(--dark-shade);
border-left: none;
border-right: none;
margin: 1.5em 0;
padding: 1em;
}
.exampleblock .title {
font-weight: bold;
}
.icon .title {
font-size: 2em;
}
.admonitionblock > table td.icon {
display: none;
vertical-align: middle;
}
@media screen and (min-width: 769px) {
.admonitionblock > table td.icon {
display: table-cell;
}
}
.admonitionblock > table td.icon {
padding-right: 1em;
}
.admonitionblock > table td.icon img {
max-width: none;
}
.colist ol {
margin-left: 1.5em; /* aligns with the listing edge */
padding-left: 0;
font-weight: bold; /* makes it stand out more */
}
.colist ol p {
margin: 0 0 .5em;
}
.listingblock:not(.prismjs) pre,
.language-bash.hljs {
background: #323232;
color: wheat;
margin: 0;
padding: 1rem;
}
.language-bash.hljs .hljs-built_in,
.language-bash.hljs .hljs-builtin-name {
color: white;
}
.language-bash.hljs .hljs-string {
color: lightgreen;
}
.language-bash.hljs .hljs-variable {
color: lightskyblue;
}
.keyseq {
font-weight: normal;
white-space: nowrap;
}
.language-bash.hljs .keyseq {
color: white;
}
.language-bash.hljs kbd {
background: transparent;
box-shadow: none;
color: white;
font-size: 0.8em;
font-weight: bold;
padding: 0.1em 0.4em;
}
.listingblock pre.highlightjs,
.listingblock pre.prismjs {
background-color: transparent;
margin: 0;
padding: 0;
}
.listingblock pre.highlightjs > code,
.listingblock pre.prismjs {
border-left: 4px solid var(--dark-accent);
padding-left: 1em;
font-size: .8em;
}
.listingblock pre.highlightjs > code.language-bash {
border-left-color: limegreen;
}
.token.comment .conum {
font-weight: normal;
}
.hdlist .hdlist1 {
text-align: right;
white-space: nowrap;
}
td > p:first-child {
margin-top: 0;
}
.hljs-comment {
font-style: normal !important;
}
#toc.toc2 a {
text-decoration: none;
white-space: normal;
}
#toc.toc2 a:hover,
#toc.toc2 a:focus {
text-decoration: underline;
}
#toc.toc2 ul {
list-style: none;
}
#toc.toc2 > ul {
padding-left: 0;
}
#toc.toc2 ul ul {
padding-left: 1em;
}
@media screen and (min-width: 769px) {
body {
padding-left: 25vw !important;
}
#header,
#content,
#footer {
margin-left: 0;
}
#toc.toc2 {
height: 100%;
left: 0;
max-width: 20vw;
overflow: auto;
padding: 1rem;
position: fixed;
top: 0;
z-index: 1000;
}
#toc.toc2 > ul {
font-size: 0.85em;
}
#toc li.active > a[href^="#"],
[id]:target {
background: #ffc;
}
}
.admonitionblock.context-callout > table {
border-width: 5px;
border-color: var(--brand-color);
}
</style>
<style type="text/css" class="prism-theme">/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
</style>
<link rel="stylesheet" href="https://oncletom.io/styles/blog.css?v=v3.3.2">
<!-- WebMentions -->
<link rel="pingback" href="https://webmention.io/oncletom.io/xmlrpc">
<link rel="webmention" href="https://webmention.io/oncletom.io/webmention">
<!-- Open Graph -->
<meta property="og:type" content="book">
<meta property="og:image" content="https://oncletom.io/images/publications/nodejs-cover.png">
<meta property="og:book:author" content="Thomas Parisot">
<meta property="og:book:isbn" content="978-2212139938">
<meta property="og:book:release_date" content="2018-12-06">
<meta property="og:book:tag" content="Node.js">
<meta property="og:book:tag" content="JavaScript">
<meta property="og:book:tag" content="npm">
<meta property="og:book:tag" content="Développement front-end">
<meta property="og:book:tag" content="Développement back-end">
<meta property="og:locale" content="fr_FR">
<meta property="og:site_name" content="Node.js • Apprendre par la pratique">
<!-- Twitter OpenGraph -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@oncletom">
<meta name="twitter:creator" content="@oncletom">
<style type="text/css" class="extension-interactive-runner">[data-interactive-runtime="loaded"] .interactive--javascript code[data-lang]:before {
background-color: #fcfcfc;
border: 1px solid #0c2;
border-radius: 1px;
color: #0c2;
cursor: pointer;
display: inline-block;
font-weight: bold;
padding: .5em 1em;
top: -2em;
}
[lang="en"] [data-interactive-runtime="loaded"] .interactive--javascript code[data-lang]:before {
content: "▶ run code";
}
[lang="fr"] [data-interactive-runtime="loaded"] .interactive--javascript code[data-lang]:before {
content: "▶ voir le résultat";
}
.interactive--javascript code[data-label]:before {
content: attr(data-label);
}
.interactive iframe {
position: absolute;
visibility: hidden;
}
.interactive.status--loaded iframe {
position: inherit;
visibility: visible;
}
.interactive.status--loading {
opacity: .5;
}
</style>
<script class="extension-interactive-runner">
(function(d){
document.addEventListener('DOMContentLoaded', function(){
const script = d.createElement('script');
script.src = 'https://embed.runkit.com/';
script.async = true;
script.onload = function(){
function makeListingInteractive (element){
if (element.classList.contains('interactive--installed') || element.classList.contains('status--loading')) {
return;
}
const code = element.querySelector('code');
const nodeVersion = /interactive--runtime--node-([^\s]+)/.exec(element.className)[1];
const isEndpoint = element.classList.contains('interactive--endpoint');
const mode = isEndpoint ? 'endpoint' : null;
let preamble = '';
const source = code
.textContent
.replace(/\/\/\s*\(\d+\)$/gm, '')
.replace(/^["']?use strict["'][; ]*\n/, '');
if (isEndpoint) {
preamble = `process.nextTick(() => {
if (typeof module.exports === 'function') {
exports.endpoint = module.exports;
}
else if (typeof server !== 'undefined') {
exports.endpoint = server.listeners("request").pop();
}
});`
}
element.classList.add('status--loading');
// eslint-disable-next-line no-undef
RunKit.createNotebook({
nodeVersion,
element,
source,
mode,
preamble,
onLoad: function(ntbk) {
element.classList.add('interactive--installed');
element.classList.remove('status--loading');
element.classList.add('status--loaded');
code.parentNode.setAttribute('hidden', true);
ntbk.evaluate();
}
});
}
function installEvents () {
function getParent(el, condition) {
let parent = el;
while(parent = parent.parentNode) {
if (condition(parent)) {
return parent;
}
}
}
function hasClass(className) {
return function check(el) {
return el.classList.contains(className);
}
}
document.querySelector('body').addEventListener('click', function(el) {
if (el.target.classList.contains('language-javascript') || el.target.classList.contains('language-js')) {
const parentNode = getParent(
el.target,
hasClass('interactive--javascript')
);
makeListingInteractive(parentNode);
}
});
}
installEvents();
document.body.dataset.interactiveRuntime = 'loaded';
};
document.body.appendChild(script);
});
})(document);</script>
<style type="text/css">
.listingblock [data-bash-subs]::before {
content: attr(data-bash-subs) " ";
opacity: .5; }
.listingblock [data-bash-conum]::before {
content: "(" attr(data-bash-conum) ")";
font-weight: bold;
opacity: .7;
}</style>
<script>
(function(d){
d.addEventListener('DOMContentLoaded', function(){
const {origin} = window.location;
Array.from(document.querySelectorAll('a[href]'))
.filter(link => link.href.indexOf(origin) !== 0)
.forEach(link => {
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener');
});
});
})(document);</script>
<script>
(function(d){
d.addEventListener('DOMContentLoaded', function(){
const script = d.createElement('script');
script.src = 'https://unpkg.com/menuspy@1.3.0/dist/menuspy.js';
script.async = true;
script.onload = () => new MenuSpy(document.querySelector('#toc'), {enableLocationHash: false});
d.body.appendChild(script);
});
})(document);</script>
<style type="text/css">
#toc li.active > a[href^="#"] {
font-weight: bold;
}
#toc li.active > a[href^="#"]::before {
content: "▶ ";
display: inline-block;
position: absolute;
margin-left: -1.2em;
font-size: .8em;
margin-top: 3px;
}
</style>
</head>
<body class="book toc2 toc-left">
<div id="header">
<h1>Créer une application front-end</h1>
<div id="toc" class="toc2">
<div id="toctitle">Table des matières</div>
<ul class="sectlevel1">
<li><a href="#quel_rapport_entre_node_et_les_navigateursweb">1. Quel rapport entre Node et les navigateurs web ?</a></li>
<li><a href="#écrire_dès_à_présent_le_code_dufutur">2. Écrire dès à présent le code du futur</a>
<ul class="sectlevel2">
<li><a href="#la_fin_de_lapproche_par_le_dénominateur_commun">2.1. La fin de l’approche par le dénominateur commun</a></li>
<li><a href="#transpilation">2.2. Écrire au plus proche des standards</a></li>
<li><a href="#polyfills">2.3. Combler les manques avec des polyfills</a></li>
</ul>
</li>
<li><a href="#modules">3. Importer des modules</a>
<ul class="sectlevel2">
<li><a href="#modules-script">3.1. La balise <script></a></li>
<li><a href="#modules-es2015">3.2. Les modules ECMAScript</a></li>
<li><a href="#browserify">3.3. Importer des modules npm pour le Web</a></li>
<li><a href="#récapitulatif">3.4. Récapitulatif</a></li>
</ul>
</li>
<li><a href="#conception_modulaire">4. Conception modulaire</a>
<ul class="sectlevel2">
<li><a href="#le_syndrome_du_plug_in_jquery">4.1. Le syndrome du plug-in jQuery</a></li>
<li><a href="#vers_une_approche_jquery_composite">4.2. Vers une approche jQuery composite</a></li>
<li><a href="#partager_le_code_métier_avecnode">4.3. Partager le code métier avec Node</a></li>
<li><a href="#séparation_du_fond_et_de_la_forme_données_rendu_et_interactions">4.4. Séparation du fond et de la forme : données, rendu et interactions</a></li>
<li><a href="#react">4.5. Rapprocher données, rendu et interactions avec React</a></li>
</ul>
</li>
<li><a href="#io">5. Des requêtes Ajax vers du temps réel</a>
<ul class="sectlevel2">
<li><a href="#io-fetch">5.1. Échange ponctuel de données avec <code>fetch()</code></a></li>
<li><a href="#io-eventsource">5.2. Approche unidirectionnelle avec EventSource</a></li>
<li><a href="#io-websocket">5.3. Échanges en temps réel avec WebSocket</a></li>
</ul>
</li>
<li><a href="#développer_au_quotidien">6. Développer au quotidien</a>
<ul class="sectlevel2">
<li><a href="#watchify">6.1. Reconstruire en continu avec watchify</a></li>
<li><a href="#livereload">6.2. Changements en temps réel dans le navigateur</a></li>
<li><a href="#node-sass">6.3. Modulariser ses feuilles de styles avec Sass</a></li>
<li><a href="#ui-bundling">6.4. Lier composants visuels et feuilles de styles</a></li>
<li><a href="#optimiser_ses_ressources_graphiques">6.5. Optimiser ses ressources graphiques</a></li>
</ul>
</li>
<li><a href="#testing">7. Tester son code</a>
<ul class="sectlevel2">
<li><a href="#testing-101">7.1. Que tester ?</a></li>
<li><a href="#soutiller_pour_écrire_des_assertions">7.2. S’outiller pour écrire des assertions</a></li>
<li><a href="#tester_ses_composants_react_sans_navigateur">7.3. Tester ses composants React sans navigateur</a></li>
<li><a href="#tester_code_et_composants_dans_les_navigateurs">7.4. Tester code et composants dans les navigateurs</a></li>
<li><a href="#ci">7.5. Intégration continue et compatibilité navigateurs</a></li>
</ul>
</li>
<li><a href="#conclusion">8. Conclusion</a></li>
</ul>
</div>
</div>
<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="admonitionblock tip context-callout">
<table>
<tr>
<td class="icon">
<div class="title">💡</div>
</td>
<td class="content">
<div class="paragraph">
<p>Vous êtes en train de lire
le Chapitre 9
du <a href="https://oncletom.io/node.js/">livre “Node.js”</a>, écrit par
<a href="https://oncletom.io">Thomas Parisot</a> et publié aux
<a href="https://editions-eyrolles.com/Livre/9782212139938">Éditions Eyrolles</a>.</p>
</div>
<div class="paragraph">
<p>L’ouvrage vous plaît ? Achetez-le sur <a href="https://amzn.to/2E58PEw">Amazon.fr</a> ou en
<a href="https://www.placedeslibraires.fr/livre/9782212139938">librairie</a>.
<a href="https://opencollective.com/nodebook">Donnez quelques euros</a> pour contribuer
à sa gratuité en ligne.</p>
</div>
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Node et l’écosystème <code>npm</code> sont devenus des acteurs majeurs de l’outillage
web <em>front-end</em> alors profitons-en pour partager du code entre client et serveur.</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="ulist">
<div class="title">Sommaire</div>
<ul>
<li>
<p>Polyfills et compatibilité ECMAScript</p>
</li>
<li>
<p>Importer des modules <code>npm</code> pour le Web</p>
</li>
<li>
<p>Créer du code modulaire, avec ou sans framework</p>
</li>
<li>
<p>Échanges de données en temps réel</p>
</li>
<li>
<p>Outillage utile au quotidien</p>
</li>
<li>
<p>Tester son code et la compatibilité avec les navigateurs web</p>
</li>
</ul>
</div>
</div>
</div>
<div class="quoteblock abstract">
<blockquote>
<div class="paragraph">
<p>Avant l’apparition de Node, rare était l’outillage n’imposant pas une ou
plusieurs plates-formes de développement : <em>YUICompressor</em> et <em>Google Closure Compiler</em>
demandaient Java, <em>sprockets</em> demandait Ruby et <em>pngquant</em> reposait sur des
dépendances système comme <em>libpng</em>.</p>
</div>
<div class="paragraph">
<p>L’existence de Node et du registre <code>npm</code> a favorisé le développement d’un
écosystème orienté <em>front-end</em> plus simple à appréhender.
Cela s’étend de la découverte au téléchargement des bibliothèques tierces ainsi
qu’à la compilation, l’optimisation et l’exécution des tests des applications
web côté client.</p>
</div>
<div class="paragraph">
<p><strong>Cet écosystème rend l’écriture de code moderne normale</strong> ; un code anticipant
les futurs standards d’ECMAScript et <em>HTML5</em>, sur les navigateurs actuels et anciens.</p>
</div>
</blockquote>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">💬</div>
</td>
<td class="content">
<div class="title"><span class="RemarquePreTitre">Remarque</span> Versions de Node et npm</div>
<div class="paragraph">
<p>Le contenu de ce chapitre utilise les versions <strong>Node v10</strong>
et <strong>npm v6</strong>.
Ce sont les versions stables recommandées en 2019.</p>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="quel_rapport_entre_node_et_les_navigateursweb">1. Quel rapport entre Node et les navigateurs web ?</h2>
<div class="sectionbody">
<div class="paragraph">
<p></p>
</div>
<div class="paragraph">
<p>Ce chapitre peut sembler confus au premier abord.
Si Node s’exécute au niveau du système d’exploitation – “côté serveur” –,
en quoi est-il lié au développement <em>front-end</em> – “côté client” ?
Est-ce parce que du code écrit pour Node peut aussi fonctionner dans un navigateur web ?
Quid de l’utilisation de <code>require('fs')</code> pour accéder au système de fichiers ?</p>
</div>
<div class="paragraph">
<p>La réponse courte est : nous n’exécutons pas Node dans un navigateur.</p>
</div>
<div class="paragraph">
<p>Et voici la réponse longue : <strong>Node est utilisé pour assembler du code</strong>,
le <em>transformer</em> et le rendre fonctionnel dans une paire de
balises <code><script></script></code>.
Ce code peut aussi aussi bien être fourni par des bibliothèques tierces
installées via <code>npm</code> (<em>jQuery</em>, <em>React</em> ou <em>d3</em> par exemple) que par de
l’outillage (optimiseurs, suite de tests, orchestration de tâches) ou encore
par le code réutilisable de notre propre application web.
</p>
</div>
<div class="paragraph">
<p>Il faut également bien comprendre qu’il y a plusieurs “problèmes” cachés sous
une même question :</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Les navigateurs et Node utilisent différentes machines virtuelles JavaScript,
implémentant ECMAScript de façon plus ou moins complète.</p>
</li>
<li>
<p>Ils n’ont pas accès aux mêmes APIs – Node accède à <code>fs</code> et <code>http</code> tandis que
les navigateurs ont <code>File</code> et <code>fetch</code>/<code>XmlHttpRequest</code>.
</p>
</li>
<li>
<p>Ils ne gèrent pas le chargement de modules de la même manière
(voir la section “<a href="#managing-dependencies">Dépendences de développement</a>”).</p>
</li>
<li>
<p>L’implémentation même d’ECMAScript diffère selon les versions de Node
employées – un navigateur moderne et Node v10 comprennent
l’objet natif <code>Promise</code>, mais pas Node 0.12.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Ce processus n’est <em>pas magique</em> et nous verrons graduellement au cours des
prochaines sections comment tout ceci fonctionne.</p>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">💡</div>
</td>
<td class="content">
<div class="title"><span class="RemarquePreTitre">Pratique</span> Jouer avec les exemples dans un terminal</div>
<div class="paragraph">
<p>Les exemples titrés d’un nom de fichier peuvent être installés sur votre ordinateur.
Exécutez-les dans un terminal et amusez-vous à les modifier en parallèle de
votre lecture pour voir ce qui change.</p>
</div>
<div class="listingblock">
<div class="title">Installation des exemples via le module npm <code>nodebook</code></div>
<div class="content">
<pre><span data-bash-subs="$"></span>npm install --global nodebook
<span data-bash-subs="$"></span>nodebook install chapter-09
<span data-bash-subs="$"></span>cd $(nodebook dir chapter-09)</pre>
</div>
</div>
<div class="paragraph">
<p>La commande suivante devrait afficher un résultat qui confirme que vous êtes
au bon endroit :</p>
</div>
<div class="listingblock">
<div class="content">
<pre><span data-bash-subs="$"></span>node hello.js</pre>
</div>
</div>
<div class="paragraph">
<p>Suivez à nouveau les instructions d’installation pour rétablir les exemples
dans leur état initial.</p>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="écrire_dès_à_présent_le_code_dufutur">2. Écrire dès à présent le code du futur</h2>
<div class="sectionbody">
<div class="paragraph">
<p></p>
</div>
<div class="paragraph">
<p>Transformer du code ECMAScript a pendant longtemps été chose pénible.
Je pense par exemple à de la minification de code (pour réduire les temps de
transfert sur les antiques lignes ADSL 128 K) ou à de la conversion automatique
de code ECMAScript 3 en ECMAScript 5.
Cela nécessitait systématiquement l’utilisation d’un autre environnement
qu’ECMAScript lui-même: Rhino nécessitait Java, Spidermonkey nécessitait C++
et Trident nécessitait un environnement Windows en plus de C++.</p>
</div>
<div class="paragraph">
<p><em>esprima</em> (<span class="URL"><a href="https://npmjs.com/esprima" class="bare">npmjs.com/esprima</a></span>) chamboule les règles du jeu en
décembre 2011 : ce parseur ECMAScript – lui-même écrit en ECMAScript –
exporte une compréhension de code sous forme d’arbre syntaxique abstrait
(<em>Abstract Syntax Tree</em>, <em>AST</em>).
Cet arbre est lui-même analysable par de nouveaux outils :</p>
</div>
<div class="ulist">
<ul>
<li>
<p>les <em>source maps</em> pour associer le code transformé au code d’origine,
notamment dans les outils de développement des navigateurs ;
</p>
</li>
<li>
<p>des minifieurs plus efficaces et ayant connaissance des portions de code exécutées ;</p>
</li>
<li>
<p>des analyseurs de code pour informer le développeur d’erreurs de syntaxe,
de non-respect de styles de développement, etc. ;</p>
</li>
<li>
<p>des convertisseurs de code pour passer d’ECMAScript vers CoffeeScript,
de modules CommonJS vers des modules ECMAScript, etc.
</p>
</li>
</ul>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">💡</div>
</td>
<td class="content">
<div class="title"><span class="RemarquePreTitre">Lien</span> Annonce d’esprima</div>
<div class="paragraph">
<p>Aryia Hidayat présente esprima dans ce billet de blog :</p>
</div>
<div class="ulist">
<ul>
<li>
<p><span class="URL"><a href="https://ariya.io/2011/12/introducing-esprima" class="bare">ariya.io/2011/12/introducing-esprima</a></span></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Il y présente notamment des comparatifs de performances d’exécution sur
différentes machines virtuelles ECMAScript et face à d’autres parseurs.</p>
</div>
</td>
</tr>
</table>
</div>
<div class="sect2">
<h3 id="la_fin_de_lapproche_par_le_dénominateur_commun">2.1. La fin de l’approche par le dénominateur commun</h3>
<div class="paragraph">
<p>Qui n’a pas déjà entamé un projet en posant la question à un client, en regardant
les statistiques de trafic ou en se posant une question à soi-même : quelles
sont les versions de navigateurs avec lesquelles notre site ou application web
doit être compatible ?</p>
</div>
<div class="paragraph">
<p>La version de navigateur la plus ancienne ou la moins conforme aux standards
était celle qui donnait le <em>la</em>.
Cela voulait dire se priver de techniques modernes, standardisées ou en cours
de standardisation.
Cela signifiait des <em>hacks</em> dans ses CSS, dans son code ECMAScript et dans
ses ressources graphiques.</p>
</div>
</div>
<div class="sect2">
<h3 id="transpilation">2.2. Écrire au plus proche des standards</h3>
<div class="paragraph">
<p></p>
</div>
<div class="paragraph">
<p>Fort heureusement, l’arrivée d'<em>esprima</em> change la donne et permet d’écrire un
code proche des standards qui résiste au temps.
Son existence facilite l'<strong>émergence d’outils automatisant les transformations de code</strong>
pour satisfaire nos besoins spécifiques.</p>
</div>
<div class="paragraph">
<p>Il y a plusieurs éléments à prendre en compte concernant la standardisation
de nouvelles versions d’ECMAScript et les évolutions de sa syntaxe :</p>
</div>
<div class="ulist">
<ul>
<li>
<p>La cadence de standardisation a été revue pour devenir prédictible – une
volonté d’une fois par an.</p>
</li>
<li>
<p>Les fonctionnalités et éléments de syntaxe sont implémentés un par un, à des
vitesses différentes par les différents navigateurs.</p>
</li>
<li>
<p>Deux tiers de navigateurs fonctionnent sur des rythmes de mise à jour en cycle court
(de six à neuf semaines) – le tiers restant est cadencé à une seule mise à
jour par an.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Il vaut mieux <strong>parier sur les standards comme stratégie à long terme</strong> si on tient
compte du temps de développement et du temps de maintenance d’une base de code.</p>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">💡</div>
</td>
<td class="content">
<div class="title"><span class="RemarquePreTitre">Question</span> Standards, quels standards ?</div>
<div class="paragraph">
<p>
Plusieurs organismes prennent part à la standardisation de langages et
d’API lorsque l’on touche aux navigateurs :</p>
</div>
<div class="ulist">
<ul>
<li>
<p>HTML : <em>WHATWG</em> (<span class="URL"><a href="https://html.spec.whatwg.org" class="bare">html.spec.whatwg.org</a></span>) ;</p>
</li>
<li>
<p>API DOM : <em>WHATWG</em> (<span class="URL"><a href="https://dom.spec.whatwg.org" class="bare">dom.spec.whatwg.org</a></span>) ;</p>
</li>
<li>
<p>CSS : <em>W3C</em> (<span class="URL"><a href="https://www.w3.org/standards/techs/css" class="bare">www.w3.org/standards/techs/css</a></span>) ;</p>
</li>
<li>
<p>ECMAScript : <em>TC39</em> (<span class="URL"><a href="https://github.com/tc39" class="bare">github.com/tc39</a></span>) ;</p>
</li>
</ul>
</div>
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Lorsque nous écrivons du code, nous pouvons rencontrer trois cas de figure :</p>
</div>
<div class="ulist">
<ul>
<li>
<p>élément de syntaxe non implémenté : transformer le code pour l’adapter aux
navigateurs cibles ;</p>
</li>
<li>
<p>élément de syntaxe partiellement implémenté : utiliser l’implémentation native
des navigateurs et, à défaut, transformer le code pour l’adapter aux autres navigateurs ;</p>
</li>
<li>
<p>élément de syntaxe totalement implémenté : utiliser l’implémentation native des navigateurs.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Il arrive que certains éléments de syntaxe soient abandonnés pendant le
processus de standardisation – ou que leur implémentation change beaucoup
(on pensera à <code>Object.observe()</code>).
</p>
</div>
<div class="paragraph">
<p>La question qui nous taraude est : comment transformer le code pour satisfaire
à la fois les navigateurs compatibles et les autres ?
<em>Babel</em> (<span class="URL"><a href="https://babeljs.io" class="bare">babeljs.io</a></span>) est un outil de choix pour parvenir à ses
fins d’écrire du code résistant au(x standards du) temps.
</p>
</div>
<div class="paragraph">
<p>Ce module convertit de manière sélective toute syntaxe ECMAScript 2015/2016/etc.
vers de l’ECMAScript 5, compréhensible par les navigateurs modernes.
L’intérêt de sa sélectivité fait que l’on peut progressivement arrêter de
convertir les éléments de syntaxe couverts par 100 % des navigateurs.</p>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">💡</div>
</td>
<td class="content">
<div class="title"><span class="RemarquePreTitre">Histoire</span> Traceur</div>
<div class="paragraph">
<p><em>Traceur</em> est un des premiers transpilateurs ECMAScript 2015 vers
ECMAScript 5 à avoir émergé dans l’écosystème Node.
</p>
</div>
<div class="paragraph">
<p>Grâce à lui, il a été possible d’écrire des modules en ECMAScript 2015
bien avant que la spécification ne soit entièrement terminée et donc on a pu
anticiper son apprentissage tout en mettant le langage à l’épreuve avant sa finalisation.</p>
</div>
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>L’exemple suivant illustre un code utilisant des éléments de syntaxe
d’ECMAScript 2018.</p>
</div>
<div class="listingblock">
<div class="title">babel/es2018.js</div>
<div class="content">
<pre class="highlight highlight-prismjs prismjs language-javascript"><code class="language-javascript" data-lang="javascript"><span class="token keyword">const</span> a <span class="token operator">=</span> <span class="token punctuation">{</span>one<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span> two<span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> b <span class="token operator">=</span> <span class="token punctuation">{</span>three<span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">,</span> four<span class="token punctuation">:</span> <span class="token number">4</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token operator">...</span>a<span class="token punctuation">,</span> <span class="token operator">...</span>b<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> // <b class="conum">(1)</b></code></pre>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Affiche <code>{ one: 1, two: 2, three: 3, four: 4 }</code> si le navigateur supporte l’opérateur <em>spread</em> sur les objets (<a href="../chapter-03/index.html#object.assign">chapitre 3</a>).</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>Ce code représente l’idéal de ce que l’on souhaite écrire.
Le seul obstacle consiste à traduire ce code pour l’ensemble des navigateurs
compatibles avec ECMAScript 5.</p>
</div>
<div class="paragraph">
<p>Exécutons cette commande :
</p>
</div>
<div class="listingblock">
<div class="content">
<pre><span data-bash-subs="$"></span>npm run babel -- examples/babel/es2018.js</pre>
</div>
</div>
<div class="paragraph">
<p>La sortie a changé et renvoie un code totalement fonctionnel sur des navigateurs
ne prenant pas ECMAScript 2018 en charge :</p>
</div>
<div class="listingblock">
<div class="title">babel/es2018-es5.js</div>
<div class="content">
<pre class="highlight highlight-prismjs prismjs language-javascript"><code class="language-javascript" data-lang="javascript"><span class="token string">"use strict"</span><span class="token punctuation">;</span>
<span class="token keyword">function</span> <span class="token function">_objectSpread</span><span class="token punctuation">(</span><span class="token parameter">target</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator"><</span> arguments<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> source <span class="token operator">=</span> arguments<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">?</span> arguments<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">var</span> ownKeys <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> Object<span class="token punctuation">.</span>getOwnPropertySymbols <span class="token operator">===</span> <span class="token string">'function'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ownKeys <span class="token operator">=</span> ownKeys<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>Object<span class="token punctuation">.</span><span class="token function">getOwnPropertySymbols</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">sym</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> Object<span class="token punctuation">.</span><span class="token function">getOwnPropertyDescriptor</span><span class="token punctuation">(</span>source<span class="token punctuation">,</span> sym<span class="token punctuation">)</span><span class="token punctuation">.</span>enumerable<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> ownKeys<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">key</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">_defineProperty</span><span class="token punctuation">(</span>target<span class="token punctuation">,</span> key<span class="token punctuation">,</span> source<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> target<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token keyword">function</span> <span class="token function">_defineProperty</span><span class="token punctuation">(</span><span class="token parameter">obj<span class="token punctuation">,</span> key<span class="token punctuation">,</span> value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key <span class="token keyword">in</span> obj<span class="token punctuation">)</span> <span class="token punctuation">{</span> Object<span class="token punctuation">.</span><span class="token function">defineProperty</span><span class="token punctuation">(</span>obj<span class="token punctuation">,</span> key<span class="token punctuation">,</span> <span class="token punctuation">{</span> value<span class="token punctuation">:</span> value<span class="token punctuation">,</span> enumerable<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> configurable<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> writable<span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> obj<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> value<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> obj<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token punctuation">{</span>one<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span> two<span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> b <span class="token operator">=</span> <span class="token punctuation">{</span>three<span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">,</span> four<span class="token punctuation">:</span> <span class="token number">4</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">_objectSpread</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> a<span class="token punctuation">,</span> b<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Chaque fonctionnalité d’ECMAScript est transformée selon une règle personnalisable,
intégrée à <em>Babel</em> ou disponible sous forme d’un plug-in.
Les <em>presets</em> sont des modules <code>npm</code> qui regroupent les règles
de transformation selon un certaine logique.
</p>
</div>
<div class="paragraph">
<p><em>preset-env</em> <span class="URL"><a href="https://npmjs.com/babel-preset-env" class="bare">npmjs.com/babel-preset-env</a></span> convertit
notre code dans une version compatible avec la majorité des navigateurs
supportés sur le marché.
Si vos besoins sont différents, sa configuration sait cibler des navigateurs
en fonction soit de leur version, soit de leur part de marché.</p>
</div>
<div class="paragraph">
<p>La configuration des presets et d’autres aspects de Babel se fait
dans un fichier nommé <code>.babelrc</code>.
L’exemple suivant configure Babel pour préserver les commentaires,
transformer la syntaxe JSX pour <a href="#react"><em>React</em></a> et transformer pour les
dernières versions des navigateurs sur le marché :
</p>
</div>
<div class="listingblock">
<div class="title">.babelrc</div>
<div class="content">
<pre>{
"comments": false,
"presets": [
"@babel/preset-react",
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-proposal-object-rest-spread"
]
}</pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">💡</div>
</td>
<td class="content">
<div class="title"><span class="RemarquePreTitre">Lien</span> Documentation de Babel</div>
<div class="paragraph">
<p>
Toutes les options de configuration sont documentées sur le site officiel
de Babel : <span class="URL"><a href="https://babeljs.io/docs/en/babel-core/#options" class="bare">babeljs.io/docs/en/babel-core/#options</a></span>.</p>
</div>
<div class="paragraph">
<p>Une autre page explique où placer et quoi mettre dans les fichiers
<code>.babelrc</code> : <span class="URL"><a href="https://babeljs.io/docs/en/config-files/" class="bare">babeljs.io/docs/en/config-files/</a></span>.</p>
</div>
</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="polyfills">2.3. Combler les manques avec des polyfills</h3>
<div class="paragraph">
<p>
</p>
</div>
<div class="paragraph">
<p>Des outils comme Babel nous permettent d’écrire avec une syntaxe moderne qui
<strong>comblent les fonctionnalités manquantes</strong> – leur implémentation.</p>
</div>
<div class="paragraph">
<p>Un <em>polyfill</em> harmonise la présence d’une fonctionnalité au sein d’une variété de
navigateurs et d’environnements ECMAScript.
Cela se fera au prix de quelques kilo-octets de code à charger en plus
dans nos applications.
L’appel à un service de <em>polyfill</em> externe entraîne un léger ralentissement du
chargement de notre page.</p>
</div>
<div class="paragraph">
<p>Prenons le bloc de code suivant :</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight highlight-prismjs prismjs language-javascript"><code class="language-javascript" data-lang="javascript"><span class="token string">'use strict'</span><span class="token punctuation">;</span>
Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token string">'ok'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Comprenons que :</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Cette syntaxe est valide dans toutes les versions d’ECMAScript
(Babel ne change rien à ce code).</p>
</li>
<li>
<p>L’objet global <code>Promise</code> existe dans un navigateur moderne.</p>
</li>
<li>
<p>L’objet global <code>Promise</code> n’existe pas dans <em>Internet Explorer 11</em>, entre autres.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Ce code fonctionnerait donc sur un navigateur moderne mais pas dans <em>IE11</em>.
L’inclusion d’un <em>polyfill</em> de <code>Promise</code> résoudrait le problème.</p>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">💡</div>
</td>
<td class="content">
<div class="title"><span class="RemarquePreTitre">Bonne pratique</span> Quand inclure les polyfills ?</div>
<div class="paragraph">
<p><strong>Un <em>polyfill</em> se charge toujours en premier</strong>.
On inclut tous les <em>polyfills</em> en une seule fois avant notre propre code.</p>
</div>
<div class="paragraph">
<p>Nous garantissons ainsi cohérence et stabilité de comportement au sein de
notre application, peu importe l’ordre d’exécution de nos scripts.</p>
</div>
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Parlons maintenant des méthodes d’inclusion des <em>polyfills</em> pour mieux
comprendre comment procéder.</p>
</div>
<div class="paragraph">
<p>Le service <em>polyfill.io</em> est de loin la méthode la plus simple à utiliser.
Il suffit d’inclure un script dans toutes vos pages web et cet outil
détermine les <em>polyfills</em> à charger en fonction de la
compatibilité du navigateur :</p>
</div>
<div class="listingblock">
<div class="title">polyfill.io.html</div>
<div class="content">
<pre class="highlight highlight-prismjs prismjs language-html"><code class="language-html" data-lang="html"><span class="token doctype"><!DOCTYPE html></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>html</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>head</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span>Example polyfill.io<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>https://cdn.polyfill.io/v2/polyfill.min.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>head</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>body</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token string">'ok'</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">msg</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>msg<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script<