After I wrote some comments about the integration of
Grunt + Bower with Maven, it happens that I had to apply this in the context of
Spring Boot.
Out of the box, Spring Boot allows you
serve some static content by placing it in the resources under
/static (or
/public, or other locations, but let's stick to
/static in this blog entry). It is all very nice if you need only to serve a few pages and files, but if you need to code a complex Javascript application and wish to use proper tools like Grunt or Bower, you may have an issue.
It seems obvious that you cannot setup your Grunt project directly under the
/src/main/resources/static directory. That would mean an awkward SCM setup and an even more awkward URL pattern.
What I chose to do was to create a separate Maven module to hold my Grunt project, and to follow the guidelines I had established
previously, with a few differences.
The general idea is that in
development mode, the Spring Boot application would serve the static content directly from the
target/dev directory of the Grunt module, and that in
release mode, the
target/prod directory of the Grunt module would be copied into the
/src/main/resources/static directory of the Spring Boot module, and packaged together with the resulting JAR file.
Development mode
In
development mode, I needed to find a way to tell Spring Boot to serve the static content from
target/dev directory of the Grunt module. It is easily done by defining a
ResourceHandler for the
development profile only:
@Configuration
@Profile("dev")
public class DevWebConfig extends WebMvcConfigurerAdapter {
@Autowired
private DevSettings devSettings;
/**
* At development time, we want the static resources served directly
* from the <code>myproject-web</code> project, under the <code>target/dev</code>
* directory.
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String staticDir = devSettings.getStaticDir();
String prefix = "file:";
registry.addResourceHandler("/app/**").addResourceLocations(prefix + staticDir + "/app/");
registry.addResourceHandler("/assets/**").addResourceLocations(prefix + staticDir + "/assets/");
registry.addResourceHandler("/vendor/**").addResourceLocations(prefix + staticDir + "/vendor/");
registry.addResourceHandler("index.html").addResourceLocations(prefix + staticDir + "/index.html");
}
}
Additionally, I needed a configuration object to map the application properties:
@Component
@ConfigurationProperties(prefix = "myproject.dev")
public class DevSettings {
private String target;
private String staticDir;
// Getters & setters...
}
In the
application-dev.properties, activated by the "dev" profile, I just define the following properties:
myproject.dev.target=target/dev
myproject.dev.staticDir=${user.dir}/myproject-web/${myproject.dev.target}
When I want to run the application, I just have to run the Web module in
watch mode:
cd myproject-web
grunt watch
and to launch my Boot application with "dev" profile enable by adding
--spring.profiles.active=dev to the program arguments.
Release mode
When the application has to be released, I active a release Maven profile in order to enable the following code in the Spring Boot POM:
<profile>
<id>release</id>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>release-web-resources</id>
<phase>compile</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>
${project.basedir}/src/main/resources/static
</outputDirectory>
<resources>
<resource>
<directory>
../myproject-web/target/prod
</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
I have added the
myproject-web Grunt module into my dependencies in order to make sure it is ready before the copy:
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>myproject-web</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
And that's it! Again, in order to see the details of how the Grunt project itself is integrated with the Maven lifecyle, have a look at
Grunt + Bower with Maven.