Vue.js2 : on s’organise !

Dans tout les exemples qu’on a vus dans les articles précédents on a placé le code JavaScript sur la page, on pourrait évidemment le mettre dans un fichier spécifique, ce qui ne changerait rien au principe. Tant qu’on a pas trop de code c’est parfait, simple et léger, mais quand le code s’accumule on aurait envie de pouvoir l’organiser plus efficacement.

D’autre part on voudrait aussi des fois effectuer d’autres actions : utiliser Babel pour coder en ES6, utiliser Pug dans notre template, utiliser Sass pour notre style, minifier notre JavaScript en production…

Le développement rapide du codage côté client a vu l’apparition d’une multitude d’outils : Grunt, Gulp, Browserify, Webpack, Lint… On finit par s’y perdre ! On a des tasks runners, des bundlers, et autres… Il est grand temps qu’une normalisation voit le jour…

Je ne vais pas clarifier tout ça dans cet article, ce n’est pas le but. On va juste voir un plugin bien pratique de vue.js : vue-loader. Ce plugin permet :

  • d’écrire chaque composant dans un fichier avec le template, le script et le style,
  • d’écrire le code JavaScript en ES6,
  • d’utiliser Webpack avec simplicité,
  • d’offrir un serveur de développement avec mise à jour en temps réel des modifications,
  • de générer les fichiers de productions…

Le fondement de tout ça c’est Webpack, alors pour ceux qui ne connaissent pas je vais en parler un peu.

Webpack

Webpack est un modules bundler (je ne trouve pas de traduction satisfaisante). Le but premier est de pouvoir organiser le code (css, images, polices, JavaScript…) en modules pour simplifier le développement. Il a tendance à détrôner Grunt et Gulp dans cette tâche.

En gros on crée des modules interdépendants, Webpack traite tout ça pour les transformer en fichiers statiques tout prêts pour le web :

img27

Webpack est capable de charger à peu près n’importe quoi à partir du moment où on lui explique comment faire (en pratique il faut un loader dédié). En plus il est assez intelligent pour pouvoir séparer le résultat en plusieurs paquets pour améliorer les performances.

Le seul repproche qu’on peut faire à Webpack c’est qu’il est pas vraiment facile à prendre en main, mais une fois qu’on le connait bien on peut vraiment tout faire !

Dans le contexte de cet article on va voir que le plugin vue-loader a bien préparé le terrain et que l’utilisation en est grandement facilitée. Si vous voulez vraiment vous lancer dans Webpack il y a le tutoriel officiel qui est bien fait.

Installer vue-loader

On va installer un outil en ligne de commande (vue-cli) pour pouvoir ensuite utiliser vue-loader.

Mais à la base il vous faut node.js. Si vous ne l’avez pas alors commencez par l’installer globalement, il vous servira pour bien d’autres choses !

Le gestionnaire de dépendances de node.js est npm. C’est l’équivalent de composer mais pour JavaScript. Lui aussi il vous le faut mais vous pouvez l’installer en  même temps que node.js.

Une fois que vous avez tout ça vous pouvez installer le plugin avec ces commandes dans votre console :

npm install -g vue-cli (1)
vue init webpack-simple test (2)
cd test (3)
npm install (4)
npm run dev (5)

Voyons cela de plus près :

  • (1) on commence par installer le plugin avec npm,
  • (2) on initialise le plugin dans le dossier test, acceptez les valeurs par défaut,
  • (3) on se positionne dans le dossier test,
  • (4) on installe toutes les dépendances (ça peut être long, ça crée un dossier node_modules bien garni), vous devriez vous retrouver avec tout ça :

img28

  • (5) on lance le serveur de développement (en localhost:8080).

Si tout se passe bien votre navigateur préféré s’ouvre avec ça :

img29

Il ne vous reste plus qu’à coder votre application !

Nota : j’utilise ici le template simplifié webpack-simple, mais il existe aussi la version complète webpack avec plein de choses détaillées sur la page github.

Etat des lieux

Pour fonctionner Webpack a besoin d’un fichier de configuration webpack.config.js. Vous en trouvez un à la racine avec ce code :

var path = require('path')
var webpack = require('webpack')

module.exports = {
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/dist/',
    filename: 'build.js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue',
        options: {
          // vue-loader options go here
        }
      },
      {
        test: /\.js$/,
        loader: 'babel',
        exclude: /node_modules/
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        loader: 'file',
        options: {
          name: '[name].[ext]?[hash]'
        }
      }
    ]
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue'
    }
  },
  devServer: {
    historyApiFallback: true,
    noInfo: true
  },
  devtool: '#eval-source-map'
}

if (process.env.NODE_ENV === 'production') {
  module.exports.devtool = '#source-map'
  // http://vue-loader.vuejs.org/en/workflow/production.html
  module.exports.plugins = (module.exports.plugins || []).concat([
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true
    })
  ])
}

