La version 1.7.0 du framework Phoenix est désormais disponible, et intègre le support de TailWind, les générateurs d'authentification LiveView, les routes vérifiées et bien plus encore

Phoenix est un framework de développement web écrit en Elixir qui implémente le modèle MVC (Model View Controller) côté serveur. Nombre de ses composants et concepts sembleront familiers à ceux d'entre nous qui ont l'expérience d'autres frameworks web tels que Ruby on Rails ou Django de Python. Phoenix offre le meilleur des deux mondes : une productivité élevée pour les développeurs et des performances élevées pour les applications. Il présente également quelques nouveautés intéressantes, comme des canaux pour l'implémentation de fonctionnalités en temps réel et des modèles précompilés pour une vitesse fulgurante.

La version finale de Phoenix 1.7 est disponible ! Phoenix 1.7 contient un certain nombre de nouvelles fonctionnalités très attendues comme les routes vérifiées, le support de Tailwind, les générateurs d'authentification LiveView, les modèles HEEx unifiés, les flux LiveView pour des collections optimisées, et bien plus encore. Il s'agit d'une version rétrocompatible avec quelques dépréciations. La plupart des gens devraient être en mesure de mettre à jour juste en changeant quelques dépendances.

Nom : phoenix framework.png
Affichages : 16991
Taille : 19,8 Ko

Note : Pour générer un nouveau projet 1.7, vous devrez installer le générateur phx.new de hex :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
mix archive.install hex phx_new
Routes vérifiées

Les routes vérifiées remplacent les router helpers par une approche basée sur les sigils (~p) et vérifiée à la compilation.

note : Les routes vérifiées utilisent les nouvelles fonctionnalités du compilateur Elixir 1.14. Phoenix supporte toujours les anciennes versions d'Elixir, mais vous devrez les mettre à jour pour profiter des nouvelles fonctionnalités de vérification à la compilation.

En pratique, cela signifie que là où vous utilisiez auparavant des fonctions autogénérées comme :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
  # router
  get "/oauth/callbacks/:id", OAuthCallbackController, :new

  # usage
  MyRouter.Helpers.o_auth_callback_path(conn, :new, "github")
  # => "/oauth/callbacks/github"

  MyRouter.Helpers.o_auth_callback_url(conn, :new, "github")
  # => "http://localhost:4000/oauth/callbacks/github"
vous pouvez maintenant faire :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
# router

  get "/oauth/callbacks/:id", OAuthCallbackController, :new


  # usage
  ~p"/oauth/callbacks/github"
  # => "/oauth/callbacks/github"

  url(~p"/oauth/callbacks/github")
  # => "http://localhost:4000/oauth/callbacks/github"
Cela présente un certain nombre d'avantages. Il n'est plus nécessaire de deviner quelle fonction a été infléchie - s'agit-il de Helpers.oauth_callback_path ou de o_auth_callback_path, etc. Vous n'avez plus besoin d'inclure le module %Plug.Conn{}, ou %Phoenix.Socket{}, ou endpoint partout alors que 99 % du temps vous savez quelle configuration endpoint doit être utilisée.

Il y a aussi maintenant une correspondance 1:1 entre les routes que vous écrivez dans le routeur, et la façon dont vous les appelez avec ~p. Vous l'écrivez simplement comme si vous codiez des chaînes en dur partout dans votre application - sauf que vous n'avez pas les problèmes de maintenance qui viennent avec le codage en dur des chaînes. Nous pouvons obtenir le meilleur des deux mondes avec la facilité d'utilisation et de maintenance parce que ~p est une vérification à la compilation des routes dans votre routeur.

Par exemple, imaginons que nous fassions une faute de frappe sur une route :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
<.link href={~p"/userz/profile"}>Profile</.link>
Le compilateur va distribuer tous les ~p au moment de la compilation par rapport à votre routeur, et vous fera savoir s'il ne trouve pas de route qui corresponde à la vôtre :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
warning: no route path for AppWeb.Router matches "/postz/#{post}"

      lib/app_web/live/post_live.ex:100: AppWeb.PostLive.render/1
Les "paramètres nommés" dynamiques sont aussi simplement interpolés comme une chaîne de caractères normale, au lieu d'arguments de fonction arbitraires :

