Javascript,
pour des applications plus modernes

SOMMAIRE

  • Introduction
  • Javascript dans vos projets
  • ES6 / ES7 / ES8, quelques éléments
  • Babel et Polyfills
  • Les Bundlers
  • Programmation réactive
  • Pourquoi un front en JS
  • JS au BE MR
  • Frameworks et Librairies de composant

Introduction

  • Javascript est une marque de chez Oracle !
  • Language normé
  • Le standard se nomme Ecmascript
  • ES5 est la dernière version des 'vieux navigateurs'
  • ES6 (ES2015) est la première version qui marque un changement profond
  • Nous en sommes à la version 8 (ES2017)

Attention

On ne traitera que du JS dans le navigateur

 

NodeJS est donc hors scope, même si on l'utilise de fait pour beaucoup de tâche de build

Vocabulaire

Callback: fonction passée à une autre fonction en vue d'être éxécutée plus tard lorsque le traitement asynchrone sera terminé

 

SPA (Single Page Application): application full javascript où le serveur HTTP ne sers qu'à distribuer une page HTML quasiment vide, à servir les assets, et à bootstraper l'application JS

 

NodeJS: Plateforme JS se reposant sur le moteur V8 de Google pour éxécuter du Javascript en dehors d'un contexte web (on parle souvent de Javascript côté serveur, mais npm sur votre machine exécute aussi NodeJS)

Vocabulaire

Transpileur: outils de transformation d'un language vers un autre language

 

Polyfills / shim: prothèse d'émulation, c'est à dire logiciel implémentant une fonctionnalité manquante dans des interfaces antérieures

 

Framework: cadre de développement, il fournit des librairies et des patterns

 

Librairies: brique applicative

Vocabulaire

Programmation réactive: paradigme de programmation visant à propager les modifications d'une source vers les éléments dépendants de cette source. (~wikipedia)

 

SourceMaps: principe de mapping des fichiers sources par rapport aux fichiers de production (bundelisé, transpilé, minifié). Il permet de faire du debug

Javascript dans vos projets

