Skip to main content

ReactJS, NPM and Maven

I'm just starting to get into working with ReactJS, Facebook's open source rendering framework. My project uses SpringBoot for annotation-driven dependency injection and MVC. I thought it would be great if I could use a bit of ReactJS to enhance the application.

If you're looking for a basic conceptual intro, I recommend ReactJS for Stupid People and of course the official documentation is quite good. In full disclosure, I still have no idea how to do "flux" yet. As an experienced Java backend developer, I'm pretty decent at hacking Maven builds - which is precisely what this blog post is going to be about.

First, a word about how React likes to be built. Like many front-end tools, there is a toolkit for the node package manager (NPM). From the command prompt, one might run npm install -g react-tools which installs the jsx command. The jsx command provides the ability to transform JSX syntax into ordinary JavaScript, which is precisely what I want. Of course, running NPM at all implies that it has been installed and configured on the system.

Here are my goals for the build process:

  1. I want to precompile JSX. The templating capabilities of JSX are nifty, but since I have to build the backend code anyway, we might as well provide clean "regular JS" to the browser.
  2. I don't want to use a global installation of NPM. My build will eventually need to run on a continuous integration server (Jenkins) and I want to be a good neighbor to any other projects that might also use NPM with ReactJS.
  3. I want to play nice with Maven and avoid writing any shell scripts. Yuck!

Installing NPM

The first thing we need to do is to have the build install a copy of NPM that we can use for installing react tools. We want NPM binaries to be contained within the software project and, ideally, to be someplace where we won't trip over it and it won't get committed to version control by accident. Installing NPM as a subdirectory of "target" during the project initialization phase would be ideal.

Thanks to Google, I found a Maven plugin already available to do much of this. The frontend plugin makes short work of installing NPM. Here's the plugin setup:

<plugin>
    <groupId>com.github.eirslett</groupId>
    <artifactId>frontend-maven-plugin</artifactId>
    <version>0.0.24</version>
    <configuration>
        <workingDirectory>${pom.basedir}/target/node</workingDirectory>
        <installDirectory>${pom.basedir}/target/node</installDirectory>
    </configuration>
    <executions>
        <execution>
            <id>frontend-step1-install-npm</id>
            <goals>
                <goal>install-node-and-npm</goal>
            </goals>
            <phase>initialize</phase>
            <configuration>
                <nodeVersion>v0.12.7</nodeVersion>
                <npmVersion>2.11.3</npmVersion>
            </configuration>
        </execution>
        ...
</executions> </plugin>

The only gotcha is that the default location of the node stuff is in a "node" directory in the project base directory. I used the install directory argument to configure the build to put node binaries into "target/node" instead. Obviously this is a personal preference you can ignore in your projects.

Installing JSX

The next thing the build needs to accomplish is installing react tools, and of course we want to use the NPM we just installed to do that. We'll add this as a second execution of the frontend plugin:

<execution>
    <id>frontend-step2-install-jsx</id>
    <goals>
        <goal>npm</goal>
    </goals>
    <phase>initialize</phase>
    <configuration>
        <arguments>install -g react-tools</arguments>
    </configuration>
</execution>

Recall we've already configured the installation directory path as "target/node", and this execution will install the jsx binary as "target/node/bin/jsx". We use this jsx binary in the next section to compile our JSX files down to plain JavaScript.

Compile JSX

With the jsx binary installed, the build should now be able to generate the JavaScript files. Here's where the frontend plugin isn't useful - we need to reach into our Maven toolkit and find another plugin. Fortunately, Maven has an execution plugin that can invoke shell commands.

<plugin>
    <artifactId>exec-maven-plugin</artifactId>
    <groupId>org.codehaus.mojo</groupId>
    <executions>
        <execution>
            <id>frontend-step3-run-jsx</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>exec</goal>
            </goals>
            <configuration>
                <executable>${basedir}/target/node/bin/jsx</executable>
                <environmentVariables>
                    <PATH>${env.PATH}:${pom.basedir}/target/node/node</PATH>                </environmentVariables>
<arguments> <argument>--extension</argument> <argument>jsx</argument> <argument>${basedir}/src/main/resources/static/js</argument> <argument>${basedir}/target/classes/static/js</argument> </arguments> </configuration> </execution> </executions> </plugin>

Few things to note about this. First, the JSX compilation is in a different phase of the build from the NPM installation. The generate-sources phase is after the initialize phase, so we're guaranteed that all the NPM and JSX goodness is ready to use in the "target" directory tree.

