Déployer du code ES2015 avec NuxtJS
Le 10/20/2020

# ES2015, Polyfills, babel … ça veut dire encore de nouvelles choses à apprendre ?!

Ne vous en faites pas on va voir ça ensemble 😉

# ES2015

Déjà ES ou EcmaScript pour les intimes :

C’est un ensemble de standards fourni par Ecma International qui vise les langages de type script tels que JavaScript ou encore ActionScript.

ES2015 et ES6, quel différence ?

On parle de la même chose, c’est la 6ème édition d’EcmaScript publié en juin 2015. C’est pourquoi elle est parfois appelée ES6, et parfois ES2015.

Compatibilité :

Comme vous le voyez ES6 est pris en charge sur la majorité des navigateurs récents (en même temps ça fait 5 ans 🙄).

compatibilité des navigateurs avec ES6

# Polyfills

les polyfills d’après wikipedia :

En programmation web, un polyfill aussi nommé shim, ou encore prothèse d’émulation désigne un palliatif logiciel implémentant une rétrocompatibilité d’une fonctionnalité ajoutée à une interface de programmation dans des versions antérieures de cette interface. Il s’agit généralement d’ensembles de fonctions, le plus souvent écrites en JavaScript ou en Flash, permettant de simuler sur un navigateur web ancien des fonctionnalités qui n’y sont pas nativement disponibles.

… ouais ok je vais donner un exemple :
imaginons qu’une nouvelle fonctionnalité trop cool vienne de sortir mais que certains de vos utilisateurs sont sur des navigateurs ne supportant pas ladite fonctionnalité. c’est casse couille embêtant.

Mais comment faire ?

On va faire une sorte de Monkey-Patching, concrétement ça pourra ressembler à ça:

window.maSuperFonctionnalite = function (arg1, arg2, ect) {
  // faire un truc super cool
}

Et ce bout de code sera chargé par le navigateur, ainsi si plus loin dans le programme vous avez besoin de window.maSuperFonctionnalite vous y avez accès.

inconvénient

  • Le code du polyfill pourrait ne pas être optimal
  • Ce n’est pas du code natif donc les performances seront légèrement moindre par rapport aux performances sur un navigateur qui supporte la fonctionnalité
  • On écrase la version fournie par le navigateur
  • Il faut télécharger le polyfill et l’appliquer

En quelque sorte on se met au niveau du plus faible (oui Internet Explorer c’est de toi que je parle) et ça a un coût.

# BabelJS

BabelJS est un transcompilateur, ce mot barbare veux dire que c’est un outil qui prend en entrée du code non compilé et en dessert sortie du code toujours non compilé mais écrit différemment, et les deux codes doivent produire le même résultat.

Concrètement si je prends une fonction fléchée

const maFonction = (nom) => {
  console.log(`Bonjour ${nom}`)
}

Babel va me sortir

'use strict'

const maFonction = function maFonction(nom) {
  console.log(`Bonjour ${nom}`)
}

C’est pareil sauf que les vieux navigateurs comprennent.
Maintenant un autre exemple avec du async/await et on rigole déjà moins :

const maFonctionAsync = async (arg) => {
  await Promise.resolve(arg)
}

Oh c’est tout mignon ça doit prendre 5 lignes max non … non ? NON !

'use strict'

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try { var info = gen[key](arg); var value = info.value }
  catch (error) { reject(error); return } if (info.done)
    resolve(value)
  else Promise.resolve(value).then(_next, _throw)
}

function _asyncToGenerator(fn) { return function () { const self = this; const args = arguments; return new Promise((resolve, reject) => { const gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value) } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err) } _next(undefined) }) } }

const maFonctionAsync = /* #__PURE__ */(function () {
  const _ref = _asyncToGenerator(/* #__PURE__ */regeneratorRuntime.mark(function _callee(arg) {
    return regeneratorRuntime.wrap((_context) => {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2
            return Promise.resolve(arg)

          case 2:
          case 'end':
            return _context.stop()
        }
      }
    }, _callee)
  }))

  return function maFonctionAsync(_x) {
    return _ref.apply(this, arguments)
  }
}())

