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.

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-domnpm 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.