Vue.js le framework killer

publié le

Vue.js est une librairie qui se définit comme suis : Reactive Components for Modern Web Interfaces. Si cette phrase vous fait penser à React.js, ce n’est pas totalement anormal, vu que ces 2 librairies fournissent le V de MVC. Et quand on est développeur front on passe le plus clair de notre temps à écrire dans ce V justement.

Angular2 (le gros) et React.js (le populaire) sont tous les 2 couverts par des tonnes d’articles, conférences, bloggers, et autre. Les ressources française et anglaise ne manque pas, mais Vue.js est très peu connue en France, pourtant la petite lib monte en ce moment et pourrais être le Graal que l’on cherche tous.

popularité de Vue.js

La promesse de Vue.js est à peu près la même que celle de React.js ( le JSX et le virtual DOM en moins), accessoirement elle se paie le luxe d’être vraiment compacte (~24kb min+gzip, no dependency).

Si vous avez lu mon article sur angular-cli, vous savez déjà que j’adore les scaffolders simples, efficaces, et qui ne ramènent pas la terre entière, Vue.js fournit également sa cli officielle, il ne m’en faut pas plus pour démarrer un petit projet de présentation.

Le prétexte

Dimanche dernier Pokémon GO est sorti officiellement en France, et avec l’engouement suscité par le jeu, je n’ai pas manqué de faire quelques recherches. J’ai découvert (sans beaucoup d’effort) que les développeurs du monde entier s’amusait avec l’API officielle du jeu, et je me suis dit que j’allais y aller de mon Yet Another Pokemon Go Radar, ce qui me donnait l’occasion de réaliser une appli moche mais avec un fonctionnement réellement intéressant.

Tutorial

Pour amorcer le projet, on utilise le scaffolder vue init <template-name> <project-name>, la cli Vue.js propose les templates webpack, browserify (tous les 2 en full tooling ou en light pour faire des prototypes) et simple, il s’agit bien de templates de projets, pas de templating des vues. On va partir avec webpack n’étant pas un fan de browserify.

vue init webpack pokeradar

Petite déception, l’ajout de babel.js dans les dépendances projet (que j’ai tout de suite supprimé). Aujourd’hui je ne m’occupe plus de ce qui n’est pas compatible avec ES2015, et je peux me passer de ES2016 jusqu’à ce que la compatibilité soit bonne (Mars 2017), donc je n’ai pas besoin de babel.js, il s’agit cependant d’une dépendance du vue-loader et donc elle pollue mes devDependencies.

npm i && npm run dev

Et voilà notre petit projet Vue.js prêt à l’emploi avec le livereload, le linter (les tests) et c’est tout. Rien de superflu, vous voulez du less ou du sass, rajoutez le vous-même, ce précepte est valable pour l’intégralité de ce que vous pouvez avoir besoin. Une petite surprise vous attend dans la console : un message indiquant l’existence d’un devtool, on va y revenir. On déplorera l’absence de commande pour créer un nouveau composant amorçant directement les fichiers de tests unitaires et e2e.

npm run build

Version de production de notre application, elle pèse 90kb (hors image), ce qui est tout à fait honorable vu qu’on se traîne webpack et Vue.js, pas la peine de le comparer aux concurrent, le service rendu n’étant pas le même c’est forcément plus léger que ceux qui en font plus, non les questions qu’on va se poser sont :

  • Est-ce que le service rendu est suffisant pour mes besoins ?
  • Est-ce que ce poids sera toujours aussi intéressant une fois que j’aurai ajouté les outils indispensables (router, communication, …)

Quelques composants plus tard

Notre application a eu besoin :

  • de la liste filtrable des Pokémons, pour chaque Pokémon on doit pouvoir activer ou désactiver la notification, stockée dans le localstorage
  • d’un service long-pulling de l’api de pop de Pokémon, récupérant et exposant la liste des Pokémons dans les 500m qui compare avec le filtre dans le localstorage et qui envoie le cas échéant une notification desktop
  • Un composant liste des Pokémons présents dans le radar, avec un code couleur pour ceux qui sont actuellement recherchés

Soit donc 2 composants Vue.js dont vous trouverez le code après la conclusion.

Le développement ne pose pas de problème si vous avez déjà fait du angularJS, cela va même très vite. On profite rapidement des avantages de la librairie en tant que templating, la seule chose qui peut poser problème aux plus jeunes est le paradigme composant.

Vue DevTool

L’extension chrome pour analyser et debugger les composants Vue.js, petit outil sympathique même s’il n’apporte pas énormément de fonctionnalité et qu’on en a vite fait le tour, ce peu est bien fait et utile. On peut faire du développement Vue.js sans lui et ce n’est pas un indispensable => pour le moment, car le projet est actif et continue de progresser.

Pros & Cons

