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>
@jing2020
Copy link

pom.xml is very helpful for my vue app

@kaliopane
Copy link

I am looking for executing my npm script on different environments. For each of the environments I have different set of values. I can do it locally something like this
npm --my-module:env=dev start
But, I need to do it as part of my maven build running on Jenkins.

Please let me know if you need more information.

You can create different maven profiles, and upon activation of different profiles (or properties, in that manner), you can customize the npm command you want to execute

@Byron2016
Copy link

Inside of maven project where is it supposed to locate gulpfile.js and package.json files?

@phillipgreenii
Copy link
Author

@Byron2016, in this setup, gulpfile.js and package.json are assumed to be in the same directory as pom.xml

@iamfarazbaig
Copy link

Was able to use an excerpt of your code for my maven and npm integration for running test script

@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