This article is a repost from Grégory Picavet’s own blog. Grégory is the tech lead on collaborative portal projects for a French Ministry, and a web development passionate.
In this article, I would like to share some information about the eXo Platform portal and ReactJS. You’ll learn how to set up a simple ReactJS and node.js development stack, build a standalone app, and package it all to a portlet. If you’re looking to develop React.js applications on the Exo Platform, you should consider hiring Node.js developers with experience in both technologies. Learn how to Create Figma to React with this comprehensive tutorial.
To efficiently manage JS library dependencies, we will use node.js development services and npm. We’ll then use Maven to build and package the portlet.
npm init
npm install --save react@15.3.1 react-dom@15.3.1 moment
npm install --save-dev babel-loader babel-core babel-preset-es2015 babel-preset-react
npm install –save-dev webpack express
/
├─src
│ ├─main
│ │ ├─java
│ │ ├─js
│ │ ├─webapp
│ │ ├─css
│ │ ├─META-INF
│ │ ├─WEB-INF
│ ├─static
├─package.json
├─pom.xml
import React from 'react';
import ReactDOM from 'react-dom';
import {Activity} from './Activity.jsx';
class Activities extends React.Component {
constructor(props) {
super(props);
this.state = {activities:[]};
}
componentDidMount() {
fetch("/rest/v1/social/activities?limit="+this.props.limit+"&expand=identity",
{credentials: 'include'})
.then((res) => {
return res.json();
})
.then((json) => {
this.setState(json);
})
}
render() {
var list = this.state.activities.map( (act) => {
return <Activity key={act.id} {...act} />
});
return <ul>{list}</ul>;
}
}
ReactDOM.render(<Activities limit="10"/>, document.getElementById('app'));
import React from 'react';
var moment = require('moment');
export class Activity extends React.Component {
constructor(props) {
super(props);
}
render() {
var titleHtml = {__html:this.props.title};
return (
<li>
<img src={this.props.identity.profile.avatar===null ? "/eXoSkin/skin/images/system/UserAvtDefault.png":this.props.identity.profile.avatar} />
<div className="block">
<div className="header">
<strong>{this.props.identity.profile.fullname}</strong>
<br/>
Posted : <span>{moment(new Date(this.props.createDate)).fromNow()}</span>
</div>
<div dangerouslySetInnerHTML={titleHtml}/>
</div>
</li>);
}
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<div id="app" class="container"></div>
<script src="js/bundle.js"></script>
</body>
</html>
import Activities from './Activities.jsx';
var path = require('path');
var webpack = require('webpack');
module.exports = {
devtool : 'cheap-module-source-map',
entry: './src/main/js/index.js',
output: { path: path.join(__dirname, 'target/react-portlet/js'),
filename: 'bundle.js'},
module: {
loaders: [
{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react']
}
}
]
},
plugins:[
new webpack.DefinePlugin({
'process.env':{
'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}
})]
};
"scripts": {
"copy": "cp -R src/static/* target/static & cp -R src/main/webapp/css target/static"
}
npm run copy
var express = require('express'); // call express
var app = express(); // define our app using express
var port = 3000; // set our port
// REGISTER STATIC FILES -------------------------------
app.use(express.static(__dirname+"/target/static/"));
app.use(express.static(__dirname+"/target/react-portlet/"));
// ROUTES FOR OUR API
// =============================================================================
var router = express.Router(); // get an instance of the express Router
router.get('/v1/social/activities', function(req, res) {
res.set('content-type','application/json; charset=utf8')
res.sendFile(__dirname+"/target/static/api/activities.json");
});
router.get('/avatar.png', function(req, res) {
res.sendFile(__dirname+"/target/static/avatar.png");
});
// REGISTER OUR ROUTES -------------------------------
app.use('/rest/api', router);
// START THE SERVER
// =============================================================================
app.listen(port);
"scripts": {
...
"watch": "npm run copy && node node_modules/webpack/bin/webpack.js --progress --colors --watch -d"
}
npm run watch
Hash: 0aed17830ca00bbed3fd
Version: webpack 1.13.2
Time: 4640ms
Asset Size Chunks Chunk Names
bundle.js 1.22 MB 0 [emitted] main
bundle.js.map 1.43 MB 0 [emitted] main
+ 278 hidden modules
npm start
"scripts": {
...
"release": "export NODE_ENV=production && webpack -p"
}
Hash: 958d20ab7b77c17c6474
Version: webpack 1.13.2
Time: 8955ms
Asset Size Chunks Chunk Names
bundle.js 418 kB 0 [emitted] main
bundle.js.map 219 bytes 0 [emitted] main
+ 278 hidden modules
<div class='react-portlet'>
<div id="app" class="container"></div>
</div>
<portlet>
<name>reactsample</name>
<module>
<script>
<path>/js/bundle.js</path>
</script>
</module>
</portlet>
<portlet-skin>
<application-name>react-portlet</application-name>
<portlet-name>reactsample</portlet-name>
<skin-name>Default</skin-name>
<css-path>/css/main.css</css-path>
</portlet-skin>
There are two main module styles in JavaScript: AMD and CommonJS. When transpiling ES2015 to ES5, Babel replaces imports with the CommonJS style. Because Exo uses AMD modules, it will automatically adapt them. However, some libraries require manual adaptation. This would be the case with React if we had to load it separately from bundle.js. When you need to hire ReactJS developers, ensuring they understand module styles and library adaptations is crucial.
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.5.0</version>
<executions>
<execution>
<id>npm install</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>install</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>npm release</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>${webpack.release}</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
Then simply type:mvn clean install
mvn clean install -Pproduction
entry: {
bundle:'./src/main/js/index.js',
'vendor-bundle': [
'react',
'react-dom',
'moment'
]
}
...
plugins:[
...
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-bundle',
filename: 'vendor-bundle.js',
minChunks: Infinity
})
]
Hash: 837db454da54d24ded16
Version: webpack 1.13.2
Time: 6187ms
Asset Size Chunks Chunk Names
bundle.js 7.13 kB 0 [emitted] bundle
vendor-bundle.js 1.21 MB 1 [emitted] vendor-bundle
bundle.js.map 3.69 kB 0 [emitted] bundle
vendor-bundle.js.map 1.44 MB 1 [emitted] vendor-bundle
[0] multi vendor-bundle 52 bytes {1} [built]
+ 278 hidden modules
<module>
<name>vendor</name>
<script>
<path>/js/vendor-bundle.js</path>
</script>
</module>
<portlet>
<name>reactsample</name>
<module>
<script>
<path>/js/bundle.js</path>
</script>
<depends>
<module>vendor</module>
</depends>
</module>
</portlet>
define('PORTLET/react-portlet/reactsample', ["SHARED/vendor"], function(vendor) {
...
Thanks again to Grégory Picavet for this great post. Make sure to check his original article and his blog for new articles.
( Your e-mail address will not be published)
There’s definately a lot to learn about this topic.
I really like all of the points you’ve made.
Hi there! This is my first visit to your blog!
We are a team of volunteers and starting a new initiative in a community
in the same niche. Your blog provided us useful information to work on. You have done a
extraordinary job!
Feel free to visit my web page – porn
Greate article. Keep writing such kind of info
on your page. Im really impressed by your blog.
Hi there, You’ve done an incredible job. I’ll definitely digg
it and personally suggest to my friends. I’m sure they’ll be benefited from this website.
Aw, this was an exceptionally nice post. Spending some time and actual effort to generate
a top notch article… but what can I say… I hesitate a lot and never seem to get nearly anything done.
It’s actually very difficult in this active life to listen news on TV, therefore I only use world wide web for
that reason, and take the hottest news.
Nice post. I was checking constantly this weblog and I am inspired! Extremely helpful information specifically the ultimate part 🙂
I take care of such information a lot. I used to be seeking this certain info for a long time.
Thank you and best of luck.
We are a group of volunteers and opening a new scheme in our community.
Your site provided us with valuable information to work on. You have done a formidable job and our whole community will be thankful to you.