The arguments are specified as an array. This may seem strange at first to developers who have not used the Java Runtime and ProcessBuilder classes, but under the hood the JVM thinks of shell commands as tokenized arrays. This means that we do not have to use quotes around each logical grouping, even if they contain whitespace (which here they don't).

The extension parameter setting of "jsx" means that only files ending in ".jsx" are going to be compiled. This produces ".js" files in the same location in the compiled code. Because we write them to the target directory, those files won't be included in version control or lie around where developers might get confused by having JS and JSX files together.

Summary

Using plugins, we were able to set up JSX compilation and meet our other goals. NPM is installed only in the "target" directory and therefore won't interfere with any other projects. Best of all, there was no shell scripting and only one execution of the jsx executable via the Maven execution plugin.

Here's the whole thing put together:

<plugins>
    <plugin>
        <groupId>com.github.eirslett</groupId>
        <artifactId>frontend-maven-plugin</artifactId>
        <version>0.0.24</version>
        <configuration>
            <workingDirectory>${pom.basedir}/target/node</workingDirectory>
        </configuration>
        <executions>
            <execution>
                <id>frontend-step1-install-npm</id>
                <goals>
                    <goal>install-node-and-npm</goal>
                </goals>
                <phase>initialize</phase>
                <configuration>
                    <nodeVersion>v0.12.7</nodeVersion>
                    <npmVersion>2.11.3</npmVersion>
                    <installDirectory>${pom.basedir}/target/node</installDirectory>
                </configuration>
            </execution>
            <execution>
                <id>frontend-step2-install-jsx</id>
                <goals>
                    <goal>npm</goal>
                </goals>
                <phase>initialize</phase>
                <configuration>
                    <arguments>install -g react-tools</arguments>
                    <installDirectory>${pom.basedir}/target/node</installDirectory>
                </configuration>
            </execution>
        </executions>
    </plugin>
    <plugin>
        <artifactId>exec-maven-plugin</artifactId>
        <groupId>org.codehaus.mojo</groupId>
        <executions>
            <execution>
                <id>frontend-step3-run-jsx</id>
                <phase>generate-sources</phase>
                <goals>
                    <goal>exec</goal>
                </goals>
                <environmentVariables>
                    <PATH>${env.PATH}:${pom.basedir}/target/node/node</PATH>                </environmentVariables>
<configuration> <executable>${basedir}/target/node/bin/jsx</executable> <arguments> <argument>--extension</argument> <argument>jsx</argument> <argument>${basedir}/src/main/resources/static/js</argument> <argument>${basedir}/target/classes/static/js</argument> </arguments> </configuration> </execution> </executions> </plugin>
    ...
</plugins>

Comments

  1. This is the kind of post I have been dreaming to find! Great work and thanks for sharing! I will try to get babel, web pack, etc install this way as well.

    ReplyDelete
  2. Looks a bit of yucky but it's a good try. I was thinking rather than tear your brains out trying to get ReactJS/Angular2 or any gulp, webpack based toolkit working with Maven. I found it was a bit saner to just keep it as a separate build process.

    In the end you're building something that should be decoupled anyway so why not?

    Another approach that you can incorporate to your Maven build system is to have a webjar building project and then include that jar as a dependency.

    ReplyDelete
    Replies
    1. Thanks for your comment. I prefer using vanilla build commands because they make packaging the project more intuitive to run. Running "mvn clean install" builds, tests, and packages the entire project. We run a continuous integration process -- using standard build commands means the configuration on Jenkins is very easy for our ops team.

      Delete
  3. I have tried with the attached plugin details in eclipse the URL showing an empty page can you please help me out to on this?

    ReplyDelete
  4. Enjoyed your approach to explaining how it works, hope to see more blog posts from you. thank you!

    eiffeltowerfacts
    Article submission sites

    ReplyDelete
  5. Actually I read it yesterday but I had some thoughts about it and today I wanted to read it again because it is very well written. keep update lot
    Ai & Artificial Intelligence Course in Chennai
    PHP Training in Chennai
    Ethical Hacking Course in Chennai Blue Prism Training in Chennai
    UiPath Training in Chennai

    ReplyDelete

Post a Comment

Popular posts from this blog

Solved: Unable to Locate Spring Namespace Handler

I attempted to run a Spring WebMVC application, and when starting up the application complained that it didn't know how to handle the MVC namespace in my XML configuration. The project runs JDK 7 and Spring 4.0.6 using Maven as the build system. The following is my XML configuration file: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        xmlns:mvc="http://www.springframework.org/schema/mvc"        xsi:schemaLocation="         http://www.springframework.org/schema/beans         http://www.springframework.org/schema/beans/spring-beans.xsd         http://www.springframework.org/schema/mvc         http://www.springframework.org/schema/mvc/spring-mvc.xsd">          <mvc:annotation-driven/>      </beans> I have a few more beans than this, but their details aren't especially relevant

Spark Cassandra Connector Tip

We're using Databricks as our provider for Spark execution, and we've been struggling to get the Spark Cassandra connector to work outside of the local development environment. The connector was attempting to connect to 127.0.0.1 even though we were passing the new host information into the getOrCreate(..) call. After working with Ganesh at Databricks support, we figured it out. The realization is that in Databricks, calls to getOrCreate() from a fat jar don't create a new SparkContext object. Thus, the configuration passed in gets ignored. If you want to update the Cassandra host information for the connector, you must update it after  the call to getOrCreate() instead. Add the configuration directly to the context and you'll be good to go!