At our company we have many legacy applications which are written using Jquery on the frontend and Java / Spring on the backend. This technique should work with any type of backend though, and is a good way to start integrating new front end technologies into your stack.

The reason we are utilizing this strategy is to be able to enhance our current application and develop them without having to rewrite the whole front end. New pages can be written in Vue.js while old pages can still be written in Jquery or any other previous technology.

The first step to start integrating typescript and vue is to create a tsconfig.json file which contains the typescript configuration. The typescript config I am using is.

Integrating Typescript

{

“compilerOptions”: {

“module”: “system”,

“moduleResolution”: “node”,

“target”: “es5”,

“sourceMap”: true,

“outDir”: “src/main/resources/static/js”,

“experimentalDecorators”: true, //angular needs decorators like @Component, @Injectable, etc.

“emitDecoratorMetadata”: true, //for angular to be able to use metadata we specify in our components.

}

}

module : system ← tell typescript to transpile our code to a format that can be used by system.js

moduleResolution: node ← tells typescript to use node module resolution. Typescript will look inside our node_modules folder for external dependencies.

outDir ← tells typescript where to output our compiled files

Now that we have this configuration, add a new folder under the root to place your typescript code. I have mine in src/typescript.

When the code compiles it will be placed under a static directory served by my backend framework. This can be changed in the ourDir setting in the typescript configuration.

When these files are compiled you will notice they have a system.register call at the top like so.

System.register(["vue", "vue-class-component"], function (exports_1, context_1) {

This tells system.js what modules depend on this file. The import/export operations in typescript let it know what modules to use. Here is an example component using the vue class syntax, that corresponds to the above output.

import Vue from 'vue'

import Component from 'vue-class-component';



@Component({

template: `<div>

<button @click="saveRecord">Save</button>

<button @click="deleteRecord">Delete</button>

<button @click="greet">Greet</button>

</div>`,

})

export default class Ticket extends Vue {

// Initial data can be declared as instance properties

msg: string = 'Hello!'



greet (): string {

console.log(this.msg + ' world');

return this.msg + ' world';

}



saveRecord (): void{

console.log('save record');

}



deleteRecord (): void{

console.log('delete record');

}

}

Integrating Node Package Manager / NPM

Once you create your first typescript component, you will notice it may not be able to find the vue external dependency. This is because the typescript config is set to look for dependencies in the node modules folder. In order to get dependencies into node_modules you need to create a package.json to fetch those dependencies from NPM (Node Package Manager)

Place a package.json file in your root folder and run the command ‘npm install’

Here is an example package.json for vue, vue-class-component, and axios

{

"name": "example",

"version": "1.0.0",

"description": "",

"main": "",

"directories": {

"lib": "lib"

},

"dependencies": {

"axios": "^0.18.0",

"vue": "^2.5.17",

"vue-class-component": "^6.2.0"

},

"devDependencies": {},

"scripts": {

"test": "echo \"Error: no test specified\" && exit 1"

},

}

At this point we have our typescript files compiling, after we fetched our external dependencies using NPM. The output javascript files are also placed in out static directory and loaded by our backend when our application starts.

Integrating SystemJS

For now we have only been concerned with the compile time. What happens at runtime? How do we load our individual compiled files?

At this point you have two options. you can bundle them all into one file using webpack, or you could use system.js to async load the files per page. In our case we would rather load the files needed for each page, so it fits better in with our framework. We don’t want dependencies from one page on another page etc…

In order to get this working download system.js and include it in your html header. Also make another file for your configuration and include it as well.

<script src="lib/system-21-5.js"></script>

<script src="lib/systemjs.config.js"></script>

Here is an example systemjs config

System.config({

packages:{

'': {defaultExtension: 'js'},

'js': {defaultExtension: 'js'}

}

});

This tells sytemjs for files in the root and js folders of my public/static directory served by the web server to add a .js extension by default. This saves us from having to include it when we write our imports in the typescript file.

Now we have a system to load our modules asynchronously. On the page you want vue to run you will need to add a div to inject vue.

<div id="app">

<homepage></homepage>

</div>

In my case I am injecting view to the “app” id on my page.

To use systemjs, simply add a script tag to your page that tells system.js to load the javascript file for that page.

<script>

System.import('js/pages/home.js');

</script>

The home page imports the ticket component referenced above and creates a new Vue instance which is injected into the #app id.

import Vue from 'vue';

import Ticket from '../components/ticket';

import TicketService from '../services/ticketservice';

import Component from 'vue-class-component';



@Component({

components: { Ticket },

template: `

<div class="homePage">

<div class="container">

<div>HomePage</div>

<ul>

<li v-for="ticket in tickets">

<Ticket ></Ticket>

</li>

</ul>

</div>

</div>

`,

})

class HomePage extends Vue {

// Initial data can be declared as instance properties

tickets:[] = null;



mounted () {

TicketService.getHoldTickets().then((data) => {

this.tickets = data;

});

}

}



Vue.component("homepage", HomePage)

new Vue({

el: '#app'

});

SystemJS will automatically load the needed modules.

In order for SystemJS to load your libraries that were downloaded to node_modules at runtime, you need to place the javascript distribution file at your public/static root.

Note: An improvement instead of manually copying these files is to add a step in your build system to copy them from node_modules when the project is built.

Running the Application

Looking at the screenshot from the network requests in chrome we can see that SystemJS took the initial file, loaded the needed modules and injected our module into the Vue instance.

This technique is good if you want to continuous integrate different pages in your application to Vue or other front end libraries using Typescript. You could also use ES6 and transpile them using Babel instead of Typescript.

We use rest calls from our vue components to our backend most of the time to grab the data where necessary. This is done using a service class like so.

import axios from 'axios';



export class TicketService {



getHoldTickets() {

return axios({

method: 'get',

url: "/holdtickets",

})

.then((response) => {

return response.data;

})

.catch( (error) => {

return error;

});

}

}



const ticketService = new TicketService();

export default ticketService;

The service is then called when another component is mounted.

mounted () {

TicketService.getHoldTickets().then((data) => {

this.tickets = data;

});

}

In Closing

This strategy creates a single vue instance per page, and your backend framework is still responsible for routing and providing the data to the vue components. For those that have requirements to continuously integrate vue into applications, this is a great choice and way to start your modern javascript development journey. You can easily convert page by page over time and eventually end up with a full SPA if you want to take that route.

Github Repository

The github repository contains an extended version of this concept, it adds a feature of loading vue templates via .html files as well.

https://github.com/seand88/vue-mvc-spring-starter