We are developing and maintaining a more than 10 years old classic multi-page application based on the Grails web framework. With the advent of HTML 5 and modern browsers with faster JavaScript engines user expect more and more dynamic and pleasant user experience (UX) from web applications. Our application is used by hundreds of users and our customer expects a stable, familiar and feature-rich experience that continues to improve over time. Something like a complete rewrite of the UI is way out of scope time- and budget-wise.
One of the new feature requests would benefit highly from a client-side JavaScript implementation so we looked at our options. Fortunately it is quite easy to integrate a react app with grails and the gradle build system. So we implemented the new page almost completely as a react app while leaving all the other pages as normal server-side rendered Groovy Server Pages (GSP). The result is quite convincing and opens up a transition path to more and more dynamic client-side pages and perhaps even to the complete transformation to a single-page-application (SPA) in a distant future.
Integrating a React-App into Grails build process
The Grails react-webpack profile can serve as a great starting point to integrate a react app into an existing grails project. First you create the react app for the new page in the folder src/main/webapp
, using the create-react-app scripts for example. Then you need to add a $GRAILS_PROJECT/webpack.config.js
to configure webpack appropriately like so:
var path = require('path'); module.exports = { entry: './src/main/webapp/index.js', output: { path: path.join(__dirname, 'grails-app/assets/javascripts'), publicPath: '/assets/', filename: 'bundle.js' }, module: { rules: [ { test: /\.js$/, include: path.join(__dirname, 'src/main/webapp'), use: { loader: 'babel-loader', options: { presets: ["@babel/preset-env", "@babel/preset-react"], plugins: ["transform-class-properties"] } } }, { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.(jpe?g|png|gif|svg)$/i, use: { loader: 'url-loader?limit=10000&prefix=assets/!img' } } ] } };
The next step is to move the package.json
to the $GRAILS_PROJECT
directory because we want gradle tasks to take care of building and bundling it as a grails asset. To make this convenient we add some gradle tasks employing yarn to our build.gradle
:
buildscript { dependencies { ... classpath "com.moowork.gradle:gradle-node-plugin:1.2.0" } } ... apply plugin:"com.moowork.node" ... node { version = '12.15.0' yarnVersion = '1.22.0' distBaseUrl = 'https://nodejs.org/dist' download = true } task bundle(type: YarnTask, dependsOn: 'yarn') { group = 'build' description = 'Build the client bundle' args = ['run', 'bundle'] } task webpack(type: YarnTask, dependsOn: 'yarn') { group = 'application' description = 'Build the client bundle in watch mode' args = ['run', 'start'] } bootRun.dependsOn(['bundle']) assetCompile.dependsOn(['bundle']) ...
Now we have integrated our new react app with the grails build system and packaging. The webpack task allows updating the javascript bundle on the fly so that we have almost the same hot reloading support when developing as with the rest of grails.
Delivering the react app as a page
Now that we have integrated the react app in the build and packaging process of our grails application we need to deliver it when the new page is requested by the browser. This is quite simple and straightforward and can be achieved with a GSP like so:
<html> <head> <meta name="layout" content="main"/> <title> <g:message code="example.header"/> </title> </head> <body> <div id="react-content"> </div> <asset:javascript src="bundle.js"/> </body> </html>
Now you just have to develop the endpoints for the javascript app in form of normal grails controllers rendering JSON instead of GSP views. This is extremely easy using groovy maps and the grails JSON converters:
import grails.converters.JSON class DataApiController { def getData = { def responseData = [ name: 'John', age: 37 ] render responseData as JSON } }
Conclusion
Grails and its build infrastructure is flexible enough to easily integrate SPA pages into an existing traditional web application. This allows you to deliver modern UX and features expected by nowadays users without completely rewriting your trusty and proven grails application. The process can be gradually and individual pages/views can be renewed when needed. That way you can continually add value to your customer while incrementally modernizing your application.