jl
Jeu. 18 Février 2016

React, ES2015 et utilisation de webpack

Ça faisait longtemps que je ne m'étais pas pencher sur le développement front-end et avec l'apparition de webpack, c'est le moment de s'y intéresser à nouveau !

Vous êtes sans doute au courant que le développement front-end subit une micro révolution depuis quelques temps, dans le sens ou on essaie de plus en plus de responsabiliser le client pour tout ce qui est rendu. C'est pour cela qu'il existe un bon nombre de framework JS permettant de gérer tout l'affichage directement du côté navigateur et de n'attendre du serveur que des données.

L'application back-end devient donc une simple API (en général REST). Plusieurs avantages à ça, notamment les tests unitaires qui deviennent beaucoup plus simple à écrire et un meilleur découpage des responsabilités grâce à la notion de ressource.

En parallèle de ça, il y'a les nouvelles spécifications de l'ECMAScript, comprenez JS, qui vont bouleverser notre manière de faire avec énormément d'ajouts au langage.

Aujourd'hui ce qui nous intéresse, c'est d'utiliser dès maintenant ces nouvelles spécifications afin d'améliorer le code que nous produisons et nous allons en profiter pour faire un brin de React.

Ce que nous allons faire

Avant de commencer, soyons clair sur ce que nous allons faire et ne pas faire. Nous n'allons pas parler des fonctionnalités de l'ES2015 utilisable dès maintenant grâce à Babel, ni de React en particulier, il existe déjà beaucoup de tutoriaux de très bonne qualité.

Nous allons mettre en place une base de travail à l'aide de webpack qui gérera aussi bien la transpilation des sources ES2015 + JSX (spécifiques à React) que l'intégration de styles SASS et en particulier Foundation for sites au sein d'une page toute simple de login qui ressemblera à ça :

Page de login avec React

Ouai c'est pas ultra sexy mais ça suffira pour mettre en place notre démarche ! Les sources sont disponibles sur mon Github.

Ce dont nous avons besoin

Une version récente de nodejs, un éditeur de texte et c'est à peu près tout.

Webp… quoi ?

Webpack ! Il s'agit d'un outil vous permettant de créer des bundle js contenant tout ce qu'il faut à votre application pour fonctionner. JS, styles, templates, TOUT ! Et tout ça sans dépendre d'une architecture de module (AMD, CommonJS, …). Il va nous permettre de pré-traiter nos fichiers suivant leurs extensions.

Logo webpack

Il existe pas mal d'outil JS qui possède plus ou moins le même but, gulp.js ou bower mais aujourd'hui, nous parlons de webpack, c'est à la mode et vous allez voir, c'est relativement simple.

Structure du projet

Et c'est parti, créons un nouveau dossier et initialisons le grâce à npm (répondez aux questions de npm comme vous le souhaitez).

mkdir webpack-sandbox
cd webpack-sandbox && npm init

Notre structure sera toute simple : les sources dans un répertoire src/ et tout le reste à la racine.

Ensuite on installe webpack de manière globale et on l'ajoute à notre package.json

npm install -g webpack && npm install webpack --save-dev

Sous Windows, si vous avez une erreur de dépendance optionnelle non satisfaite (fsevents), vous pouvez l'ignorer.

Webpack est désormais installé et prêt à faire feu. Pour que ce soit plus simple, on va modifier notre package.json de manière à avoir ceci (pour la partie scripts) :

{
  "name": "...",
  "version": "...",
  "scripts": {
    "watch": "webpack --watch --progress --colors"
  },
  "devDependencies": {
    "webpack": "..."
  }
}

Cela nous permet de lancer le process de packaging en tapant npm run watch.

Les loaders

Pour pouvoir traiter nos fichiers, webpack utilise des loaders. Ce sont eux qui effectuent les traitements nécessaires suivant un fichier de configuration webpack que nous allons voir par la suite.

Dans notre cas, nous avons besoin des loaders suivants :

  • babel-loader : Transpileur qui va convertir notre code ES2015 en ES5 interprétable par nos navigateurs, il se chargera aussi des jsx,
  • css-loader, style-loader et sass-loader : Pour l'intégration des fichiers de styles SASS

Les loaders que vous utilisez dépendent du travail à accomplir. Globalement, la manœuvre est toujours la même : on cherche le loader à utiliser, on installe les packages nécessaires avec npm <packages> --save-dev afin de modifier notre package.json et pour finir on modifie le fichier de configuration pour utiliser ce loader.