On ne va pas détailler tout ça mais au moins le minimum :

  • entry : là on définit le fichier qui est le point d’entrée de l’application : src/main.js
  • output : là on définit le fichier de sortie, ça ne sert pas pour le développement mais pour la distribution : dist/build.js
  • module : là on met les modules dont on va avoir besoin, les loaders, on a par défaut vue, babel et file

Pour le moment on va se contenter de ça.

Les fichiers de développement de notre application sont dans le dossier src (source) :

img30

Un fichier qui contient un composant a comme suffixe vue, ici on n’en a qu’un : App.vue. Voici son code :

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <h1>{{ msg }}</h1>
    <h2>Essential Links</h2>
    <ul>
      <li><a href="https://vuejs.org" target="_blank">Core Docs</a></li>
      <li><a href="https://forum.vuejs.org" target="_blank">Forum</a></li>
      <li><a href="https://gitter.im/vuejs/vue" target="_blank">Gitter Chat</a></li>
      <li><a href="https://twitter.com/vuejs" target="_blank">Twitter</a></li>
    </ul>
    <h2>Ecosystem</h2>
    <ul>
      <li><a href="http://router.vuejs.org/" target="_blank">vue-router</a></li>
      <li><a href="http://vuex.vuejs.org/" target="_blank">vuex</a></li>
      <li><a href="http://vue-loader.vuejs.org/" target="_blank">vue-loader</a></li>
      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a></li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'app',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

h1, h2 {
  font-weight: normal;
}

ul {
  list-style-type: none;
  padding: 0;
}

li {
  display: inline-block;
  margin: 0 10px;
}

a {
  color: #42b983;
}
</style>

On a trois blocs :

  • template : pour le template HTML,
  • script : pour le code JavaScript, on peut utiliser ES6 parce que par défaut babel est activé, c’est pour ça qu’il est prévu un export dans le code du composant,
  • style : pour le style CSS.

vue-loader sera chargé d’extraire ces blocs, de transformer le code avec les loaders prévus, et enfin de les assembler en un module qui pourra être pris en charge pas Webpack.

On a vu que le point d’entrée est main.js :

import Vue from 'vue'
import App from './App.vue'

new Vue({
  el: '#app',
  render: h => h(App)
})

On importe Vue évidemment et aussi le seul composant App. Ne vous inquiétez pas de la syntaxe avec le render, on verra ça peut-être dans un autre article.

Au niveau de index.html on a du classique :

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>test</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="/dist/build.js"></script>
  </body>
</html>

Le panier encore revisité

Maintenant qu’on a toute l’intendance on va à nouveau coder le panier qu’on a vu lors des précédents articles mais cette fois à la mode vue-loader.

On va conserver inchangé le fichier main.js.

index.html

On va adapter index.html essentiellement pour intégrer Bootstrap :

<!DOCTYPE html>
<html lang="fr">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Test vue.js</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/united/bootstrap.min.css">
  </head>
  <body>
    <div class="container">
        <br>
        <div id="app"></div>
    </div>
    <script src="/dist/build.js"></script>
  </body>
</html>

Le composant Panier

On va créer un fichier Panier.vue pour le composant du panier :

<template>
<div class="panel panel-primary">
  <div class="panel-heading">Panier</div>        
  <table class="table table-bordered table-striped">
    <thead>
      <tr>
       <th class="col-sm-4">Article</th>
       <th class="col-sm-2">Quantité</th>
       <th class="col-sm-2">Prix</th>
       <th class="col-sm-2">Total</th>
       <th class="col-sm-1"></th>
       <th class="col-sm-1"></th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(item, index) in panier">
        <td>{{ item.article }}</td>
        <td>{{ item.quantite }}</td> 
        <td>{{ item.prix }} €</td>
        <td>{{ (item.quantite * item.prix).toFixed(2) }} €</td>
        <td><button class="btn btn-info btn-block" @click="modifier(index)"><i class="fa fa-edit fa-lg"></i></button></td>
        <td><button class="btn btn-danger btn-block" @click="supprimer(index)"><i class="fa fa-trash-o fa-lg"></i></button></td>
      </tr> 
      <tr>
        <td colspan="3"></td>
        <td><strong>{{ total }} €</strong></td>
        <td colspan="2"></td>
      </tr> 
      <editeur :article="article" @add="ajouter"></editeur>
    </tbody>       
  </table>
</div>
</template>

<script>
import Editeur from './Editeur.vue'