De plus, les valeurs ~p interpolées sont encodées via le protocole Phoenix.Param. Par exemple, une structure %Post{} dans votre application peut dériver le protocole Phoenix.Param pour générer des chemins basés sur les slugs plutôt que sur les ID. Cela vous permet d'utiliser ~p"/posts/#{post}" plutôt que ~p"/posts/#{post.slug}" dans votre application.

Les chaînes de requête sont également prises en charge dans les itinéraires vérifiés, soit sous la forme traditionnelle d'une chaîne de requête :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
~p"/posts?page=#{page}"
Ou sous la forme d'une liste de mots-clés ou d'une carte de valeurs :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
params = %{page: 1, direction: "asc"}

~p"/posts?#{params}"
Comme les segments de chemin, les paramètres des chaînes de requête sont correctement encodés dans l'URL et peuvent être interpolés directement dans la chaîne ~p.

Une fois que vous aurez essayé cette nouvelle fonctionnalité, vous ne pourrez plus revenir aux router helpers. Les nouveaux générateurs phx.gen.html|live|json|auth utilisent des routes vérifiées.

Générateurs Tailwind basés sur des composants

Phoenix 1.7 est livré avec TailwindCSS par défaut, sans dépendance avec nodejs sur le système. TailwindCSS est le meilleur moyen que j'ai trouvé pour styliser les interfaces en 20 ans de développement web. Son approche axée sur l'utilité est bien plus facile à maintenir et plus productive que n'importe quel système ou framework CSS que j'ai utilisé. Son approche colocalisée s'aligne parfaitement avec le composant de fonction et le paysage LiveView.

L'équipe de Tailwind a également généreusement conçu la nouvelle page d'accueil du projet, les pages CRUD et les pages du système d'authentification pour les nouveaux projets, vous donnant un point de départ de première classe et poli pour la construction de vos applications.

Un nouveau projet phx.new contiendra un module CoreComponents, abritant un ensemble de composants d'interface utilisateur tels que des tables, des fenêtres modales, des formulaires et des listes de données. La suite de générateurs Phoenix (phx.gen.html|live|json|auth) utilise les composants de base. Cela présente un certain nombre d'avantages intéressants.

Tout d'abord, vous pouvez personnaliser les composants de base de l'interface utilisateur en fonction de vos besoins, de vos conceptions et de vos goûts. Si vous souhaitez utiliser Bulma ou Bootstrap au lieu de Tailwind, pas de problème ! Remplacez simplement les définitions de fonctions dans core_components.ex par vos implémentations spécifiques au framework/UI et les générateurs continuent à fournir un excellent point de départ pour de nouvelles fonctionnalités, que vous soyez un débutant ou un expert chevronné construisant des fonctionnalités de produit sur mesure.

En pratique, les générateurs vous donnent des modèles qui utilisent vos composants de base, qui ressemblent à ceci :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<.header>

  New Post
  <:subtitle>Use this form to manage post records in your database.</:subtitle>
</.header>

<.simple_form for={@form} action={~p"/posts"}>
  <input field={@form[:title]} type="text" label="Title" />
  <input field={@form[:views]} type="number" label="Views" />

  <:actions>
    <.button>Save Post</.button>
  </:actions>
</.simple_form>

<.back navigate={~p"/posts"}>Back to posts></.back>
Nous aimons ce que l'équipe de Tailwind a conçu pour les nouvelles applications, mais nous sommes également impatients de voir la communauté publier ses propres remplacements de core_components.ex pour les différents frameworks de son choix.

Composants de fonction unifiés pour les contrôleurs et les LiveViews

Les composants de fonction fournis par HEEx, avec des affectations et des emplacements déclaratifs, constituent un changement radical dans la manière dont nous écrivons du HTML dans les projets Phoenix. Les composants de fonction fournissent des blocs de construction de l'interface utilisateur, permettant aux fonctionnalités d'être encapsulées et mieux étendues par rapport à l'approche précédente des modèles dans Phoenix.View. Vous bénéficiez d'une manière plus naturelle d'écrire des balises dynamiques, d'une interface utilisateur réutilisable qui peut être étendue par l'appelant, et de fonctions de compilation qui font de l'écriture d'applications HTML une expérience de premier ordre.

