Skip to content

Commit

Permalink
Added an self-mounting components example.
Browse files Browse the repository at this point in the history
These replicate much of the behaviour that was formerly available in python-react.
  • Loading branch information
markfinger committed Jul 10, 2015
1 parent 056ca70 commit 72dda6c
Show file tree
Hide file tree
Showing 16 changed files with 413 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,4 @@ docs/_build/
target/

node_modules
example/static/webpack
.webpack_build_cache
1 change: 1 addition & 0 deletions examples/self_mounting_components/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
static
37 changes: 37 additions & 0 deletions examples/self_mounting_components/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
python + react - self mounting components
=========================================

This example illustrates a workflow where webpack is used to generate bundles so that the root React
component can immediately mount itself over the markup that was pre-rendered with the same data.

This workflow is similar to what was provided in older versions of python-react. It can be useful
if you want to add a little interactivity to an otherwise backend-heavy site.

Be aware that while this workflow can be initially convenient, it tends to rely on components maintaining
large amounts of state. A better workflow is for your components to minimize state by delegating all
data storage to external services. If you're looking for something to handle your data, the multitude
of Flux implementations are a reasonable starting point.


### Running the example

Install the dependencies

```
pip install -r requirements.txt
npm install
```

Start the server

```
node server.js
```

Start the python server

```
python example.py
```

And visit [http://127.0.0.1:5000](http://127.0.0.1:5000)
14 changes: 14 additions & 0 deletions examples/self_mounting_components/app/Comment.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';

class Comment extends React.Component {
render() {
return (
<div>
<h3>{this.props.name}</h3>
{this.props.text}
</div>
);
}
}

export default Comment;
11 changes: 11 additions & 0 deletions examples/self_mounting_components/app/CommentBox.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
h2 {
border-bottom: 1px solid #eee;
}

form label {
display: block;
}

form button {
margin-left: 5px;
}
44 changes: 44 additions & 0 deletions examples/self_mounting_components/app/CommentBox.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// CSS dependencies
import 'bootstrap/dist/css/bootstrap.css';
import './CommentBox.css';

import React from 'react';
import CommentList from './CommentList.jsx';
import CommentForm from './CommentForm.jsx';
import $ from 'jquery';

class CommentBox extends React.Component {
constructor(props) {
super(props);

this.state = {comments: props.comments};
}
submitComment(name, text) {
$.ajax({
url: this.props.url,
method: 'post',
data: {
name,
text
},
success: (obj) => {
this.setState({
comments: obj.comments
});
},
error: (err) => {
console.error(err);
}
});
}
render() {
return (
<div>
<CommentList comments={this.state.comments} />
<CommentForm submitComment={this.submitComment.bind(this)} />
</div>
);
}
}

export default CommentBox;
37 changes: 37 additions & 0 deletions examples/self_mounting_components/app/CommentForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';

class CommentForm extends React.Component {
handleSubmit(event) {
event.preventDefault();

var name = this.refs.name.getDOMNode().value.trim();
var text = this.refs.text.getDOMNode().value.trim();

this.props.submitComment(name, text);
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<h2>Submit a comment</h2>
<div className="form-group">
<label>
Your name
<input ref="name" type="text" className="form-control" placeholder="..." />
</label>
</div>
<div className="form-group">
<label>
Say something...
<textarea ref="text" className="form-control" placeholder="..." />
</label>
</div>
<div className="text-right">
<button type="reset" className="btn btn-default">Reset</button>
<button type="submit" className="btn btn-primary">Submit</button>
</div>
</form>
);
}
}

export default CommentForm;
20 changes: 20 additions & 0 deletions examples/self_mounting_components/app/CommentList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import Comment from './Comment.jsx';

class CommentList extends React.Component {
render() {
if (!this.props.comments.length) {
return null;
}
return (
<div>
<h2>Comments</h2>
{this.props.comments.map((comment, index) => {
return <Comment name={comment.name} text={comment.text} key={index} />;
})}
</div>
);
}
}

export default CommentList;
78 changes: 78 additions & 0 deletions examples/self_mounting_components/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import os
import json
from flask import Flask, render_template, request, redirect, jsonify
from react.conf import settings as react_settings
from react.render import render_component
from webpack.conf import settings as webpack_settings
from webpack.compiler import webpack

DEBUG = True
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# As a convenience for development, only connect to the
# render server when DEBUG is False
react_settings.configure(RENDER=not DEBUG)

