Angular server-side rendering dans Node avec le moteur Express Universal

Publié le

Nous avons traduit pour vous l’article Medium de notre formateur contributeur : Angular server-side rendering dans Node avec le moteur Express Universal.

Note : le CLI 1.6 fait désormais l’essentiel du travail pour vous. Pour des informations à jour, consultez plutôt la version anglaise de cet article.

Le projet Universal a commencé il y a longtemps, alors qu’Angular était encore en beta. Après plus d’un an et demi d’attente, il est enfin possible d’activer le pré-rendu serveur dans une vraie application Angular !

Mais il ne s’agit pas simplement d’une configuration à activer pour l’instant, je vais donc vous expliquer toutes les étapes. Merci à Philippe Martin et son article Creating an Angular Universal app with the Angular CLI, reproduit dans le wiki officiel du CLI. J’ai démarré de là and beaucoup de points y sont déjà expliqués, mais certains points manquent pour appliquer ça à une vraie app, alors c’est parti !

Universal bundle

D’abord, vous aurez besoin d’Angular ≥ 4.3. Si vous n’avez pas encore fait la mise à jour, faites-le maintenant, c’est vraiment simple.

Vous aurez aussi besoin d’Angular CLI ≥ 1.3.0-rc.3 et du module platform-server :

npm install @angular/cli@1.3.0-rc.3 @angular/platform-server

Attention : une mise à jour directe du CLI engendre souvent des problèmes. Si des erreurs surviennent lors du build, supprimez le répertoire node_modules et le package-lock.json, et relancez npm install.

Les étapes qui suivent sont juste du copier/coller, le CLI pourrait les automatiser prochainement.

Premièrement, vous devez ajouter une nouvelle configuration dans votre .angular-cli.json, pour générer le bundle pour le serveur, en complément de celui pour le navigateur :

Il vous faut ensuite un AppServerModule spécifique, à côté du AppModule normal, qui va surcharger toutes les APIs pour les adapter à un usage côté serveur :

Ensuite un nouveau point d’entrée main-server.ts, à côté du main.ts classique :

Et un nouveau tsconfig.server.json, à côté du tsconfig.app.json normal :

Enfin, un léger changement dans votre AppModule classique :

Pourquoi cette modification ? Car maintenant que le contenu va être pré-généré par le serveur, le navigateur n’a plus besoin de créer l’intégralité de l’application par lui-même : le HTML et le CSS sont déjà là, il n’aura plus qu’à appliquer les bindings et autres fonctionnalités d’Angular pour rendre la page dynamique.

Vous pouvez maintenant mettre à jour votre script npm dans le package.json :

Et :

npm build

Server-side rendering dans Node

A l’exception de l’option output-hashing, les étapes de build sont déjà expliquées dans le wiki. Mais les explications sur le serveur sont assez légères.

Contraitement au wiki, nous n’utiliserons pas platform-server directement, mais le moteur Universal officiel pour Express :

npm install express @nguniversal/express-engine

Vous pouvez ensuite créer votre fichier server.js :

Et lancer :

node server.js

Ta da ! Pour être sûr que le serveur fait son boulot, désactivez JavaScript dans votre navigation, afin de vérifier que quelque chose est bien pré-généré.

Mais il est probable que cela ne fonctionne pas tout à fait. Des points importants doivent être gérés dans une vraie application.

Piège n°1 : être compatible Universal

Pourquoi le projet de pré-rendu serveur s’appelle-t-il Angular ? Car le code Angular peut être utilisé dans n’importe quel contexte, pas seulement dans un navigateur. Votre code JavaScript/TypeScript peut ainsi fonctionner également côté serveur.

Pour cela, vous devez respecter de façon très stricte le principe de rester « platform agnostic ». Cela signifie de ne jamais accéder directement à des APIs spécifiques au navigateur. Cela inclut :

  • window, document… et tous les autres objets du navigateur, ainsi que leur méthodes (comme setTimeout)
  • toutes les APIs du DOM
  • toutes les autres APIs spécifiques au navigateur, comme localStorage, IndexedDB

Les deux premiers ne sont pas un problème : il vous suffit d’utiliser le templating Angular (ou l’API de rendu d’Angular pour des besoins avancés). Mais comment faire si vous avez vraiment besoin de localStorage ou d’une autre API spécifique au navigateur ?

Solution minimale : un try/catch pour rendre l’erreur silencieuse quand le code sera utilisé en dehors du navigateur. Une meilleure solution : Angular vous indique quel est le contexte actuel :

Piège n°2 : les requêtes Http

Vous allez découvrir que tout le contenu provenant de requêtes Http n’est pas pré-généré : c’est parce qu’Universal a besoin d’URLs absolues.

Comme votre serveur de développement et celui de production n’ont pas la même URL, c’est contraignant de gérer cela manuellement.

Ma solution pour automatiser cela : utiliser la nouvelle fonctionnalité d’intercepteur d’Angular 4.3, combinée avec le moteur Express.

L’intercepteur attrape toutes les requêtes quand l’application est en contexte serveur, pour y ajouter une URL absolute. Il ressemble à cela (vous pouvez juste copier/coller) :

Fournissez-le ensuite ainsi dans votre AppServerModule :

Vous pouvez maintenant utiliser le moteur Express pour passer l’URL absolue à Angular, en mettant à jour votre server.js :

Note : vous devez utiliser la nouvelle API HttpClient d’Angular 4.3. Cela ne fonctionnera pas si vous faites vos requêtes avec la précédente API Http.

J’ai ouvert un ticket pour proposer une PR incluant cela directement dans le moteur d’Express, sentez-vous libres de me soutenir.

Piège n°3 : librairies tierces et betas

Dernier problème : tous les modules que vous utilisez dans votre application doivent être compatibles Universal et éviter le premier piège.

Tous les modules Angular4+ stables sont compatibles Universal. Mais méfiez-vous des betas :

  • la dernière beta de Material est compatible Universal (mais attention à importer hammerjs dans main.ts et non ailleurs) ;
  • la beta 8 de FlexLayout n’est pas compatible Universal ; la prochaine beta 9 devrait l’être (PR ici).

Pour les librairies tierces, vous devrez tester par vous-même. La plupart causeront un problème pour l’instant (« Unexpected token import ») à cause d’un problème de format (ticket en cours de résolution).

Conclusion

Un dépôt d’exemple est disponible ici.

J’espère vous avoir aidé/e. Comme Angular CLI 1.3 et le moteur Express sont encore en betas, je mettrai à jour cet article au besoin.

Découvrez notre formation Angular, animée par l’auteur de cet article !


Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.