How to Adapt Your Favorite Frontend for Liferay

TLDR;

I built a Yeoman generator to adapt Elm apps for Liferay using Liferay JS Toolkit.

Introduction

A year ago, I came across Elm and it instantly became my favorite language to develop frontend applications. Quickly after playing with it, I wanted to be able to create Elm apps and deploy them to Liferay.

I could have simply created an MVC Portlet and add my Elm app in the resources to then call it from the main JSP view. But, Liferay provides an SDK called Liferay JS Toolkit which allows developers to adapt React, Vue.js and Angular applications for Liferay without including any Java component. It uses Yeoman to add boilerplate code which automagically creates a JAR from your JS files. This JAR is an OSGi module you can then deploy to Liferay.

So I choose to follow this path, but only three frameworks are supported by the adapt mechanism. So what about Elm? Or any other JavaScript framework? Well, this is the story I’d like to share with you today: my journey into adapting Elm apps for Liferay using the Liferay JS Toolkit.

What is Elm?

As the official site states: “Elm is a delightful language for reliable webapps”. How? By providing features such as:

  • No Runtime Exceptions

  • Great Performance

  • Enforced Semantic Versioning

  • Small Assets

  • JavaScript Interop

If you want to learn more about this language, you can checkout this non-exhaustive list of nice talks about Elm:

Going on an Adventure

First, I needed to have a better understanding of what Liferay JS Toolkit provides exactly. We know there is the Yeoman generator, but we need to understand what automagically creates the JAR. And that’s the Liferay NPM Bundler. For the documentation, I originally headed to the GitHub Wiki since the Help Center was only providing high level information about it. And the documentation you can now find on Learn Liferay didn’t exist at the time.

To simplify, the Liferay NPM Bundler acts like Webpack which means that it will use a set of rules that you define in a file called “.npmbundlerrc” providing loaders that transform your code with instructions to produce a comprehensive output to bundle your application.

My next question was: how do I define rules and which loaders are available? I needed more than documentation, I needed actual code. Not only about loaders and rules themselves, but how they are applied to React, Vue.js and Angular inside of Liferay JS Adapt. But if you want to start looking at a basic example, you can find the Liferay JS Toolkit Showcase. Here’s a short snippet:

{
  "sources": ["src", "assets"],
  "rules": [
    {
      "test": "\\.scss$",
      "exclude": "node_modules",
      "use": ["sass-loader", "css-loader"]
    },
    {
      "test": "\\.js$",
      "exclude": "node_modules",
      "use": [
        {
          "loader": "babel-loader",
          "options": {
            "presets": ["env", "react"]
          }
        }
      ]
    }
  ]
}

Another thing to consider was what did Elm and its community provide. An important aspect of Elm is that it doesn't require all the JS — hellish — ecosystem to work. Meaning that you don’t need NPM or Webpack: Elm provides a CLI tool that allows you to simply run elm make to create the JS output you need to embed it within an HTML document. At this point, I was wondering whether I chose the right path. What’s sure is that I didn’t choose the easy one. But I found a really nice project out there: Create Elm App. It overcomes some limitations you hit with Elm like using Hot Module Replacement. Actually, this tool is very similar to Create React App, which is a cool thing for React developers to get their hands on Elm. But in my case it was very important because React is supported by Liferay JS Adapt. So I started to look at the configuration used by Liferay in the React case, and found this file.

If you have to adapt another JS framework, this might not be as easy as copy-pasting an existing configuration from Liferay JS Toolkit. But you might have more resources regarding your framework and Webpack loaders that you could reuse with Liferay NPM Bundler.

So is it done yet? Nope. I tried the configuration, and it wasn’t working. I noticed that this configuration was using “src.liferay” as a source folder:

{
    ...
    "sources": ["package.json", "build", "src.liferay"],
    "rules": [
        {
            "description": "Copy static assets",
            "test": "",
            "include": [
                "^build/static/css/.*\\.css$",
                "^build/static/media/.*"
            ],
            "use": [
                {
                    "loader": "copy-loader"
                }
            ]
        },
        {
            "description": "Transform Liferay's source files",
            "test": "^src.liferay/.*\\.js$",
            "use": [
                {
                    "loader": "babel-loader",
                    "options": {
                        "presets": ["babel-preset-liferay-standard"]
                    }
                }
            ]
        }
    ],
    ...
}

I didn’t get where it should have come from. So I tried an adapted React app and realized that this folder is created and deleted during the build process. Looking at the scripts of the adapted app, you could see that it uses “lnbs-build” and “lnbs-deploy”, which are parts of “liferay-npm-build-support”. The build script basically wraps your bundled JS files to support Liferay AMD Loader and creates a main entrypoint for your app as a module. The deploy script is pretty straightforward: just note that the destination folder is read from “.npmbuildrc” which is supplied during the generation process. Anyway, it’s time to copy-paste again. I simply put them under a bin folder and used them in package.json. In this file, I added Liferay NPM bundler dependencies since I used them in the build script after creating “src.liferay”, and portlet attributes. For internationalization, I reused the same structure “features/Language.properties”.

It was now finally working. I assembled all the necessary pieces. All I needed to do now to complete my goal was to turn the example into a template I could use in my own Yeoman generator. I’m not going to detail the steps of creating the generator itself, it’s well covered in Yeoman documentation. And the process of templating the example is not very interesting.

In the end

You can find the project here: https://github.com/lgdd/generator-liferay-elm. To use it, you need Node 10 or greater to install Yeoman and the generator itself with the command “npm install -g yo generator-liferay-elm” ("generator-" is the standard prefix for Yeoman generators). Then simply type “yo liferay-elm” and it will ask basic questions about your app (e.g. name, description):

An option I recently added is the ability to generate a pre-configured Liferay Docker environment. The pre-configuration includes Dockerfile and docker-compose with the mapping to a default deploy folder and Liferay Portal CORS configuration so you can use Liferay APIs when running the app in development mode with “yarn start” (or npm start, both are supported). About Liferay itself, be aware that I only tested it with some 7.2 and 7.3 distributions, I don’t have a clear compatibility matrix — yet. I’m also working on a number of more real life examples if you just want to clone something ready to test: https://github.com/lgdd/liferay-elm-examples.

I hope you found this article helpful. If you have any questions, don’t hesitate to reach out.

Blogs

Thank you Louis-Guillaume! The Elm language looks so promising. Thank you very much for sharing this, that will help me get a sample Elm project running in no time as soon as I have some time to test it! :)