En se rendant sur les pages respectives des loaders, on obtient donc les packages npm à installer. On exécute donc les commandes suivantes :

npm install babel-loader babel-core babel-preset-es2015 babel-preset-stage-0 babel-preset-react --save-dev
npm install css-loader style-loader --save-dev
npm install sass-loader node-sass --save-dev

La majorité du temps, le loader ne contient pas de dépendance pour le package “core” qu'il utilise comme peer dependency de manière à vous permettre de garder le package à jour.

Le fichier de configuration webpack

Quand vous lancez, la commande webpack, elle prend par défaut le fichier de configuration webpack.config.js. Voyons voir ce qu'il contient dans notre cas :

var path = require('path');

module.exports = {
  context: path.join(__dirname, 'src'), // Détermine le répertoire de travail
  entry: "./entry.js", // Point d'entrée, relatif au répertoire de travail
  output: {
    path: path.join(__dirname, 'build'), // Où poser le fichier final
    filename: 'bundle.js' // Nom du fichier final
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/, // On prend tous les fichiers .js ou .jsx
        exclude: /node_modules/,
        loader: 'babel', // On applique le loader babel
        query: {
          // Arguments passés à babel https://babeljs.io/docs/plugins/
          presets: ['es2015', 'stage-0', 'react']
        }
      },
      {
        test: /\.scss$/, // On fait de même pour tous les fichiers .scss
        loaders: ['style', 'css', 'sass']
      }
    ]
  }
}

Notre projet

Dépendances

Pour pouvoir fonctionner, nous allons avoir besoin des packages react et foundation, tous disponibles sur npm.

npm install react react-dom foundation-sites --save

Pareil ici, le process sera identique si vous avez d'autres dépendances : on installe les paquets npm via npm install <package> --save et ils seront disponibles dans vos sources.

Les sources !

De manière à pouvoir tester notre petit bundle, on crée un fichier index.html à la racine de notre projet :

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Webpack + React!</title>
</head>
<body> 
  <div id="root" class="row align-middle align-center">
  </div>

  <script src="build/bundle.js"></script>
</body>
</html>

Ok, il est temps de créer notre point d'entrée src/entry.js :

// On importe les librairies nécessaires, les styles et le composant React qui nous servira à afficher le formulaire de connexion.
import React from 'react'
import ReactDOM from 'react-dom'

import './styles.scss' // En important directement le style tel quel, il sera ajouté dans la balise head automatiquement
import LoginForm from './login'

ReactDOM.render(
  <LoginForm />,
  document.getElementById('root')
)

Notre fichier src/styles.scss est très simple, vous remarquerez l'utilisation du ~ pour faire référence au package foundation dans le répertoire node_modules/ :

@import '~foundation-sites/scss/foundation';
@include foundation-everything(true);

#root {
  height: 100%; // Pour que le root prenne la totalité de la hauteur
}

Et notre fichier src/login.js qui se trouve être le cœur de notre petite démonstration :

import React from 'react'

class LoginForm extends React.Component {

  state = { username: '', password: '' }

  handleChange = (e, field) => {
    this.setState({
      [field]: e.target.value
    });
  }

  handleSubmit = (e) => {
    e.preventDefault()
    alert(`Should login user ${this.state.username} with pass ${this.state.password}`)
  }

  render() { 
    return (
      <form className="columns small-12 medium-3" onSubmit={this.handleSubmit}>
        <label>Username
          <input type="text" onChange={(e) => this.handleChange(e, 'username')} value={this.state.username} />
        </label>

        <label>Password
          <input type="password" onChange={(e) => this.handleChange(e, 'password')} value={this.state.password} />
        </label>

          <button type="submit" disabled={!this.state.username || !this.state.password} className="primary button float-right">
            Log in
          </button>
      </form>
    )
  }

}

export default LoginForm

On utilise ici les dernières spécifications JS et ça se voit ! C'est autrement plus propre que de l'ES5.

Pour aller plus loin

Nous avons terminer notre petite démo et il ne tient qu'à vous d'aller plus loin. La prochaine étape, c'est l'utilisation de Redux, une implémentation de Flux, de manière à mieux organiser et maîtriser notre code front-end. Attention cependant, c'est assez déroutant au début.