Il y a 10 ans -
Temps de lecture 11 minutes
Précompiler et minifier les templates Ember avec Wro4j
Ember est un framework Javascript dont l’objectif principal est de faciliter la mise en place d’architectures MVC dans les navigateurs. L’utilisation d’Ember sur un projet pose toutefois un double problème :
- Les fichiers Javascript sont faciles à découper (un fichier par classe Javascript par exemple), mais le fait de charger des dizaines de fichiers différents est contre-performant ;
- Les templates Ember sont tous présents directement dans la page html, ce qui mène rapidement à avoir une page de plusieurs milliers de lignes, et donc difficilement maintenable.
Cet article se propose donc de montrer, via l’utilisation de wro4j, comment résoudre ces deux problèmes.
Survol de Ember
De plus en plus de projets se lancent en choisissant une architecture de type mono-page. Ember est un framework Javascript MVC fullstack, facilitant la création de ce genre d’application. Pour afficher les vues, Ember s’appuie sur des templates Handlebars légèrement modifiés pour pouvoir y ajouter du « binding » : lorsqu’une valeur du modèle Javascript est modifiée, elle est automatiquement mise à jour dans la vue, et vice-versa.
Ces templates doivent être présents dans le corps de la page et s’écrivent sous la forme suivante:
<script type="text/x-handlebars" data-template-name="nomDuTemplate"> Exemple de template affichant une variable : {{variable}} </script>
Lorsqu’Ember a besoin d’afficher un template, il va d’abord le compiler sous forme de fonction Javascript, puis ensuite enregistrer dans l’objet global Ember.TEMPLATES
le template compilé.
Cependant, cette façon de faire pose deux problèmes :
- les templates se trouvent tous directement dans la page principale, qui finit vite par faire des milliers de lignes ;
- bien que la compilation soit relativement rapide, chaque utilisateur doit à chaque fois repasser par cette étape.
De plus, sur une application de taille moyenne, on arrive assez vite à avoir plusieurs dizaines de fichiers Javascript pour les modèles, vues, contrôleurs, etc.
Pour résoudre ces problèmes, nous proposons dans ce billet de :
- précompiler coté serveur les templates qui seraient stockés dans des fichiers Javascript ;
- réunir et minifier tous les fichiers Javascript (templates précompilés compris) en un seul fichier.
Pour la démonstration ci-dessous, nous partirons de l’exemple TODO MVC fait avec Ember, et nous nous contenterons de le transformer en projet java / maven en créant un pom.xml minimal et en déplaçant les sources vers src/main/webapp.
wro4j
wro4j est une librairie dont l’objectif principal est de prétraiter les ressources Javascript et CSS d’une application web, puis de les réunir sous forme de bundle. Malheureusement, bien qu’ayant par défaut un précompilateur Handlebars, wro4j ne fournit pas de précompilateur pour les templates Ember. Nous allons donc développer le nôtre.
Par ailleurs, bien que wro4j permette d’effectuer la précompilation à la construction de l’application, nous utiliserons plutôt une approche basée sur les filtres, permettant le rechargement à chaud des fichiers Javascript et des templates.
Description du projet initial
On supposera donc que l’application TODO MVC existe déjà dans un projet maven et fonctionne. La structure du projet devrait donc ressembler à :
-
src/main/webapp
-
js
-
controllers
- entries.js
- todo.js
-
libs
- ember-latest.js
-
models
- store.js
- todo.js
-
views
- application.js
- todo.js
- router.js
- app.js
-
controllers
-
assets
- base.css
- base.js
- bg.png
- handlebars.min.js
- jquery.min.js
- index.html
-
js
- pom.xml
On voit donc que même sur une application simple, on charge au démarrage 12 fichiers Javascript et 4 templates présents dans le fichier index.html
.
Mise en place de wro4j
La première étape consiste à ajouter wro4j aux dépendances du projet dans le pom.xml
:
<dependencies> <!-- other dependencies... --> <dependency> <groupId>ro.isdc.wro4j</groupId> <artifactId>wro4j-core</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>ro.isdc.wro4j</groupId> <artifactId>wro4j-extensions</artifactId> <version>1.6.1</version> </dependency> <!-- other dependencies... --> </dependencies>
On peut ensuite éditer (ou créer) le fichier web.xml
dans le répertoire src/main/webapp/WEB-INF
pour ajouter le filtre :
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <!-- other things if you need it --> <filter> <filter-name>WebResourceOptimizer</filter-name> <filter-class>ro.isdc.wro.http.WroFilter</filter-class> </filter> <filter-mapping> <filter-name>WebResourceOptimizer</filter-name> <url-pattern>/wro/*</url-pattern> </filter-mapping> <!-- other things if you need it --> </web-app>
On indique ensuite que toutes les URL commençant par /wro/
iront chercher les ressources définies dans le fichier wro.xml
présent dans le répertoire src/main/webapp/WEB-INF
, fichier que nous allons créer :
<groups xmlns="http://www.isdc.ro/wro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.isdc.ro/wro wro.xsd"> <group name="todo"> <js>/js/app.js</js> <js>/js/router.js</js> <js>/js/models/*</js> <js>/js/controllers/*</js> <js>/js/views/*</js> </group> </groups>
Il existe de nombreuses façons de configurer les groupes Wro4j, comme indiqué dans la documentation, le fichier proposé ci-dessus n’est qu’un exemple simple, permettant de rassembler toutes les ressources Javascript applicatives dans un « fichier » nommé todo.js
Dans le fichier index.html
, on remplace les appels Javascript ci-dessous…
<script src="js/app.js"></script> <script src="js/router.js"></script> <script src="js/models/todo.js"></script> <script src="js/models/store.js"></script> <script src="js/controllers/entries.js"></script> <script src="js/controllers/todos.js"></script> <script src="js/views/application.js"></script> <script src="js/views/todos.js"></script>
… par l’unique appel à la ressource wro compilée :
<script src="wro/todo.js"></script>
L’application est toujours fonctionnelle, mais il n’y a plus qu’un fichier Javascript correspondant à notre application, et celui-ci est minifié.
Si l’on a résolu le problème des multiples appels de fichiers, il nous reste encore à résoudre le problème des templates présents dans le fichier index.html
Création d’un compilateur spécifique pour les templates Ember
Nous allons ici créer un compilateur de template Ember. Pour cela, il faut créer 3 classes :
src/main/java/fr/xebia/wro/processor/ember/EmberJs.java
, classe qui effectuera la compilation proprement dite :
package fr.xebia.wro.processor.ember; import java.io.InputStream; import java.io.SequenceInputStream; import ro.isdc.wro.extensions.processor.support.template.AbstractJsTemplateCompiler; public class EmberJs extends AbstractJsTemplateCompiler { @Override public String compile(final String content, final String name) { return "(function() {Ember.TEMPLATES[" + name + "] = Ember.Handlebars.template(" + super.compile(content, "") + ")})();"; } @Override protected String getCompileCommand() { // Function present in headless-ember return "precompileEmberHandlebars"; } @Override protected InputStream getCompilerAsStream() { final ClassLoader classLoader = EmberJs.class.getClassLoader(); final InputStream handlebars = classLoader.getResourceAsStream("fr/xebia/wro/processor/ember/handlebars.min.js"); final InputStream headlessEmber = classLoader.getResourceAsStream("fr/xebia/wro/processor/ember/headless-ember.js"); final InputStream ember = classLoader.getResourceAsStream("fr/xebia/wro/processor/ember/ember-latest.js"); return new SequenceInputStream(new SequenceInputStream(handlebars, headlessEmber), ember); } }
src/main/java/fr/xebia/wro/processor/js/EmberProcessor.java
, le processeur, au sens wro4j qui fera l’appel à la classe EmberJs pour précompiler
package fr.xebia.wro.processor.js; import org.apache.commons.io.FilenameUtils; import ro.isdc.wro.extensions.processor.js.JsTemplateCompilerProcessor; import ro.isdc.wro.extensions.processor.support.template.AbstractJsTemplateCompiler; import ro.isdc.wro.model.resource.Resource; import ro.isdc.wro.model.resource.ResourceType; import ro.isdc.wro.model.resource.SupportedResourceType; import fr.xebia.wro.processor.ember.EmberJs; @SupportedResourceType(ResourceType.JS) public class EmberProcessor extends JsTemplateCompilerProcessor { public static final String ALIAS = "emberJs"; @Override protected AbstractJsTemplateCompiler createCompiler() { return new EmberJs(); } @Override protected String getArgument(Resource resource) { final String name = resource == null ? "" : FilenameUtils.getBaseName(resource.getUri()); return String.format("'%s'", name); } }
src/main/java/fr/xebia/wro/manager/factory/MyConfigurableWroManagerFactory.java
, la factory permettant d’ajouter notre processeur à la liste de ceux déjà fournis par wro4j
package fr.xebia.wro.manager.factory; import java.util.Map; import ro.isdc.wro.manager.factory.ConfigurableWroManagerFactory; import ro.isdc.wro.model.resource.processor.ResourcePreProcessor; import fr.xebia.wro.processor.js.EmberProcessor; public class MyConfigurableWroManagerFactory extends ConfigurableWroManagerFactory { @Override protected void contributePreProcessors(Map<String, ResourcePreProcessor> map) { map.put(EmberProcessor.ALIAS, new EmberProcessor()); } }
Par ailleurs, il est nécessaire de fournir à ces classes les fichiers Ember permettant la précompilation :
src/main/resources/fr/xebia/wro/processor/ember/ember-latest.js
src/main/resources/fr/xebia/wro/processor/ember/handlebars.min.js
src/main/resources/fr/xebia/wro/processor/ember/headless-ember.js
Les deux premiers fichiers peuvent se copier/coller depuis les répertoires src/main/webapp/js
et src/main/webapp/asset
Le fichier headless-ember.js
se trouve directement dans le repository github de ember : https://github.com/emberjs/ember.js/blob/master/lib/headless-ember.js , et permet d’executer Ember coté serveur en simulant un environnement de navigateur.
Une fois tout cela déclaré, il faut indiquer à wro4j qu’il doit utiliser notre ManagerFactory
pour executer ses processeurs. Pour cela nous allons éditer le fichier wro.properties
qu’il faut ajouter dans le répertoire src/main/webapp/WEB-INF
managerFactoryClassName=fr.xebia.wro.manager.factory.MyConfigurableWroManagerFactory preProcessors=cssUrlRewriting,cssImport,emberJs.emtpl,semicolonAppender,cssMin,jsMin
Par défaut, wro4j exploitera tous les fichiers, sans tenir compte du type ; or dans notre cas, on ne veut pour notre précompilateur Ember que les fichiers ayant l’extension emtpl (le nom de l’extension est arbitraire). La notation « .emtpl
» permet de dire que l’on ne compilera avec notre préprocesseur que les fichiers finissant en « .emtpl
« . Il existe beaucoup d’autres options de configuration, comme l’indique la documentation.
Nous allons ensuite créer un répertoire pour les templates,
/src/main/webapp/templates
et y déplacer de index.html
nos templates (sans les balises scripts).
/src/main/webapp/templates/statsTemplate.emtpl
/src/main/webapp/templates/filtersTemplate.emtpl
/src/main/webapp/templates/clearBtnTemplate.emtpl
/src/main/webapp/templates/todosTemplate.emtpl
Le nom du template correspondra alors au nom du fichier, sans l’extension.
Il n’y a plus qu’à ajouter notre répertoire dans la configuration de wro.xml
<groups xmlns="http://www.isdc.ro/wro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.isdc.ro/wro wro.xsd"> <group name="todo"> <js>/js/app.js</js> <js>/js/router.js</js> <js>/js/models/*</js> <js>/js/controllers/*</js> <js>/js/views/*</js> <js>/templates/**</js> </group> </groups>
Notre application doit toujours fonctionner, mais les templates sont maintenant séparés proprement dans les fichiers .emtpl, et les fichiers sont correctement compilés, minifiés et mis en cache par wro4j.
Conclusion
La librairie wro4j est relativement simple d’utilisation, mais d’une grande puissance ; on aura vu qu’avec les quelques lignes ci-dessus on a réussi à améliorer les performances globales de l’application (précompilation, minification et gestion du cache), mais aussi à organiser de façon beaucoup plus propre les fichiers. Cependant, ce tutoriel n’est qu’une première étape dans l’industrialisation de projet Java / Ember, car il y a encore beaucoup de voies d’amélioration comme par exemple penser à une gestion plus fine des noms de templates dans le préprocesseur, ou encore à un moyen d’éviter la duplication des ressources js entre la webapp et le précompilateur.
N.B. : À partir de la version 1.6.2, Wro4J devrait embarquer par défaut le précompilateur Ember.
Commentaire
2 réponses pour " Précompiler et minifier les templates Ember avec Wro4j "
Published by Alex , Il y a 10 ans
Hi Benoit,
thank you for this useful blog post. Have you considered to create a pull request with emberJs contribution? I think it would be useful to have ember processor provided by default.
Published by Benoît Lemoine , Il y a 10 ans
Hi Alex,
it’s a great idea, and I’ll start working on it right away