Ce que j’ai aimé dans Vue.js :

  • les shorthands @ et : must-have pour la fluidité du template
  • les eventModifiers qui m’ont rendu service avec ma checkbox
  • la flexibilité et la liberté, on s’approche d’un micro-framework
  • la CLI, même si elle doit progresser, elle rend service dans l’amorce projet en configurant tout ce qui est chiant à configurer au démarrage projet
  • les plugins qui transforme Vue.js en framework mais qui sont facilement échangeables par d’autres composants (un autre routeur, un autre composant de communication, …)
  • La compatibilité navigateur, sans aucun ajout de shym/polyfill, si cela a de l’importance pour vous. Pour ma part je recommande ES6 en prod, donc une compatibilté moindre
  • les style scoped vraiment très puissant, reste à voir si à l’instar de Polymer Vue.js fonctionne avec des Ponts ou bien s’il faut utiliser des Dragons

Ce que je n’ai pas aimé :

  • L’inspiration angularJS, on sent clairement la filiation avec le framework de google qui n’est pas mon préféré. Cependant cela peut-être un avantage vu que cela réduit considérablement la courbe d’apprentissage de Vue.js si vous connaissez déjà angularJS mais que vous cherchez un truc plus light
  • L’absence de documentation avec une syntaxe ES6
  • L’absence de plus-value par rapport à ES6, et c’est bien là le même principal reproche que je fais à Vue.js et à React.js, aujourd’hui si vous faites le choix de coder en ES6, ces 2 librairies apportent assez peu par rapport à du VanillaJS (pour peu que vous n’ayez pas besoin d’un DOM hyper optimisé (par le virtualDOM de React.js ou le refresh ciblé de Vue.js, ce qui est souvent le cas) couplé avec une librairie de templating (après Vue.js est à peine plus lourd que handlebars.js donc il y a templating et templating aussi), pourquoi s’enquiquiner à apprendre un framework quand le langage est assez puissant par lui-même

Conclusion

Vue.js est une excellente suite pour progresser dans l’univers JavaScript si vous n’avez connu qu’angularJS car il vous prépare à l’approche Composant qui revient en force (Angular2, React.js, WebComponents, …). C’est aussi une excellente alternative à React.js si comme moi le concept même de JSX vous fait hérisser les poils sur les bras (parce que à côté de ça le virtualDom c’est une bonne idée). C’est un bon choix de templating si vous considérez handlebars.js ces 2 lib offrant des forces différentes. Vue.js a le vent en poupe outre-Atlantique et n’est sincèrement pas à ignorer dans le choix du framework de votre prochain projet SPA. Cependant Angular2 grâce à ses annotations complètement démentes décuple la productivité du développeur, et si votre application ne sort pas en 2016, les efforts actuellement fournis par Angular2 pour maigrir au packaging devraient s’avérer payant sur le long terme. En gros mon avis qui n’engage que moi : SPA livrée en 2016 : Vue.js, SPA livrée plus tard : Angular2, vous pouvez également mettre React.js dans la balance il a ses propres forces, mais moi je ne le choisirai pas.

PokelistConfig.vue

<template>
    <div>
        <input type ="text" placeholder="search..."  v-model="searchFilter"/>
        <ul>
            <li v-for="item in items | filterBy searchFilter" id="" @mousedown.capture="toggleSkip">
                <img src="/static/icons/\{\{item.id | threeDigits}}.png"/><span></span><input type="checkbox" :checked="! item.skipped" @click.capture.stop.prevent=""/>
            </li>
        </ul>
    </div>
</template>

<script>
    /*global localStorage*/
    var pokemons = require('../generation1.json')
    var filters = JSON.parse(localStorage.getItem('skipDetectionFor')) || []

    export default {
        data  () {
            return {
                items: pokemons.map(item => {
                    if (filters.indexOf(item.id) >= 0) {
                        item.skipped = true
                    }
                    return item
                })
            }
        },
        methods: {
            toggleSkip: function (evt) {
                evt.preventDefault()
                evt.stopPropagation()
                var el = evt.currentTarget.querySelector('input[type="checkbox"]') || evt.currentTarget
                var checked = el.checked
                el.checked = !checked
                var id = parseInt(evt.currentTarget.id)
                this.items.find(_ => _.id === id).skipped = checked
                if (checked) {
                    filters.push(id)
                } else {
                    filters.splice(filters.indexOf(id), 1)
                }

                localStorage.setItem('skipDetectionFor', JSON.stringify(filters))
                setTimeout(_ => console.log('done'), 0)
            }
        }
    }
</script>
<style scoped>
    div {
        flex: 1;
        display: flex;
        flex-direction: column;
    }
    ul {
        overflow: auto;
        padding: 0;
    }
    li {
        list-style-type: none;
        display: flex;
        justify-content: space-between;
        align-items: center;
        cursor: pointer;
    }
    input[type='checkbox']{
        float: right;
    }
</style>

ClosePokemons.vue

<template>
    <div>
        <ul>
            <li v-for="item in items" id="" :class="{'searched' : item.searched}">
                <img src="/static/icons/\{\{item.id | threeDigits }}.png"/><span> @ \{\{item.coords | showCoords}} </span>
            </li>
        </ul>
    </div>
</template>

<script>
    export default {
        props: {
            items: {
                type: Array,
                default: []
            },
        }
    }
</script>