Phoenix : changer de bundler


Depuis sa version 1.4, Phoenix embarque Webpack pour bundler les différentes dépendances. Nous allons voir qu’il est facile de changer d’outil en prenant Parcel comme exemple. Cet outil a l’avantage de ne pas nécessiter de configuration pour fonctionner.

Initialisation

Créons un nouveau projet, sans “ecto” pour ne pas avoir besoin de configurer une base de données.

mix phx.new app --no-ecto

Lançons le serveur de Phoenix.

mix phx.server

Phoenix exécute Webpack pour nous et écoute les changements de fichiers pour nous apporter les modifications en hot reload.

phoenix-launch-webpack

Si j’édite le fichier assets/css/app.css pour changer la couleur de fond du body on peut voir le changement sans recharger la page.

/* This file is for your main application css. */

@import "./phoenix.css";

body {
  background: darkgray;
}

webpack-change-color

Notre but est donc de garder ce comportement, qui offre un confort de développement, mais en utilisant un autre outil.

Webpack build nos assets à destination de priv/static. Phoenix nécessite que nos fichiers statiques soient buildés à destination de ce dossier. Cela reste néanmoins configurable

webpack-build

Avant de continuer coupons notre serveur et supprimons le dossier généré par Webpack pour partir sur une base propre

rm -rf priv/static

Configuration de Parcel au sein de Phoenix

Installons Parcel ainsi qu’un plugin magique dont le détail ne rentre pas dans le cadre de cet article.

cd assets/ && npm install --save-dev parcel-bundler parcel-plugin-static-files-copy

Dans le fichiers config/dev.exs il faut remplacer

config :app, AppWeb.Endpoint,
  http: [port: 4000],
  debug_errors: true,
  code_reloader: true,
  check_origin: false,
  watchers: [
    node: [
      "node_modules/webpack/bin/webpack.js",
      "--mode",
      "development",
      "--watch-stdin",
      cd: Path.expand("../assets", __DIR__)
    ]
  ]

par

config :app, AppWeb.Endpoint,
  http: [port: 4000],
  debug_errors: true,
  code_reloader: true,
  check_origin: false,
  watchers: [
    node: [
      "node_modules/.bin/parcel",
      "watch",
      "js/app.js",
      "-d",
      "../priv/static",
      cd: Path.expand("../assets", __DIR__)
    ]
  ]

Désormais lorsque nous lançons un mix phx.server nous pouvons voir que Phoenix lance désormais Parcel.

phoenix-launch-parcel

Et le dossier de destination est bien priv/static

build-parcel-priv

Mais malheureusement, nos css et js ne sont plus chargés

broken-page

Webpack générait un dossier css et un dossier js contenant chacun leur bundle. Parcel se passe des dossiers intermédiaires. Il faut donc aller mettre à jour les chemins des fichiers importés dans le template lib/app_web/templates/layout/app.html.eex.

On remplace

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
  </head>
  <body>
    ###
    <script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
  </body>
</html>

par

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="<%= Routes.static_path(@conn, "/app.css") %>"/>
  </head>
  <body>
    ###
    <script type="text/javascript" src="<%= Routes.static_path(@conn, "/app.js") %>"></script>
  </body>
</html>

Et 🎉, ça ne marche pas 😢.

Ce comportement vient du fait que Phoenix autorise uniquement les fichiers respectant certains patterns à être servi comme fichiers statiques. Ces patterns sont configurables dans le fichier lib/app_web/endpoint.ex

  plug Plug.Static,
    at: "/",
    from: :app,
    gzip: false,
    only: ~w(css fonts images js favicon.ico robots.txt)

Cette configuration signifie que seul les fichiers présents dans les dossiers css fonts images js ou les fichiers favicon.ico et robots.txt sont autorisés à être servi par Phoenix. Ces dossiers doivent bien entendu être présent dans priv/static 😉.

  plug Plug.Static,
    at: "/",
    from: :app,
    gzip: false,
    only: ~w(app.css fonts images app.js favicon.ico robots.txt)

Comme nous n’avons pas de dossiers intermédiaires css et js nous les remplaçons par les fichiers app.css et app.js générés par Parcel.

Maintenant l’application fonctionne correctement. Si j’édite le fichier assets/js/app.js pour y ajouter un console.log

import "phoenix_html";
import css from "../css/app.css";

console.log("Meeeeeeeeh");

Un “Meeeeeeeeh” apparait dans la console de notre navigateur sans avoir besoin de recharger la page 🎉.

meeeeeh

Nettoyage

Maintenant que nous avons ajouté Parcel il ne nous reste plus qu’à supprimer les vestiges de Webpack :

cd assets && npm uninstall babel-loader copy-webpack-plugin css-loader mini-css-extract-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin webpack webpack-cli && rm webpack.config.js

Conclusion

Peut importe que vous choisissiez de garder Webpack, d’utiliser Rollup, Parcel… Si vous choisissez de modifier la configuration de votre bundler, il faut penser à :

  • configurer correctement son bundler (facile avec Parcel)
  • changer le script surveillant les modifications de fichiers (dev.exs)
  • veiller à ce que le fichiers statiques puissent être servi correctement (endpoint.ex)
  • modifier les chemins dans les templates (app.html.eex)

Liens utiles