Posted on by | Posted in Developer Blog, synyx Blog | Tagged , , , , ,

This is the first article of a series about server side rendering and progressive enhancement. We will implement a product list that can be sorted by two parameters. Furthermore the app will be progressively enhanced, means the html document is rendered on the server and javascript will just enhance the app on the client if possible.


springboot & reactjs article series

  1. server side rendering ✅
  2. progressive enhancement based on list sorting 🆕
  3. improving developer experience
  4. lessons learned

Today we are going to create an awesome product list while using progressive enhancement to provide the best user experience possible on each device. Progressive enhancement is a method in web development that sets focus on content first. The content will then be enhanced with dynamic features (JavaScript) and Layout (css). To provide content even with disabled JavaScript the page has to be rendered on the server. For reasons (explained below) we use Java & spring on the backend and ReactJS on the client side.

But why do we even want to make the effort of developing a universal web application? And even a progressive one? Of course this is overhead that must be handled and we have to implement and maintain more APIs as we will see later. However, the user experience is worth this effort in my opinion.

Benefits of universal applications

thanks to server side rendered markup
- the app is fully functional from the start
- the app is usable instantly

JavaScript just enhances the features
- ajax calls without full page reloading are super fast

tl;dr
project source code is available on github

Why Java for the backend?

To start with our little project we first have to think about the technologies we want to use. At synyx we are mainly using Java for the backend. And over the last year we have built up a large knowledge around the spring ecosystem as well.

So to answer the question

  • team knowledge (Spring, ...)
  • battle tested solutions (Spring Security, Spring MVC, ...)

 

Why ReactJS for the client?

Well, personally I am a fan of react and it's ecosystem. That's it! 😎
Okay... actually there are good reasons to use react. React provides a simple API to develop UI components and it works well with reactive architectures like Flux and its successor redux.
Furthermore it supports server side rendering quite well. (afaik Angular 2 and Ember could also be used, or cyclejs, or ...)

Disclaimer
I will not explain spring, react or $technology. Nor I will explain Java or JavaScript, es2015 syntax in particular. There already are excellent articles out there on the web like how to start with react (without using webpack). Additionally I recommend to have a look at the official documentation for react as well as spring of course.

Spring Boot Initializr

At first we're going to generate a bootstrap project with the awesome spring starter web interface. We use gradle as build system, thymeleaf as template engine to render our views on the server and good old spring-web. Handlebars would also be an option as template engine but it is not supported by spring initializr.

springboot-initializr

Backend

Starting with the backend we have to create the following files.

index.html

The html template is as simple as it could be. We just need a div that acts as container for our ReactJS app. th:utext="${content}" is thymeleaf specific and injects the content attribute of the view model as unescaped string.

React.java

In React.java we are going to instantiate the Nashorn engine to be able to interpret JavaScript code which we will add later. Nashorn is just a runtime environment for JavaScript on the JVM. It neither provides a window object nor a console for logging. But since the latter one is required by ReactJS we have to load a nashorn-polyfill.js file before anything else.

We are loading our JavaScript sources into Nashorn with nashornScriptEngine.eval ("load ('...')"). This is the same as including a script tag in a html document.
However, we could also call nashorn.eval (new InputStreamReader (...)) to load the JavaScript files instead of using the Nashorn specific load function. But we would lose the ability to debug the JavaScript code while running in Nashorn (at least with IntelliJ). Which could be... useful 😉

Furthermore we have to implement a method React#renderProducts which will invoke a global renderServer function defined in app.bundle.js to create the rendered html string.

nashorn-polyfill.js

The polyfill for nashorn has to define a global variable (for reasons I will explain later) and the already mentioned console. print is a Nashorn function that logs on stdout.

ProductController.java

The ProductController is responsible for getting the products and for setting the rendered html string as the content attribute of the view model.

Additionally we need a Product.java POJO and a ProductRepository.java. I think this is very straight forward and code snippets are obsolete here.

Frontend

With the backend part ready we can start with the frontend.
At first we have to do a small setup to enable es2015 compilation and module bundling with webpack. Webpack is actually not required but eases our developer lives immensely. Babel-cli could also be used with small adjustments in React.java.

$ npm init
$ npm i --save-dev webpack babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom

webpack.config.js

Next we configure webpack to generate a bundle of our JavaScript files including the ReactJS library and our app business logic. Please note output.filename which is the file loaded by React.java.

Webpack can then simply be used to create the bundle by a npm task.

// package.json
"scripts": {
  "build": "webpack"
}

ProducList.js

To start with the UI components we are going to create the ProductList as the main component. It will be, drum roll, responsible for displaying a list of products which have a name and a price (remember Product.java?). Therefore we implement a function that takes our products and returns the representational markup. To avoid "Cannot read property 'map' of undefined" type errors we simply assign an empty array to the products by default.

main.js

Next we need the entry point of our ReactJS app to define the renderServer function invoked by Nashorn. Remember the global variable set in nashorn-polyfill.js? We use this variable now to "export" our renderServer function. If you are familiar with the NodeJS environment, you already know that the global object is the equivalent to the window object available in the browser. And Nashorn is our equivalent of NodeJS 😉

Running the app

That's it!

Now we can run our first universal server side rendered springboot application to admire our graceful product list. Go on, run

$ npm run build
$ ./gradlew bootRun

open your Browser and load http://localhost:8080.

Just...
to see...
a wonderful stacktrace...

jdk.nashorn.internal.runtime.ECMAException: TypeError:
[de.synyx...Product@553287f8, Product@65ae29e6] has no such function "map"
	at jdk.nashorn.internal.runtime.ECMAErrors.error(ECMAErrors.java:58) ~[nashorn.jar:na]
	at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:214) ~[nashorn.jar:na]
	at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:186) ~[nashorn.jar:na]
	at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:173) ~[nashorn.jar:na]

The reason is that Nashorn interprets Java objects as, surprise, Java objects. As you remember, our ReactJS component <ProductList /> expects a list of products (actually a JavaScript array). But currently the type of products is a java.util.List which doesn't have the map method. Note the datatype of products in the image below.

nashorn-debugging

Luckily this is fixed quickly with mapping java.util.Collections to a JavaScript array with Java.from. Shamelessly copied from openjdk wiki:

"Given a Java array or Collection, this function returns a JavaScript array with a shallow copy of its contents"

So our renderServer function defined in main.js must be extended to:

Now we're ready to go 🙂
Rebuild the frontend with npm run build, restart the Spring Boot application, reload http://localhost:8080 and admire our awesome product list.

awesome-product-list-001

That's it for today.

We can now use Nashorn to send server side rendered html to the client without losing the awesomeness of ReactJS. Well... indeed... currently ReactJS doesn't provide even one benefit...

What do we have learned so far?

  • using Nashorn is no rocket science
  • load js files via nashornScriptEngine.eval ("load ('...')") to enable debugging (at least in IntelliJ)
  • java.util.List must be converted to JavaScript array with Java.from
  • manually rebuilding and reloading the ReactJS app sucks (autoreload would be cool, right)

js-webpack-nashorn

The next steps will be

  • using webpack to enhance developer experience
  • implementing the sorting feature

Stay tuned and keep learning!



One Response to springboot & reactjs #1 | server side rendering

  1. Pingback: springboot & reactjs #2 | progressive enhancement based on list sorting | synyx - Blog

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.