Fusion ou l'art d'intégrer PHP dans des composants Vue

Cet article est vraiment spécial.

Il y a deux semaines, les 3 et 4 février 2025, Laracon EU a eu lieu à Amsterdam. Bien que je n'ai pas pu y assister en personne, j'ai suivi l'événement entier en direct. Les conférences offrent toujours une occasion fantastique d'acquérir de nouvelles connaissances et de s'immerger davantage dans le merveilleux écosystème Laravel. J'espère assister à Laracon un jour.

Une des présentations qui a retenu mon attention était "Introducing [REDACTED]: A New Paradigm for Laravel + Javascript" par Aaron Francis. Cela m'a intrigué puisqu'il existe déjà Inertia et Livewire, qui sont d'excellents outils pour créer des applications modernes avec Laravel. Quelles nouvelles avancées pouvaient-elles alors être proposées ?

Miniature de la vidéo "Introducing REDACTED: a new paradigm for Laravel + Javascript" par Aaron Francis
Miniature de la vidéo "Introducing [REDACTED]: a new paradigm for Laravel + Javascript" par Aaron Francis

Aaron a présenté Fusion, décrit comme une approche novatrice pour intégrer votre frontend moderne avec votre backend Laravel. Le concept permet d'écrire du code PHP directement dans un fichier frontend, et à travers un certain traitement, le code PHP est exécuté côté serveur, avec les résultats envoyés au frontend. Cela rappelle Inertia, mais poussé beaucoup plus loin.

Note

Veuillez visionner la vidéo. C'est fascinant, facile à suivre et cela aidera considérablement à comprendre le reste de cet article.

Lors d'une interview sur le podcast Navbar, Aaron a articulé l'objectif de Fusion : combler le fossé entre le frontend et le backend, simplifiant le développement et transformant Laravel avec Inertia pour ressembler à des méta-frameworks actuels comme Next ou Nuxt. Avec son partenaire commercial Steve, Aaron a découvert que les conventions Laravel, comme les routes dans web.php, n'étaient pas intuitives pour les développeurs JavaScript qui préfèrent le routage basé sur des fichiers. L'objectif d'Aaron est de rendre Laravel plus accessible aux développeurs JavaScript.

Avec Fusion, un nouveau bloc est introduit dans le SFC de Vue pour écrire du code PHP, en étant une partie intégrante de l'application Laravel.

vue
<php>
  // Définir une prop en PHP
  $name = prop(Auth::user()->name);
</php>

<template>
  <!-- Utilisez-le dans Vue ! -->
  Bonjour {{ name }} !
</template>

Cet article n'a pas pour but de déterminer si Fusion devrait être utilisé en production ou si l'intégration de PHP dans un SFC de Vue est conseillée. Ce débat est bien couvert, notamment sur X ou Reddit.

Au lieu de cela, je compte explorer le fonctionnement de Fusion pour deux raisons principales :

  1. Il y a beaucoup à apprendre en examinant le code. Très honnêtement, je ne lis pas beaucoup de livres, mais je parcoure le code des projets, volant des astuces et des manières de faire négligés ailleurs.
  2. Fusion incarne un sommet technique de ce qui est réalisable avec Laravel, Vue, et Vite. Mon désir de comprendre son fonctionnement m'a conduit à rédiger cet article. En ce moment, j'étudie également la bibliothèque Alien Signals de Johnson Chu de la même manière. Comprendre quelque chose en profondeur implique souvent de l'expliquer aux autres, d'où cet article.

Note

L'intégration de Vite avec Laravel est exceptionnelle, offrant de nombreux avantages aux développeurs Laravel. Si vous n'avez pas encore exploré Vite, vous devriez absolument le faire.

Maintenant, plongeons dans le code.

Note

Lire la base de code est fascinant ; cela ressemble à lire du PHP écrit en JavaScript.

Le code de Fusion

Une semaine après sa présentation à Laracon EU, Aaron a rendu la base de code de Fusion publique. Curieux de ses mécanismes, j'ai supposé qu'un plugin Vite pourrait extraire le PHP d'un SFC de Vue, de manière similaire à la façon dont le plugin Vue de Vite gère les balises script, template et style.

Le support pour HTML, CSS et JavaScript est simple pour Vite puisqu'ils sont gérés nativement par Vite. Cependant, le PHP pose un défi car il n'est pas pris en charge.

Pour mieux comprendre l'organisation de l'article, posons-nous quelques questions. D'abord : comment Fusion gère-t-il le code PHP ?

Vidéo Hype de Fusion par Aaron Francis et Steve Tenuto, disponible sur X.

En regardant la vidéo hype, il est évident qu'il y a plus que simplement extraire le code PHP et l'exécuter. Fusion passe les résultats du code PHP en tant que props au composant Vue et prend en charge le RPC sur le backend Laravel depuis le client. De plus, le mot-clé sync permet la synchronisation entre les états frontend et backend.

Par exemple, une fonction appelée favorite devient facilement utilisable dans le frontend :

vue
<php>
use function \Fusion\{prop, expose};
use \App\Models\Podcast;

$podcasts = prop(fn () => Podcast::all())->readonly();

expose(favorite: fn (Podcast $podcast) => response()->json($podcast->toggleFavorite()));
</php>

<script lang="ts" setup>
import Podcast from '@Components/Podcast.vue'
</script>

<template>
  <Podcast
    v-for="podcast in podcasts"
    :key="podcast.id"
    v-bind="podcast"
    @favorite="favorite({ podcast }).then(fav => podcast.favorited = fav)"
  />
</template>

Une autre question se pose : que fait Fusion en coulisses pour permettre cette fonctionnalité ?

Enfin, il est dit que Fusion repousse Inertia au-delà de ses limites actuelles. Donc, une autre question : comment Fusion exploite-t-il Inertia ?