Les composants de fonction apportent une nouvelle façon d'écrire des applications HTML dans Phoenix, avec de nouveaux ensembles de conventions. En outre, les utilisateurs ont eu du mal à combiner les fonctionnalités Phoenix.View basées sur les contrôleurs avec les fonctionnalités Phoenix.LiveView dans leurs applications. Les utilisateurs se sont retrouvés à écrire render("table", user : user) dans des modèles basés sur des contrôleurs, tandis que leurs LiveViews utilisaient les nouvelles fonctionnalités <.table rows={@users}>. Il n'existait pas de moyen efficace de partager les approches dans une application.

Pour ces raisons, l'équipe de Phoenix a unifié les approches de rendu HTML, que ce soit à partir d'une requête de contrôleur ou d'une LiveView. Ce changement nous a également permis de revoir les conventions et de nous aligner sur l'approche LiveView qui consiste à regrouper les modèles et le code de l'application.

Les nouvelles applications (et les générateurs phx) suppriment Phoenix.View en tant que dépendance en faveur d'une nouvelle dépendance Phoenix.Template, qui utilise des composants de fonction comme base pour tous les rendus dans le framework.

Vos contrôleurs restent les mêmes :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
defmodule AppWeb.UserController do
  use MyAppWeb, :controller

  def index(conn, _params) do
    users = ...
    render(conn, :index, users: users)
  end
end
Mais au lieu que le contrôleur appelle AppWeb.UserView.render("index.html", assigns), nous allons d'abord chercher un composant de fonction index/1 sur le module de vue, et l'appeler pour le rendu s'il existe. De plus, nous avons également renommé le module de vue infléchi pour qu'il recherche AppWeb.UserHTML, ou AppWeb.UserJSON, et ainsi de suite pour une approche vue par format pour le rendu des modèles. Tout ceci est fait de manière rétrocompatible, et est basé sur les options d'utilisation de use Phoenix.Controller.

Tout le rendu HTML est alors basé sur des composants de fonction, qui peuvent être écrits directement dans un module, ou intégrés à partir d'un fichier externe avec la nouvelle macro embed_templates fournie par Phoenix.Component. Votre module PageHTML dans une nouvelle application ressemble à ceci :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
defmodule AppWeb.PageHTML do
  use AppWeb, :html

  embed_templates "page_html/*"
end
La nouvelle structure de répertoire ressemblera à quelque chose comme ceci :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
lib/app_wb
├── controllers
│   ├── page_controller.ex
│   ├── page_html.ex
│   ├── error_html.ex
│   ├── error_json.ex
│   └── page_html
│       └── home.html.heex
├── live
│   ├── home_live.ex
├── components
│   ├── core_components.ex
│   ├── layouts.ex
│   └── layouts
│       ├── app.html.heex
│       └── root.html.heex
├── endpoint.ex
└── router.ex
Votre rendu basé sur les contrôleurs ou sur LiveView partage désormais les mêmes composants de fonction et les mêmes dispositions. Que vous exécutiez phx.gen.html, phx.gen.live ou phx.gen.auth, les nouveaux modèles générés utilisent tous vos définitions components/core_components.ex.

En outre, nous avons placé les modules de vue à côté de leurs fichiers de contrôle. Cela présente les mêmes avantages que la colocalisation LiveView : les fichiers fortement couplés vivent ensemble. Les fichiers qui doivent changer ensemble vivent désormais ensemble, qu'il s'agisse d'écrire des fonctionnalités LiveView ou des fonctionnalités de contrôleur.

Ces changements avaient pour but d'améliorer l'écriture des applications HTML, mais ils ont également simplifié le rendu d'autres formats, comme JSON. Par exemple, les modules de vue basés sur JSON suivent les mêmes conventions - Phoenix cherchera d'abord une fonction index/1 lors du rendu du modèle d'index, avant d'essayer render/2. Cela nous a permis de simplifier le rendu JSON en général et de supprimer des concepts tels que Phoenix.View.render_one|render_many.

