Maven Build Node Project - Combine Java and Typescript in one project

Maven Build Node Project – Combine Java and Typescript in one project

Single Page Applications (SPAs) have been the most significant web development course for a while now. Developers from all around the planet have begun building new applications based on this method and moving existing ones. Java developers relied on technologies particular to the Java world for several years, such as JavaServer Faces. However, as JavaScript frameworks got more strength and became more successful and well-established, Java developers began moving their front-end code to these frameworks. Now we will discuss how we combine Node.js build tools used to prepare JavaScript source code into Maven’s lifecycle. Let’s see how to use the Maven Build with a Node Project.

Maven Build with a Node Project: Node.js Build Tools

One of the most energetic communities of the whole time, it is no surprise that the community produced many build tools to automate jobs like concatenation of source files, minification of JavaScript code, etc. Fortunately, the community created all the standard tools around a particular technology, Node.js, which made it simpler to combine with another technology, such as Maven.

This article will apply a Maven plugin, Frontend Maven Plugin, to blend Angular CLI tasks into Maven’s generate-resources phase. Fear not if you are using any other tool, like Webpack or Grunt, instead of Angular. The approach shown in the article is equivalent to those.

Workspace and project configuration

Before we get started, let’s see the project structure as an example, where we will base, to configure all path and configuration files.

Maven Build Node Project - Maven Project Structure
Maven Build Node Project – Maven Project Structure

Notice that inside the src folder, we have one for java and another for javascript (typescript in this case). And we have angular.json on the src/javascript/angular.json. 

A particular project configuration file, angular.json, is generated at the top level of the project. This is where you can place per-project defaults for CLI command options and define configurations to apply when the CLI builds a project for several targets.

The ng config command allows you to place and recover configuration values from the command line or write the angular.json file quickly. See that option names in the configuration file must adopt camelCase, while option names provided to commands can apply both camelCase or dash-case.

Let’s an example of the angular.json file:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "core-ui": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {},
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "../webapp/dist",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/vendor/selectize.js/dist/css/selectize.bootstrap2.css",
               "./node_modules/pnotify/dist/PNotifyBrightTheme.css",
               "./node_modules/owl.carousel/dist/assets/owl.carousel.min.css",
               "./node_modules/ngx-select-dropdown/dist/assets/style.css"
            ],
            "scripts": [
     		       "./node_modules/chart.js/dist/Chart.bundle.min.js",
        	     "./node_modules/chart.js/dist/Chart.min.js",
			         "./node_modules/jquery/dist/jquery.js",
     		       "./node_modules/owl.carousel/dist/owl.carousel.min.js",
       		     "./node_modules/clipboard/dist/clipboard.min.js",
       		   
               "src/vendor/selectize.js/dist/js/standalone/selectize.js",
               "./node_modules/pnotify/dist/umd/PNotify.js",
               "./node_modules/pnotify/dist/umd/PNotifyAnimate.js",
               "./node_modules/pnotify/dist/umd/PNotifyButtons.js",
               "./node_modules/pnotify/dist/umd/PNotifyCallbacks.js",
               "./node_modules/pnotify/dist/umd/PNotifyConfirm.js",
               "./node_modules/pnotify/dist/umd/PNotifyDesktop.js",
               "./node_modules/pnotify/dist/umd/PNotifyHistory.js",
               "./node_modules/pnotify/dist/umd/PNotifyMobile.js",
               "./node_modules/pnotify/dist/umd/PNotifyNonBlock.js"
      ],
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                }
              ]
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "core-ui:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "core-ui:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "core-ui:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.spec.json",
            "karmaConfig": "src/karma.conf.js",
            "styles": [
              "src/styles.css"
            ],
            "scripts": [],
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ]
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "src/tsconfig.app.json",
              "src/tsconfig.spec.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    },
    "core-ui-e2e": {
      "root": "e2e/",
      "projectType": "application",
      "prefix": "",
      "architect": {
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "core-ui:serve"
          },
          "configurations": {
            "production": {
              "devServerTarget": "core-ui:serve:production"
            }
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": "e2e/tsconfig.e2e.json",
            "exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    }
  },
  "defaultProject": "core-ui"
}

The most important attribute is the outputPath, where we define where the compilation files (the Javascript files) will be placed where the compilation ends, “../webapp/dist”.

Besides Angular, we also use the npm. The developer can use npm for both backend and frontend applications. However, Npm has nothing to do with the frontend. It is just a package manager. So, where you need to use it (backend/frontend) is reliant on the developer.



Maven Plugin frontend-maven-plugin

The community created the plugin to use a local installation of the node. 

Installing node locally enables developers who have not installed nodes globally or are using another version to build the project without making anything more complex than mvn clean install.

However, if you need to use the node already installed on your computer, this plugin does not support already installed Node or npm versions. Use the exec-maven-plugin instead.

Now let’s see how we can configure the Maven Project. First, follow the example of one pom.xml file.