Répondons à ces questions une par une.

  1. Comment Fusion gère-t-il le code PHP ?
  2. Quels mécanismes Fusion emploie-t-il en coulisses pour permettre le RPC ?
  1. De quelle manière Fusion utilise-t-il Inertia ?

Prêts pour une exploration ? Commençons.

Comment Fusion gère-t-il le code PHP ?

Tout commence avec Vite. Comme d'habitude.

Pour ceux qui ne le connaissent pas, Vite est un outil de build frontend remarquablement rapide qui alimente la prochaine génération d'applications web. Il a introduit une nouvelle philosophie connue sous le nom de philosophie à la demande. Essentiellement, Vite agit comme un proxy entre le navigateur, qui demande des fichiers, et le système de fichiers qui les contient. Lorsque le navigateur demande un fichier, Vite intercepte la requête, récupère le fichier et le renvoie. Cependant, agir comme un proxy permet à Vite des capacités supplémentaires, telles que la transformation de fichiers, l'injection de code, la récupération de fichiers distants et la génération de fichiers virtuels (fichiers non présents sur le système de fichiers). C'est semblable à un pipeline. Sans aucun doute, l'avènement de Vite a été transformateur pour l'écosystème frontend. En ajoutant des plugins à son pipeline, il peut être adapté à n'importe quel besoin.

Avec cela en tête, nous pouvons comprendre comment un fichier SFC subit un traitement par Vite.

vue
<script setup>
// Le code JavaScript ira ici
</script>

<template>
  <div />
  <!-- Votre code de modèle ira ici -->
</template>

<style scoped>
  /* Votre code CSS ira ici */
</style>

Lorsqu'une requête de fichier .vue atteint le navigateur, un plugin Vite nommé vite-plugin-vue transforme le fichier en traitant le script et le modèle pour en faire du code JavaScript lisible. Le CSS est extrait et envoyé dans le pipeline CSS avant d'être dispatché au navigateur. L'aspect intéressant est la possibilité de créer un plugin capable d'examiner le pipeline de transformation. Mieux encore, il n'est pas nécessaire de le construire de toutes pièces ; c'est exactement ce que fait le plugin vite-plugin-inspect.

Par exemple, la transformation d'un SFC de Vue ressemble à ceci :

Transformation d'un SFC de Vue vue à l'aide du plugin vite-plugin-inspect
Transformation d'un SFC de Vue vue à l'aide du plugin vite-plugin-inspect

D'accord, merci pour l'explication, mais que se passe-t-il dans Fusion ? Exactement la même chose mais pour le bloc PHP. Lorsqu'une requête contacte le serveur Vite en utilisant un fichier .vue, un plugin nommé fusion-vue extrait le bloc PHP. Ce processus commence dans la fonction transform ci-dessous :

ts
function transform(code, filename) {
  if (!filename.endsWith('.vue')) {
    return code
  }

  return new Transformer({
    config: fusionConfig,
    code,
    filename,
    isProd
  }).transform()
}