Par exemple, voici une vue JSON générée par phx.gen.json :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
defmodule AppWeb.PostJSON do
  alias AppWeb.Blog.Post

  @doc """
  Renders a list of posts.
  """
  def index(%{posts: posts}) do
    %{data: for(post <- posts, do: data(post))}
  end

  @doc """
  Renders a single post.
  """
  def show(%{post: post}) do
    %{data: data(post)}
  end

  defp data(%Post{} = post) do
    %{
      id: post.id,
      title: post.title
    }
  end
end
Remarquez qu'il s'agit simplement de fonctions Elixir normales - comme il se doit !

Ces fonctionnalités fournissent un modèle de rendu unifié pour les applications qui vont de l'avant avec une manière nouvelle et améliorée d'écrire des interfaces utilisateur, mais elles s'écartent des pratiques précédentes. La plupart des grandes applications établies sont probablement mieux servies en continuant à dépendre de Phoenix.View.

Streams LiveView

LiveView comprend désormais une interface de flux permettant de gérer de grandes collections dans l'interface utilisateur sans avoir à stocker les collections en mémoire sur le serveur. Avec quelques appels de fonction, vous pouvez insérer de nouveaux éléments dans l'interface utilisateur, les ajouter ou les ajouter dynamiquement, ou les réorganiser sans les recharger sur le serveur.

Le générateur CRUD phx.gen.live live de Phoenix 1.7 utilise des flux pour gérer votre liste d'éléments. Cela permet la saisie de données, les mises à jour et les suppressions sans jamais avoir besoin de récupérer la liste des éléments après le chargement initial. Voyons comment.

Le module PostLive.Index suivant est généré lorsque vous exécutez mix phx.gen.live Blog Post posts title views:integer
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
defmodule DemoWeb.PostLive.Index do

  use DemoWeb, :live_view


  alias Demo.Blog
  alias Demo.Blog.Post

  @impl true
  def mount(_params, _session, socket) do
    {:ok, stream(socket, :posts, Blog.list_posts())}
  end

  ...
end
Remarquez qu'au lieu de l'habituel assign(socket, :posts, Blog.list_posts()), nous avons une nouvelle interface stream/3. Cela permet de configurer le flux avec la collection initiale de billets. Ensuite, dans le modèle index.html.heex généré, nous consommons le flux pour rendre la table des billets :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<.table
  id="posts"
  rows={@streams.posts}
  row_click={fn {_id, post} -> JS.navigate(~p"/posts/#{post}") end}
>
  <:col :let={{_id, post}} label="Title"><%= post.title %></:col>
  <:col :let={{_id, post}} label="Views"><%= post.views %></:col>
  <:action :let={{_id, post}}>
    <div class="sr-only">
      <.link navigate={~p"/posts/#{post}"}>Show</.link>
    </div>
    <.link patch={~p"/posts/#{post}/edit"}>Edit</.link>
  </:action>
  <:action :let={{id, post}}>
    <.link
      phx-click={JS.push("delete", value: %{id: post.id}) |> hide("##{id}")}
      data-confirm="Are you sure?"
    >
      Delete
    </.link>
  </:action>
</.table>
Cela ressemble beaucoup à l'ancien modèle, mais au lieu d'accéder à l'assignation @posts nue, nous passons @stream.posts à notre table. Lors de la consommation d'un flux, nous recevons également l'identifiant DOM du flux, ainsi que l'élément.

De retour sur le serveur, nous pouvons voir à quel point il est simple d'insérer de nouveaux éléments dans la table. Lorsque notre FormComponent généré met à jour ou enregistre un article via le formulaire, nous envoyons un paquet de messages au LiveView parent PostLive.Index à propos de l'article nouveau ou mis à jour :

PostLive.FormComponent :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
defmodule DemoWeb.PostLive.FormComponent do
  ...
  defp save_post(socket, :new, post_params) do
    case Blog.create_post(post_params) do
      {:ok, post} ->
        notify_parent({:saved, post})

        {:noreply,
         socket
         |> put_flash(:info, "Post created successfully")
         |> push_patch(to: socket.assigns.patch)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign_form(socket, changeset)}
    end
  end

  defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
