Tag Archives: frontend-maven-plugin

Simplified delivery of react.js from apache karaf

This article is about a new servlet base class I have created to simplify serving up javascript frontends from the OSGi web whiteboard.

This article won’t go into the structure of the files that must be served. See Deliver react.js from apache karaf and A Java programmers guide to delivering webapp frontends to get an overview of files.

The short story is that the servlet needs to deliver two files:

  1. An index.html containing the initial DOM tree of the web application (if loaded in a web browser that will be all that’s shown), e.g. like this
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <title>Frontend Karaf Test</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"></meta>
    </head>
    <body>
    <div id="root"></div>
    <script src="bundle.js" type="application/javascript"></script>
    <noscript>This webpage requires javascript in the browser!</noscript>
    </body>
    </html>
    view raw index.html hosted with ❤ by GitHub
  2. A bundle.js file containing the javascript of the application as well as all dependencies, all packed together to be as compact as possible

The index.html file and the bundle.js files are added as classpath resources in the OSGi bundle containing the servlet.

The index.html file is added by putting it into src/main/resources of the maven project for the OSGi bundle.

The bundle.js file is added to the classpath by letting the frontend-maven-plugin drop the created bundle.js into target/classes/

<project>
<build>
<plugins>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.6</version>
<configuration>
<nodeVersion>v10.4.0</nodeVersion>
<workingDirectory>src/main/frontend</workingDirectory>
<installDirectory>target</installDirectory>
</configuration>
<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>
</execution>
<execution>
<id>webpack build</id>
<goals>
<goal>webpack</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
view raw pom.xml hosted with ❤ by GitHub

Everything covered so far is identical to what’s described in the first blog post.

The difference is how much simpler creating the servlet component is.

A web whiteboard DS component for serving a frontend without any routing could look like this:

@Component(service={Servlet.class}, property={"alias=/frontend-karaf-demo"})
public class ReactServlet extends FrontendServlet {
@Reference
public void setLogservice(LogService logservice) {
super.setLogService(logservice);
}
}
view raw ReactServlet.java hosted with ❤ by GitHub

The LogService service injection is passed to the FrontendServlet base class, where it is used to report problems when serving files.

If the web application has a router that can be used to edit the local part, like e.g. so

<Provider store={store}>
<Router>
<div className="App">
<Switch>
<Route exact path="/frontend-karaf-demo/" component={Home} />
<Route path="/frontend-karaf-demo/counter" component={Counter} />
<Route path="/frontend-karaf-demo/about" component={About} />
</Switch>
</div>
</Router>
</Provider>
view raw routes.jsx hosted with ❤ by GitHub

then the servlet needs to have matching routes (that’s the setRoutes() call in the constructor):
@Component(service={Servlet.class}, property={"alias=/frontend-karaf-demo"})
public class ReactServlet extends FrontendServlet {
public ReactServlet() {
super();
setRoutes("/", "/counter", "/about");
}
...
}
view raw ReactServlet.java hosted with ❤ by GitHub

The paths are used when reloading a subpage, e.g. by pressing F5 in the web browser. All paths will return the index.html file which in turn will load the bundle.js which will navigate to the correct sub-page based on the path.

Note that the react paths have the application web context, while the paths on the java side are without the application web part:

React routes Java routes
/frontend-karaf-demo/ /
/frontend-karaf-demo/counter /counter
/frontend-karaf-demo/about /about

A Java programmer’s guide to delivering webapp frontends

When I started investigating (googling) web frontends, I found tutorials for various frameworks and I found tutorials using node.js to deliver frontends to web browsers on localhost.

What I did not find, was something telling me how I should pack up and deliver the webapp frontend from my production system, so that comes here:

  1. A web application frontend  is a piece of javascript code, that builds its user interface by rebuilding the DOM tree of the webpage that loaded it
  2. The javascript needed for a webapp is packaged up as a big javascript file, typically named bundle.js,
  3. The initial HTML file references bundle.js
  4. The initial HTML file also contains an element that matches what the javascript code expects to be its start element (i.e. the point in the DOM of the parsed HTML file, where the javascript program starts rebuilding its own DOM)

