Templating des ressources Gradle selon la cible
Cette documentation explique comment remplacer des variables dans des fichiers HTML, CSS, JavaScript ou texte avec une syntaxe de type :
@PUBLIC_BASE_URL@@PUBLIC_API_URL@Les remplacements sont effectués pendant le traitement des ressources. Les fichiers sources ne sont jamais modifiés.
1. Définir les valeurs
Section intitulée « 1. Définir les valeurs »La priorité recommandée est :
- Variable d’environnement CI ou locale
- Propriété Gradle locale
val publicBaseUrl = providers.environmentVariable("PUBLIC_BASE_URL") .orElse(providers.gradleProperty("publicBaseUrl"))
val publicApiUrl = providers.environmentVariable("PUBLIC_API_URL") .orElse(providers.gradleProperty("publicApiUrl"))Exemple de gradle.properties local :
publicBaseUrl=http://localhost:8080publicApiUrl=http://localhost:8080Exemple CI :
PUBLIC_BASE_URL=https://example.comPUBLIC_API_URL=https://api.example.comUne valeur publique peut être injectée dans une ressource. Un secret ne doit jamais l’être, car il sera visible dans le fichier final.
2. Syntaxe des templates
Section intitulée « 2. Syntaxe des templates »Exemple HTML :
<link rel="icon" href="@PUBLIC_BASE_URL@/static/favicon/favicon.png"><script src="@PUBLIC_API_URL@/auth/bootstrap.js"></script>Exemple CSS :
@font-face { src: url("@PUBLIC_BASE_URL@/static/fonts/nunito-variable.woff2");}Exemple JavaScript :
fetch(`@PUBLIC_API_URL@/auth/me`);Le même token peut apparaître plusieurs fois dans un fichier. Plusieurs tokens différents peuvent également être utilisés.
3. Pourquoi ReplaceTokens
Section intitulée « 3. Pourquoi ReplaceTokens »Utiliser ReplaceTokens avec une syntaxe @TOKEN@ évite les collisions avec :
- les chaînes JavaScript contenant
$ - les templates JavaScript
- les expressions CSS
- les caractères
${...}utilisés ailleurs
expand est moins adapté aux fichiers JavaScript, car il interprète aussi les expressions Groovy $variable et ${expression}.
4. Module JVM
Section intitulée « 4. Module JVM »Les ressources JVM sont généralement placées dans :
src/main/resources/La tâche standard est :
processResourcesConfiguration :
import org.apache.tools.ant.filters.ReplaceTokensimport org.gradle.language.jvm.tasks.ProcessResources
val publicBaseUrl = providers.environmentVariable("PUBLIC_BASE_URL") .orElse(providers.gradleProperty("publicBaseUrl"))
val publicApiUrl = providers.environmentVariable("PUBLIC_API_URL") .orElse(providers.gradleProperty("publicApiUrl"))
tasks.named<ProcessResources>("processResources") { inputs.property("publicBaseUrl", publicBaseUrl) inputs.property("publicApiUrl", publicApiUrl)
filteringCharset = "UTF-8"
filesMatching( listOf( "static/**/*.html", "static/**/*.css", "static/**/*.js", ) ) { filter( ReplaceTokens::class, "tokens" to mapOf( "PUBLIC_BASE_URL" to publicBaseUrl.get().removeSuffix("/"), "PUBLIC_API_URL" to publicApiUrl.get().removeSuffix("/"), ), ) }}Les fichiers transformés sont produits dans :
build/resources/main/La tâche est automatiquement exécutée avant la création du JAR.
5. Kotlin Multiplatform Web
Section intitulée « 5. Kotlin Multiplatform Web »Les ressources Web sont généralement placées dans :
src/webMain/resources/Les cibles Web utilisent des tâches spécifiques. Il n’existe pas de tâche JVM générique processResources.
Kotlin/Wasm
Section intitulée « Kotlin/Wasm »Pour Wasm :
import org.apache.tools.ant.filters.ReplaceTokensimport org.gradle.api.tasks.Copy
val publicBaseUrl = providers.environmentVariable("PUBLIC_BASE_URL") .orElse(providers.gradleProperty("publicBaseUrl"))
val publicApiUrl = providers.environmentVariable("PUBLIC_API_URL") .orElse(providers.gradleProperty("publicApiUrl"))
tasks.named<Copy>("wasmJsProcessResources") { inputs.property("publicBaseUrl", publicBaseUrl) inputs.property("publicApiUrl", publicApiUrl)
filteringCharset = "UTF-8"
filesMatching( listOf( "**/*.html", "static/**/*.css", "static/**/*.js", ) ) { filter( ReplaceTokens::class, "tokens" to mapOf( "PUBLIC_BASE_URL" to publicBaseUrl.get().removeSuffix("/"), "PUBLIC_API_URL" to publicApiUrl.get().removeSuffix("/"), ), ) }}La distribution est généralement produite avec :
wasmJsBrowserDistributionLe résultat se trouve généralement dans :
build/dist/wasmJs/productionExecutable/Kotlin/JS
Section intitulée « Kotlin/JS »Pour la cible JavaScript classique, utiliser :
tasks.named<Copy>("jsProcessResources") { inputs.property("publicBaseUrl", publicBaseUrl) inputs.property("publicApiUrl", publicApiUrl)
filteringCharset = "UTF-8"
filesMatching( listOf( "**/*.html", "static/**/*.css", "static/**/*.js", ) ) { filter( ReplaceTokens::class, "tokens" to mapOf( "PUBLIC_BASE_URL" to publicBaseUrl.get().removeSuffix("/"), "PUBLIC_API_URL" to publicApiUrl.get().removeSuffix("/"), ), ) }}Un module possédant js et wasmJs doit configurer les deux tâches.
tasks.named<Copy>("jsProcessResources") { // Configuration JavaScript}
tasks.named<Copy>("wasmJsProcessResources") { // Configuration Wasm}Une configuration Wasm ne modifie pas automatiquement la cible JavaScript.
6. Kotlin Multiplatform Android
Section intitulée « 6. Kotlin Multiplatform Android »Android ne fonctionne pas comme un module JVM classique.
Il faut distinguer deux cas.
Fichier texte Android
Section intitulée « Fichier texte Android »Pour un fichier texte ou JSON sous :
src/androidMain/resources/Le traitement dépend de la configuration Android et de la variante produite. Les tâches peuvent être spécifiques, par exemple :
processDebugJavaResprocessReleaseJavaResIl est déconseillé de dépendre directement d’un nom de tâche de variante si plusieurs variantes existent.
Une tâche Copy dédiée est plus stable :
import org.apache.tools.ant.filters.ReplaceTokensimport org.gradle.api.tasks.Copy
val publicBaseUrl = providers.environmentVariable("PUBLIC_BASE_URL") .orElse(providers.gradleProperty("publicBaseUrl"))
val generateAndroidTemplates by tasks.registering(Copy::class) { from("src/androidMain/resources") into(layout.buildDirectory.dir("generated/androidResources"))
filteringCharset = "UTF-8"
filesMatching("**/*.json") { filter( ReplaceTokens::class, "tokens" to mapOf( "PUBLIC_BASE_URL" to publicBaseUrl.get().removeSuffix("/"), ), ) }}Cette sortie doit ensuite être ajoutée aux ressources Android de la variante concernée.
Ressources Android res/
Section intitulée « Ressources Android res/ »Pour les fichiers sous :
src/androidMain/res/src/main/res/Il ne faut pas appliquer un remplacement textuel aveugle à tous les fichiers.
Utiliser plutôt :
resValuebuildConfigField- une ressource XML générée
- une configuration runtime
Exemple :
android { defaultConfig { buildConfigField( "String", "PUBLIC_BASE_URL", "\"https://example.com\"", ) }}Puis dans Kotlin :
val publicBaseUrl = BuildConfig.PUBLIC_BASE_URLPour Android, les valeurs de configuration sont souvent plus propres dans BuildConfig ou dans une ressource XML que dans un template HTML filtré.
7. Ressources Compose Multiplatform
Section intitulée « 7. Ressources Compose Multiplatform »Les ressources Compose Multiplatform se trouvent généralement dans :
src/commonMain/composeResources/ou :
src/commonMain/resources/Elles sont traitées par le plugin Compose Multiplatform et peuvent être générées pour plusieurs plateformes.
Elles conviennent pour :
- images
- icônes
- textes
- fichiers embarqués
- ressources localisées
Elles ne sont pas le meilleur endroit pour injecter une URL différente selon l’environnement.
Pour une configuration dépendante de l’environnement, préférer :
expect val publicBaseUrl: Stringavec des implémentations spécifiques :
// commonMainexpect val publicBaseUrl: String// androidMainactual val publicBaseUrl: String get() = BuildConfig.PUBLIC_BASE_URL// wasmJsMainactual val publicBaseUrl: String get() = "https://example.com"Cette approche convient lorsque la valeur doit être connue par le code Kotlin.
8. Kotlin/Native et Desktop
Section intitulée « 8. Kotlin/Native et Desktop »Desktop JVM
Section intitulée « Desktop JVM »Une cible Desktop JVM utilise généralement :
processResourcesLa configuration est donc proche d’un module JVM classique.
Les cibles Native n’utilisent pas nécessairement un pipeline de ressources identique à JVM.
Pour des fichiers générés par cible, utiliser :
- une tâche Gradle
Copy - une tâche Gradle
Sync - une source générée par cible
- une configuration runtime
Il faut éviter de supposer qu’une tâche processResources existe pour chaque cible Native.
9. Compilation ou runtime
Section intitulée « 9. Compilation ou runtime »Le templating Gradle produit un artefact différent pour chaque environnement.
local -> http://localhost:8080staging -> https://staging.example.comproduction -> https://example.comAvantages :
- le fichier final est directement utilisable
- aucune logique supplémentaire au runtime
- adapté aux fichiers statiques
- adapté aux URL publiques
Inconvénients :
- un artefact différent par environnement
- il faut reconstruire après chaque changement de valeur
- la valeur doit être connue au moment du build
Si le même artefact doit être déployé partout, utiliser une configuration runtime :
data class WebConfig( val publicBaseUrl: String, val publicApiUrl: String,)Puis remplacer le template au démarrage ou générer la page depuis Kotlin.
10. Exécution locale
Section intitulée « 10. Exécution locale »Avec des propriétés Gradle :
.\gradlew.bat :src:application:backend:processResources ` -PpublicBaseUrl=http://localhost:8080 ` -PpublicApiUrl=http://localhost:8080Pour Wasm :
.\gradlew.bat ` :src:application:backoffice-app:backoffice-webApp:wasmJsBrowserDistribution ` -PpublicBaseUrl=http://localhost:8080 ` -PpublicApiUrl=http://localhost:8080Avec des variables d’environnement :
$env:PUBLIC_BASE_URL = "http://localhost:8080"$env:PUBLIC_API_URL = "http://localhost:8080"
.\gradlew.bat ` :src:application:backoffice-app:backoffice-webApp:wasmJsBrowserDistributionUne variable configurée uniquement dans une tâche de lancement IntelliJ peut ne pas être disponible dans le processus Gradle. Elle doit être configurée dans la tâche Gradle elle-même ou être passée avec -P.
11. Erreurs fréquentes
Section intitulée « 11. Erreurs fréquentes »- Utiliser
processResourcesdans un module Kotlin Multiplatform Web. - Utiliser
static/html/**/*.htmlalors que les fichiers sont danssrc/webMain/resources. - Oublier
inputs.property. - Utiliser
expandsur du JavaScript contenant des expressions$. - Filtrer les fichiers générés au lieu des fichiers source.
- Injecter des secrets dans une ressource Web.
- Configurer uniquement
wasmJsProcessResourcesalors que la cible construite estjs. - Modifier
src/.../resourcesdirectement pendant le build. - Ajouter une valeur par défaut de production dangereuse en développement.
12. Choix recommandé
Section intitulée « 12. Choix recommandé »Pour ce projet :
- backend JVM :
processResources - backoffice Wasm :
wasmJsProcessResources - backoffice JavaScript :
jsProcessResources - Android :
BuildConfig,resValueou configuration runtime - Compose Multiplatform : ressources Compose pour les fichiers statiques, configuration spécifique par cible pour les URL
- valeurs publiques : variables d’environnement CI avec fallback dans
gradle.properties - secrets : jamais dans les templates Web