Empaquetar varias aplicaciones Vue en un JAR de Spring Boot con Gradle
En esta página
Para empaquetar varias aplicaciones Vue en un JAR de Spring Boot, asigna a cada una su propia tarea npm ci y de compilación, prepara los archivos generados en build/ y añádelos a bootJar. No compartas la tarea de instalación de una aplicación con las demás ni escribas los paquetes generados directamente en src/main/resources.
Este ejemplo ejecutable sirve dos aplicaciones en /project1/index.html y /project2/index.html. Su entorno de reproducción es JDK17.0.8.1, Gradle8.4, Node24.13.0/npm11.6.2, Boot3.5.7, Vue3.5.44 y Vite8.3.5. Son versiones fijadas para reproducirlo, no recomendaciones de dependencias actuales para producción. Comprueba por separado los requisitos de ejecución de Vite y los de la versión de Boot elegida.
Flujo de procesamiento
Cada aplicación Vue genera su propia salida. Una tarea Gradle Sync reúne únicamente esas salidas y bootJar las coloca en rutas estáticas separadas.
Tareas por aplicación y empaquetado
Guarda estos archivos en un proyecto vacío. Cada aplicación tiene su propio package.json, archivo de bloqueo, node_modules y dist. prepare.sh copia el HTML/JS compartido en la segunda aplicación y crea ambos archivos de bloqueo.
settings.gradle
rootProject.name = 'multi-frontend'
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.7'
}
group = 'example'
version = '1.0.0'
repositories { mavenCentral() }
dependencies {
implementation platform('org.springframework.boot:spring-boot-dependencies:3.5.7')
implementation 'org.springframework.boot:spring-boot-starter-web'
}
def apps = ['project1', 'project2']
def npmCommand = System.getProperty('os.name').toLowerCase().contains('windows') ? 'npm.cmd' : 'npm'
def frontendBuilds = apps.collect { app ->
def frontend = layout.projectDirectory.dir("frontend/${app}")
def install = tasks.register("${app}NpmCi", Exec) {
workingDir frontend
commandLine npmCommand, 'ci', '--no-audit', '--no-fund'
inputs.files(frontend.file('package.json'), frontend.file('package-lock.json'))
outputs.dir(frontend.dir('node_modules'))
}
tasks.register("${app}Build", Exec) {
dependsOn install
workingDir frontend
commandLine npmCommand, 'run', 'build'
inputs.files(fileTree(frontend) {
exclude 'node_modules/**', 'dist/**'
})
outputs.dir(frontend.dir('dist'))
}
}
def collectFrontend = tasks.register('collectFrontend', Sync) {
dependsOn frontendBuilds
into layout.buildDirectory.dir('frontend-resources')
apps.each { app ->
from("frontend/${app}/dist") { into "static/${app}" }
}
}
tasks.named('bootJar') {
dependsOn collectFrontend
from(collectFrontend) { into 'BOOT-INF/classes' }
}
src/main/java/example/Application.java
package example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
frontend/project1/package.json
{
"name": "project1",
"private": true,
"type": "module",
"scripts": {
"build": "vite build --base=/project1/"
},
"dependencies": {
"vue": "3.5.44"
},
"devDependencies": {
"vite": "8.3.5"
}
}
frontend/project2/package.json
{
"name": "project2",
"private": true,
"type": "module",
"scripts": {
"build": "vite build --base=/project2/"
},
"dependencies": {
"vue": "3.5.44"
},
"devDependencies": {
"vite": "8.3.5"
}
}
frontend/project1/index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Vue build example</title></head>
<body><div id="app"></div><script type="module" src="/src/main.js"></script></body></html>
frontend/project1/src/main.js
import { createApp, h } from 'vue';
createApp({ render: () => h('h1', `Vue app at ${import.meta.env.BASE_URL}`) }).mount('#app');
prepare.sh
#!/bin/sh
set -eu
mkdir -p frontend/project2/src
cp frontend/project1/index.html frontend/project2/index.html
cp frontend/project1/src/main.js frontend/project2/src/main.js
for app in project1 project2; do
(cd "frontend/$app" && npm install --package-lock-only --ignore-scripts --no-audit --no-fund)
done
Ejecuta prepare.sh al preparar inicialmente este ejemplo y registra ambos archivos package-lock.json generados. Las siguientes compilaciones de CI usan npm ci con los archivos de bloqueo guardados. No los regeneres en cada compilación.
Compilar y ejecutar
Desde la raíz del proyecto, ejecuta lo siguiente con Node/npm disponibles en PATH:
node --version
npm --version
sh prepare.sh
gradle --no-daemon clean bootJar
java -jar build/libs/multi-frontend-1.0.0.jar
En un proyecto existente con wrapper, usa su versión de Gradle mediante ./gradlew. Tras el arranque, visita http://localhost:8080/project1/index.html y http://localhost:8080/project2/index.html. Las páginas muestran Vue app at /project1/ y Vue app at /project2/, respectivamente.
La opción base de Vite proporciona las URL de recursos adecuadas a cada aplicación. No configura el enrutamiento SPA del servidor. Este ejemplo usa URL index.html explícitas; la navegación directa con history-router u otra ruta de contexto requiere configuración adicional.
Inspeccionar las rutas reales del JAR
BOOT-INF/classes/static/project1/index.html
BOOT-INF/classes/static/project1/assets/...
BOOT-INF/classes/static/project2/index.html
BOOT-INF/classes/static/project2/assets/...
Tras compilar cada aplicación, collectFrontend usa Sync para preparar solo las salidas actuales. bootJar las añade al classpath del JAR ejecutable. Consulta la gestión de archivos de Gradle y el empaquetado de archivos de Boot.
Ambas aplicaciones se compilaron y empaquetaron con esta configuración, y el JAR en ejecución sirvió sus recursos HTML y JS por HTTP. Después se eliminó un archivo del directorio public de una aplicación y se reconstruyó el JAR sin clean para comprobar que el archivo obsoleto no permaneciera. Los archivos de bloqueo de ambas aplicaciones se comprobaron por separado.
Adaptarlo a un proyecto existente
- Ajusta la lista
appsa los directorios y proporciona cada archivo de bloqueo. - Mantén los espacios generados
static/project1ystatic/project2separados de los recursos escritos manualmente. - Para componentes Vue SFC, añade el plugin y la configuración Vite adecuados. Este ejemplo pequeño usa funciones de renderizado de Vue.
- La integración se dirige al
bootJarejecutable, no aljarnormal. Inspecciona el artefacto que desplegarás.
En una aplicación existente, comprueba la base de migración de Java y Jakarta en Spring Boot independientemente del empaquetado. Si también ejecuta trabajos Batch, sigue las comprobaciones de migración del repositorio Batch y reinicio; servir recursos de interfaz desde un JAR no valida los datos persistidos de los trabajos.
Para automatización basada en versiones, consulta la selección de versiones y recursos de GitHub y el contrato de entrada del comparador numérico. La guía de funciones POSIX sh trata la selección del intérprete para scripts de preparación.
Comentarios