Pourquoi Vite ne build pas ?

Tutoriels
Publié par Mathieu De Gracia

Après plusieurs années de bons et loyaux services, Mix laissera finalement sa place à Vite à partir de Laravel 9.

Plus moderne et surtout plus rapide, Vite est un puissant tooling front qui compilera vos assets CSS et JS à la vitesse de la lumière.

Une fois intégré à Laravel, Vite dispose de deux modes de fonctionnement.

Le mode dev, accessible avec la commande npm run dev, offrant un performant système de hot reload recompilant à chaud toutes vos assets JS et CSS.

Ainsi qu'un mode build ,npm run build, adapté à la production, compressant et minifiant toutes vos assets.

Pour intégrer ces assets, rien de plus simple, Laravel propose une nouvelle directive @vite() :

@vite(['resources/css/app.css', 'resources/js/app.js'])

Ces deux modes de fonctionnement sont clairement visibles dans votre HTML, la directive @vite intégrera automatiquement les bonnes assets selon le mode que vous utilisez :

# npm run dev
<script type="module" src="http://localhost:5173/@vite/client"></script>
<script type="module" src="http://localhost:5173/resources/js/app.js">

# npm run build
<link rel="stylesheet" href="http://localhost:8089/build/assets/app.5c5855fa.css" />
<script type="module" src="http://localhost:8089/build/assets/app.64e7ccdb.js"></script>

5173 est le port par défaut du serveur hot reload

Cependant, l’intégration de Vite à Laravel est encore jeune et il subsiste des zones d’ombre ou des nouvelles habitudes à prendre.

Par exemple, il est possible qu’après avoir lancé avec succès un npm run dev que l'application reste bloquée en mode Dev et que vous n'arriviez plus à build les assets.

La directive @vite() continuera d'inclure le serveur de hot reload alors que vous désirez utiliser les assets compilées.

Laissez-moi vous présenter en quelques minutes un retour d'expérience douloureux sur Vite !

La directive vite

La directive @vite() se cache dans la class CompilesHelpers au même niveau que les directives @dd() et @crsf() :

protected function compileVite($arguments)
{
    $arguments ??= '()';

    $class = Vite::class;

    return "<?php echo app('$class'){$arguments}; ?>";
}

Son code est plutôt succinct, la méthode se contente de manipuler une instance de Illuminate\Foundation\Vite.

Vite est une class invokable, son point d’entrée est donc la méthode __invoke.

public function __invoke($entrypoints, $buildDirectory = 'build')
{
    static $manifests = [];

    $entrypoints = collect($entrypoints);
    $buildDirectory = Str::start($buildDirectory, '/');

    if (is_file(public_path('/hot'))) {
        $url = rtrim(file_get_contents(public_path('/hot')));

Un élément saute rapidement aux yeux, la méthode __invoke vérifie la présence d’un fichier /hot à la racine du dossier public.

if (is_file(public_path('/hot'))) {

Si ce fichier est présent, alors la directive considère que l’application est en mode dev et cherchera à contacter le serveur de hot reload.

ce fichier hot est ignoré par votre .gitignore , il n’apparaitra pas dans vos fichiers versionnés

Dans un second temps, la directive vérifiera la présence du fichier manifest.json lui assurant que l’application possède bien des assets compilées et en conclura que l’application est en mode build.

Nous savons désormais comment s’effectue la balance entre le mode dev et build, il reste toujours deux questions en suspend : comment est manipulé ce fichier hot et dans quelles circonstances l’application peut se retrouver bloquée en mode dev ?

Le fichier hot

C’est le plugin JS laravel-vite-plugin qui détient la responsabilité de manipuler ce fichier /hot depuis laravel-vite-plugin\dist\index.js.

Ce plugin simplifie l'intégration de Vite à un backend Laravel.

On remarque rapidement que le fichier /hot est tout simplement créé lors du lancement du serveur de hot reload :

const hotFile = path_1.default.join(pluginConfig.publicDirectory, 'hot');

//

fs_1.default.writeFileSync(hotFile, viteDevServerUrl);

Et que ce fichier est supprimé lors de la fermeture de ce même serveur :

const clean = () => {
    if (fs_1.default.existsSync(hotFile)) {
        fs_1.default.rmSync(hotFile);
    }
};

process.on('exit', clean);
process.on('SIGINT', process.exit);
process.on('SIGTERM', process.exit);
process.on('SIGHUP', process.exit)

La suppression du fichier hot s'effectue en vérifiant les signaux systèmes, si la commande détecte un signal de sortie elle utilisera sa méthode clean, supprimant le fichier hot, avant de fermer le serveur.

Un SIGTERM est le signal envoyé vers le serveur en faisant un ctrl + c

Conclusion

Nous avons désormais toutes les cartes en main.

Nous savons que la présence du fichier hot détermine le status de l'application, que ce fichier est ignoré par git et qu’il est supprimé lors de la fermeture du serveur de hot reload.

Le cœur du problème est là, il est nécessaire que le serveur de hot reload soit proprement fermé (à l’aide d’un signal système) pour que le fichier hot soit correctement supprimé.

Désormais, imaginez un docker capricieux qui explose, une coupure de courant, n'importe quel évènement chaotique … ce fichier hot deviendra orphelin et le jeu des permissions pourront le rendre inaccessible à un futur lancement de la commande npm run dev.

Le fichier hot restera présent à la racine du dossier /public, invisible à vos yeux, et bloquera toute tentative de build l’application … et vous passerez une bonne après-midi à comprendre ce qu’il se passe !