La fonction transform forme le cœur du pipeline de Vite. Elle transforme les fichiers avant qu'ils n'atteignent le navigateur. Dans le cas de Fusion, elle passe le fichier (l'intégralité du contenu) à une classe nommée Transformer, qui gère la transformation du fichier. Vous pourriez remarquer la condition filename.endsWith('.vue'), signifiant que la transformation ne s'applique qu'aux fichiers .vue. Cette approche est un modèle courant parmi les plugins Vite.

La classe Transformer est responsable de :

  1. Extraire le bloc PHP du SFC de Vue. Il utilise ici un parseur de @vue/compiler-sfc pour accomplir cette tâche. Notamment, le plugin vite-plugin-vue utilise ce même parseur, illustrant qu'ajouter un bloc personnalisé à un SFC de Vue est simple. J'ai déjà observé cette pratique ; le projet Vue I18n supporte un bloc personnalisé i18n spécifiant des traductions au sein d'un SFC de Vue.
js
import { parse } from '@vue/compiler-sfc'

const descriptor = parse(code, { filename }).descriptor
const php = descriptor.customBlocks.find(block => block.type === 'php')
Le SFC de Vue transformé dans le pipeline Vite
Le SFC de Vue transformé dans le pipeline Vite
  1. Supprimer le bloc PHP du SFC de Vue.
  2. Déterminer si le code PHP mérite d'être persisté. Un autre aspect critique que nous examinerons dans la section suivante.
  3. Valider et sauvegarder le code PHP. Cette vérification se fait via un processus externe exécutant une commande Laravel.
ts
await runPhp(['fusion:conform', this.relativeFilename])

Tout code PHP comportant des erreurs déclenche un message d'erreur interprété par Vite, si précisément qu'il s'affiche dans le navigateur pour indiquer une erreur dans le bloc de code PHP.

L'overlay d'erreur Vite montrant l'erreur dans le code PHP
L'overlay d'erreur Vite montrant l'erreur dans le code PHP

Si le fonctionnement se déroule sans accroc, le code PHP, stocké en tant que fichier extrait, est sauvegardé dans le dossier storage. Cet aspect est à notable, car nous allons l'explorer davantage dans la section suivante.

  1. Création d'un fichier JavaScript basé sur le code PHP qui fait le pont entre les besoins du frontend et le code du bloc PHP. Cette étape intéressante nécessite une exploration plus approfondie.

Encore une fois, une commande Laravel est invoquée durant cette étape :

ts
await runPhp(['fusion:shim', this.relativeFilename])
  1. Enfin, elle renvoie le code transformé à Vite, permettant au SFC de poursuivre son chemin.

Voici une illustration de la transformation du SFC de Vue :

Schéma montrant toutes les étapes que nous avons vues jusqu'à présent
Schéma montrant toutes les étapes que nous avons vues jusqu'à présent

Dans Fusion, les fichiers SFC de Vue fonctionnent comme un routeur basé sur des fichiers à l'instar du mécanisme de Nuxt. La première requête atteint généralement le serveur PHP, qui répond avec le code initial côté client, lequel vise ensuite le serveur Vite pour des requêtes de ressources supplémentaires. Cependant, si une route SFC de Vue n'a pas été visitée, la fonctionnalité PHP correspondante ne sera pas prête à la gérer, résultant en une erreur 404 du serveur Laravel. En conséquence, le serveur Vite reste intouché, entravant le processus de transformation. Cela pose un obstacle, un énorme.

Pour comprendre pleinement comment ce problème est abordé, nous devons examiner tous les aspects du plugin Vite.

Dans la configuration Vite, nous trouvons deux caractéristiques notables :

  1. La configuration Vite où Vite reconnaît l'emplacement pour trouver le fichier JavaScript généré (le shim), qui reçoit un alias attribué dans le résolveur Vite. Cet alias obscurcit l'emplacement du fichier, encourageant les meilleures pratiques comme éviter les chemins codés en dur et rendant l'emplacement du code agnostique aux changements de chemin. Cette configuration liée à Vite provient d'un fichier config PHP pour éliminer les configurations redondantes, donc le plugin Vite Fusion engendre un processus exécutant une commande Laravel pour résoudre les nécessités de configuration.
  2. Un observateur ajouté pour surveiller les changements dans l'ensemble du projet utilisant l'observateur Vite, une instance Chokidar.
ts
function configureServer(server) {
  server.watcher.on('all', (event, filename) => {
    // Faites ce que vous voulez
  })
}

Cet observateur prend en charge deux fonctions :

  1. Nettoyer la base de données et le dossier de stockage lors de la suppression de fichiers ou de dossiers. Le dossier de stockage contient les fichiers SFC de Vue transformés, servant de routeur basé sur des fichiers. Sans suppression, l'utilisateur peut toujours accéder aux fichiers supprimés.
  2. Déclencher la transformation de fichiers. Cela fonctionne de manière identique à la fonction transform rencontrée précédemment, nécessitant un chargement manuel de fichiers. Seuls les fichiers SFC de Vue subissent une transformation.
ts
function configureServer(server) {
  server.watcher.on('all', async (event, filename) => {
    if (filename.endsWith('.vue')) {
      const fileContent = fs.readFileSync(filename, 'utf-8')

      await new Transformer({
        config: fusionConfig,
        code: fileContent,
        filename,
        isProd
      }).transform()
    }
  })
}

Mais pourquoi transformer des fichiers lors de leur création ou modification ? Cette approche semble contredire la philosophie à la demande et la fonction transform définie précédemment. Pourtant, une justification existe.

Considerons qu'une erreur se produise dans le bloc PHP, comme un oubli dans l'importation d'une fonction ou d'une classe, ou en tentant de visiter une route pour la première fois. Lorsque la page est rechargée, le serveur PHP renvoie un message d'erreur, comme Laravel le fait par défaut. Cependant, corriger le bloc PHP du SFC de Vue ne rectifie pas l'erreur du serveur PHP puisque la transformation Vite ne déclenchera pas à nouveau. En conséquence, appuyer à plusieurs reprises sur cmd+r ne sert à rien.

Pour cette raison, le système de fichiers doit être surveillé pour les changements SFC de Vue, déclenchant une transformation manuelle. Bien que suboptimale, cette méthode est nécessaire. Aaron a conçu une stratégie priorisant les transformations via la méthode transform sur celles déclenchées par l'observateur, avec application d'un throttling sur l'observateur.

C'est également pour résoudre le problème vu précédemment : lorsque le serveur PHP est touché en premier, le fichier PHP généré n'est pas encore créé.

Résumons nos apprentissages jusqu'à présent avec ce schéma :

Un schéma montrant toutes les étapes qui font fonctionner Fusion.
Un schéma montrant toutes les étapes qui font fonctionner Fusion.

Bien que nous n'ayons pas complètement couvert chaque composant, ce diagramme aide notre compréhension du reste de l'article.

Note

Assurez-vous que le serveur Vite fonctionne correctement pour que tout fonctionne sans accroc.

Cette exploration nous a profondément engagés dans le plugin Vite de Fusion. Passons maintenant à comprendre comment Fusion organise la gestion de son code PHP.

Note

Vous avez des questions ? Demandez-moi tout ou laissez un commentaire ci-dessous. J'aime vraiment lire vos retours !

Que fait Fusion en coulisses pour rendre cela possible ?

Plongeons dans les opérations côté PHP.

Nous avons vu le code PHP extrait, validé et sauvegardé sur le disque. Nous devons maintenant dénouer deux étapes cruciales pour mieux comprendre le fonctionnement de Fusion :

  1. Déterminer si le code PHP doit être persistant.
  2. Sauvegarder le code PHP dans le système de fichiers.

Pour faciliter la compréhension, abordons d'abord la deuxième étape. La première étape deviendra ensuite claire.

Écriture du code PHP sur le système de fichiers

Une fois extrait du SFC de Vue, le code PHP doit être persistant sur le système de fichiers. Ce processus est crucial puisque le PHP écrit au sein du SFC de Vue devient une partie intégrante de l'application Laravel, non pas simplement un script PHP indépendant. Ce fichier généré est essentiel pour que l'application Laravel réponde aux requêtes entrantes.

Après l'extraction, le code PHP est placé dans un fichier temporaire. Ce fichier temporaire maintient le contenu exact du code inhérent au SFC de Vue. Par la suite, Fusion crée une entrée dans une base de données SQLite dédiée. Nous reviendrons sur cette base de données plus tard, en comprenant qu'elle facilite la communication entre les processus PHP et JavaScript.

Enfin, le plugin Vite déclenche la commande fusion:conform.

La commande fusion:conform

Nous avons retardé la compréhension de cette commande ; il est maintenant temps de clarifier. Dans la commande fusion:conform—une commande Laravel écrite en PHP—ni Vite ni JavaScript ne s'appliquent, strictement du PHP.

Lors de l'invocation, cette commande utilise un argument src représentant le chemin du fichier SFC de Vue. Agissant comme un identifiant unique, elle permet à Fusion de récupérer l'entrée de la base de données pertinente.

Cette entrée contient le chemin de destination pour le fichier PHP final qui sera utilisé par l'application Laravel. Actuellement, ce fichier n'existe pas ; un fichier temporaire conserve seulement le code. Ainsi, l'étape suivante consiste à lire le contenu du fichier temporaire avant un traitement ultérieur.

Le processus suivant mobilise une classe conformante, chargée de :

  1. Modifier le code pour l'adapter à l'application Laravel.
  2. S'assurer de la validité du code en le passant par un parseur PHP.

La fonction examine la conformité du code PHP en le faisant passer par une séquence de classes de transformation.

Chaque classe de transformer étend une super-classe Transformer, conçue sur mesure pour étendre NodeVisitorAbstract et met en œuvre l'interface TransformerInterface provenant de PHP Parser.

php
<?php

namespace Fusion\Conformity\Transformers;

abstract class Transformer extends NodeVisitorAbstract implements TransformerInterface
{
    public function __construct(
        protected ?string $filename = null
    ) {}
}

Chaque transformateur personnalisé, de plus, implémente la méthode enterNode pour faire avancer la transformation du code.

php
<?php

namespace Fusion\Conformity\Transformers;

class PropsTransformer extends Transformer
{
    public function enterNode(Node $node)
    {
        if ($node instanceof MethodCall) {
            // Effectuer une opération
        }
    }
}

Note

Une exploration détaillée des transformateurs ou des parseurs PHP est en dehors du champ d'application de cet article car ils offrent une fonctionnalité avancée pas immédiatement nécessaire pour comprendre le fonctionnement de Fusion.

Au total, dix transformateurs sont impliqués, chacun ayant une responsabilité spécifique. Concevez-les comme des étapes au sein d'un pipeline—un modèle conventionnel en programmation conçu pour décomposer des tâches complexes en segments plus petits et abordables. Par conséquent, lorsque le code utilisateur subit une transformation et est rendu compatible avec l'application Laravel, la sortie d'un transformateur devient l'entrée du suivant.

txt
Code PHP du SFC de Vue
    | |
[Transformateur procédural]: Transforme le code procédural en une méthode de classe anonyme intitulée runProceduralCode.
[Transformateur de props]: Change les appels de fonction prop() en appels de méthode de cet objet dans la classe.
[Transformateur d'importation de fonction]: Supprime les importations de fonction inutiles, spécifiquement celles connues à l'avance.
[Transformateur de valeur de prop]: Spécifie que les chaînes d'appels de méthode $this->prop() se terminent par ->value().
[Transformateur d'exposition]: Modifie les appels de fonction expose() en appels de méthode de cet objet, excluant les affectations de variable.
[Transformateur de montage]: Valide et modifie les appels de fonction mount(), s'assurant qu'il n'y ait qu'un seul argument fonction anonyme.
[Transformateur de découverte de prop]: Découvre et documente les noms de propriétés des fonctions prop() rencontrées.
[Transformateur de découverte d'action]: Découvre et construit des méthodes pour les gestionnaires d'action exposés via expose().
[Transformateur de retour anonyme]: Garantit des retours d'expression directs pour les retours de classe anonyme.
[Transformateur de classe anonyme]: Convertit les classes anonymes en classes nommées, dans un espace de noms généré, rassemblant les exigences nécessaires.
    | |
Code PHP utilisable

Considérons un exemple concret avant de continuer. Supposons que le code PHP suivant existe au sein du SFC de Vue :

vue
<php>
use function \Fusion\{prop};

$name = prop('bienvenue');

$name = strtoupper($name);
</php>

Après avoir passé par la transformation, cela donne :

php
<?php

/**
 * Fichier auto-généré par Fusion.
 * Les modifications ne sont pas conseillées.
 */
namespace Fusion\Generated\Pages;

class IndexGenerated extends \Fusion\FusionPage
{
    #[\Fusion\Attributes\ServerOnly]
    public array $discoveredProps = ['name'];
    use \Fusion\Concerns\IsProceduralPage;
    public function runProceduralCode()
    {
        $name = $this->prop(name: 'name', default: 'bienvenue')->value();
        $name = strtoupper($name);
        $this->syncProps(get_defined_vars());
    }
}

Cette classe IndexGenerated sera utilisée par l'application Laravel lors du traitement des requêtes à la route '/'. Visualisez-la comme un mini-contrôleur contenant le code utilisateur. Dans les coulisses, une grande partie de la complexité est abstraite dans la classe FusionPage.

Le tableau discoveredProps enregistre les noms des propriétés traitées lors des appels de fonction prop(), assurant la synchronisation entre les propriétés frontend et backend.

La méthode runProceduralCode, invoquée lors du chargement de la page, exécute le code utilisateur aux côtés du code généré par Fusion, garantissant la synchronisation des props entre le frontend et le backend.

La méthode syncProps maintient la synchronisation des propriétés intérieur-extérieur. Cela récupère les variables définies dans la méthode, et le contenu du tableau discoveredProps, garantissant que la propriété props générée contient uniquement les données requises par l'utilisateur.

Note

Utiliser get_defined_vars—une fonction PHP intégrée—retourne un tableau de variables représentatives de la portée actuelle. Dans ce cas, cela constitue ['name' => 'bienvenue'].

Note

#[ServerOnly] classe les propriétés pour une application exclusivement côté serveur. Analogues décorateurs TypeScript fonctionnent de manière similaire sous reflect-metadata.

Enfin, Fusion place le contenu transformé dans le chemin de fichier fourni par la base de données, mettant à jour les entrées associées avec le nom législatif de la classe composée du nom d'espace et de la désignation de la classe. La procédure reste enfermée dans un bloc try/catch, prête à traiter toute erreur de transformation qui pourrait survenir. Lorsqu'une erreur se produit, un fichier temporaire est supprimé tandis qu'un message d'erreur est dirigé vers Vite, comme noté plus tôt.

À ce stade, notre système de fichiers apparaît maintenant comme suit :

    Une application Fusion

  • resources/js/Pages
  • [Podcast].vue
  • Index.vue
  • Search.vue
  • storages/fusion
  • fusion.sqlite

Ensuite, le plugin Vite reprend son opération, comme décrit dans la section suivante.

La base de données

Puisque nous avons mentionné une base de données au cours de cet article, nous avons abstenu de donner des détails jusqu'à présent. Il est temps de comprendre son but !

La base de données aide à établir la communication entre les processus PHP et JavaScript, stockant des informations essentielles à cette fin. D'un coup d'œil, nous pouvons discerner les données stockées et son utilisation.

idsrcphp_classphp_pathshim_pathphp_hash
1/resources/js/Pages/Index.vue\Fusion\Generated\Pages\IndexGenerated/storage/fusion/PHP/Pages/IndexGenerated.php/storage/fusion/JavaScript/Pages/Index.jsphp_1e182535deedd9b60e12c277bef58603
2/resources/js/Pages/Search.vue\Fusion\Generated\Pages\SearchGenerated/storage/fusion/PHP/Pages/SearchGenerated.php/storage/fusion/JavaScript/Pages/Search.jsphp_468b4badedbe0cb6ce4734fcc921ea5f
3/resources/js/Pages/[Podcast].vue\Fusion\Generated\Pages\DynamicPodcastGenerated/storage/fusion/PHP/Pages/DynamicPodcastGenerated.php/storage/fusion/JavaScript/Pages/[Podcast].jsphp_2ca978b1c880558f54c4a59ce7584e6c

Lors de la transformation des SFC de Vue et de l'extraction du code PHP, un calcul de hash se produit, enregistré dans la colonne php_hash de la base de données.

Le hash sert à vérifier si le code PHP a été modifié. Un hash correspondant signifie que le code PHP n'a pas changé, introduisant une optimisation pour omettre les transformations redondantes.

Note

Un hash est une fonction à sens unique générant une chaîne de caractères de taille fixe à partir d'une entrée, généralement composée de séquences numériques et alphanumériques. Un hash identifie de manière unique son entrée, signifiant que des hashes identiques indiquent des entrées identiques.

Ce dépôt de base de données incarne des données supplémentaires comme les chemins de fichiers PHP et JavaScript utilisés principalement pour gérer efficacement les requêtes entrantes.

Une question supplémentaire se pose : comment la base de données SQLite affecte-t-elle l'expérience développeur ?

Tout en utilisant une base de données SQLite, Fusion ne dicte pas le choix de la base de données de l'application Laravel : les deux restent entièrement distincts.

Le fournisseur de services de Fusion attribue une connexion de base de données appelée __fusion utilisant SQLite, qui n'interfère pas avec la connexion de la base de données de l'application Laravel. Cela signifie qu'il est possible d'avoir plusieurs connexions de base de données au sein d'une seule application Laravel. Vraiment intéressant à savoir !

php
public function boot()
{
  ConfigFacade::set('database.connections.__fusion', [
    'name' => '__fusion',
    'driver' => 'sqlite',
    'database' => Fusion::storage('fusion.sqlite'),
    'foreign_key_constraints' => true,
    'journal_mode' => 'OFF',
  ]);
}

Note

Un fournisseur de services Laravel s'exécute lors du démarrage de l'application pour enregistrer des services ou lier des classes dans le conteneur de services. C'est un lieu capital pour gérer et configurer des services.

Le modèle Component utilise désormais cette connexion :

php
<?php

namespace Fusion\Models;

use Illuminate\Database\Eloquent\Model;

class Component extends Model
{
    protected $connection = '__fusion';
}

Avec cette configuration, la classe Component utilise la connexion de base de données __fusion. Par conséquent, la base de données SQLite est uniquement utilisée par Fusion ; elle n'exerce aucun impact visible sur l'application Laravel, permettant à l'application de choisir librement son mode de connexion de base de données. C'est une stratégie simple et intelligemment mise en œuvre.

Gestion des requêtes entrantes

Plongeons dans peut-être le segment le plus intrigant de l'article : la gestion des requêtes entrantes via Fusion.

En commençant à lire, nous avons largement couvert le développement, incluant le fonctionnement du plugin Vite et la transformation PHP. Notre tâche consiste maintenant à comprendre comment Fusion harmonise ces éléments pour la gestion des requêtes.

Fusion adopte une approche de routeur basé sur des fichiers. Les SFC de Vue s'enregistrent automatiquement en tant que routes au sein de l'application Laravel, réalisé en appelant Fusion comme un facade, soutenu par le gestionnaire Fusion, qui est ensuite enregistré dans le FusionServiceProvider dans le cadre de la configuration habituelle des routes dans routes/web.php.

php
<?php

use Fusion\Fusion;

Fusion::pages();

Cela semble être toute l'explication mais continuons pour dévoiler davantage de complexités. Dans la méthode pages, tous les composants SFC de Vue situés dans resources/js/Pages sont localisés et enregistrés méticuleusement. Utiliser le routeur Laravel facilite l'implémentation.

php
Route::any($uri, [FusionController::class, 'handle'])
  ->defaults('__component', $component)
  ->defaults('__class', Component::where('src', $src)->first()?->php_class);

Notamment, le processus d'acheminement attribue deux valeurs par défaut :

  1. __component: Chemin menant au fichier SFC de Vue.
  2. __class: Nom de classe PHP pleinement qualifié—cette classe a été générée dans la commande fusion:conform.

Par conséquent, lorsqu'une requête accède à la route /, l'opération handle du FusionController s'active. À l'intérieur du contrôleur se trouve un système qui gère les différentes possibilités tout en synchronisant les actions, et en interagissant sans accroc avec le frontend.

Après la gestion des requêtes, une réponse appropriée est envoyée, guidée par la nature de la requête comme suit :

  1. Les requêtes HMR ou d'action reçoivent une réponse JSON.
  2. Les requêtes standards délèguent les tâches de rendu à Inertia.

L'attribut data-page révèle l'objet Inertia accompagné des composants Fusion associés :

html
<div id="app" data-page="{&quot;component&quot;:&quot;Index&quot;,&quot;props&quot;:{&quot;errors&quot;:{},&quot;auth&quot;:{&quot;user&quot;:null},&quot;fusion&quot;:{&quot;meta&quot;:[],&quot;state&quot;:{&quot;name&quot;:&quot;AARON&quot;},&quot;actions&quot;:[{&quot;handler&quot;:&quot;applyServerState&quot;,&quot;priority&quot;:40,&quot;_handler&quot;:&quot;Fusion\\Http\\Response\\Actions\\ApplyServerState&quot;}]}},&quot;url&quot;:&quot;\/&quot;,&quot;version&quot;:&quot;f7e418d032bf1c4cc3cc55d947cab622&quot;,&quot;clearHistory&quot;:false,&quot;encryptHistory&quot;:false}"></div>

L'objet fusion est d'un intérêt particulier. Il est composé de données initialisées dans le bloc PHP du SFC de Vue via la fonction prop ; comprenant state et actions injectées au client en conséquence. La classe PendingResponse, responsable de la gestion, catégorise spécifiquement ces opérations au sein de Fusion comme suit.

Spécifiquement, l'action applyServerState utilise :

js
import { ref, unref } from 'vue'

export default function applyServerState({ fusion, state }, next) {
  Object.keys(fusion.state).forEach((key) => {
    state[key] = ref(unref(fusion.state[key]))
  })

  return next({ fusion, state })
}

Les développeurs bénéficient d'exploiter automatiquement le traitement des actions par Fusion sans appels manuels.

Je n’ai pas encore tout révélé. En plus d'extraire du bloc PHP du SFC de Vue, Fusion injecte de la logique JavaScript dans le SFC de Vue, précédant la transformation. Ce qui a commencé comme un composant initial :

vue
<php>
use function \Fusion\{prop};

$name = prop('Aaron');
$name = strtoupper($name);
</php>

<template>
  Bonjour {{ name }} !
</template>

Se transforme durant la transformation du plugin Fusion (plus tard gérée par Vite) comme suit :

vue
<script setup>
import { useFusion } from '$fusion/Pages/Index.js'
import useHotFusion from '@fusion/vue/hmr'

// __props est une variable magique fournie au script setup par Vue.
const __fusionData = useFusion(['name', 'fusion'], __props.fusion)

const { name, fusion } = __fusionData

useHotFusion(__fusionData, { id: 'hot_07f7c82af56ef80f306d905892430461', hot: import.meta?.hot })
</script>

<template>
  Bonjour {{ name }} !
</template>

Notez le bloc script setup injecté synthétiquement, ne nécessitant aucun input manuel. Il présente un composable useFusion, acceptant deux arguments, le premier détaillant les clés de props nécessitant extraction (affinées par la reconnaissance des variables PHP via les appels à prop) et un argument props signifiant les props. En essence, cela ressemble à l'utilisation de const props = defineProps(). Fusion déclenche la variable __props, fournie par Vue, permettant aux développeurs d'écrire leur bloc script setup sans entraves. Cela permet de conserver la syntaxe normale des SFC de Vue non entravée par les processus Fusion.

Essentiellement, les valeurs composables name et fusion sont extraites via script setup avec une injection de modèle sans interruption.

D'où vient useFusion ? Le préfixe $fusion/Pages révèle la vue d'ensemble.

Concernant $fusion/Pages, nous avons déjà vu sa fonctionnalité en action via l'alias généré par le plugin Vite. Cet alias abstrait l'emplacement réel du fichier JavaScript, provenant d'une précédente entreprise formulant le fusion:shim. Son contenu se lit :

js
import ActionFactory from '@fusion/vue/actionFactory'
import Pipeline from '@fusion/vue/pipeline'

export const state = ['name']
export const actions = []
export const fusionActions = ['fusionSync']

let cachedState

export function useFusion(keys = [], props = {}, useCachedState = false) {
  const state = (useCachedState && cachedState) ? cachedState : new Pipeline(props).createState()

  if (!useCachedState) {
    cachedState = state
  }

  const all = {
    ...state,
    ...new ActionFactory([...actions, ...fusionActions], state),
  }

  const shouldExport = {}
  for (const key of keys) {
    if (key in all) {
      shouldExport[key] = all[key]
    }
  }

  return shouldExport
}

Les variables state et fusion résident dans ce fichier, reflétant les déclarations du SFC de Vue dans les fonctions PHP prop et expose. Accompagné d'un ActionFactory responsable de l'application des actions imposées par le serveur à l'état, y compris applyServerState, la synchronisation automatique et transparente se produit.

En tout, Fusion propose plusieurs actions préétablies, telles que :

  • applyServerState: Synchronise les données d'état entre le backend et le client, mettant à jour l'état pour correspondre aux attentes du serveur en temps réel.
  • syncQueryString: Synchronise les paramètres de chaîne de requête et l'état.
  • log: Permet l'enregistrement à des fins de débogage.
  • logStack: Enregistre la progression de l'action pour le débogage.

La discussion devient encore plus captivante en voyant la fonction expose. Cette méthode exposent une fonctionnalité PHP côté serveur au client, créant des comportements de type RPC permettant à des requêtes côté client de déclencher des opérations côté serveur.

Note

Le concept expose est apparenté à des approches comme tRPC et React Server Functions.

Un exemple illustratif :

vue
<php>
expose(favorite: function(Podcast $podcast) {
    return response()->json($podcast->toggleFavorite());
});
</php>

<template>
  <button @click="favorite({ podcast }).then(fav => podcast.favorited = fav)">
    Favori
  </button>
</template>

L'exposition d'une méthode comme favorite facilite son utilisation au sein du modèle. Le composable useFusion décrit précédemment gère la fonctionnalité, néanmoins, Fusion garantit un traitement approfondi permettant l'interaction de bouton côté client pour déclencher des opérations soutenues par le serveur.

  1. Le client envoie une requête au serveur pourvue des en-têtes x-fusion-action-request et x-fusion-action-handler.
  2. Le backend traite ces requêtes, aidé par les métadonnées fournies, répondant au client en conséquence, semblable à comment Inertia gère son protocole avec les en-têtes.
  3. L'état côté client correspond aux retours du serveur, après traitement.

Cliquer sur le bouton favori dans l'exemple ci-dessus génère cette requête ciblée vers le serveur :

http
POST /search HTTP/1.1
Accept: application/json, text/plain, */*
X-Fusion-Action-Handler: favorite
X-Fusion-Action-Request: true

{
  "fusion": {
    "args": {
      "podcast": {
        "id": 1,
        "title": "Art Of Product",
        "author": "Derrick Reimer & Ben Orenstein",
        "image": "/art/art-of-product.jpg",
        "favorited": false,
        "created_at": "2025-02-15T18:01:36.000000Z",
        "updated_at": "2025-02-19T20:49:13.000000Z"
      }
    },
    "state": {
      "search": "",
      "podcasts": [
        {
          "id": 1,
          "title": "Art Of Product",
          "author": "Derrick Reimer & Ben Orenstein",
          "image": "/art/art-of-product.jpg",
          "favorited": false,
          "created_at": "2025-02-15T18:01:36.000000Z",
          "updated_at": "2025-02-19T20:49:13.000000Z"
        }
      ]
    }
  }
}

Pendant ce temps, le serveur traite ce type de requête en interprétant les en-têtes x-fusion-action-request et x-fusion-action-handler. Dans son fournisseur de services, Fusion enregistre des macros de requête pour une identification et un traitement faciles des types de requête.

À l'intérieur du corps de la requête, args détient les arguments de la méthode (podcast), tandis que state comprend l'état actuel de l'application.

En réponse, le serveur envoie la suivante :

json
true

Ce qui est la réponse de la méthode favorite. Le client met ensuite à jour l'état en conséquence.

Fusion offre une méthode sync favorisant la synchronisation des états entre le frontend et le backend. Par exemple, un champ de recherche pourrait filtrer des éléments basés sur des calculs du backend, réalisé via sync. Essentiellement, sync imite une action catégorisée par le gestionnaire fusionSync !

Comment Fusion utilise-t-il Inertia ?

En vérité, Fusion simplifie ingénieusement et s'appuie sur l'intégration transparente d'Inertia pour traiter efficacement les SFC de Vue sans réinventer son framework frontend. La méthode Inertia::render illustre le rendu basé sur Inertia tandis que createInertiaApp établit l'environnement de l'application Vue de manière efficace, constituant l'ampleur totale de l'empreinte d'Inertia dans le projet Fusion.

Construire sur Inertia se révèle judicieux, permettant à Aaron de se concentrer sur l'innovation autour de l'objectif de Fusion sans être distrait par les complexités d'intégration de Vue au sein de Laravel. L'utilité d'Inertia annule toute redondance. De plus, transcender le parcours intermédiaire fait évoluer Fusion pour englober d'autres frameworks comme React de manière fluide.

Dernières pensées

Fusion a un potentiel, notamment pour les développeurs en transition de Next ou Nuxt vers une capacité full-stack, accueillant avec enthousiasme Laravel avec Inertia. En préservant des concepts chers aux développeurs de ces frameworks, tels que le routage basé sur des fichiers et la communication pilotée par le RPC, tout en les enrichissant avec la puissance de Laravel, Fusion devient un choix convaincant. Vous devriez absolument l'essayer !

Au sein de la feuille de route de Fusion, Aaron envisage la compatibilité avec React, réalisable en s'appuyant sur les épaules d'Inertia. Toutefois, je suis impatient de voir la perspective d'Aaron sur la gestion de l'exécution du code serveur dans JSX, peut-être à l'aide d'une directive 'use php' ? 🤔

Ce projet illustre efficacement la force de la relation profonde entre Vite et Laravel. Accéder sans effort à l'utilité complète de Vite demeure une capacité révolutionnaire. Cela réaffirme également la capacité modulaire du SFC dans Vue—Fusion le confirme assurément. Il n'y a pratiquement aucune limite à ce que vous pouvez réaliser en utilisant Vite et des composants SFC de Vue !

Enfin, la communication de la base de données SQLite inter-processus de Fusion renforce un modèle élégant et intelligent !

L'article a été passionnant à rédiger alors que j'explorais la base de code de Fusion. J'espère qu'il a révélé de nouvelles perspectives pour vous, et si vous êtes enclin à explorer les subtilités d'une autre bibliothèque, n'hésitez pas à commenter ou à utiliser la page Demandez-moi tout.

Mais j'ai un supplément pour vous ! 👇

Une dernière chose

Tout en rédigeant cet article, les exemples de code PHP en bloc fréquents manquaient de surlignement syntaxique approprié. J'ai réfléchi et décidé de m'attaquer à cette tâche. Puisque Shiki est utilisé pour la mise en surbrillance des codes sur ce site et qu'il utilise TextMate grammars, je sais que je pourrais ajouter le support pour le bloc PHP dans VSCode.

Mais comment ?

En fouillant, j'ai réalisé que l'intégration de langages dans des contextes à fichier unique n'est pas nouvelle. Les SFC de Vue sont notables, tout comme Markdown, Angular, et même HTML impliquant différents langages. Par la suite, je me suis concentré sur la grammaire TextMate des SFC de Vue.

Note

Une grammaire TextMate définit les mises en surbrillance de syntaxe du langage de codage via des modèles et définit des règles. Fonctionnant derrière VSCode et Shiki, c'est le moteur du surligneur de syntaxe.

Examiner le dépôt textmate-grammars-themes de Shikijs qui s'occupe de chaque langue et thème soutenu par Shiki a mis au jour la grammaire Vue. Bien que long, des concepts éclairants émergent lors de la lecture, tels que les intégrations HTML, CSS, et JavaScript au sein des blocs respectifs:

json
{
  "begin": "(script)\\b",
  "beginCaptures": {
    "1": {
      "name": "entity.name.tag.$1.html.vue"
    }
  },
  "end": "(</)(\\1)\\s*(?=>)",
  "endCaptures": {
    "1": {
      "name": "punctuation.definition.tag.begin.html.vue"
    },
    "2": {
      "name": "entity.name.tag.$2.html.vue"
    }
  },
  "patterns": [
    {
      "include": "#tag-stuff"
    },
    {
      "begin": "(?<=>)",
      "end": "(?=<\\/script\\b)",
      "name": "source.js",
      "patterns": [
        {
          "include": "source.js"
        }
      ]
    }
  ]
}

Cela gère la mise en surbrillance de JavaScript au sein des blocs <script> en utilisant l'inclusion de la grammaire JavaScript à travers include. Le même modèle s'applique probablement à d'autres langages, y compris PHP.

C'est exactement ce que je veux atteindre, mais avec <php> et la grammaire PHP. Bien sûr, il existe déjà une grammaire PHP, donc je dois juste l'inclure de la même manière. J'essaie, et ça fonctionne. Youpi ! 🥳

Mais attendez, je ne peux pas soumettre un PR à Vue Language Tools (Shiki récupère la grammaire de ce dépôt) pour ajouter le support de la grammaire PHP. Cela n'a aucun sens, et il n'y a aucune raison de leur part de l'accepter. Donc, je dois trouver une autre solution.

Après avoir continué ma recherche, j'ai trouvé la grammaire angular-inline-style. Celle-ci est utilisée pour mettre en évidence les styles (CSS, SCSS) à l'intérieur d'un composant Angular (TypeScript). Pour ce faire, la grammaire utilise le mot clé injectTo, qui agit comme une extension d'une grammaire existante—dans ce cas, source.ts.ng. C'est parfait ! Que se passerait-il si je créais une nouvelle grammaire qui étend la grammaire Vue en injectant la grammaire PHP ? Rétrospectivement, cela semblait plus facile que cela ne l'a été en réalité.

Après quelques heures d'essais et d'erreurs, j'ai finalement réussi à créer une grammaire qui fonctionne dans Shiki. L'un des éléments les plus compliqués a été de comprendre ce qui n'allait pas lorsque les choses ne fonctionnaient pas. Avec ce genre de configuration, il n'y a pas de messages d'erreur, pas d'avertissements—rien. Vous devez juste deviner ce qui ne va pas. Vous pouvez trouver la solution fonctionnelle dans le code source de mon générateur de code en image.

Enfin, j'ai créé une extension VSCode que tout le monde peut utiliser pour ajouter le support des blocs PHP dans les SFC de Vue. Vous pouvez la trouver sur le marketplace avec la grammaire fonctionnelle : source.vue.php.

Si Fusion devient populaire, je pourrais éventuellement soumettre un PR pour ajouter le language à Shiki, rendant plus facile à tous la mise en surbrillance des blocs PHP.

Bon code ! 👨‍💻

Pd

Merci de me lire ! Je m'appelle Estéban, et j'adore écrire sur le développement web.

Je code depuis plusieurs années maintenant, et j'apprends encore de nouvelles choses chaque jour. J'aime partager mes connaissances avec les autres, car j'aurais aimé avoir accès à des ressources aussi claires et complètes lorsque j'ai commencé à apprendre la programmation.

Si vous avez des questions ou souhaitez discuter, n'hésitez pas à commenter ci-dessous ou à me contacter sur Bluesky, X, et LinkedIn.

J'espère que vous avez apprécié cet article et appris quelque chose de nouveau. N'hésitez pas à le partager avec vos amis ou sur les réseaux sociaux, et laissez un commentaire ou une réaction ci-dessous—cela me ferait très plaisir ! Si vous souhaitez soutenir mon travail, vous pouvez me sponsoriser sur GitHub !

Réactions

Discussions

Ajouter un commentaire

Vous devez être connecté pour accéder à cette fonctionnalité.

Se connecter avec GitHub
Soutenez mon travail
Suivez-moi sur