Filtering avec Gradle
La problématique
J’ai plusieurs vues et chacune d’entre-elles possède un fichier Javascript qui lui est dédié. Pour le charger, j’utilise la taglib liferay-util
afin d’injecter le fichier à la fin de la page :
<!-- views/foo.jsp -->
<!-- ... -->
<liferay-util:html-bottom>
<script src="/o/foo-portlet/js/foo.js"></script>
</liferay-util:html-bottom>
Lors du déploiement du module, aucun problème, tout fonctionne comme prévu. En revanche, lors des déploiements suivants, Liferay a mis en cache le fichier, et mes nouveaux développements ne sont pas pris en compte, et cela devient rapidement une source de frustration et une perte de temps.
Solutions étudiées
Désactiver le “fast-load”
Mon premier réflexe a été de désactiver cette option via le fichier de configuration portal-ext.properties
:
theme.js.fast.load=false
Hélas, cela ne fonctionne pas avec les fichiers injectés de cette manière.
Vider la cache depuis l’interface d’administration
Étonnamment, cette méthode ne semble pas fonctionner dans mon cas de figure, et de toute manière c’est rapidement fastidieux de devoir aller vider la cache après chaque déploiement.
Utiliser la configuration du portlet
Une autre manière est d’utiliser la configuration du portlet. En effet Liferay gère bien mieux l’invalidation du cache de cette manière, car il ajoute lui-même un timestamp lors de l’injection du fichier dans la page :
@Component(
immediate = true,
property = {
"com.liferay.portlet.footer-portlet-javascript=/js/foo.js",
// ...
},
service = Portlet.class
}
public class MyPortlet extends MVCPortlet {
// ...
}
Encore hélas, cette solution ne convient pas à mon cas de figure, car elle injectera tous les fichiers, quelque soit la vue et je ne veux charger que le fichier nécessaire à chaque vue.
Modifier le nom du fichier Javascript
Vu que Liferay se base sur le nom du fichier, on peut facilement contourner le cache en changeant le nom du fichier avant chaque déploiement. Réaliser cette opération manuellement nécessite d’une part de renommer le fichier, puis de mettre le nouveaux nom dans le fichier JSP, ce qui devient rapidement fastidieux.
Utiliser Gradle pour générer un suffixe automatiquement
Avec Maven, on dispose d’un mécanisme de Filtering
qui permet de remplacer des tokens particuliers placés dans les fichiers du projet par des valeurs indiquées dans la configuration du build. Gradle dispose également de cette fonctionnalité, et nous allons pouvoir l’utiliser pour ajouter automatiquement un suffixe aux imports des scripts.
Ce token particulier est une simple chaîne de caractères entourées d’un @
de part et d’autre. Voici ce que cela donne dans mon exemple :
<liferay-util:html-bottom>
<script src="/o/foo-portlet/js/foo-@timestamp@.js"></script>
</liferay-util:html-bottom>
Une fois les fichiers JSP prêts, il faut désormais configurer Gradle.
Tout d’abord, on va créer une variable avec la valeur du suffixe :
def timestamp = 'v1'
On va ensuite activer le Filtering
en configurant la tâche processResources
import org.apache.tools.ant.filters.ReplaceTokens
// ...
processResources {
filter ReplaceTokens, tokens: [
"timestamp": timestamp
]
}
On va ajouter une nouvelle tâche pour ajouter automatiquement le suffixe aux fichiers Javascript :
task cacheBusting (type: Copy) {
with copySpec {
from('src/main/resources') {
include '**/*.js'
}
rename('(.*)\\.js', "\$1-${timestamp}.js")
}
into("$buildDir/resources/main")
}
En l’état, les fichiers Javascript sont copiés deux fois, l’une par notre tâche avec les suffixe, et l’autre par processResources
sans le suffixe. On va donc lui retirer ces fichiers :
processResources {
// ...
exclude('**/*.js')
}
Enfin, on va brancher notre tâche cacheBusting
au sein du processus de build Gradle :
task cacheBusting (type: Copy) {
// ...
dependsOn(processResources)
}
jar {
dependsOn(cacheBusting)
}
Configuration finale
// ...
import org.apache.tools.ant.filters.ReplaceTokens
def timestamp = "v1"
processResources {
// replace @timestamp@ with the defined value in every resource files
// note : it uses Ant under the hood
filter ReplaceTokens, tokens: [
"timestamp": timestamp
]
// do not process js files to let cacheBusting task handle them
exclude('**/*.js')
}
// handle js files with this new task to rename them with the timestamp
task cacheBusting (type: Copy) {
with copySpec {
from('src/main/resources') {
include '**/*.js'
}
rename('(.*)\\.js', "\$1-${timestamp}.js")
}
into("$buildDir/resources/main")
dependsOn(processResources)
}
jar {
dependsOn(cacheBusting) // insert the task into the gradle lifecycle
}
Résultat
build
├── resources
│ └── main
│ ├── content
│ │ └── Language.properties
│ └── META-INF
│ └── resources
│ ├── css
│ │ └── main.scss
│ ├── images
│ │ └── foo.jpg
│ ├── init.jsp
│ ├── js
│ │ ├── bar-v1.js
│ │ ├── baz-v1.js
│ │ └── foo-v1.js
│ └── views
│ ├── bar.jsp
│ ├── baz.jsp
│ └── foo.jsp
...
<!-- foo.jsp -->
<!-- ... -->
<liferay-util:html-bottom>
<script src="/o/foo-portlet/js/foo-v1.js"></script>
</liferay-util:html-bottom>