{# twig #}

{% if pagination is defined %}
... HTML ...
<script>
    $(document).ready(function () {
        //on change submit
        $('#paginationLimit').change(function () {
            window.location.href = this.value;
        });
    });
</script>
... HTML ...
{% endif %}

{# twig #}
{{ form_start(filter, {'method': 'POST', 'attr': {'class': 'smart-form' }}) }}
... HTML ...
{% if searchMode is defined and searchMode == true %}
    <script>
        $(document).ready(function () {
            var searchType = $('#ref_app_merger_search_type_searchType');
            var csrfToken = $('#ref_app_merger_search_type__token');
            var btn = $('button[type=submit]');

            function generateFilters() {
                // ... retrieve the corresponding form.
                var form = $(this).closest('form');
                // Simulate form data, but only include the selected sport value.
                var data = {};
                data[searchType.attr('name')] = searchType.val();
                data[csrfToken.attr('name')] = csrfToken.val();
                data['filter'] = true;
                // Submit data via AJAX to the form's action path.
                $.ajax({
                    url: form.attr('action'),
                    type: 'POST',
                    dataType: 'text',
                    data: data,
                    success: function (html) {
                        $('#filter').html(html);
                    },
                    error: function (xhr, status, error) {
                        //console.log(xhr);
                    }
                });
            }

            // When searchType gets selected ...
            searchType.change(function () {
                generateFilters();
            });

            //when keyup on inputs
            $('input:text').keyup(function () {
                var disable = true;
                //for each input
                $('input:text').each(function () {
                    //test if input has value and for IE9 if the input value is not equal to the placeholder value
                    if ($(this).val() && $(this).val() != $(this).attr('placeholder')) {
                        disable = false;
                    }
                });
                //change disabled state of button search
                $(btn).prop('disabled', disable);
            });
        });
    </script>
{% else %}
<script>
    $(document).ready(function () {
        //on change submit
        $('#ref_app_merger_mergerfilter_status').change(function () {
            $(this).closest('form').submit();
        });
    });
</script>
{% endif %}
... HTML ...
... Et encore du JS ...
... Loop Over ...

Bilan

  • Au mieux vous faites de l'ES5
  • Vous ne pouvez pas testez unitairement vos scripts
  • Comment vous assurez-vous de la compatibilité des navigateurs cibles ?
  • Comment réutilisez-vous efficacement ces scripts ?
  • Et encore, je ne montre pas la pluie de callback que l'on trouve

 

Bref, vous faites du JQuery ! pas du Javascript

ES 6 / 7 / 8

var name_1 = 'Benjamin'; // ne plus utiliser

let name_2 = 'François'; // disponible dans le scope de création: fonction/boucle/condition/globale, 
                         // ne peut être recréé

const name_3 = 'Didier'; // idem let, mais non réaffectable

Scope: fin du hoisting

const txt = `merci ${maVar}, aujourd'hui il est 
${(new Date()).toString()}, et 1 + 1 = ${1+1}`

// mieux que:
txt = 'merci ' + maVar + ', aujourd\'hui il est '\
    (new Date()).toString() + ', et 1 + 1 = ' + (1+1)

Template String

ES 6 / 7 / 8

'toto'.startsWith('to') // String.endsWith('to') also exists

String: nouvelles méthodes

[1, 2, 3, 4].includes(2)

Array: nouvelles méthodes

const getAge = function (age = 15) {
  console.log(age)
}

getAge() // display 15

Function: default value

const getAge = (age = 15) => console.log(age) // fait un return de console.log(age) donc undefined

Function: Arrow function

ES 6 / 7 / 8

let default = [1, 2, 1, 1, 3, 4, 5, 4, 3]
let set = new Set(default);

console.log(default.length); // 9
console.log(set.size); // 5

set.add(1)

console.log(set.size); // 5
console.log(set.has(5)); 

Set et Hash

Et plein d'autres choses: Promise, Spread operator, Async/Await ...

ES 6 / 7 / 8

pour découvrir toutes les nouvelles API disponibles ou expérimentales:

  • Web worker
  • BroadcastApiChannel
  • MutationObserver,
  • FormData,
  • Notification,
  • ...

 

Babel et Polyfills

ES6+ c'est bien mais mon client il utilise du IE10, ou pire du Safari

 

Et bien ne vous privez pas, codez dans la dernière version d'Ecmascript et transpilez votre code dans une version compatible.

 

Babel = transpileur de code Javascript vers un code compatible Ecmascript 5

 

Attention, si le navigateur n'est pas compatible ES5 (IE9 n'a pas toutes les features), ajoutez des polyfills via core-js (inclus dans Babel)

Babel et Polyfills

Exemple: pour une cible IE9 avec utilisation de es6.array.for-each

// fichier vendor.js inclus dans toutes vos pages 

// Don't use dynamic import to get Promise available immediatly
require ('core-js/modules/es6.promise')

// dynamic import from babel
import ('babel-polyfill')

// dynamic import for all required shim and polyfills from core-js ([].forEach not available in IE9)
import ('core-js/modules/es6.array.for-each.js')

Les bundlers

Aujourd'hui on ne livre plus un gros fichier JS pour toute une application

 

Aujourd'hui on split son application JS en bundle par domaine métier

 

Webpack va nous aider à faire ces bundles (avant on utilisait Browserify ou d'autres outils, depuis peu ParcelJS tente de se faire une place grace à une simplicité d'utilisation)

 

Mais Webpack c'est pas si simple (avant la v4)

Les bundlers

@Symfony/webpack-encore: pour simplifier notre vie (Laravel a le sien aussi)

 

  1. Définissez vos sources (les répertoires assets/js et assets/css)
  2. Définissez vos bundles communs (app.js / app.sass)
  3. Définissez vos bundles fonctionnels (compta.js, paie.js, ...)
  4. Choisissez d'activer ou non les sourceMaps et le versioning (pour mieux debugger et pour éviter de mettre un timestamp dans la query string de l'asset chargé)
  5. Configurez Babel au besoin
  6. Enregistrez vos loader (Vue, React, Sass)
  7. C'est parti

C'est vite compliqué, et ça change souvent

Les bundlers

var Encore = require('@symfony/webpack-encore')

