Skip to content

Instantly share code, notes, and snippets.

@phillipgreenii
Last active October 3, 2024 10:52
Show Gist options
  • Save phillipgreenii/7c954e3c3911e5c32bd0 to your computer and use it in GitHub Desktop.
Save phillipgreenii/7c954e3c3911e5c32bd0 to your computer and use it in GitHub Desktop.
Running NPM Scripts through maven

I am in the process of introducing single page applications to where I work. For development, using node based build tools is much easier for the single page applications. However, the build process for our organization is based upon maven. Our solution started with the maven plugin frontend-maven-plugin. It worked great at first, but then we ran into a situation that I couldn't make work with it.

As stated before, at our organization, we have the older ecosystem which is maven and the newer ecosystem which is node. Our goal was to keep the hacking to a minimum. We did this by putting all of the hacks into a single super node based build file. This is what maven calls and the reason frontend-maven-plugin wasn't sufficient. The super node based build script calls all of the other build scripts by spawning npm run. Try as I might, I could not figure out how to make the spawn work. front-end-maven-plugin downloads npm and calls it directly within itself and I was not able to fix my PATH to allow spawn to work.

The following files demonstrate our build process as of today. The gulp file has been simplified, but that part isn't important for this discussion. Gulp could be replaced with grunt or brocolli or anything else. The significant part is that maven calls npm scripts; how the npm script is defined doesn't matter. I included gulpfile.js to show how we use the skipTests flag

###References

var gulp = require( 'gulp' );
var gutil = require('gulp-util');
var argv = require('yargs').argv;
var del = require('del');
var mkdirp = require('mkdirp');
var touch = require('touch');
function stringToBoolean(string){
if(!string) {
return false;
}
switch(string.toLowerCase()) {
case "false": return false;
case "no": return false;
case "0": return false;
case "": return false;
case "true": return true;
default:
throw new Error("unknown string: " + string);
}
}
argv.skipTests = stringToBoolean(argv.skipTests);
/* TASKS */
gulp.task('clean', function(cb){
del(['./build'], cb);
});
gulp.task( 'build', function () {
mkdirp.sync('./build');
touch.sync('./build/index.html');
} );
gulp.task( 'test', function () {
if(argv.skipTests) {
gutil.log(gutil.colors.yellow('Skipping Tests'));
return;
}
gutil.log('Running Tests');
} );
gulp.task('prepare-for-maven-war', function(){
return gulp.src('./build/**')
.pipe(gulp.dest('target/gulp'));
});
{
"name": "npm-to-maven-adapator",
"version": "0.0.1-SNAPSHOT",
"description": "Example of node project with maven build scripts",
"private": true,
"scripts": {
"clean": "node node_modules/gulp/bin/gulp.js --no-color clean",
"build": "node node_modules/gulp/bin/gulp.js --no-color build",
"test": "node node_modules/gulp/bin/gulp.js --no-color test",
"prepare-for-maven-war": "node node_modules/gulp/bin/gulp.js --no-color prepare-for-maven-war"
},
"devDependencies": {
"del": "^1.1.1",
"gulp": "^3.8.11",
"gulp-util": "^3.0.1",
"mkdirp": "^0.5.0",
"touch": "0.0.3",
"yargs": "^1.3.3"
}
}
<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>phillipgreenii</groupId>
<artifactId>npm-to-maven-adapator</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.test.skip>false</maven.test.skip>
<gulp.output.directory>target/gulp</gulp.output.directory>
</properties>
<build>
<plugins>
<!-- Standard plugin to generate WAR -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<webResources>
<resource>
<directory>${gulp.output.directory}</directory>
</resource>
</webResources>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.3.2</version>
<executions>
<!-- Required: The following will ensure `npm install` is called
before anything else during the 'Default Lifecycle' -->
<execution>
<id>npm install (initialize)</id>
<goals>
<goal>exec</goal>
</goals>
<phase>initialize</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>install</argument>
</arguments>
</configuration>
</execution>
<!-- Required: The following will ensure `npm install` is called
before anything else during the 'Clean Lifecycle' -->
<execution>
<id>npm install (clean)</id>
<goals>
<goal>exec</goal>
</goals>
<phase>pre-clean</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>install</argument>
</arguments>
</configuration>
</execution>
<!-- Optional: The following will output the npm configuration.
I do this so my CI logs will show the npm information used
for the build -->
<execution>
<id>npm config list (validate)</id>
<goals>
<goal>exec</goal>
</goals>
<phase>validate</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>config</argument>
<argument>list</argument>
</arguments>
</configuration>
</execution>
<!-- Required: This following calls `npm run build` where 'build' is
the script name I used in my project, change this if yours is
different -->
<execution>
<id>npm run build (compile)</id>
<goals>
<goal>exec</goal>
</goals>
<phase>compile</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>build</argument>
</arguments>
</configuration>
</execution>
<!-- Optional: The following runs the script that copies the
appropriate files from the npm build directory into the location
'maven-war-plugin' is expecting. The copying could be done
during the 'build' script, but I like to keep it separate.
Idealy in the future, I won't need maven at which, I can just
delete the 'prepare-for-maven-war' script. -->
<execution>
<id>npm run prepare-for-maven (prepare-package)</id>
<goals>
<goal>exec</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>prepare-for-maven-war</argument>
</arguments>
</configuration>
</execution>
<!-- Optional: The following will publish to npm if you run
`mvn deploy`. -->
<execution>
<id>npm run publish (deploy)</id>
<goals>
<goal>exec</goal>
</goals>
<phase>deploy</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>publish</argument>
</arguments>
</configuration>
</execution>
<!-- Required: The following will run unit tests. My test scripts
in npm look for the property 'skipTests', so I map it to
'maven.test.skip'
Note: the douple '-' syntax used below only works with npm >= 2. -->
<execution>
<id>npm run test (test)</id>
<goals>
<goal>exec</goal>
</goals>
<phase>test</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>test</argument>
<argument>--</argument>
<argument>--skipTests=${maven.test.skip}</argument>
</arguments>
</configuration>
</execution>
<!-- Required: The following calls the npm script that cleans
up the build. -->
<execution>
<id>npm run clean (clean)</id>
<goals>
<goal>exec</goal>
</goals>
<phase>clean</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>clean</argument>
</arguments>
</configuration>
</execution>
</executions>
<configuration>
<environmentVariables>
<!-- The following parameters create an NPM sandbox for CI -->
<NPM_CONFIG_PREFIX>${basedir}/npm</NPM_CONFIG_PREFIX>
<NPM_CONFIG_CACHE>${NPM_CONFIG_PREFIX}/cache</NPM_CONFIG_CACHE>
<NPM_CONFIG_TMP>${project.build.directory}/npmtmp</NPM_CONFIG_TMP>
</environmentVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>
@jreed-cartago
Copy link