export default {
  props: ['panier'],
  data: function () {
    return {
      article: { article: '', quantite: 0, prix: 0 }
    }
  },
  computed: {
    total: function () {
      let total = 0;
      for(let el of this.panier) {
        total += el.prix * el.quantite
      }
      return total.toFixed(2)
    }
  },
  methods: {
    modifier: function(index) {
      this.article = this.panier[index]
      this.panier.splice(index, 1)
    },
    supprimer: function(index) {
      this.panier.splice(index, 1)
    },
    ajouter: function(input) {
      this.panier.push(input)
      this.article = { article: '', quantite: 0, prix: 0 }
    }
  },
  components: {
    Editeur
  }
}
</script>

Je n’ai rien changé au template.

Pour le Javascript on sait qu’il faudra importer le composant Editeur :

import Editeur from './Editeur.vue'

...

components: {
  Editeur
}

D’autre part je profite de la mise à disposition de ES6 pour voir si babel fonctionne bien :

let total = 0;
for(let el of this.panier) {
  total += el.prix * el.quantite
}

Le composant Editeur

De la même manière j’ai créé le fichier Editeur.vue pour le composant Editeur :

<template>
<tr>
  <td><input type="text" class="form-control" v-model="input.article" ref="modif" placeholder="Article"></td>
  <td><input type="text" class="form-control" v-model="input.quantite" placeholder="Quantité"></td>
  <td><input type="text" class="form-control" v-model="input.prix" placeholder="Prix"></td>
  <td colspan="3"><button class="btn btn-primary btn-block" @click="ajouter()">Ajouter</button></td>
</tr>
</template>

<script>
export default {
  props: ['article'],
  computed: {
    input: function() {
      return this.article;
    }
  },
  methods: {
    ajouter: function() {
      this.$emit('add', this.input);
    }
  }  
}
</script>

Là je n’ai rien changé.

Le composant principal

Pour terminer il faut modifier le composant App.vue :

<template>
  <div id="app">
    <panier :panier="panier"></panier> 
  </div>
</template>

<script>
import Panier from './Panier.vue'

export default {
  name: 'application',
  data () {
    return {
      panier: [
        { article: "Cahier", quantite: 2, prix: '5.30' },
        { article: "Crayon", quantite: 4, prix: '1.10' },
        { article: "Gomme", quantite: 1, prix: '3.25' }
      ]
    }
  },
  components: {
    Panier
  }
}
</script>

On importe le composant Panier.

A la fin vous devez avoir ces fichiers :

img31

Et normalement le panier à l’écran :

img23

Si ce n’est pas le cas relisez l’article pour voir le code que vous avez raté ! Ou lisez les informations fournies par les outils de développement du navigateur (je rappelle qu’il y a une extension pour vue.js dans Chrome).

Jouer avec les templates et les styles

Les templates

On va voir qu’on peut vraiment faire beaucoup de choses, par exemple utiliser un outil de template comme Pug. On va commencer par l’installer avec npm :

npm install pug --save-dev

Ensuite changez ainsi le code du template du composant Editeur.vue :

<template lang="pug">
tr
  td
    input(type="text" class="form-control" v-model="input.article" ref="modif" placeholder="Article")
  td
    input(type="text" class="form-control" v-model="input.quantite" placeholder="Quantité")
  td
    input(type="text" class="form-control" v-model="input.prix" placeholder="Prix")
  td(colspan="3")
    button(class="btn btn-primary btn-block" @click="ajouter()") "Ajouter"
</template>

On déclare le système de template utilisé avec un attribut dans la balise template. Ensuite on l’utilise dans le code.

Si tout passe bien le bon code HTML doit être généré. Pratique non ?

Le style

On peut aussi ajouter du style au composant Editeur.vue. Là aussi on peut utiliser un préprocesseur comme Sass. On va d’abord l’installer :

npm install sass-loader node-sass --save-dev

Et ensuite ajouter un bloc de style :

<style lang="sass">
$fond: lightblue;
.bleu {
  background-color: $fond !important;
}
</style>

Il faut quand même préciser où agit la classe dans le template :

tr(class="bleu")

Avec ce résultat :

img32

Le CSS a été correctement généré !

La production

Le but c’est quand même de générer les fichiers de production. Là c’est tout simple :

npm run build

img33

Voyons le dossier dist :

img34

Si vous ouvrez le fichier build.js vous verrez que le code a été « uglifié » et minimisé. Mais maintenant est-ce que ça fonctionne ? Il suffit d’ouvrir index.html dans votre navigateur. Il faudra peut-être changer la référence du fichier build.js si vous n’êtes pas avec un hôte virtuel :

<script src="dist/build.js"></script>

Normalement tout fonctionne bien, même le style ajouté à l’éditeur, le tout dans un fichier de 580 Ko !

Si vous voulez voir le code produit sans la compression commentez ces lignes dans webpack.config.js :

new webpack.optimize.UglifyJsPlugin({
  compress: {
    warnings: false
  }
}),
new webpack.LoaderOptionsPlugin({
  minimize: true
})

Si vous voulez voir ce que ça donne je l’ai mis en ligne ici.

 

Laisser un commentaire