Article
Création de logiciels
3.11.2017
Perturbation continuum temporel
5 minutes
Temps d'un café
Optimiser une application Angular avec la compilation AOT
Optimiser une application Angular avec la compilation AOT
Emilie Paillous
Note : Ce contenu a été créé avant que Fabernovel ne fasse partie du groupe EY, le 5 juillet 2022.

La compilation AoT permet d’obtenir une version pré-compilée de l’application, ce qui permet d’éliminer le temps de compilation au moment du chargement de la page. De plus, avec la compilation AoT, il n’est plus nécessaire d’intégrer le compilateur Angular à l’intérieur de l’application.

Idéalement, cette compilation doit être mise en place dès le début du projet, car c’est un travail fastidieux et difficile à débugger une fois que la codebase est très grande.

Pour ce faire, nous avons utilisé le plugin AotPlugin de webpack, dans la configuration de production (ce qui nous permet de garder la compilation JIT classique pendant le développement).

Mise en place

Dans un premier temps, installez le package ngtools/webpack :

npm install --save-dev @ngtools/webpack

Puis ajoutez cette règle à votre configuration webpack de production :

{ test: /\.ts$/, use: ['@ngtools/webpack'] }

En prenant soin d’enlever la règle de base sur les fichiers .ts et de la placer seulement dans votre fichier de configuration locale :

{ test: /\.ts$/, use: ['awesome-typescript-loader?silent=true', 'angular2-template-loader', 'angular-router-loader'] }

Enfin, ajoutez le plugin AoT à la liste des plugins de votre configuration de production webpack :

var AotPlugin = require('@ngtools/webpack').AotPlugin; ... new AotPlugin({ tsConfigPath: 'tsconfig-aot.json', entryModule: helpers.root('src/app/app.module#AppModule') })

Le fichier tsconfig correspondant ainsi que le main-aot.ts

Puis, définissez une commande de votre package.json pour générer le build avec votre configuration de production :

“build”: rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail

Dans un premier temps, il faudra corriger les différentes erreurs “simples” que vous obtiendrez en sortie de votre build : une erreur classique est l’utilisation de variables non publiques dans les templates html.

Il se peut que vous rencontriez ensuite d’autres problèmes au fur et à mesure de la mise en place de la configuration. Voici ce qui a été réalisé pour pallier ces difficultés :

npm-shrinkwrap

Quelque soit le gestionnaire de paquets que vous utilisez, il est absolument indispensable de créer un fichier permettant de fixer les différentes versions des paquets. C’est vraiment capital, et nous l’avions dès le début du développement du projet. Sans ce fichier, il est compliqué de garantir une installation propre et claire du projet d’une machine à l’autre.

Nous utilisons, en plus de la commande npm shrinkwrap, le package npm-shrinkwrap qui permet de vérifier que les nodes modules, le package.json et le npm-shrinkwrap.json sont sur la même longueur d’onde. Il élague également certains champs non utilisés dans le npm-shrinkwrap.

Version de typescript et node

Il semble que la compilation AoT ne soit pas supportée par les récentes versions de Typescript. Nous avons fixé la version à 2.2.2. De même pour node : nous utilisons nvm afin de gérer en parallèle plusieurs versions de node, ainsi qu’un fichier .nvmrc précisant la version de node à utiliser pour ce projet. Nous avons fixé la version à 7.10.1. Il est possible que vous rencontriez ce type d’erreurs en essayant d’utiliser la compilation AoT avec node 5 :

const { __NGTOOLS_PRIVATE_API_2 } = require('@angular/compiler-cli');     ^ SyntaxError: Unexpected token {   at exports.runInThisContext (vm.js:53:16)

Priorité des plugins Webpack

Ce n’est pas forcément évident, et parfois ce n’est pas important, mais l’ordre des différents plugins que vous appliquez dans webpack a une influence sur le build en sortie. En l’occurrence, le plugin AoT doit être placé en premier dans la liste des plugins de la configuration. Ainsi, si vous avez besoin de merger plusieurs configurations, il faut que la configuration contenant le plugin AoT soit le premier argument de webpackMerge. Sans cela, nous n’arrivions pas à obtenir en sortie nos fichiers “chunks” correspondant aux différents modules lazy-loadés de l’application.

BundleAnalyzer

Ce plugin webpack permet d’analyser les différents imports utilisés par l’application. Il peut être utilisé complètement en dehors de la mise en place AoT, il nous a permis d’optimiser la taille de notre application en enlevant les imports inutiles.

Résultats

Avant la compilation AOT :

On remarque sans surprise que ce sont les bibliothèques utilisées, ainsi qu’Angular lui même qui constituent le plus gros de notre application (1,3 Mo). La taille totale de l'application est de 2,8 Mo (sans compter les assets).

L’application met environ 2 secondes à charger.

Après compilation AOT :

Après la compilation AOT, il n'y a plus besoin de fournir le compilateur d'angular dans votre application, ce qui réduit en partie la taille de la partie vendor. La taille totale de l'application est à présent de 2,06Mo, soit environ 700Ko de moins qu'avant la mise en place de la compilation AOT. Ainsi, en dépit de la compilation des templates HTML/CSS en fichiers Javascript, la taille de l'export final est réduit.

Le chargement de l'application prends à présent un peu plus d'une seconde, c'est à dire un temps pratiquement divisé par 2 après compilation AOT. Ce gain est très visible lorsqu'on accède au site via un téléphone mobile, dont le moteur javascript est probablement moins puissant. En particulier, sur les versions antérieures à 4.4 d'Android, on constatait des temps de chargement allant de 10 à 15 secondes !

Conclusion et amélioration possible

La mise en place de la compilation AOT, bien que fastidieuse, nous a permis de gagner un temps de chargement considérable au lancement de l'application. Conjointement utilisé avec les modules lazy-loadés, elle nous permet de proposer une expérience utilisateur agréable et fluide. Il serait encore possible d'améliorer la taille de l'application en analysant plus précisément les bibliothèques utilisées : par exemple, la bibliothèque moment.js charge la totalité des locales du package, or nous ne les utilisons pas toutes. Pour n’importer que les locales effectivement utilisées, nous avons ajouté à la liste des plugins webpack la ligne suivante :

new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en|fr/),

pour ne charger que les locales françaises et anglaises.

No items found.
Pour aller plus loin :