This looks interesting as I'm working on integrating a vue.js project as a deliverable package with along side a maven based backend service, but in this case it isn't a WAR file and so we only need to package up the vue.js after it is built. I just wonder how your maven project structure looks, did you put the source code for your node projects under a src/ui folder as mentioned in the frontend-maven-plugin or if you have some other structure?

@phillipgreenii
Copy link
Author

@jreed-cartago, i have used similar code to the above with both WAR and JAR. That part doesn't matter. In this particular example, I had created an npm script called prepare-for-maven-war which would copy the appropriate files from the ui build directory into the appropriate place in the target directory of the maven project. It gets called during prepare-package of the maven build. The ui and backend code was in the same git repository but as siblings. Ie, the root of the project had a ui directory (contained npm project (package.json)) and a web directory (contained maven project (pom.xml)). It may also be possible to set the resource-directory in the pom to pull directly from the ui build directory.

@jreed-cartago
Copy link

Awesome thanks for the information. This is very helpful.

@DeegC
Copy link

DeegC commented Sep 5, 2021

Thanks for this! Very helpful. One small change I made for my project was how I skipped the tests if -DskipTests=true. I use the skip config for the exec plugin. All I needed was:

          <!-- test -->
					<execution>
						<id>npm test</id>
						<goals>
							<goal>exec</goal>
						</goals>
						<phase>test</phase>
						<configuration>
							<executable>npm</executable>
                                                        <skip>${skipTests}</skip>
							<arguments>
								<argument>run</argument>
								<argument>test</argument>
							</arguments>
						</configuration>
					</execution>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment