Marc - 32 ans
Lead développeur - Frontend - Acme corporation
Vincent Ogloblinsky
Architecte logiciel
" Technologies frontend "
@vogloblinsky
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'
}
});
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
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';
- object literal pattern
- IIFE / revealing module pattern
- CommonJS
- AMD
- UMD
- ES6 modules
Chrome 61
Safari 10.1
Edge 16
Firefox 60
On sait clairement qui supporte quoi
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>
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
import dynamique
draft en cours - stage 3
im dyna
import dynamique
Safari 10.1
Chrome 63
Safari Technology Preview 24
im dyna
import dynamique
Safari 10.1
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
nouvelle extension de fichier : *.mjs
support début 2018 ( v8.5.0 )
migration douce vers ESM car commune avec le browser
require | import |
---|---|
évaluation dynamique | évaluation statique |
lève des erreurs au run | lève des erreurs au parsing |
<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
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> bloquants par défaut
modules defer par défaut
Attributs communs
type : "module" ou "text/javascript"
src
defer (par défault pour les modules)
async
crossorigin
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';
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>
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>
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>
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>
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;
}
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
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
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
this === undefined
// module.js
console.log(this); // undefined
// script.js
console.log(this); // window
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"}
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
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');
}
);
support.js
main.js
module1
module2
support-es6.js
// 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
// 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
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 |
Moto G4 - Chrome beta - Emerging market 3G
multiplexing
push
- compression des entêtes
- etc...
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
Le serveur a besoin de savoir quoi pushé ?
N'est pas la recette magique : pas très cache friendly
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
La combinaison
Push + service worker est une solution mais :
- requiert du setup serveur et client
- n'est pas encore cross-browsers
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
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 ”
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 ?
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/
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/
bit.ly/es6-modules-in-browser