end
Nous récupérons ensuite le message dans une clause PostLive.Index handle_info :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
@impl true
def handle_info({DemoWeb.PostLive.FormComponent, {:saved, post}}, socket) do
  {:noreply, stream_insert(socket, :posts, post)}
end
Ainsi, le formulaire nous indique qu'il a sauvegardé un message, et nous insérons simplement le message stream_insert dans notre flux. C'est tout ! Si un message existe déjà dans l'interface utilisateur, il sera mis à jour sur place. Sinon, il est ajouté au conteneur par défaut. Vous pouvez également ajouter au conteneur avec stream_insert(socket, :posts, post, at : 0), ou passer n'importe quel index à :at pour une insertion arbitraire d'éléments ou un réordonnancement.

Les flux ont été l'un des derniers blocs de construction sur notre chemin vers LiveView 1.0 et je suis super content de ce que nous avons obtenu.

Nouvelle structure de données pour les champs de formulaire

Nous sommes tous familiers avec les primitives de formulaire Phoenix.HTML de <.form for={@changeset}>, où le formulaire prend une structure de données qui implémente le protocole Phoenix.HTML.FormData et renvoie un %Phoenix.HTML.Form{}. L'un des problèmes de notre approche est que la structure de données du formulaire ne pouvait pas suivre les modifications des champs individuels du formulaire. Cela rendait les optimisations impossibles dans LiveView, où nous devions redonner un nouveau rendu et renvoyer le formulaire pour chaque changement individuel. Avec l'introduction de Phoenix.HTML.FormData.to_form et Phoenix.Component.to_form, nous disposons désormais d'une structure de données %Phoenix.HTML.FormField{} pour les changements de champs individuels.

Les nouveaux générateurs phx.gen.live et votre core_components.ex tirent parti de ces nouveaux ajouts.

Prochaines étapes pour Phoenix et LiveView

Les générateurs Phoenix utilisent les dernières fonctionnalités de LiveView, qui continueront à se développer. Avec les collections en streaming par défaut, nous pouvons aller vers des fonctionnalités plus avancées pour nos générateurs CRUD en direct dans phx.gen.live. Par exemple, nous prévoyons d'introduire des interfaces utilisateur synchronisées prêtes à l'emploi pour les ressources. Les fonctionnalités des formulaires générés par Phoenix continueront d'évoluer avec l'ajout de la nouvelle interface to_form.

Pour LiveView, to_form nous a permis de livrer la base de formulaires optimisés. Désormais, une modification individuelle d'un champ du formulaire produira un formulaire optimisé.

Suite à ce travail d'optimisation, la principale caractéristique restante de LiveView 1.0 est une API de formulaire étendue qui prend mieux en charge les entrées de formulaire dynamiques, les formulaires de style assistant et la délégation des entrées de formulaire aux LiveComponents enfants.

Support de serveurs web alternatifs

Grâce au travail de Mat Trudel, nous avons maintenant la base pour un support de serveur web de première classe dans Plug et Phoenix, permettant à d'autres serveurs web comme Bandit d'être échangés dans Phoenix tout en profitant de toutes les fonctionnalités comme WebSockets, Channels, et LiveView. Restez à l'écoute du projet Bandit si vous êtes intéressé par un serveur HTTP en Elixir pur ou essayez-le dans vos propres projets Phoenix !

Prochaines étapes

Comme toujours, des guides de mise à jour étape par étape sont disponibles pour faire passer vos applications 1.6.x à la 1.7.

Retrouvez-nous sur elixir slack ou sur les forums si vous avez des problèmes.

Bon codage !

-Chris

Source : Phoenix Framework

Et vous ?

Que pensez-vous des nouvelles fonctionnalités proposées par cette version Phoenix ?

Voir aussi :

La version 1.9 d'Elixir est publiée avec l'ajout de nouvelles fonctionnalités et une configuration améliorée

La version 3.0 de Tailwind CSS, le framework qui permet de créer rapidement des sites Web modernes, est disponible, il apporte des gains de performance incroyables

La version 3.2 du framework Django est disponible, avec la découverte automatique d'AppConfig, elle apporte de nouveaux décorateurs pour le module d'administration

La version 3.2 du langage de programmation Ruby est disponible, elle apporte de nombreuses fonctionnalités et améliore les performances