Most react.js tutorials start with an example like this (this is the “main()” of the javascript program):

ReactDOM.render(
<App/>,
document.getElementById('root'));
view raw index.js hosted with ❤ by GitHub

The above code example tries to find the element with id root in the DOM of the surrounding HTML file, and replacing it with the DOM of the <App/> react component (i.e. running the render() method of the component).

This means that for anything to appear in the HTML page there must be an element in the HTML page with id “root”.  If not, react won’t find a place to start rewriting the DOM and won’t do anything.

A minimal HTML page loading bundle.js and with an element where javascript can start rebuilding the DOM, looks like this:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Frontend Karaf Test</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"></meta>
</head>
<body>
<div id="root"></div>
<script src="bundle.js" type="application/javascript"></script
<noscript>This webpage requires javascript in the browser!</noscript>
</body>
</html>
view raw index.html hosted with ❤ by GitHub

This page will load the bundle.js and contains the element <div id=”root” /> where react can start rewriting the DOM.

It is possible to serve this two part web application as static files, i.e. dropping the index.html and bundle.js files into a directory served by nginx or apache will be sufficient to make the webapp load and start rendering in a web browser.

The frontend people and most frontend tutorials use node.js to build the bundle.js and serve the index.html and the bundle.js. That’s fine for developing the actual frontend application, but not for delivering the application to the production enviroment.

Working in java my first thought was “how do I package up the frontend in a jar file that can be deployed to a maven repository?”. The rest of this article attempts to answer this question.

  1. The frontend-maven-plugin is used to build a bundle.js from the src/main/frontend/ directory of a project
    <plugin>
    <groupId>com.github.eirslett</groupId>
    <artifactId>frontend-maven-plugin</artifactId>
    <version>1.6</version>
    <configuration>
    <nodeVersion>v10.4.0</nodeVersion>
    <workingDirectory>src/main/frontend</workingDirectory>
    <installDirectory>target</installDirectory>
    </configuration>
    <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>
    </execution>
    <execution>
    <id>webpack build</id>
    <goals>
    <goal>webpack</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    view raw pom.xml hosted with ❤ by GitHub
  2. The bundle.js created by frontend-maven-plugin is included as a resource in the jar (handled by having frontend-maven-plugin drop the bundle.js in the target/classes/ directory)
  3. The index.html file is put into src/main/resources/ and end up as a resource in the jar
  4. A servlet is added to the code.  The servlet will return “index.html” for the request path “/” and otherwise look through the classpath for a match
    public class ReactServlet extends HttpServlet {
    private static final long serialVersionUID = 250817058831319271L;
    // The paths used to serve index.html
    private final List<String> routes = Arrays.asList("/", "/counter", "/about");
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) {
    String pathInfo = request.getPathInfo();
    try {
    if (pathInfo == null) {
    // Browsers won't redirect to bundle.js if the servlet path doesn't end with a "/"
    addSlashToServletPath(request, response);
    return;
    }
    String resource = findResourceFromPathInfo(pathInfo);
    String contentType = guessContentTypeFromResourceName(resource);
    response.setContentType(contentType);
    try(ServletOutputStream responseBody = response.getOutputStream()) {
    try(InputStream resourceFromClasspath = getClass().getClassLoader().getResourceAsStream(resource)) {
    if (resourceFromClasspath != null) {
    copyStream(resourceFromClasspath, responseBody);
    response.setStatus(200);
    return;
    }
    String message = String.format("Resource \"%s\" not found on the classpath", resource);
    response.sendError(404, message);
    }
    }
    } catch (IOException e) {
    response.setStatus(500); // Report internal server error
    }
    }
    String guessContentTypeFromResourceName(String resource) {
    String contentType = URLConnection.guessContentTypeFromName(resource);
    if (contentType != null) {
    return contentType;
    }
    String extension = resource.substring(resource.lastIndexOf('.') + 1);
    if ("xhtml".equals(extension)) {
    return "text/html";
    }
    if ("js".equals(extension)) {
    return "application/javascript";
    }
    if ("css".equals(extension)) {
    return "text/css";
    }
    return null;
    }
    private String findResourceFromPathInfo(String pathInfo) {
    if (routes.contains(pathInfo)) {
    return "index.xhtml";
    }
    return pathInfo;
    }
    private void addSlashToServletPath(HttpServletRequest request, HttpServletResponse response) throws IOException {
    response.sendRedirect(String.format("%s/", request.getServletPath()));
    }
    private void copyStream(InputStream input, ServletOutputStream output) throws IOException {
    int c;
    while((c = input.read()) != -1) {
    output.write(c);
    }
    }
    }
    view raw ReactServlet.java hosted with ❤ by GitHub