Encore
    // the project directory where compiled assets will be stored
    .setOutputPath('public/dist/')
    // the public path used by the web server to access the previous directory
    .setPublicPath('/dist')
    .cleanupOutputBeforeBuild()
    .enableSourceMaps(!Encore.isProduction())
    // uncomment to create hashed filenames (e.g. app.abc123.css)
    .enableVersioning(Encore.isProduction())
    .configureBabel(function(babelConfig) {
        // add additional presets
        babelConfig.presets.push('es2017')
        babelConfig.presets.push('react')

        // no plugins are added by default, but you can add some
        // babelConfig.plugins.push('styled-jsx/babel');
    })

    // this one was used for this npm package: offline-plugin to manage cache (angular has already its own worker since ng-5.2 & cli-1.6)
    .addEntry('service-worker', './assets/js/lib/service-worker.js')

    .addEntry('js/vuejs', './assets/js/vuejs/app.js')
    .addEntry('js/login', './assets/js/login/app.js')

    // for specific page css (not managed by vue file per example
    .addStyleEntry('css/quasar-bootstrap', './assets/css/quasar-bootstrap.scss')

    // this creates a 'vendor.js' file with common js code
    .createSharedEntry('vendor', [
        './assets/js/app.js',
        './assets/css/app.scss',
    ])

    .enableSassLoader(function(sassOptions) {}, {
        resolveUrlLoader: false,
    })
    .enableVueLoader()
    .enableReactPreset()

module.exports = config

Exemple

Les bundlers

Demo

Programmation Réactive

L'arrivée des frameworks comme AngularJS, EmberJS, React, Angular, Vue a introduit une notion intéressante en développement web: la programmation réactive (~les signaux avec Qt)

 

Dans l'exemple suivant l'input écoute les changements sur message et les applique à son attribut value

<template>
    <div>
        <p>{{ message }}</p>
        <input v-model="message">
    </div>
</template>

<script>
export default {
    name: 'Text',
    data: () => {
        return {message: 'init'}
    }
}
</script>

Pourquoi un front en JS ?

  • Pour définir des composants indépendants et réutilisables
  • Pour éviter de faire transiter du HTML sur le réseau: Data only
  • Pour permettre des mécanismes avancés tel que:
    • Offline + Resynchronisation
    • Anticipation de réponse du serveur (Optimistic UI)
    • ...
  • Pour pouvoir debugger simplement
  • Pour gérer les états de ses composants et pouvoir reproduire les issues
  • Pour pouvoir tester unitairement ses composants

Comment feriez vous cette application en full PHP ?

ouvrir le debugger Chrome

Linkedin est en full EmberJS 2 depuis 2016

Et la plupart des grands du web utilisent des SPA en interne ou sur le web

Etat du JS au BE MR

  • Depuis décembre 2017: ES6 obligatoire sur les projets PHP et utilisation de Webpack-encore + polyfills pour builder des app compatibles IE9
  • Depuis janvier 2017: introduction de VueJS (widget) dans un projet PHP
  • Depuis mars 2018: introduction de VueJS (widget) dans un projet JSP

 

Pas de migration prévu pour le code existant, mais cohabitation de l'historique avec ES6+ et VueJS 

Les frameworks aujourd'hui

Le choix d'un framework est généralement issue d'une réflexion biaisée par une idéologie. Celle des décideurs !

 

React (via CreateReactApp) vs VueJS vs Angular5

 

Ils sont presque tous pareil (même si c'est un brin provocateur)

Les librairies de composant

  1. Définissez vos besoins fonctionnels
  2. Identifiez les librairies de composant
  3. Utilisez le framework sur lequel se repose la librairie

Les librairies de composant

With community edition under condition

  • DevExpress: it has some more functionnal components like calendar: angular2+, there is also a React version but it only has the Grid component for instance. Oh, i forgot: it's free for open-source project (or non commercial ?). It seems that a VueJS version is comming: https://github.com/DevExpress/devextreme-vue it's based on v18-pre of devxtreme
  • SyncFusion: like DevExpress a quite complete list of components: Angular/React/ES6/ES5

Without community edition

  • KendoUI: Angular/React/Vue                                                                  
  • Sencha: React

Les librairies de composant

Generics (Html/Css/Js)

  • Clarity                                                                                                        
  • PrimeUI

Angular2+ (complete list angular.io)

Les librairies de composant

React

  • PrimeReact                                                                                               
  • Ant with rich components like calendar
  • Fabric
  • React-Bootstrap

VueJS (complete list: awesome-vue)

Les librairies de composant

Les librairies de composant

Avantages:

  1. Ergonomie et design consistant
  2. Composants maintenus
  3. Large palette de fonctionnalité
  4. Pas de maintenance à réaliser sur ces composants
  5. Réduit le temps de développement des applications

Inconvénients:

  1. Coût d'achat potentiel                                                     
  2. Nouvelle API à appréhender
  3. Nouvelle dépendance à un tiers

Merci