Javalin-Vite is a Javalin integration which allows us to create complex Vue 3 apps with ease.
Javalin-Vite has the following advantages compared to the official JavalinVue integration:
- One Click Production Ready Packaging - Frontend files will be packed and minified before packaging the final .jar file. This reduces file size and increases performance of your app.
- Hot Module Replacement (HMR) - During development, all edits to frontend files will be reflected in the browser immediately, without manually hitting refresh.
- NPM support - You can install frontend packages using NPM. This has the benefit that builds can be optimized for your frontend. E.g. tailwind.css has a development size of about 3 MB, whereas a typical production file size is only about 10 KB.
Additionally, Javalin-Vite includes the following features similar to JavalinVue:
- Multiple independent Vue apps for multiple routes are possible
- The server side state is shared between backend and frontend. There even is a local state for each component.
open the javalin-vite directory and run this command to install it locally.
mvn clean install
You can either use the example to build your project or you can add javalin vite to your existing project. We recommend that you always start using the example and add your existing code to it.
Just copy the example project included in this repository and run the Main.kt function. You need to start the app with the dev argument or else Javalin-Vite tries to server the compiled production data which is not available in the IDE. Javalin will listen on http://localhost:7000/ and an example app will be served.
The following changes to your pom.xml are needed:
<dependency>
<groupId>io.javalin</groupId>
<artifactId>javalin-vite</artifactId>
<version>0.9.2</version>
</dependency>
<properties>
<nodeVersion>v14.17.0</nodeVersion>
<npmVersion>provided</npmVersion>
</properties>
<build>
<plugins>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.12.0</version>
<executions>
<!-- We install node and npm, if not already done -->
<execution>
<id>install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<nodeVersion>${nodeVersion}</nodeVersion>
<npmVersion>${npmVersion}</npmVersion>
</configuration>
</execution>
<!-- we call npm install in order to fetch missing libs -->
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
<!-- we build a production version of the frontend which will then be included in the jar -->
<execution>
<id>npm build production</id>
<goals>
<goal>npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<build>
<resources>
<!-- We will build our frontend project in the frontend_dist and JavalinVite expects the assets in the frontend path. -->
<resource>
<directory>frontend_dist</directory>
<targetPath>/frontend</targetPath>
</resource>
<!-- Filtered resource in order to tell the JavalinVite runtime the node and npm versions which are also used during builds -->
<resource>
<directory>src/main/resources-filtered</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
</resource>
</resources>
</build>
This file must be located at your project root. It defines directories and files used by javalin-vite.
//vite.config.js
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
// We use cleaner to clean the frontend_dist folder before packing it into .jar files.
// Else the new files will be added and old files will still be included in the build
import cleaner from 'rollup-plugin-cleaner';
// We need globby to select the javascript entry files in the frontend/pages directory.
// Unfortunately, rollupjs can not natively handle wildcards here.
import globby from "globby";
const {resolve} = require('path')
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue()
],
build: {
manifest: true,
rollupOptions: {
plugins: [
cleaner({
targets: [
'./frontend_dist/'
]
})
],
input: globby.sync("frontend/pages/*.js"),
output: {
dir: resolve(__dirname, "./frontend_dist")
}
}
}
})
At resources/vite/layout.html.
<html>
<head>
<meta charset="utf8">
@viteMountPoint
</head>
<body>
<main id="app" v-cloak></main>
</body>
</html>
Next to your resources directory, add another directory named resources-filtered, which contains a pom.properties file.
nodeVersion=${nodeVersion}
npmVersion=${npmVersion}
This directory named frontend has to be located at the project root. It should contain two other directories named components and pages. The components directory is used to store the project's *.vue components.
<template>
<h1>App.vue</h1>
<p>This is the App.vue file. It is the first component of our app and is returned on the / path via Javalin.</p>
<p>We can easily pass data from Javalin to our frontend components:<br/> Javalin says: This page was accessed
{{ page_loads }} times since the server started.</p>
<p>
We can also pass a global state from Javalin. This will be visible in all components: {{global_state}}
</p>
<a href="/app2">Go to App2.vue</a>
</template>
<script>
export default {
data() {
return {
page_loads: $javalin.state.pageLoads,
global_state: $javalin.globalState
}
}
}
</script>
The pages directory contains javascript files which can be attached to Javalin using the ViteHandler class, in order to bind components to get endpoints.
import {createApp} from "vue";
import App from '../components/App.vue'
createApp(App).mount("#app")
import io.javalin.javalin.vite.JavalinVite
import io.javalin.javalin.vite.ViteHandler
import io.javalin.Javalin
fun main(args: Array<String>) {
val isDevMode = args.isNotEmpty() && "DEV".equals(args[0], true)
val app = Javalin.create { config ->
JavalinVite.configure(config, isDevMode)
}.start(7000)
var i = 0
app.get("/", ViteHandler("pages/app.js") {
mapOf("pageLoads" to i++)
})
}