keyboard_arrow_up

Utiliser React dans un thème Drupal 8

Rédigé par Sylvain Lavielle
Développeur web freelance expert Drupal sur Toulouse

Le 08/10/2019

L'objectif de cet article est de décrire comment définir et utiliser des composants React au sein d'un thème Drupal existant pour y ajouter des parties développées en React. Il ne s'agit donc pas ici de présenter une solution pour réaliser des sites headless en React couplés à un Drupal.

Il est possible d'utiliser React dans un site web en utilisant des scripts prêts à l'emploi disponibles via des CDN. C'est beaucoup plus simple à faire mais cependant, si on souhaite disposer d'un environnement React correct permettant de tirer partie d'ES2015 (ES6) et d'avoir la possibilité de définir chaque composant React dans un fichier séparé, il est alors nécessaire d'utiliser Babel et Webpack. C'est cette dernière solution dont je vais présenter la mise en œuvre.

Le contexte

Notre site web existant dispose d'un thème custom (que nous appellerons my_custom_theme). Ce thème custom utilise Gulp comme task runner.

Les assets sources sont localisés dans des sous-répertoires de [drupal-root]/themes/custom/my_custom_theme/assets/

Les éléments compilés en sortie des tâches Gulp sont localisés dans des sous-répertoires de [drupal-root]/themes/custom/my_custom_theme/dist/

L'objectif

L'objectif est donc d'ajouter la possibilité de compiler les composants React et de les bundliser avec Webpack tout en intégrant tout cela avec notre process Gulp déjà en place.

Étape 1 : Installation de React, Babel et WebPack

Babel est un transpiler qui va nous permettre d'utiliser l'ES2015 et JSX pour développer nos composants React,

Webpack quant à lui est un bundler. Il va générer un script qui va nous permettre de charger nos composants React.

Les commandes npm ci-dessous sont bien sûr à exécuter à la racine du thème [drupal-root]/themes/custom/my_custom_theme

Installation de React

$ npm i --save react react-dom

Installation de Babel

$ npm i --save-dev @babel/core  @babel/preset-env @babel/preset-react babel-loader

Créer un fichier .babelrc à la racine du thème (au même niveau que le package.json) contenant la configuration suivante :

[drupal-root]/themes/custom/my_custom_theme/.babelrc

{
  "presets": ["@babel/preset-react", "@babel/preset-env"]
}

Installation de Webpack

$ npm i --save-dev webpack webpack-stream

webpack-stream est un module qui sera utilisé pour lancer Webpack à partir de Gulp (nous verrons sa mise en œuvre ensuite)

Créer un fichier webpack.config.js à la racine du projet (au même niveau que le package.json)

[drupal-root]/themes/custom/my_custom_theme/webpack.config.js

module.exports = {
  mode: 'development',
  entry: __dirname + '/assets/react/react-container.js',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: [
          __dirname + './node_modules/',
          __dirname + './dist/',
          __dirname + './assets/scripts/',
        ],
        loader: 'babel-loader',
      },
    ],
  },
  output:{
    filename: 'react-container-bundle.js',
    path: __dirname + './dist/scripts/'
  }
}

Étape 2 : Créer le container React et le composant

Nous allons maintenant créer un simple container et un composant ... juste pour dire "bonjour" ...

[drupal-root]/themes/custom/my_custom_theme/assets/react/react-container.js

import React, { Component } from "react";
import ReactDOM from "react-dom";
import Hello from "./components/Hello";

ReactDOM.render(
  <Hello name="Sylvain" />,
  document.getElementById('react-container')
);

[drupal-root]/themes/custom/my_custom_theme/assets/react/components/Hello.js

import React, { Component } from "react";
import ReactDOM from "react-dom";

class Hello extends React.Component {

  render() {
    return (
      <div className="hello-from-react">
        <h1>Hello { this.props.name } from React </h1>
      </div>
    );
  }
}

export default Hello;

Étape 3 : Créer une tâche Gulp pour lancer Webpack

Comme nous avons un process Gulp existant, l'idéal serait de pouvoir lancer Webpack à partir de Gulp, histoire d'éviter d'avoir plusieurs commandes à lancer pour builder le thème. Pour se faire il suffit d'écrire une tâche Gulp utilisant webpack-stream que nous avons installé un peu avant.

Le répertoire .gulpfile.js est structuré comme présenté dans un précédent article "Organiser ses tâches Gulp"

[drupal-root]/themes/custom/my_custom_theme/gulpfile.js/tasks/webpack.js

const path = require('path'),
      webpack_stream = require('webpack-stream'),
      webpack_config = require(path.resolve ('./webpack.config.js')),
      gulp = require('gulp'),
      conf = require('../conf');

module.exports = function () {
  return webpack_stream(webpack_config)
    .pipe(gulp.dest(conf.tasks.webpack.paths.dist.path));
};

Puis de déclarer la configuration de la tâche :

[drupal-root]/themes/custom/my_custom_theme/gulpfile.js/conf.js

module.exports = {
  tasks: {

    // Other tasks conf

    // Webpack tasks conf
    webpack: {
      paths: {
        dist:{
          path: './dist/scripts/',
        }
      }
    }
  }
};

Puis d'insérer la tâche "webpack" dans le fichier gulpfile.js/index.js dans le processus de build

var gulp = require("gulp");

// Single tasks
gulp.task("webpack", require("./tasks/webpack"));
// Other tasks here ...

// Meta tasks
gulp.task("build", gulp.parallel("styles", "scripts", "svg", "fonts", "image", "webpack"));

// Default tasks
gulp.task("default", gulp.series("clean","build"));

// dev tasks
gulp.task("w", gulp.series('default', require("./tasks/watch")));

Étape 4 : Déclaration du bundle Webpack à Drupal

À ce stade nous pouvons créer notre bundle Webpack, mais il va nous falloir le déclarer dans Drupal. Nous allons pour cela déclarer une librairie dans le fichier de librairies de notre thème.

[drupal-root]/themes/custom/my_custom_theme/my_custom_theme.libraries.yml

react-container-bundle:
  js:
    dist/scripts/react-container-bundle.js: {preprocess: false}

A noter que le bundle produit par Webpack est ici exclu du préprocessing javascript Drupal (preprocess: false). En clair il ne sera pas aggrégé avec les autres scripts si l'aggrégation de scripts est activée. Curieusement Drupal semble digérer moyennement la syntaxe produite par le bundler Webpack. lorsque de mes tests, le script aggrégé contenant le bundle Webpack produisait une erreur de syntaxe. C'est la raison pour laquelle je l'ai exclu du préprocessing javascript.

Étape 5 : Implantation du container et de la librairie

La librairie contenant notre bundle Webpack est maintenant déclarée et il va nous falloir l'implanter dans le Drupal, ainsi qu'un élément HTML qui va servir de conteneur pour notre composant React principal ( voir le fichier [drupal-root]/themes/custom/my_custom_theme/assets/react/react-container.js).

Il existe plusieurs façons de faire cela, mais pour l'exemple nous allons simplement déclarer un élément <div> pour le container dans le twig de notre choix, et utiliser la fonction twig attach_library pour ajouter notre librairie drupal contenant notre bundle Webpack.

[drupal-root]/themes/custom/my_custom_theme/templates/**/[any-twig-file].twig

{{ attach_library('my_custom_theme/react-container-bundle') }}
<div id="react-container"></div>

Et voilà.

Moyennant quelques étapes, on y est arrivé ! Ne reste plus qu'à développer des composants React un peu plus sympa :)

 

Sujets abordés dans cet article