inconvénient

  • Le code généré n’est pas optimal
  • Le code est plus lourd, il mettra donc plus de temps à être téléchargé

Et vous savez quoi ?
Les polyfills eux mêmes peuvent avoir besoin d’être transpilé. Et dans les codes transpilés il peut y avoir besoin de pollyfills…

Math

# Mode modern chez VueJS et NuxtJS

Petit rappel: on veut à la fois que les utilisateurs de navigateurs anciens aient les polyfills et le build transpillé tandis que les navigateurs récent ne doivent pas les avoir.
Nous allons donc avoir 2 builds: un moderne et un legacy

# Le flag --modern à la rescousse

# chez VueJS

VueJS offre un moyen extrêmement simple de faire tout ça grâce au flag --modern, webpack (on en parlera un jour promis) va donc générer deux builds: un build “modern” et un build “legacy”.

Si l’on regarde ce que webpack crache en sorti on va avoir:

<!doctype html>
<html data-n-head-ssr lang="fr" data-n-head="%7B%22lang%22:%7B%22ssr%22:%22fr%22%7D%7D">
  <head>
    [...]
    <link rel="modulepreload" href="/_nuxt/c0bcacc.modern.js" as="script">
    <link rel="modulepreload" href="/_nuxt/ba0971c.modern.js" as="script">
    <link rel="modulepreload" href="/_nuxt/edcde26.modern.js" as="script">
    <link rel="modulepreload" href="/_nuxt/0907d2b.modern.js" as="script">
    <link rel="modulepreload" href="/_nuxt/26edac4.modern.js" as="script">
    [...]
  <head>

  [...]
  <script nomodule src="/_nuxt/177b217.js" defer></script>
  <script type="module" src="/_nuxt/c0bcacc.modern.js" defer></script>
  <script nomodule src="/_nuxt/2550638.js" defer></script>
  <script type="module" src="/_nuxt/26edac4.modern.js" defer></script>
  <script nomodule src="/_nuxt/d764faa.js" defer></script>
  <script type="module" src="/_nuxt/ba0971c.modern.js" defer></script>
  <script nomodule src="/_nuxt/5beb621.js" defer></script>
  <script type="module" src="/_nuxt/edcde26.modern.js" defer></script>
  <script nomodule src="/_nuxt/abdbdac.js" defer></script>
  <script type="module" src="/_nuxt/0907d2b.modern.js" defer></script>

Les navigateurs comprenant l’attribut type="module" vont charger le script correspondant au build modern et ne vont pas charger les scripts avec l’attribut nomodule tandis que les vieux navigateurs ne comprendront pas l’attribut type="module" et chargeront donc le build legacy (compatible IE9 par défaut)

Info
Un bug (présent sous safari 10.1 et safari 10.3iOS) fait que Safari charge quand même les scripts nomodule, heureusement VueJS applique un fix.

# Le cas de Nuxt

Nuxt offre également un moyen de faire ça :

  • --modern
    Lorsque ce flag est mis en mode SSR (Server Side Rendering) le serveur de rendu servira le build en fonction de l’user-agent du navigateur.
    Sinon en mode SPA (Single page Application) ou SSG (Server Side Generate) le fonctionnement est le même que celui avec Vuejs.

  • --modern=server
    Le serveur NodeJS servira le build en fonction de l’user-agent.

Attention
Uniquement disponible en mode SSR.
  • --modern=client
    Même fonctionnement que celui de Vuejs.

# Quel Gain ?

  • Gain d’environ 20% sur la taille du build chez moi
  • ~20% de bande passante utilisé en moins (j’auto héberge le site sur mon ADSL 😥 plus maintenant 😅),
  • votre site charge donc ~20% plus vite
  • meilleurs performances puisqu’on ne se trimbale pas les polyfills ni le code transpillé

Bien sur le gain pourra varier d’environ plus ou moins 5% je dirais que c’est toujours ça de pris.
de même le gain étant relatif, si vous avez 300mo d’assets il est clair que vous n’aurez pas 20% de gain sur la taille finale du build, seulement 20% sur le JavaScript.

Alors, pas cher payé pour une dizaine de caractères hein ?

clin d'oeil

Pour aller plus loin :