Integrate Parcel into a Maven project

Published: January 13, 2018  •  Updated: January 25, 2018  •  java, javascript

In the previous blog post I wrote about Parcel, a new bundler for JavaScript applications.

In this post I'm going to show you how you can integrate Parcel into a Maven project and ultimately create a Spring Boot executable jar containing the back- and the front end code.

The Spring Boot / Maven project is based on the starter application generated with the https://start.spring.io website.

I use src/main/frontend as the project directory for the JavaScript / CSS / HTML code, but you can use any directory. In there I create package.json, set up Babel and Parcel and add a couple of tasks to package.json

  "scripts": {
    "prebuild": "shx rm -rf dist/*",
    "build": "parcel build src/index.html --public-url ./",
    "postbuild": "gzip-all \"dist/*.*\"",
    "start": "parcel src/index.html --public-url ./",
    "watch": "parcel watch src/index.html --public-url ./"
  },

src/main/frontend/package.json

npm run build builds the production package and writes the output into src/main/frontend/dist.
npm run start starts Parcel in watch mode and a http server on port 1234.
npm run watch just starts the watcher but no http server. Parcel still supports hot code reloading in the browser in this mode.


Development

During development, I start the Spring Boot server inside the IDE by starting the main application class (with development profile) or in the shell with mvn spring-boot:run -Dspring.profiles.active=development.

And in another shell I start Parcel with npm run watch. This does not start the web server in Parcel, because I want to serve the front end application through the embedded http server in Spring Boot.
The advantage of this is that I don't need to enable CORS on the endpoints, just for development, and the code can reference the Spring Boot endpoints with relative paths.
And thanks to the watch mode from Parcel every time I change something in the JavaScript code it automatically reloads the change into the browser.

Here the configuration class that serves all requests (/**) from the src/main/frontend/dist directory when the development profile is activated.

@Configuration
class ResourceConfig implements WebMvcConfigurer {

  @Autowired
  private Environment environment;

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (this.environment.acceptsProfiles("development")) {
      String userDir = System.getProperty("user.dir");
      registry.addResourceHandler("/**")
          .addResourceLocations(
              Paths.get(userDir, "src/main/frontend/dist").toUri().toString())
          .setCachePeriod(0);
    }
    else {
      registry.addResourceHandler("/", "/index.html")
          .addResourceLocations("classpath:/static/")
          .setCacheControl(CacheControl.noCache())
          .resourceChain(false).addResolver(new GzipResourceResolver());
      
      registry.addResourceHandler("/**")
          .addResourceLocations("classpath:/static/")
          .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS).cachePublic())
          .resourceChain(false).addResolver(new GzipResourceResolver());
    }
  }

}

src/main/java/ch/rasc/demo/demo/ResourceConfig.java

When the default profile is activated (production) the configuration runs the else clause and sets the web root to the /static folder in the classpath. index.html is served with caching disabled, the other resources are, thanks to Parcel revisioned, and so we can serve them with a cache expiry date one year in the future.


Production

The package process for the production needs to

For these tasks we use the spring-boot-maven-plugin and the frontend-maven-plugin Maven plugins.

<plugin>
  <groupId>com.github.eirslett</groupId>
  <artifactId>frontend-maven-plugin</artifactId>
  <version>1.6</version>
  <executions>
    <execution>
      <id>install node and npm</id>
      <goals>
        <goal>install-node-and-npm</goal>
      </goals>
    </execution>
    <execution>
      <id>npm install</id>
      <goals>
        <goal>npm</goal>
      </goals>
      <configuration>
        <arguments>install</arguments>
      </configuration>
    </execution>					
    <execution>
      <id>build</id>
      <goals>
        <goal>npm</goal>
      </goals>
      <phase>prepare-package</phase>
      <configuration>
        <arguments>run build</arguments>
      </configuration>
    </execution>
  </executions>
  <configuration>
    <nodeVersion>v8.9.4</nodeVersion>
    <workingDirectory>src/main/frontend</workingDirectory>					
    <NODE_ENV>production</NODE_ENV>
  </configuration>
</plugin>

pom.xml

frontend-maven-plugin downloads and installs Node.js locally into the project and I specify a node version so it will always build the application with the same Node.js version regardless what version is globally installed. Another benefit is that I can build the application on any computer without installing Node.js globally.

There are three executions configured. The first installs node and npm, the second runs npm install and the third runs npm run build during the prepare-package phase. This is the task that calls the Parcel build process.


Because the output from Parcel build needs to end up in the final jar, Maven needs to copy the contents of this folder to the target folder.
For this purpose I added the maven-antrun-plugin with a copy task.

<plugin>
  <artifactId>maven-antrun-plugin</artifactId>
  <executions>
    <execution>
      <phase>prepare-package</phase>
      <configuration>
        <target>
          <copy todir="${basedir}/target/classes/static">
            <fileset dir="${basedir}/src/main/frontend/dist">
              <include name="**" />
            </fileset>
          </copy>
        </target>
      </configuration>
      <goals>
        <goal>run</goal>
      </goals>
    </execution>
  </executions>
</plugin>

pom.xml


The spring-boot-maven-plugin runs after the copy step and packages everything together into one jar file.

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

pom.xml

The name of the jar file is defined with the finalName configuration

<finalName>demo</finalName>

pom.xml


To start the production package I can now enter

java -jar target/demo.jar

and open a browser with the URL http://localhost


You find the complete project on GitHub: https://github.com/ralscha/blog/tree/master/parcelmaven