Comment écrire du Javascript dans Rails 6 | Webpacker, Yarn et Sprockets

Younes Serraj
Younes Serraj13 déc. 2019
Webpacker

Webpacker

Vous sentez-vous perdu avec tous les changements liés aux actifs et au Javascript ? Npm, Babel, ES6, Yarn, Webpack, Webpacker, Sprockets, tout cela vous semble-t-il complètement étranger ?


Si vous avez besoin d'un topo rapide et facile à comprendre sur la façon dont tout cet écosystème Javascript fonctionne dans une application Rails 6, cet article est ce que vous cherchez.


Je terminerai cet article par une section expliquant pas à pas comment ajouter Bootstrap 4 et FontAwesome 5 à un projet Rails 6.


NPM

NPM est un gestionnaire de paquets Javascript (modules NodeJS pour être précis). C'est le Rubygems du monde Javascript.

npm install <package>

Si vous voulez installer bootstrap par exemple :

npm install bootstrap

NPM stocke les paquets téléchargés dans ./node_modules et conserve une liste de ces paquets dans ./package.json.

À ce stade, je ne fais aucun lien entre NPM et Rails, continuez à lire pour comprendre pourquoi.


Yarn

Yarn est un gestionnaire de paquets plus récent pour Javascript. Il récupère les paquets du dépôt NPM mais fait plus que cela. Il vous permet de verrouiller les versions souhaitées de vos paquets NPM dans un fichier yarn.lock auto-généré (similaire à Gemfile.lock), il est beaucoup plus rapide que NPM, etc.


Dans une application Rails 6, lorsque vous avez besoin d'une bibliothèque Javascript, vous:

  • avez l'habitude d'ajouter une gemme qui la fournit, puis vous l'avez requise dans

app/assets/application.js (qui a été compilé par Sprockets) 

  • devez maintenant l'ajouter par le biais de Yarn ():

     yarn add <package>, alors vous l'exigez (nous verrons comment plus tard).

Note: Depuis, NPM a également ajouté une fonction de verrouillage par l'intermédiaire de package-lock.json


ES6

ES6 est une nouvelle norme Javascript (une nouvelle version de Javascript si vous voulez). Elle s'accompagne de nouvelles fonctionnalités très pratiques telles que la définition de classes, la déstructuration, les fonctions fléchées, etc.

Au revoir Coffeescript, je t'ai toujours détesté.


Babel

Comme tous les navigateurs Web ne comprennent pas encore l'ES6, vous avez besoin d'un outil qui lit votre code Javascript ES6 et le traduit en ancien Javascript ES5 pour qu'il fonctionne sur tous les navigateurs. Babel est le compilateur qui effectue cette traduction.


Webpack

Il y a Babel et il y a Yarn et leurs fichiers de configuration et il y a le besoin d'automatiser la compilation de vos actifs et la gestion des environnements et ainsi de suite.


Comme vous souhaitez vous concentrer sur l'écriture du code et automatiser la précompilation des ressources, vous utiliserez Webpack qui joue le rôle de chef d'orchestre. Il prend vos ressources et transmet chacune d'entre elles aux plugins appropriés. Les plugins font ensuite en sorte que le bon outil traite le fichier d'entrée et donne le résultat attendu.

Par exemple, Webpack peut :

  • prendre votre code Javascript ES6,

  • utiliser le babel-loader pour que Babel compile du code Javascript ES6 en ES5,

  • puis sortir le paquet résultant dans un fichier que vous pouvez inclure dans votre DOM HTML (<script type="text/javascript" src="path-to-es5-javascript-pack.js"></script>).


Webpacker

Webpacker est une gemme qui permet d'inclure facilement Webpack dans votre application Rails. Il est livré avec quelques fichiers de configuration initiaux (et suffisants pour commencer) afin que vous puissiez commencer à écrire du code réel sans vous soucier de la configuration.

La configuration par défaut de Webpacker est la suivante :

  • app/javascript/packs/ doit contenir vos packs Javascript (par exemple :
     application.js)

  • Vous pouvez inclure un pack Javascript dans vos vues en utilisant

    javascript_pack_tag '<pack_name>'(par exemple : <%= javascript_pack_tag 'my_app' %> comprendra app/javascript/packs/my_app.js)

Je donnerai un exemple très clair de la façon dont tout cela fonctionne à la fin de cet article, mais je dois d'abord parler un peu de Sprockets.

Note: une autre configuration par défaut est extract_css: false (config/webpacker.yml), ce qui signifie que, bien que Webpack sache comment servir les paquets CSS avec stylesheet_pack_tag, vous lui demandez de ne pas le faire. Cet article est centré sur le Javascript, je ne vais donc pas en dire plus à ce sujet, mais gardez à l'esprit que cette option est désactivée par défaut, ce qui vous évitera de perdre du temps à débogguer ce qui est un comportement par défaut, et non un bug.

