Creating a basic Gatsby source plugin for Liferay
In the first part we basically set everything up. We created a Gatsby site and a few web content articles in Liferay.
In this part we will create the Gatsby source plugin for Liferay and use the headless API to pull in our articles.
Upfront, a lot of the steps I outline below for creating the plugin I got from the Gatsby site. Therefore, I will leave out some explanations regarding syntax and methods allowing you to read more details for yourself there. I found the Gatsby docs was very informative, allowing me to get up and running quickly.
Creating the Plugin
To begin creating our liferay source plugin, we want to first create a “plugins” directory inside our gatsby-liferay-site directory. Followed by creating another directory for our actual plugin titled “gatsby-source-liferay”.
Your path should now look something like the following:
.../gatsby-liferay-site/plugins/gatsby-source-liferay
Inside this directory, issue the following command to create a package.json file which will describe our plugin and any other dependencies it may need.
npm init --yes
Next, let’s install a few modules that we will need to make the request to our Liferay server.
npm install node-fetch query-string --save
Now, let’s create the main file for our source plugin. Create a file called gatsby-node.js in the .../plugins/gatsby-source-liferay directory and add the following contents.
const fetch = require("node-fetch")
const queryString = require("query-string")
exports.sourceNodes = (
{ actions, createNodeId, createContentDigest },
configOptions
) => {
const { createNode } = actions
// Gatsby adds a configOption that's not needed for this plugin, delete it
delete configOptions.plugins
// plugin code goes here...
console.log("Testing my plugin", configOptions)
}
Adding the plugin to our Gatsby site
Next we need to tell our Gatsby site about our plugin. Edit the file gatsby-config.js located in your ../gatsby-liferay-site/ directory.
We can just add our source plugin towards the top of the file. Your file should look similar to the this.
module.exports = {
siteMetadata: {
title: `Gatsby Default Starter`,
description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
author: `@gatsbyjs`,
},
plugins: [
{
resolve: "gatsby-source-liferay",
options: {
host: "localhost:8080",
siteId: "20123",
authKey: "dGVzdEBsaWZlcmF5LmNvbTp0ZXN0"
},
},
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
….
I went ahead and added some options we will need when we call the Liferay server. You can create the authKey by executing the following to get a Base 64 encoding.
echo -n "test@liferay.com:test" | base64
Now let’s check and make sure our plugin loads.
From the ../gatsby-liferay-site/ directory, enter gatsby develop to fire up a development server.
We should see output in the console similar to the following.
success initialize cache - 0.038 s
success copy gatsby files - 0.071 s
success onPreBootstrap - 0.025 s
Testing my plugin { host: 'localhost:8080',
siteId: '20123',
authKey: 'dGVzdEBsaWZlcmF5LmNvbTp0ZXN0' }
warn The gatsby-source-liferay plugin has generated no Gatsby nodes. Do you need it?
Great, our plugin works. Hit CTRL-C and kill the development server.
Use the web service in our plugin
Let’s now move to finally using the web service to pull back our web content articles.
Edit the gatsby-node.js file again in your ../plugins/gatsby-source-liferay directory and replace the contents with the following.
const fetch = require("node-fetch")
const queryString = require("query-string")
exports.sourceNodes = (
{actions, createNodeId, createContentDigest},
configOptions
) => {
const {createNode, createNodeField} = actions
// Gatsby adds a configOption that's not needed for this plugin, delete it
delete configOptions.plugins
// Helper function that processes a photo to match Gatsby's node structure
const processContent = content => {
const nodeId = createNodeId(`liferay-content-${content.id}`)
const nodeContent = JSON.stringify(content)
const nodeData = Object.assign({}, content, {
slug: `news/${content.friendlyUrlPath}.html`,
id: nodeId,
parent: null,
children: [],
internal: {
type: `LiferayStructuredContent`,
mediaType: `text/html`,
content: nodeContent,
contentDigest: createContentDigest(content),
},
})
return nodeData
}
// plugin code goes here...
// console.log("Testing gatsby-source-liferay plugin", configOptions)
// Construct Liferay API URL for structured-content
const apiUrl = `http://${configOptions.host}/o/headless-delivery/v1.0/sites/${configOptions.siteId}/structured-contents/`
const init = {
method: 'GET',
headers: {
'Accept': 'application/json',
'Authorization': `Basic ${configOptions.authKey}`
}
}
// Gatsby expects sourceNodes to return a promise
return (
// Fetch a response from the apiUrl
fetch(apiUrl, init)
// Parse the response as JSON
.then(response => response.json())
// Process the JSON data into a node
.then(data => {
// For each query result (or 'items')
data.items.forEach(content => {
console.log("Liferay article: ", content.title)
// Process the data to match the structure of a Gatsby node
const nodeData = processContent(content)
//console.log("nodeData--> ", nodeData);
// Use Gatsby's createNode helper to create a node from the node data
createNode(nodeData)
})
})
)
}
Success, plugin works!
Start up your Gatsby development server again. This time the console output should display the titles of our web content articles!
success load plugins - 0.309 s success onPreInit - 0.018 s success initialize cache - 0.022 s success copy gatsby files - 0.058 s success onPreBootstrap - 0.020 s Liferay article: A simple article Liferay article: Another simple article Liferay article: Another simple article wo tag success source and transform nodes - 0.291 s success Add explicit types - 0.020 s
We have successfully invoked the web service and retrieved our web content articles. There are two things I will mention about the code above. First, in the processContent() method, I am creating a property “slug” for the page location and using /news/ + the friendlyUrlPath of the web content article. For example, this will give us an article that will be available at “/news/a-simple-article.html”. Second, notice we are using the Gatsby createNode() method. This is important, because it populates Gatsby’s data layer which will then let us create pages from our web content.
This means we can now query our data that we just loaded into Gatsby with GraphQL. You probably noticed GraphQL when we started up the Gatsby development server.
Open a browser and hit http://localhost:8000/___graphql
Go ahead and paste the following in the GraphiQL interface and view the results. This is a great tool to test out your queries before using them in your React components.
query MyQuery {
allLiferayStructuredContent {
edges {
node {
id
title
description
}
}
}
}
Creating a template for our content
Now that our plugin is complete, it’s time to generate pages for each of our web content articles. To do this we need to create a React component that will be the template for displaying the web content articles.
Create a “templates” directory in ../gatsby-liferay-site/src/.
Then create a file called news.js inside the ../gatsby-liferay-site/src/templates/ directory and paste the following contents inside it.
import {graphql} from "gatsby";
import React from "react";
import Layout from "../components/layout"
export default class News extends React.Component{
render() {
const { data } = this.props;
const { liferayStructuredContent: { contentFields, dateModified, creator: { name }, title, description } } = data;
function createMarkup(html) {
// console.log("createMarkup-->" + html);
return {__html: html};
}
return(
<Layout>
<div className="news">
<div className="container-fluid">
<div className="row">
<div className="intro blog-intro text-center col">
<div className="container-fluid container-fluid-max-lg">
<h1 className="h1">News</h1>
<h2 className="h3">Latest updates from an external Liferay</h2>
</div>
</div>
</div>
</div>
<div className="container">
<div className="row">
<div className="col-md-12">
<article>
<small>
{dateModified} {name}
</small>
<div dangerouslySetInnerHTML={createMarkup(description) } />
<h1>{title}</h1>
<div dangerouslySetInnerHTML={createMarkup(contentFields[0].value.data) } />
</article>
</div>
</div>
</div>
</div>
</Layout>
)
}
}
export const pageQuery = graphql`
query($slug: String!){
liferayStructuredContent(slug:{eq: $slug }) {
key
dateModified(formatString: "MMMM DD, YYYY")
title
description
slug
creator {
name
familyName
givenName
}
contentFields{
value{
data
}
}
}
}
`;
Great, now we have our template. It is time to test everything.
We can get a listing of our pages by triggering a 404. Go ahead and enter a bogus URL for your Gatsby site. You should see something like the following.
All our web content articles are listed now as site pages, very cool!
Click on one of them and you will see the contents just as if it originated from the site.
Click back and try another. You will notice all the html styling (h1, h2, ul, etc..) created in Liferay is present. You can even use CSS class names when authoring the content in Liferay. You would just have to include the styling definitions in your Gatsby site.
Lastly, remember we tagged our web content articles as “news”. If you look back at part 1, the curl output shows tags listed as an array in the keywords field. We could add a filter to the web service to only pull articles with our tag.
For example)
curl "http://localhost:8080/o/headless-delivery/v1.0/sites/20123/structured-contents/?fields=title,keywords&filter=keywords/any(k:contains(k,'news'))" -u 'test@liferay.com:test'
Conclusion
What I really liked was how easy I could use Liferay to author content and use the Headless API to easily have it rendered elsewhere. This was a vastly different experience than if I had attempted to use the traditional JSON webservice to accomplish this solution.
Great job Liferay!
I hope this is example is useful.


