Maven, Node & NPM
Pour développer une application web, je pars généralement sur une stack qui consiste en un backend java, et une application frontend dans un framework type Angular, React, VueJS, ou autre.
Avec cette stack, je vais avoir inévitablement deux outils de build aux concepts différents :
- Maven pour la partie back (à noter qu’il existe aussi Gradle, qui est un excellent outil mais Maven reste généralement mon premier choix).
- NodeJS et son package manager :
npm
,yarn
oupnpm
(les trois sont très bien et le choix dépendra de votre contexte).
Se pose donc la question de faire cohabiter ces deux outils de build de manière simple.
Etant un peu éxigeant, je vais rajouter plusieurs critères pour dire que l’intégration est réussie :
- Un même cycle de vie.
- Une unique commande pour lancer mes différentes tâches (téléchargement des dépendances, build, tests, etc.).
- Je dois pouvoir travailler sur chaque projet de manière indépendante, mais l’ensemble doit pouvoir être buildé en une seule commande que ce soit sur mon environnement de dev ou sur mon outil de CI.
Cycle de vie maven
Pour ceux qui ne débuteraient avec maven, il possède un cycle de vie qu’il me semble important de bien comprendre. Lorsqu’on lance la commande mvn verify
, l’application va être buildé suivant un cycle de vie bien précis. Je ne rentrerai pas dans tous les détails, la documentation maven étant très bien faite.
Pour rester simple, on peut résumer les grandes étapes du build :
- Téléchargement des dépendances.
- Validation.
- Compilation / Build
- Tests unitaires
- Packaging
- Tests d’intégration.
Personnellement, je trouve que ce cycle de vie pourrait correspondre assez bien au cycle de vie d’une application frontend :
- Téléchagement des dépendances déclarées dans le fichier
package.json
- Validation syntaxique avec
ESLint
,TSLint
, etc. - Build de l’application avec
webpack
,rollup
, etc. - Tests (
jasmine
,jest
,karma
, etc.). - Packaging.
- Tests end to end
C’est dans cette optique que j’ai développé il y a déjà un petit moment un plugin maven dont le seul rôle sera de lancer les scripts déclarés dans le fichier package.json
associés à chaque étape de ce cyle de vie.
Initialisation du projet
Pour la simplification, je présenterai uniquement les fichiers de build, les fameux pom.xml
et package.json
de chaque partie.
Afin d’avoir deux projets indépendants mais pouvant être buildé de manière unifiée, nous allons utiliser un projet multi-module de maven dont voici l’arborescence :
<root>
backend/
src/
main/
java/
test/
java
pom.xml
frontend/
src/
package.json
pom.xml
pom.xml
Le pom.xml
racine va être assez simple, on déclarera simplement nos sous-modules :
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.org</groupId>
<artifactId>my-app</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>frontend</module>
<module>backend</module>
</modules>
</project>
Backend
Le fichier backend/pom.xml
est également assez simple, puisqu’il consistera juste à builder notre application java :
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.org</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>my-app-backend</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
</dependencies>
</project>
Frontend
Regardons maintenant le fichier frontend/pom.xml
:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.org</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>my-app-frontend</artifactId>
<build>
<plugins>
<plugin>
<groupId>com.github.mjeanroy</groupId>
<artifactId>node-maven-plugin</artifactId>
<version>0.4.0</version>
<executions>
<execution>
<id>npm</id>
<goals>
<goal>check</goal>
<goal>install</goal>
<goal>lint</goal>
<goal>test</goal>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Ici, nous utilisons le plugin node-maven-plugin
pour intégrer nos commandes npm au cycle de vie maven. Chaque goal maven précisé plus haut est bindé sur une phase du cycle de vie maven (plus d’infos sur le readme du projet).
Regardons à quoi ressemble le fichier package.json
associé :
{
"name": "my-app",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --aot=true --optimization=true --prod=true --watch=false",
"test": "ng test -- --no-watch --no-progress --browsers=ChromeHeadlessCI",
"tdd": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
},
"devDependencies": {
}
}
Chaque script va donc être appelé automatiquement par node-maven-plugin
. Le plugin va également s’intégrer aux paramètres maven que vous utilisez peut-être comme -DskipTests
, -Dmaven.test.skip
, etc.
Evidemment, travailler sur la partie front ne nécessite absolument pas d’installer java ou maven, il est tout à fait possible de lancer les scripts npm de manière totalement indépendante.
Intégration sur la CI
Le build du projet avec travis-ci va donc être assez simple car une seule commande sera suffisante pour builder / tester la partie back et front :
dist: trusty
sudo: false
language: java
jdk:
- openjdk8
env:
- NODE_VERSION="12.13.0"
addons:
apt:
sources:
- google-chrome
packages:
- google-chrome-stable
before_install:
- nvm install $NODE_VERSION
Je l’utilise depuis maintenant quelques années, mais n’hésitez pas à aller lire le README du projet, et à ouvrir les issues appropriées si vous pensez que quelque chose manque !
Notez que vous pouvez aussi utiliser github actions, c’est tout aussi simple :
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 8
- uses: actions/setup-node@v1
name: Set up NodeJS
with:
node-version: '12.16.0'
- name: Build
run: mvn -B package -Dmaven.test.skip.exec=true --file pom.xml
- name: Test
run: mvn -B test --file pom.xml
C’est tout pour cet article !