<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.bitslovers.web</groupId>
	<artifactId>maven-build-node-project</artifactId>
	<version>1.0.0</version>
	<packaging>war</packaging>
	<dependencies></dependencies>
	<build>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
			</resource>
		</resources>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>11</source>
					<target>11</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.6</version>
				<configuration>
					<failOnMissingWebXml>false</failOnMissingWebXml>
					<webXml>src/main/webapp/WEB-INF/web.xml</webXml>
					<attachClasses>true</attachClasses>
					<classesClassifier>classes</classesClassifier>
					<packagingExcludes>src/,node_modules/</packagingExcludes>
					<packagingExcludes>dist/</packagingExcludes>
				</configuration>
			</plugin>
			<!-- Plugin to execute command "npm install" and "npm run build" inside 
				/src/main/javascript directory -->
			<plugin>
				<groupId>com.github.eirslett</groupId>
				<artifactId>frontend-maven-plugin</artifactId>
				<configuration>
					<workingDirectory>src/main/javascript/</workingDirectory>
					<installDirectory>temp</installDirectory>
				</configuration>
				<executions>
					<!-- It will install nodejs and npm -->
					 <execution> 
						 <id>install node and npm</id>
						<goals>
							<goal>install-node-and-npm</goal>
						</goals>
						<configuration>
							<nodeVersion>v10.14.1</nodeVersion>
							<npmVersion>3.10.10</npmVersion>
						</configuration>
					</execution>
					<!-- It will execute command "npm install" inside "/src/main/javascript" 
						directory -->
					<execution>
						<id>npm install</id>
						<goals>
							<goal>npm</goal>
						</goals>
						<configuration>
							<arguments>install</arguments>
						</configuration>
					</execution>
					<!-- It will execute command "npm build" inside "/src/main/javascript" 
						directory to clean and create "/dist" directory -->
					 <execution>
						<id>npm build</id>
						<goals>
							<goal>npm</goal>
						</goals>
						<configuration>
							<arguments>run build</arguments>
						</configuration>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-resources-plugin</artifactId>
				<version>2.4.2</version>
				<executions>
					<execution>
						<id>default-copy-resources</id>
						<phase>process-resources</phase>
						<goals>
							<goal>copy-resources</goal>
						</goals>
						<configuration>
							<overwrite>true</overwrite>
							<outputDirectory>${project.build.directory}/${project.build.finalName}/</outputDirectory>
							<resources>
							<resource>
								<directory>${project.basedir}/src/main/webapp/</directory>
							</resource>
							<resource>
								<directory>${project.basedir}/src/main/webapp/dist</directory>
							</resource>
							</resources>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Also, pay attention to the plugin maven-resources-plugin. We are using that plugin to copy the javascript files for our webapp folder after compiled the Typescript files. Check if the path matches your project structure folder.

<configuration>
   <overwrite>true</overwrite>
   <outputdirectory>${project.build.directory}/${project.build.finalName}/</outputdirectory>
   <resources>
      <resource>
         <directory>${project.basedir}/src/main/webapp/</directory>
      </resource>
      <resource>
         <directory>${project.basedir}/src/main/webapp/dist</directory>
      </resource>
   </resources>
</configuration>

Remember that on angular.json, we have configured the output to webapp/dist.

Maven Build Exclude node_modules

Also, on the pom.xml, we are using the maven-war-plugin, it’s a plugin that packages our project to one WAR file. Also, it helps us to exclude the files that we don’t need to pack. For example, exclude the /src and node_module, and dist folder when generating the WAR file.

<plugin>
  <groupid>org.apache.maven.plugins</groupid>
  <artifactid>maven-war-plugin</artifactid>
  <configuration>
    <failonmissingwebxml>false</failonmissingwebxml>
    <webxml>src/main/webapp/WEB-INF/web.xml</webxml>
    <attachclasses>true</attachclasses>
    <classesclassifier>classes</classesclassifier>
    <packagingexcludes>src/,node_modules/</packagingexcludes>
    <archive>
      <manifestentries>
        <version>${project.version}</version>
        <customname>${palindrome-context}</customname>
      </manifestentries>
    </archive>
    <packagingexcludes>dist/</packagingexcludes>
  </configuration>
  <version>2.6</version>
</plugin>

Advantage and Disadvantage of employing Maven front-end plugin for Angular 

Separating modules for the frontend and backend makes development teams more comfortable working on the project. The development and deployment method is simplified. You can efficiently run diverse environments, such as the backend and frontend.

One problem worth mentioning is that when you execute the backend and front-end build, you can’t hold a unique path for the API and Front-end because of conflict. That happens because both the backend and front-end try to manage the path. 

Also, using the Maven Build with a Node Project helps us migrate the project build process to a CI/CD Tool, like Gitlab. See how you can use Gitlab to build your Java application.

Besides building the Javascript and Java in the same project, it’s an excellent approach to perform some static analysis on Javascript code with Sonarqube, which can also be done using Gitlab.

Conclusion

Using the Maven Build with a Node Project, it’s a great method to save time when you have a Java project with any frontend framework.

Leave a Comment

Your email address will not be published. Required fields are marked *

Free PDF with a useful Mind Map that illustrates everything you should know about AWS VPC in a single view.