And that’s it, basically.

Edit: I got tired of copy-pasting the above code, so I packed it up in a base class and deployed it to maven central. It’s available under the Apache v2 license (see also Simplified delivery of react.js from apache karaf)  

You will need stuff to wire up your servlet to a path in the servlet container.   This could e.g. be a web-inf/web.xml deployment descriptor of a war file, or it could be annotations of servlet 3.0. Personally I use the OSGi web whiteboard: the servlet becomes a declarative services (DS) component that is started and plugs into a path (web context) in the servlet container.

Another thing to consider, is if you use the react-router or similar to rewrite the URL when moving to a different page, then you would like to enter the frontend application at all of the paths matching a page.

Some frontend examples (all using OSGi web whiteboard):

  1. The frontend-karaf-demo (a single maven module creating a declarative services component plugging into the web whiteboard on the webcontext “frontend-karaf-demo” and would become available at http://localhost:8181/frontend-karaf-demo/ when being loaded into apache karaf)
  2. The authservice user administration GUI
  3. The ukelonn weekly allowance web application

Deliver react.js from apache karaf

Edit: delivering react.js has been made simpler, see Simplified delivery of react.js from apache karaf for details.

A small barebones demo application/testbed, delivering a single-page web application from apache karaf, can be found at https://github.com/steinarb/frontend-karaf-demo

The JavaScript framework used to write the application is react.js

Screenshot of the demo open on the “counter” page

The frontend application is webpack‘d into a single bundle.js file. The packing is done by the frontend-maven-plugin (i.e. no local node.js installation required).

The bundle.js and its wrapping index.xhtml page, are served as static resources from the OSGi bundle’s classpath by a DS component that registers a Servlet service with the web whiteboard extender on the path /frontend-karaf-demo

The react.js features featured in this application, are:

  • react.js to render the application’s page
  • redux to hold the application’s state
  • redux-saga and axios for REST API communication and asynchronous update of the application’s state (the REST service used by axios, is provided by an OSGi DS whiteboard Servlet)
  • react-router to route between different pages (however this requires hardcoding the ReactServlet’s path into the react app, and requires the ReactServlet to return the top level index.html from all routes)
  • react-bootstrap and bootstrap v3 to style the application in a responsive way (I haven’t made any effort to make it look good. I just include the boostrap style and activate the responsive formatting in a meta header tag in the index.html page)

To try out the application:

  1. Clone this project and build it:
    git clone https://github.com/steinarb/frontend-karaf-demo.git
    cd frontend-karaf-demo/
    git checkout release/1.0.0
    mvn clean install
  2. Install apache karaf and start it  according to the karaf quick start guide (alternatively, see Develop OSGi applications using karaf)
  3. At the karaf command line give the following commands
    feature:repo-add mvn:no.priv.bang.demos/frontend-karaf-demo/LATEST/xml/features
    feature:install frontend-karaf-demo
  4. Open http://localhost:8181/frontend-karaf-demo in a web browser and try it out