Encore une autre remarque : lorsque vous exécutez rails assets:precompile, vous pourriez penser que Rails ne pré-compilera que ce qui se trouve dans le fichier app/assets/. En fait, Rails pré-compilera à la fois les actifs de Webpack app/javascript/ et ceux de Sprockets app/assets/.


Sprockets 4

Comme Webpack, Sprockets est un pipeline d'actifs, ce qui signifie qu'il prend des fichiers d'actifs en entrée (Javascript, CSS, images, etc.) et les traite pour produire une sortie au format souhaité.

À partir de Rails 6, Webpack(er) remplace Sprockets comme nouvelle norme pour l'écriture de Javascript dans vos applications Rails. Cependant, Sprockets reste le moyen par défaut d'ajouter du CSS à vos applications.

Avec Sprockets vous:

  • aviez l'habitude de lister les actifs disponibles en config.assets.precompile (Sprockets 3, Rails 5)

  • devez maintenant le faire dans un fichier manifeste app/assets/config/manifest.js (Sprockets 4, Rails 6)

Si vous voulez inclure un actif du pipeline Sprockets, vous devrez :

  • Ecrire votre CSS (par exemple : app/assets/stylesheets/my_makeup.css)

  • Assurer app/assets/config/manifest.js le rendre disponible pour stylesheet_link_tag soit par le biais d'un link_tree, link_directory ou un link déclaration (par exemple : link my_makeup.css)

  • Incluez-le dans votre vue en utilisant stylesheet_link_tag (<%= stylesheet_link_tag 'my_makeup' %>)


N'essayez pas d'utiliser Webpack comme vous le feriez avec Sprockets !

Il est essentiel que vous compreniez ce qui suit si vous ne voulez pas perdre d'innombrables heures à ramer à contre-courant. Idéalement, vous devriez passer du temps à apprendre ES6, mais en attendant, je peux au moins dire ceci :


Webpack est différent de Sprockets dans le sens où il compile des modules.

Des modules ES6 pour être précis (dans le cas de Rails 6 avec une configuration par défaut). Qu'est-ce que cela implique ? Eh bien, cela implique que tout ce que vous déclarez dans un module est en quelque sorte namespaced parce qu'il n'est pas destiné à être accessible depuis la portée globale, mais plutôt importé puis utilisé. Laissez-moi vous donner un exemple.

Vous pouvez faire ce qui suit avec Sprockets:


app/assets/javascripts/hello.js:

function hello(name) {console.log("Hello " + name + "!");}

app/assets/javascripts/user_greeting.js:

function greet_user(last_name, first_name) { hello(last_name + " " + first_name);}

app/views/my_controller/index.html.erb:

<%= javascript_link_tag 'hello' %>
<%= javascript_link_tag 'user_greeting' %><button onclick="greet_user('Dire', 'Straits')">Hey!</button>

C'est assez simple à comprendre. Et maintenant avec Webpacker ?

Si vous pensiez simplement déplacer ces fichiers JS sous app/javascript/packs, les inclure en utilisant javascript_pack_tag et être fait, laissez-moi vous arrêter là : ça ne marcherait pas.

Pourquoi? Parce que hello() serait compilé comme étant dans un module ES6 (de même pour le module user_greeting()), ce qui signifie que dans la mesure où user_greeting() va, même après que les deux fichiers JS soient inclus dans la vue, la fonction hello() la fonction n'existe pas.

Alors comment obtenir le même résultat avec Webpack :
app/javascript/packs/hello.js:

export function hello(name) {console.log("Hello " + name + "!");}

app/javascript/packs/user_greeting.js:

import { hello } from './hello';function greet_user(last_name, first_name) { hello(last_name + " " + first_name);}

app/views/my_controller/index.html.erb:

<%= javascript_pack_tag 'user_greeting' %><button onclick="greet_user('Dire', 'Straits')">Hey!</button>

Est-ce que ça marcherait ? Non. Pourquoi ? Encore une fois, pour la même raison : greet_user n'est pas accessible depuis la vue car il est caché à l'intérieur d'un module une fois qu'il est compilé.

Nous arrivons enfin au point le plus important de cette section :

  • Avec Sprockets: les vues peuvent interagir avec ce que vos fichiers JS exposent (utiliser une variable, appeler une fonction, ...)

  • Avec Webpack: Les vues n'ont PAS accès à ce que contiennent vos packs JS.

Alors comment faire pour qu'un bouton déclenche une action JS ? À partir d'un pack, vous ajoutez un comportement à un élément HTML. Vous pouvez le faire en utilisant vanilla JS, JQuery, StimulusJS, etc.

