Liferay 7 + React JS (or anyone other JS framework)

I won’t say why it need to do, but if you want to do this just read my article.

Liferay 7 + React

You can choose one of methods to add you JS framework/library to your project. Custom method —using module loaders mechanism. Another way — using Webpack 2 + Gradle.

If you don’t want read about creating MVC portlet from scratch skip next section and go to section 2 or section 3.

UPD: Link to sources — https://github.com/sani-banani/react-liferay-portlet

1. Creating MVC portlet

Type command below and view available archetypes

mvn archetype:generate -Dfilter=liferay

Select com.liferay:com.liferay.project.templates.mvc.portlet (Creates a Liferay MVC portlet as a module project.)

2. Configuring via Module Loaders

Using portlet structure in previous section, add to META-INF.resources.libs folder react library sources: react.min.js and react-dom.min.js

Add to META-INF/resources folder config.js

Liferay.Loader.addModule(
{
dependencies: [],
name: 'react',
anonymous: true,
path: MODULE_PATH + '/js/libs/react.min.js'
}
);
Liferay.Loader.addModule(
{
dependencies: [],
name: 'react-dom',
anonymous: true,
path: MODULE_PATH + '/js/libs/react-dom.min.js'
}
);

Add to bnd.bnd in root folder

Liferay-JS-Config: /META-INF/resources/config.js

Add your JS script, in my case META-INF/resources/js/main.js

require('react', function (React) {
require('react-dom', function (ReactDOM) {
    // some code
    ReactDOM.render(React.createElement(Application, null), document.getElementById('root'));
});
});

Then deploy portlet using command: gradle clean deploy

And we have jar with JS libraries.

3. Configuring via Webpack 2 and Gradle

Another method is adding gradle task to run webpack.

Install npm packages:

npm install --save react react-dom
npm install --save-dev babel-core babel-loader babel-polyfill babel-preset-es2015 babel-preset-react css-loader file-loader html-webpack-plugin path style-loader url-loader webpack

Add .babelrc:

{
"presets": ["es2015", "react"]
}

Add simple webpack config:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const SRC = path.resolve(__dirname, 'app');
const DEST = path.resolve(__dirname, 'src/main/resources/META-INF/resources/dist');

module.exports = {
entry: {
app: SRC + '/app.jsx'
},
resolve: {
extensions: ['.js','.jsx']
},
output: {
path: DEST,
filename: 'app.js'
},
module: {
loaders: [
{
test: /\.jsx?$/,
loaders: ['babel-loader'],
include: SRC
},
{test: /\.css$/, loader: 'style-loader!css-loader'},
{test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/font-woff'},
{test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/octet-stream'},
{test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file'},
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=image/svg+xml'}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './app/index.html',
filename: 'index.html',
inject: 'body'
})
]
};

Now you can use all benefits for developing in React (ES6, JSX, webpack-dev-server and others)

Add code to app folder in root directory.

app.jsx

import React from 'react';
import ReactDOM from 'react-dom';

class ColorPicker extends React.Component {
constructor(props) {
super(props);
this.state = {color: props.value};
}

colorChange(type, e) {
let currentColor = this.state.color; //this.props.value;//
currentColor[type] = parseInt(e.target.value);
this.setState({color: currentColor});
this.props.onChange(currentColor);
}

render() {
const rgba = {
r: Math.round(this.state.color[0] * 255 / 100),
g: Math.round(this.state.color[1] * 255 / 100),
b: Math.round(this.state.color[2] * 255 / 100),
a: 1.0,
};
return (
<div className="colorPicker" style={{'backgroundColor': `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`}}>
<div>
R
<input
type="range"
value={this.state.color[0]}
onChange={this.colorChange.bind(this, 0)}
/>
</div>
<div>
G
<input
type="range"
value={this.state.color[1]}
onChange={this.colorChange.bind(this, 1)}
/>
</div>
<div>
B
<input
type="range"
value={this.state.color[2]}
onChange={this.colorChange.bind(this, 2)}
/>
</div>
</div>
);
}
}


ColorPicker.propTypes = {
value: React.PropTypes.Array,
onChange: React.PropTypes.func,
}
ColorPicker.defaultProps = {value: [0, 0, 0], onChange: () => {}}

const Application = () => (
<ColorPicker
value={[90, 10, 20]}
/>
)

ReactDOM.render(<Application />, document.getElementById('app'));

index.html for webpack-dev-server

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>React Home Page</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

For local testing use webpack-dev-server command

Now, you need add gradle task to add you JS code to build jar:

buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins", version: "3.3.9"
}

repositories {
maven {
url "https://cdn.lfrs.sl/repository.liferay.com/nexus/content/groups/public"
}
}
}

apply plugin: "com.liferay.plugin"

dependencies {
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.0.0"
compileOnly group: "com.liferay.portal", name: "com.liferay.util.taglib", version: "2.0.0"
compileOnly group: "javax.portlet", name: "portlet-api", version: "2.0"
compileOnly group: "javax.servlet", name: "javax.servlet-api", version: "3.0.1"
compileOnly group: "jstl", name: "jstl", version: "1.2"
compileOnly group: "org.osgi", name: "osgi.cmpn", version: "6.0.0"
}

repositories {
mavenLocal()

maven {
url "https://cdn.lfrs.sl/repository.liferay.com/nexus/content/groups/public"
}
}

import com.liferay.gradle.plugins.node.tasks.ExecuteNodeTask

task buildWebpack(type: ExecuteNodeTask)
task cleanJSDist

buildWebpack {
dependsOn npmInstall

args = "./node_modules/webpack/bin/webpack.js"
}

cleanJSDist {
delete "./src/main/resources/META-INF/resources/dist"
}

classes {
dependsOn buildWebpack
}

Finally, for building portlet run command gradle clean buildWebpack deploy cleanJSDist.