diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..b0c0c8b
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,3 @@
+{
+ "extends": "airbnb"
+}
diff --git a/package.json b/package.json
index 7876824..2fbaf4a 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "springboot-react-webpack-demo",
"version": "1.0.0",
"dependencies": {
- "bootstrap": "^3.3.6",
+ "bootstrap": "^3.3.6",
"jquery": "^2.2.2",
"marked": "^0.3.5",
"react": "^0.14.7",
@@ -10,10 +10,15 @@
},
"devDependencies": {
"babel-core": "^6.7.4",
+ "babel-eslint": "^6.0.2",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"css-loader": "^0.23.1",
+ "eslint": "^2.5.3",
+ "eslint-config-airbnb": "^6.2.0",
+ "eslint-loader": "^1.3.0",
+ "eslint-plugin-react": "^4.2.3",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.8.5",
"html-loader": "^0.4.3",
@@ -27,8 +32,9 @@
"webpack-dev-server": "^1.14.1"
},
"scripts": {
- "watch": "NODE_ENV=development webpack-dev-server --hot --inline",
- "dev-build": "NODE_ENV=development webpack --d --progress --colors",
- "prod-build": "webpack -d -p --colors"
+ "dev-build": "NODE_ENV=development webpack --d --progress --colors",
+ "lint": "eslint --ignore-pattern **/nashorn-*.js --ext .js,.jsx src/main/resources/static/js/**",
+ "prod-build": "webpack -d -p --colors",
+ "watch": "NODE_ENV=development webpack-dev-server --hot --inline"
}
}
diff --git a/src/main/resources/static/js/app.jsx b/src/main/resources/static/js/app.jsx
deleted file mode 100644
index 57c3111..0000000
--- a/src/main/resources/static/js/app.jsx
+++ /dev/null
@@ -1,135 +0,0 @@
-'use strict';
-
-var React = require('react'),
- marked = require('marked'),
- $ = require('jquery');
-
-var Comment = React.createClass({
- rawMarkup: function() {
- var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
- return { __html: rawMarkup };
- },
- render: function() {
- return (
-
-
- {this.props.author}
-
-
-
- );
- }
-});
-
-var CommentForm = React.createClass({
- getInitialState: function() {
- return {author: '', text: ''};
- },
- handleAuthorChange: function(e) {
- this.setState({author: e.target.value});
- },
- handleTextChange: function(e) {
- this.setState({text: e.target.value});
- },
- handleSubmit: function(e) {
- e.preventDefault();
- var author = this.state.author.trim();
- var text = this.state.text.trim();
- if( !text || !author ) {
- return;
- }
- this.props.onCommentSubmit({author: author, text: text})
- this.setState({author: '', text: ''});
- },
- render: function() {
- return (
-
- );
- }
-});
-
-var CommentBox = React.createClass({
- getInitialState: function() {
- return {data: this.props.data || []};
- },
- loadCommentsFromServer: function() {
- $.ajax({
- url: this.props.url,
- dataType: 'json',
- cache: false,
- success: function(data) {
- this.setState({data: data});
- }.bind(this),
- error: function(xhr, status, err) {
- console.error(this.props.url, status, err.toString());
- }.bind(this)
- });
- },
- handleCommentSubmit: function(comment) {
- var comments = this.state.data;
- comment.id = Date.now();
- var newComments = comments.concat([comment]);
- this.setState({data: newComments});
- $.ajax({
- url: this.props.url,
- method: 'POST',
- dataType: 'json',
- data: comment,
- success: function(data) {
- this.setState({data: data});
- }.bind(this),
- error: function(xhr, status, err) {
- this.setState({data: comments});
- console.error(this.props.url, status, err.toString());
- }.bind(this)
- });
- },
- componentDidMount: function() {
- this.loadCommentsFromServer();
- setInterval(this.loadCommentsFromServer, this.props.pollInterval);
- },
- render: function() {
- return (
-
-
Comments
-
-
-
- );
- }
-});
-var CommentList = React.createClass({
- render: function() {
- var commentNodes = this.props.data.map( function(comment) {
- return (
- {comment.text}
- );
- });
- return (
-
- {commentNodes}
-
- )
- }
-});
-
-module.exports = {
- CommentBox: CommentBox,
- CommentList: CommentList,
- CommentForm: CommentForm,
- Comment: Comment
-};
\ No newline at end of file
diff --git a/src/main/resources/static/js/app.render.jsx b/src/main/resources/static/js/app.render.jsx
index beed912..9d34e8c 100644
--- a/src/main/resources/static/js/app.render.jsx
+++ b/src/main/resources/static/js/app.render.jsx
@@ -1,32 +1,27 @@
-"use strict";
-
-
import React from 'react';
import ReactDOM from 'react-dom';
import ReactDOMServer from 'react-dom/server';
-import App from './app.jsx';
-import $ from 'jquery';
+import CommentBox from './comment_box.jsx';
+import 'bootstrap/dist/css/bootstrap.css';
+import '../css/comments.css';
+import '../css/comments.less';
-require('bootstrap/dist/css/bootstrap.css');
-require('../css/comments.css');
-require('../css/comments.less');
-
-global.renderClient = function (comments) {
- var data = comments || [];
- ReactDOM.render(
- ,
- document.getElementById('content')
- );
+global.renderClient = function renderClient(comments) {
+ const data = comments || [];
+ ReactDOM.render(
+ ,
+ document.getElementById('content')
+ );
};
-global.renderServer = function (comments) {
- var data = Java.from(comments);
- return ReactDOMServer.renderToString(
-
- );
+global.renderServer = function renderServer(comments) {
+ const data = Java.from(comments);
+ return ReactDOMServer.renderToString(
+
+ );
};
-if( !global.nashorn ) {
- renderClient(initialData);
-};
\ No newline at end of file
+if (!global.nashorn) {
+ global.renderClient(global.initialData);
+}
diff --git a/src/main/resources/static/js/comment.jsx b/src/main/resources/static/js/comment.jsx
new file mode 100644
index 0000000..a7429c0
--- /dev/null
+++ b/src/main/resources/static/js/comment.jsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import marked from 'marked';
+
+class Comment extends React.Component {
+ rawMarkup() {
+ const rawMarkup = marked(this.props.children.toString(), { sanitize: true });
+ return { __html: rawMarkup };
+ }
+ render() {
+ return (
+
+
+ {this.props.author}
+
+
+
+ );
+ }
+}
+Comment.propTypes = {
+ author: React.PropTypes.string,
+ children: React.PropTypes.node,
+};
diff --git a/src/main/resources/static/js/comment_box.jsx b/src/main/resources/static/js/comment_box.jsx
new file mode 100644
index 0000000..6480ddd
--- /dev/null
+++ b/src/main/resources/static/js/comment_box.jsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import $ from 'jquery';
+import CommentForm from './comment_form.jsx';
+import CommentList from './comment_list.jsx';
+
+class CommentBox extends React.Component {
+ componentDidMount() {
+ this.loadCommentsFromServer();
+ setInterval(this.loadCommentsFromServer, this.props.pollInterval);
+ }
+ getInitialStatefunction() {
+ return { data: this.props.data || [] };
+ }
+ loadCommentsFromServer() {
+ $.ajax({
+ url: this.props.url,
+ dataType: 'json',
+ cache: false,
+ success: function success(data) {
+ this.setState({ data });
+ }.bind(this),
+ error: function error(xhr, status, err) {
+ console.error(this.props.url, status, err.toString());
+ }.bind(this),
+ });
+ }
+ handleCommentSubmit(comment) {
+ const comments = this.state.data;
+ const copy = comment;
+ copy.id = Date.now();
+ const newComments = comments.concat([copy]);
+ this.setState({ data: newComments });
+ $.ajax({
+ url: this.props.url,
+ method: 'POST',
+ dataType: 'json',
+ data: comment,
+ success: function success(data) {
+ this.setState({ data });
+ }.bind(this),
+ error: function error(xhr, status, err) {
+ this.setState({ data: comments });
+ console.error(this.props.url, status, err.toString());
+ }.bind(this),
+ });
+ }
+ render() {
+ return (
+
+
Comments
+
+
+
+ );
+ }
+}
+CommentBox.propTypes = {
+ data: React.PropTypes.shape({
+ id: React.PropTypes.number,
+ author: React.PropTypes.string,
+ text: React.PropTypes.string,
+ }),
+ pollInterval: React.PropTypes.numbe,
+ url: React.PropTypes.string,
+};
diff --git a/src/main/resources/static/js/comment_form.jsx b/src/main/resources/static/js/comment_form.jsx
new file mode 100644
index 0000000..0809a24
--- /dev/null
+++ b/src/main/resources/static/js/comment_form.jsx
@@ -0,0 +1,47 @@
+import React from 'react';
+
+class CommentForm extends React.Component {
+ getInitialState() {
+ return { author: '', text: '' };
+ }
+ handleAuthorChange(e) {
+ this.setState({ author: e.target.value });
+ }
+ handleTextChange(e) {
+ this.setState({ text: e.target.value });
+ }
+ handleSubmit(e) {
+ e.preventDefault();
+ const author = this.state.author.trim();
+ const text = this.state.text.trim();
+ if (!text || !author) {
+ return;
+ }
+ this.props.onCommentSubmit({ author, text });
+ this.setState({ author: '', text: '' });
+ }
+ render() {
+ return (
+
+ );
+ }
+}
+CommentForm.propTypes = {
+ author: React.PropTypes.string,
+ text: React.PropTypes.string,
+ onCommentSubmit: React.PropTypes.func,
+};
diff --git a/src/main/resources/static/js/vendors.js b/src/main/resources/static/js/vendors.js
index 4b36877..57b10a4 100644
--- a/src/main/resources/static/js/vendors.js
+++ b/src/main/resources/static/js/vendors.js
@@ -1,7 +1,5 @@
-"use strict";
+import 'react';
+import 'react-dom';
+import 'jquery';
-require('react');
-require('react-dom');
-require('jquery');
-
-require('bootstrap/dist/css/bootstrap.css');
\ No newline at end of file
+import 'bootstrap/dist/css/bootstrap.css';
diff --git a/webpack.config.js b/webpack.config.js
index 72995bd..0859a9f 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -39,6 +39,13 @@ var config = {
publicPath: 'http://localhost:' + dev_port + '/'
},
module: {
+ preLoaders: [
+ {
+ test: /\.jsx$|\.js$/,
+ loader: 'eslint-loader',
+ include: __dirname + '/src/main/resources/static/js'
+ }
+ ],
loaders: [
{
test: /\.jsx?$/,
@@ -83,7 +90,7 @@ var config = {
return [];
},
htmlLoader: {
- removeAttributeQuotes: false,
+ removeAttributeQuotes: false
}
};