Voici un exemple utilisant JQuery :

import $ from 'jquery';
import { hello } from './hello';function greet_user(last_name, first_name) {
hello(last_name + " " + first_name);
}$(document).ready(function() {
$('button#greet-user-button').on(
'click',
function() {
greet_user('Dire', 'Strait');
}
);
});/* Or the ES6 version for this: */
$(() =>
$('button#greet-user-button').on('click', () => greet_user('Dire', 'Strait'))
);

app/views/my_controller/index.html.erb:

<%= javascript_pack_tag 'user_greeting' %><button id="greet-user-button">Hey!</button>

Règle générale : avec Webpack, vous configurez le comportement souhaité dans les packs, pas dans les vues.


Permettez-moi de me répéter avec un dernier exemple :

Si vous devez utiliser une bibliothèque (select2 ou jQuery par exemple), est-il possible de l'importer dans un pack et de l'utiliser dans une vue ? Non. Soit vous l'importez dans un pack et l'utilisez dans ce pack, soit vous lisez la section suivante de cet article.

Si vous voulez apprendre à utiliser StimulusJS pour structurer votre code JS et attacher des comportements à vos éléments HTML, je vous conseille de lire StimulusJS on Rails 101.

Pour ceux qui veulent comprendre comment cette “tout est caché/namespaced” fonctionne: lorsqu'un module ES6 est compilé en code ES5, le contenu du module est emballé dans une fonction anonyme de sorte qu'en dehors de cette fonction, vous ne pouvez accéder à aucune variable/fonction déclarée dans le module.


Vous pouvez toujours utiliser Sprockets pour le code Javascript.

La documentation de Webpacker indique ce qui suit :

[…] le but premier de webpack est le JavaScript de type application, et non les images, les CSS, ou même les Sprinkles JavaScript (tout cela continue à vivre dans app/assets).

