Enriching your Spring Boot apps with Vue.js

17/09/2019

Spring boot and Vue.js make a great combination:  In the server side you get the rich Java ecosystem, static typing, and a powerful framework to develop from small up to enterprise grade complex backends. 

And then there's Vue.js. It's a bit like React, a bit like Angular.js, combines the best of both in a simple and easy to pick learning curve, providing a UI layer to create fast, powerful and complex wep apps. Vue is growing really fast, with even projects like Weex for creating native mobile apps with Vue, or just Vue-based component libraries like Quasar, or many others.

The problem is that probably, you already have your project built with Spring, using JSP or Thymeleaf for the UI layer. Sure you could go straight and write js code directly in your views with Vue, but, wouldn't it be nice to be able to use single file templates (.vue templates), ES6, and other stuff like managing dependencies with webpack? The problem is that this approach is often focused on writing single page applicaions (SPA). And you don't want to start the UI from scratch in order to convert it into an SPA. So you may want to keep using Spring MVC, but let some spring views hand to control over to Vue.js. Here's how I do it:

Spring MVC Integration

 Here, I will use Thymeleaf, but you can use JSP, or any other template engine.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<body>
<div id="wrapper">
    <div th:replace="layout/common :: leftmenu"></div>

    <div id="page-content-wrapper">
        <div id="app"></div>
    </div>
</div>

<script th:if="${#arrays.contains(@environment.getActiveProfiles(),'dev')}"
        src="http://localhost:8888/dist/build.js"></script>
<script th:if="${!#arrays.contains(@environment.getActiveProfiles(),'dev')}" src="/static/js/build.js"></script>

</body>
</html>

This is an startand thymeleaf template, and the only thing it does is to render a menu, and a div where we bind the vue.js app. Other than that, we only need to invoke the javascript app, generated with Webpack: build.js. But here, If I'm on development profile, I'll use the build.js provided by the local node.js listening on port 8888. If I'm in production, buil.js will be deployed as a static file (more on that later).

The Vue app

 The front-end code is deployed in src/front (but it could be deployed somewhere else). I use vue-cli to scaffold all the bits an pieces invovled (after all, I'm just a mere mortal). So, when in development mode, I open a console, and run npm run dev inside src/front directory. 

Now, in the front end code, the main entry point with the Vue app is main.js (at least if you use vue-cli). You'll see something like this:

import Vue from 'vue'
import App from './App.vue'


new Vue({
  el: '#app',
  render: h => h(App)
})

 

Here, we create a new vue instance (App), bound to the dom element with id = "app". You will probably want to have multiple vue applications though, where each application is isolated from the rest, but, again, without creating a full blown SPA, so you'll want server-side session management and authentication for example, but each URL on the application corresponds to a different Vue app. To do so, I use something like this:

 

import Vue from 'vue'
import App from './App.vue'


var app1 = document.getElementById("app");
if(app1!=null) {
    new Vue({
        el: '#app',
        render: h => h(App)
    })
}

The idea is to fire up a given vue app depending on the presence of a given container in the DOM that should host it. That way we can use a single codebase for all the different apps that we need. Although those apps in the end are just vue components.

Build and deployment

Maven will now only generate a jar file for the spring app, but we need the build.js that webpack generates to be copied into src/main/resources/static before building the jar. To do so, I use the frontend-maven-plugin, and then copy the resulting js file into static/js:

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<executable>true</executable>
				</configuration>
			</plugin>
			<plugin>
				<groupId>com.github.eirslett</groupId>
				<artifactId>frontend-maven-plugin</artifactId>

				<configuration>
					<workingDirectory>src/front</workingDirectory>
				</configuration>
				<executions>
					<execution>
						<id>install node and npm</id>
						<goals>
							<goal>install-node-and-npm</goal>
						</goals>
						<configuration>
							<nodeVersion>v4.2.1</nodeVersion>
							<npmVersion>3.5.3</npmVersion>
						</configuration>
					</execution>

					<execution>
						<id>npm install</id>
						<goals>
							<goal>npm</goal>
						</goals>
						<configuration>
							<arguments>install</arguments>
						</configuration>
					</execution>

					<execution>
						<id>npm run build</id>
						<goals>
							<goal>npm</goal>
						</goals>
						<configuration>
							<arguments>run build</arguments>
						</configuration>
					</execution>

				</executions>
			</plugin>
		</plugins>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
			</resource>
			<resource>
				<filtering>false</filtering>
				<directory>src/front/dist</directory>
				<includes>
					<include>**build.js</include>
				</includes>
				<targetPath>static/js</targetPath>
			</resource>
		</resources>
	</build>

 

 The maven resources plugin will look for build.js in src/front/dist, and copy it into static/js before generating the final jar. So when you run mvn package, this plugin will download and install nodejs (if required), run webpack, compile, test, copy dist.js into the static/js folder, and package the whole thing into a jar.

Nice!