webpack_settings.configure(
STATIC_ROOT=os.path.join(BASE_DIR, 'static'),
STATIC_URL='/static/',
WATCH=DEBUG,
HMR=DEBUG,
CONFIG_DIRS=BASE_DIR,
CONTEXT={
'DEBUG': DEBUG,
},
)


app = Flask(__name__)
app.debug = DEBUG

comments = []


@app.route('/')
def index():
config_file = os.path.join(BASE_DIR, 'example.webpack.js')

component = os.path.join(BASE_DIR, 'app', 'CommentBox.jsx')

props = {
'comments': comments,
'url': '/comment/',
}

rendered = render_component(component, props)

webpack_context = {
'component': component,
'props_var': 'window.mountProps',
'container': 'mount-container',
}

bundle = webpack(config_file, context=webpack_context)

return render_template(
'index.html',
bundle=bundle,
webpack_context=webpack_context,
rendered=rendered,
)


@app.route('/comment/', methods=('POST',))
def comment():
comments.append({
'name': request.form['name'],
'text': request.form['text'],
})

if request.is_xhr:
return jsonify(comments=comments)

return redirect('/')



if __name__ == '__main__':
app.run()
76 changes: 76 additions & 0 deletions examples/self_mounting_components/example.webpack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
var path = require('path');
var webpack = require('webpack');
var autoprefixer = require('autoprefixer-core');
var ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = function(opts) {
var config = {
context: __dirname,
entry: './mount',
output: {
filename: '[name]-[hash].js',
pathinfo: opts.context.DEBUG
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: (opts.hmr ? 'react-hot-loader!': '') + 'babel-loader'
},
{
test: /\.css$/,
loader: opts.hmr ?
'style!css-loader?sourceMap!postcss-loader' :
ExtractTextPlugin.extract('style', 'css-loader?sourceMap!postcss-loader')
},
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url-loader?limit=10000&mimetype=application/font-woff'
},
{
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader'
}
]
},
postcss: [autoprefixer],
resolve: {
alias: {
__react_mount_component__: opts.context.component
}
},
plugins: [
// Define the variables in `./mount.js` that webpack will replace with data from python
new webpack.DefinePlugin({
__react_mount_props_variable__: opts.context.props_var,
__react_mount_container__: JSON.stringify(opts.context.container)
}),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(
opts.context.DEBUG ? 'development' : 'production'
)
}
})
],
devtool: opts.context.DEBUG ? 'eval-source-map' : 'source-map'
};

if (!opts.hmr) {
// Move css assets into separate files
config.plugins.push(new ExtractTextPlugin('[name]-[contenthash].css'));
}

if (!opts.context.DEBUG) {
// Remove duplicates and activate compression
config.plugins.push(
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin()
);
}

return config;
};
12 changes: 12 additions & 0 deletions examples/self_mounting_components/mount.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

// During the build process webpack aliases this import to the desired component
import Component from '__react_mount_component__';

// During the build process webpack will replace these variable with
// the names passed from the python process
const props = __react_mount_props_variable__;
const container = document.getElementById(__react_mount_container__);

const element = React.createElement(Component, props);
React.render(element, container);
28 changes: 28 additions & 0 deletions examples/self_mounting_components/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"private": true,
"dependencies": {
"autoprefixer-core": "^5.2.1",
"babel": "^5.6.14",
"babel-core": "^5.6.17",
"babel-loader": "^5.3.1",
"body-parser": "^1.13.2",
"bootstrap": "^3.3.5",
"css-loader": "^0.15.2",
"express": "^4.13.1",
"extract-text-webpack-plugin": "^0.8.2",
"file-loader": "^0.8.4",
"jquery": "^2.1.4",
"node-libs-browser": "^0.5.2",
"postcss-loader": "^0.5.1",
"react": "^0.13.3",
"react-hot-loader": "^1.2.8",
"react-render": "^0.3.0",
"style-loader": "^0.12.3",
"url-loader": "^0.5.6",
"webpack": "^1.10.1",
"webpack-build": "^0.13.0"
},
"scripts": {
"webpack-build": "webpack-build"
}
}
1 change: 1 addition & 0 deletions examples/self_mounting_components/react
5 changes: 5 additions & 0 deletions examples/self_mounting_components/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
flask==0.10.1
optional-django==0.3.0
requests==2.7.0
-e git+ssh://git@github.com/markfinger/python-webpack.git@5ace0f26cdfd345c14ef8f608352d128511edc31#egg=webpack
react
Loading

0 comments on commit 72dda6c

Please sign in to comment.