Cela signifie que si vous avez besoin ou si vous voulez rendre certains éléments Javascript disponibles pour les vues, vous pouvez toujours utiliser Sprockets.

  • Créer le app/assets/javascripts répertoire (remarquez que les javascripts sont ici au pluriel)

  • Mettre à jour app/assets/config/manifest.js en conséquence (//= link_directory ../javascripts .js )

  • Incluez vos fichiers Javascript Sprockets dans vos vues en utilisant javascript_include_tag (remarquez la différence :javascript_include_tag

    pour Sprockets,javascript_pack_tag pour Webpacker)

  • Faites votre truc.

J'essaie personnellement d'éviter cela autant que possible, mais cela vaut la peine de le savoir.

Note : vous pourriez vous demander pourquoi il y a à la fois un manifest.js fichier et un config.assets.precompile qui servent le même objectif d'exposer les cibles de haut niveau à la compilation. Ceci est pour des raisons de rétro-compatibilité. Les instructions de mise à niveau vous déconseillent d'utiliser ce dernier.


Comment ajouter bootstrap 4 et fontawesome 5 à une application Rails 6 ?

Pour vous aider à mieux comprendre, je vous conseille d'appliquer au fur et à mesure de votre lecture. Cela vous aidera beaucoup à appréhender ces nouveautés.


1. Créer une nouvelle application Rails 6

rails new bloggy

J'aimerais que vous jetiez un coup d'œil aux fichiers suivants. Le but n'est pas que vous compreniez tout ce qu'ils contiennent, mais simplement que vous sachiez qu'ils existent et que vous ayez une vague image mentale de ce qu'ils contiennent afin de pouvoir y revenir facilement plus tard si nécessaire.


Yarn:

  • package.json

Webpacker:

  • config/webpacker.yml

  • app/javascript/packs/application.js

  • app/views/layouts/application.html.erb

Sprockets:

  • app/assets/config/manifest.json


2. Ajouter une page racine

rails generate controller welcome index

Et ajouter une racine à : 'welcome#index' in config/routes.rb.

Exécuter rails server et s'assurer que tout est bon jusqu'à présent.


3. Ajouter les paquets Yarn requis

Nous voulons ajouter bootstrap 4 (qui nécessite jquery et popper.js) et font-awesome 5.

Jetez un coup d'œil rapide au moteur de recherche de Yarn et essayez de trouver les paquets nécessaires par vous-même (remarquez le nombre de téléchargements pour chaque paquet), puis poursuivez avec ce tutoriel.


yarn add bootstrap jquery popper.js @fortawesome/fontawesome-free

Yarn les a maintenant mis en cache dans ./bloggy/node_modules/ et mis à jour  package.json. Cependant, ces paquets ne sont toujours pas utilisés dans notre application. Corrigeons cela. Nous allons commencer par inclure la partie JS et nous nous occuperons de la partie CSS plus tard.


4. Inclure la partie JS de bootstrap et fontawesome

Dans votre mise en page, il y a déjà javascript_pack_tag 'application' ce qui signifie que vous demandez à Webpack de compiler app/javascript/packs/application.js et inclure la sortie dans cette mise en page. Pour ajouter bootstrap, nous pouvons soit créer un autre pack exclusivement destiné à inclure bootstrap, soit utiliser le pack  application.js. Faisons la seconde solution puisque nous ne construisons pas une vraie application.

Ajoutez le texte suivant à app/javascript/packs/application.js:

require("bootstrap");
require("@fortawesome/fontawesome-free");

Notice : J'ai exigé “bootstrap”, et non “bootstrap/dist/js/bootstrap.min”. En effet, à moins que je ne spécifie le chemin d'un fichier, le package.json du module (bloggy/node_modules/bootstrap/package.json) donnera les informations nécessaires sur le fichier à inclure. J'aurais pu exiger "bootstrap/dist/js/bootstrap.min" et cela aurait très bien fonctionné.

Revenons à la mise en place de bootstrap et fontawesome dans notre application. Si vous démarrez votre serveur Rails et regardez la console Javascript, vous verrez qu'il fonctionne correctement même si nous n'avons pas exigé jQuery in application.js.

Si vous avez déjà consulté d'autres tutoriels expliquant comment inclure bootstrap via Webpacker, vous avez probablement remarqué que la plupart d'entre eux exigent d'abord jQuery, puis bootstrap. Ceci est en fait inutile.


Pourquoi ? Parce que puisque nous avons installé jQuery par le biais de Yarn, bootstrap peut à lui seul exiger jQuery. Il n'y a aucune raison pour nous de l'exiger en application.js parce que cela le rendrait disponible dans application.js, pas dans le module bootstrap. Donc, à moins que vous n'ayez besoin d'utiliser jQuery directement dans l'application application.js, il n'est pas nécessaire de l'exiger à cet endroit.


5. Inclure la partie (S)CSS de bootstrap et fontawesome

J'aime travailler avec SCSS, donc avant d'inclure bootstrap et font-awesome, renommons application.css en application.scss et le vider de tous les commentaires et autres instructions de Sprockets.

Maintenant, ajoutez-y le code suivant :

$fa-font-path: '@fortawesome/fontawesome-free/webfonts';
@import '@fortawesome/fontawesome-free/scss/fontawesome';
@import '@fortawesome/fontawesome-free/scss/regular';
@import '@fortawesome/fontawesome-free/scss/solid';
@import '@fortawesome/fontawesome-free/scss/brands';@import 'bootstrap/scss/bootstrap';

Note: Contrairement à Webpack, Sprockets ne lit pas les fichiers package.json des modules npm pour déterminer le fichier à inclure, et vous ne pouvez donc pas importer un module simplement par son nom. Vous devez indiquer le chemin d'accès au(x) fichier(s) que vous souhaitez importer (l'extension du fichier est facultative).

Vous êtes prêt !


Ajoutons un bouton et une icône dans notre vue pour nous assurer que tout fonctionne correctement :


Ajouter <a href="#" class="btn btn-primary">Yeah <i class="far fa-thumbs-up"></i></a> to app/views/welcome/index.html.erb, exécutez votre serveur rails et assurez-vous que le bouton primaire et l'icône apparaissent correctement.


Rendre jQuery disponible dans tous packs

Si vous avez besoin d'utiliser jQuery (ou toute dépendance) dans la plupart de vos packs, l'exiger dans chaque pack est encombrant. Une solution que j'apprécie est de le rendre disponible pour tous les packs via la configuration (encore une fois, il ne sera pas disponible dans les vues, seulement dans les packs).

Pour ce faire, copiez/collez ce qui suit dans le fichier config/webpack/environment.js:

const { environment } = require('@rails/webpacker')
var webpack = require('webpack');environment.plugins.append(
'Provide',
new webpack.ProvidePlugin({
$: 'jquery',
})
)module.exports = environment

Ce snippet fait en sorte que Webpack "fournisse" le module jQuery à tous les packs par le biais du nom $. Cela équivaut à ajouter ce qui suit au début de chaque pack :


import $ from 'jquery';

Merci d'avoir lu!

Partager
Younes Serraj
Younes Serraj13 déc. 2019

Blog de Capsens

Capsens est une agence spécialisée dans le développement de solutions fintech. Nous aimons les startups, la méthodologie scrum, le Ruby et le React.

Ruby Biscuit

La newsletter française des développeurs Ruby on Rails.
Retrouve du contenu similaire gratuitement tous les mois dans ta boîte mail !
S'inscrire