Modules ES6 & HTTP/2
Va-t-on arrêter de packager notre code JavaScript ?
Marc - 32 ans
Lead développeur - Frontend - Acme corporation
ES6
Vincent Ogloblinsky
Architecte logiciel
" Technologies frontend "
@vogloblinsky
Les modules
en JavaScript
Pas nouveau
Apports :
- séparation
- gestion de dépendances
- gestion du namespace
- implémentation commune
Existe sous différentes approches depuis longtemps :
- object literal pattern
- IIFE / revealing module pattern
- CommonJS
- AMD
- UMD
- ES6 modules (ESM ou Native modules)
Object literal pattern
Tire partie de la structure native objet
Avantage : simple à comprendre et à implémenter
Inconvénient : repose sur une variable globale
//person.js
var person = {
bipedal: true,
species: 'homo sapiens'
}
IIFE / Revealing module pattern
Tire partie du pattern façade
Avantage :
tire partie de la closure, contrôle plus fin. découplage plus facile
Inconvénient :
syntaxe pas élégante, repose encore un peu sur le scope global
//person.js
var person = (function(name) {
var innerName = (!name) ? 'Anonymous' : name;
return {
name: function() {
return innerName;
},
bipedal: true,
species: 'homo sapiens'
}
})(outerName);
Vient du monde Node.JS
Requiert un loader compatible
Avantage :
- syntaxe claire et concise, a pas mal influencé celle en ES6
- modules limitent la portée, plus de déclaration globale
Inconvénient :
- pas très adapté aux environnements asynchrones
- lazy loading également
module.exports = function() {
return {
name: innerName,
bipedal: true,
species: 'homo sapiens'
}
};
Asynchronous
Module
Definition
A émergé pendant les discussions sur CommonJS
Requiert un loader (Require.js)
Avantage :
- Leader pendant pas mal de temps
Inconvénient :
- syntaxe très verbeuse
- caractère synchrone difficile à analyser statiquement
define('person', function(name) {
var innerName = (!name) ? 'Anonymous' : name;
return {
name: function() { return innerName; },
bipedal: true,
species: 'homo sapiens'
}
});
UMD
Universal Module Definition
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('jquery'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
// methods
function myFunc(){};
// exposed public method
return myFunc;
}));
Combinaison d'AMD et CommonJS
Avantage :
- pas vraiment
Inconvénient :
- inconvénients des sous-formats
ES6
MODULES
Le TC39 a appris de ses erreurs
Fournit quelque chose de simple et adapté
Manque de support dans les browser à la sortie d'ES6
→ recours à Babel ou Traceur
var person = {
bipedal: true,
species: 'homo sapiens'
};
export default { person };
import person from './person.js';
Recap
- object literal pattern
- IIFE / revealing module pattern
- CommonJS
- AMD
- UMD
- ES6 modules
Les modules ES6
Chrome 61
Safari 10.1
Edge 16
Firefox 60
ES6 Modules in the browser
On sait clairement qui supporte quoi
nomodule pour les "anciens"
polyfill pour iOS 10.3 et 10.1
<!-- JS pour navigateurs "modernes" -->
<script type="module" src="main.js"></script>
<!-- JS transpilé en ES5 pour navigateurs "anciens" -->
<script nomodule src="main-legacy.js"></script>
Avantages
syntaxe compacte
structure statique (facilite le tree-shaking, optimisation, analyse statique, ...)
support automatique des dépendances cycliques
fin de la fragmentation des "standards" de modules
fin des APIs dans navigator ?
im dyna
Inconvénients
import dynamique
draft en cours - stage 3
im dyna
Inconvénients
import dynamique
Safari 10.1
Chrome 63
Safari Technology Preview 24
im dyna
Inconvénients
import dynamique
Safari 10.1
Inconvénients
non supporté dans les web workers (actuellement)
// worker.js
importScripts('subworker.js');
//subworker.js
export const sqrt = Math.sqrt;
// Uncaught SyntaxError: Unexpected token export
//lib.js
export const sqrt = Math.sqrt;
//subworker.js
import sqrt from './lib.js';
// Uncaught SyntaxError: Unexpected token export
conflit avec le format CommonJS
Et dans Node.js
nouvelle extension de fichier : *.mjs
support début 2018 ( v8.5.0 )
migration douce vers ESM car commune avec le browser
Différences ESM / CommonJS
require | import |
---|---|
évaluation dynamique | évaluation statique |
lève des erreurs au run | lève des erreurs au parsing |
<script> vs "module"
<script> pour le code JS classique avec namespace global
module : pour du code modulaire avec imports/exports explicites
les 2 mixés ne marchent pas bien entendu
<script> vs "module"
scripts | modules | |
---|---|---|
Element HTML | <script> | <script type="module"> |
Mode par défaut | non-strict | strict |
Variable de haut niveau | globale | locale au module |
Valeur de this | window | undefined |
Exécution | synchrone | asynchrone |
Imports déclaratifs | non | oui |
Extension fichier | .js | .js |
<script> vs "module"
<script> bloquants par défaut
modules defer par défaut
<script> vs "module"
Attributs communs
type : "module" ou "text/javascript"
src
defer (par défault pour les modules)
async
crossorigin
Nuances de syntaxe
chemin dans les imports
// Supporté
import {foo} from 'https://www.website.com/utils/bar.js';
import {foo} from '/utils/bar.js';
import {foo} from './bar.js';
import {foo} from '../bar.js';
// Non supporté
import {foo} from 'bar.js';
import {foo} from 'utils/bar.js';
Nuances de syntaxe
module inline aussi defer
<!-- This script will execute after… -->
<script type="module">
addTextToBody("Inline module executed");
</script>
<!-- …this script… -->
<script src="1.js"></script>
<!-- …and this script… -->
<script defer>
addTextToBody("Inline script executed");
</script>
<!-- …but before this script. -->
<script defer src="2.js"></script>
Nuances de syntaxe
async aussi pour les modules inline
<!-- This executes as soon as its imports have fetched -->
<script async type="module">
import {addTextToBody} from './utils.js';
addTextToBody('Inline module executed.');
</script>
<!-- This executes as soon as it & its imports have fetched -->
<script async type="module" src="1.js"></script>
Nuances de syntaxe
exécution unique
<!-- 1.js only executes once -->
<script type="module" src="1.js"></script>
<script type="module" src="1.js"></script>
<script type="module">
import "./1.js";
</script>
<!-- Whereas normal scripts execute multiple times -->
<script src="2.js"></script>
<script src="2.js"></script>
Nuances de syntaxe
toujours récupéré en mode CORS
<!-- This will not execute, as it fails a CORS check -->
<script type="module" src="https://….now.sh/no-cors"></script>
<!-- This will not execute, as one of its imports fails a CORS check -->
<script type="module">
import 'https://….now.sh/no-cors';
addTextToBody("This will not execute.");
</script>
<!-- This will execute as it passes CORS checks -->
<script type="module" src="https://….now.sh/cors"></script>
Nuances de syntaxe
mode strict par défaut
// Plus de 'use strict'
mistypeVariable = 17; // Uncaught ReferenceError: mistypeVariable is not defined
delete Object.prototype; // Uncaught TypeError: Cannot delete property 'prototype'
function sum(a, a, c) { // Uncaught SyntaxError: Duplicate parameter name not allowed
return a + b + c;
}
Nuances de syntaxe
imports hoisted
alert(`main-bundled.js - counter: ${counter}`);
import {counter} from './increment.js';
comme les fonction en JS
exécuté dans l'ordre du graphe de dépendances
Nuances de syntaxe
import / export de haut niveau
if (Math.random()>0.5) {
import './module1.js'; // SyntaxError: Unexpected token 'import'
}
const import2 = (import './main2.js'); // SyntaxError: Unexpected token 'import'
try {
import './module3.js'; // SyntaxError: Unexpected token 'import'
} catch(err) {
console.error(err);
}
const moduleNumber = 4;
import module4 from `module${moduleNumber}`; // SyntaxError: Unexpected token
Nuances de syntaxe
imports en lecture seule d'un export
//------ lib.js ------
export let counter = 3;
export function incCounter() {
counter++;
}
//------ main.js ------
import { counter, incCounter } from './lib';
// The imported value `counter` is live
console.log(counter); // 3
incCounter();
console.log(counter); // 4
counter += 1; // Uncaught TypeError: Assignment to constant variable.
im dyna
Nuances de syntaxe
this === undefined
// module.js
console.log(this); // undefined
// script.js
console.log(this); // window
Nuances de syntaxe
Dépendances circulaires autorisées
// vehicle.js
import { Car } from './car.js';
let id = 0;
export class Vehicle {
constructor() {
this.id = ++id;
}
static build() {
return new Car();
}
}
// car.js
import { Vehicle } from './vehicle.js';
export class Car extends Vehicle {
constructor() {
super();
this.model = 'Generic';
}
}
// main.js
import { Car } from './car.js';
let myCar = new Car()
console.log(myCar);
// Car {id: 1, model: "Generic"}
Utilitaire
Détecter si le script courant est exécuté comme un module
const isAModule = (this === undefined);
const isNotAModule = (this === window);
// WARNING , this peut aussi bien être bindé
bref pas de moyen clair
Utilitaire
Détecter si le type="module" est supporté
function checkJsModulesSupport() {
// create an empty ES module
const scriptAsBlob = new Blob([''], {
type: 'application/javascript'
});
const srcObjectURL = URL.createObjectURL(scriptAsBlob);
// insert the ES module and listen events on it
const script = document.createElement('script');
script.type = 'module';
document.head.appendChild(script);
// return the loading script Promise
return new Promise((resolve, reject) => {
// HELPERS
let isFulfilled = false;
function triggerResolve() {
if (isFulfilled) return;
isFulfilled = true;
resolve();
onFulfill();
}
function triggerReject() {
if (isFulfilled) return;
isFulfilled = true;
reject();
onFulfill();
}
function onFulfill() {
// cleaning
URL.revokeObjectURL(srcObjectURL);
script.parentNode.removeChild(script)
}
// EVENTS
script.onload = triggerResolve;
script.onerror = triggerReject;
setTimeout(triggerReject, 100); // reject on timeout
// start loading the script
script.src = srcObjectURL;
});
};
checkJsModulesSupport().then(
() => {
console.log('ES modules ARE supported');
},
() => {
console.log('ES modules are NOT supported');
}
);
Intégration de code externe
Code externe
support.js
main.js
module1
module2
support-es6.js
Code externe - CommonJS
// support.js
var leftpad = require('left-pad');
module.exports = leftpad;
rollup
+ plugins node et commonjs
// support-cjs.js
var leftpad = function (a, b, c) { ... };
module.exports = leftpad;
cjs-to-es6
// support-cjs-to-es6.js
var leftpad = function (a, b, c) { ... };
export default leftpad;
// main.js
import leftpad from './support-cjs.js';
console.log(leftpad('foo', 5));
// foo
Code externe ES6
// AVANT
import * as lodash from 'lodash';
// MAINTENANT
import add from '../node_modules/lodash-es/add.js';
Support aisé du tree-shaking
rollup-node-resolve par exemple injectera le code dans le package full ES6
Webpack & co ont-ils encore un intérêt ?
Etude
Webpagetest.org
Moto G4 - Chrome beta - Emerging market 3G
Livrer 3 versions différentes sur 3 urls différentes
Code ES5
Code ES6 non bundlé
Code ES6 bundlé
Version | Poids (non-minifié) |
Parse/eval time (ms) |
Load time (ms) |
---|---|---|---|
ES2015 - unbunded | 23 | 60 | 4.42 |
ES2015 - bundled | 23 | 65 | 3.14 |
ES5 | 240 | 190 | 3.06 |
Webpagetest.org
Moto G4 - Chrome beta - Emerging market 3G
HTTP/2 à la rescousse
Features
multiplexing
push
- compression des entêtes
- etc...
Et pour nous les "fronteux"
Légende urbaine :
" livrer des petits fichiers plutôt qu'un gros "
Problèmes de performances sur la compression des entêtes
Ne pas dépasser un certain nombre de bundles pour tirer partie du multiplexing
Un peu de push ?
Push
Le serveur a besoin de savoir quoi pushé ?
N'est pas la recette magique : pas très cache friendly
Service worker
Script tournant en tâche de fond
Middleware entre le navigateur et l'extérieur
Caractéristiques : synchronisation, push notification et aussi du CACHE
prpl pattern
Service worker
Recap
La combinaison
Push + service worker est une solution mais :
- requiert du setup serveur et client
- n'est pas encore cross-browsers
Build : au final on fait quoi ?
Avec Webpack ou Rollup.js
<script nomodule src="main.legacy.js"></script>
Concaténer vos modules ES6 dans un seul fichier transpilé en ES5
avec Babel + babel-preset-env
<script type="module" src="main.js"></script>
Avec Webpack ou Rollup.js
Réaliser la même concaténation sans transpilage
Vision à moyen terme
Communauté web et open-source très dynamique
Support full ES6 des browsers + outils de debugs
En mode développement, cela peut aider
Plus de polyfills, moins de transpilage
→code plus léger
“ Ecrire du code ES2015 est un gain pour les développeurs, et livrer en prod du code ES2015 est aussi un gain pour les utilisateurs ”
ES6
Ressources
ES6 modules
Native ECMAScript modules - the first overview
https://hospodarets.com/native-ecmascript-modules-the-first-overview
Native ECMAScript modules: the new features and differences from Webpack modules
https://hospodarets.com/native-ecmascript-modules-new-features
ES6 modules support lands in browsers: is it time to rethink bundling ?
Ressources
ES6 modules
Ressources
ES6 modules
Browser module loading - can we stop bundling yet?
https://sgom.es/posts/2017-06-30-ecmascript-module-loading-can-we-unbundle-yet/
Deploying ES2015+ Code in Production Today
https://philipwalton.com/articles/deploying-es2015-code-in-production-today/
Using modules in browser
https://publishing-project.rivendellweb.net/using-modules-in-browser/
Ressources
ES6 modules
The state of JavaScript modules
https://medium.com/webpack/the-state-of-javascript-modules-4636d1774358
Native ES Modules — Ready for Prime Time ?
https://hackernoon.com/native-es-modules-ready-for-prime-time-87c64d294d3c
ECMAScript modules in browsers
HTTP/2 push is tougher than I thought
https://jakearchibald.com/2017/h2-push-tougher-than-i-thought/
Performance Best Practices in the HTTP/2 Era
https://deliciousbrains.com/performance-best-practices-http2/
HTTP/2 Server Push and Service Workers : The Perfect Partnership
https://24ways.org/2016/http2-server-push-and-service-workers/
Ressources
HTTP/2
Merci
pour votre écoute, une question ?
bit.ly/es6-modules-in-browser
Modules ES6 & HTTP/2 - Va-t-on arrêter de packager notre code JavaScript ?
By Vincent Ogloblinsky
Modules ES6 & HTTP/2 - Va-t-on arrêter de packager notre code JavaScript ?
- 5,957