RESTfulAPIのFrontWebをAngular.jsで書こうかと思ったのですが、FRPパラダイムのReact.jsが面白そうなのでこちらで構築してみます。
環境
- node.js v0.11.14
- npm v2.1.11
- bower v1.3.12
- gulp v3.8.8
- Yeoman v1.3.3
- React.js latest
インストール
nodeとnpmをインストールします。
$ brew install node
$ brew install npm
npmで以下ライブラリをインストールします。
$ npm install -g yo bower gulp generator-react-gulp-browserify
ジェネレート
Yeomanで雛形プロジェクトをジェネレートします。
以下の項目を個別に設定できます。
❯◉ Sass with Compass
◉ Bootstrap
◉ Modernizr
◉ HTML template - Jade
◉ CoffeeScript for JavaScript
◉ Jest for unit tests
$ mkdir Test
$ cd Test
$ yo react-gulp-browserify
_-----_
| |
|--(o)--| .--------------------------.
`---------´ | Welcome to Yeoman, |
( _´U`_ ) | ladies and gentlemen! |
/___A___\ '__________________________'
| ~ |
__'.___.'__
´ ` |° ´ Y `
You're using the fantastic React generator. We provide you full JavaScript solution with Sass support!
? What is this project's name? Test
❯◉ Sass with Compass
◉ Bootstrap
◉ Modernizr
◉ HTML template - Jade
◉ CoffeeScript for JavaScript
◉ Jest for unit tests
? What more would you like? Sass with Compass, Bootstrap, Modernizr, HTML template - Jade, CoffeeScript for JavaScript, Jest for unit tests
create package.json
create gulpfile.js
create bower.json
create app/styles/main.scss
create app/index.html
create app/scripts/app.coffee
create app/scripts/ui/Timer.coffee
create app/scripts/ui/__tests__/Timer-test.js
create app/favicon.ico
create .bowerrc
create .gitignore
create app/robots.txt
create .editorconfig
create .jshintrc
I'm all done. Running bower install & npm install for you to install the required dependencies. If this fails, try running the command yourself.
npm WARN package.json test@0.0.0 No description
npm WARN package.json test@0.0.0 No repository field.
npm WARN package.json test@0.0.0 No README data
bower not-cached git://github.com/Modernizr/Modernizr.git#^2.8.3
bower resolve git://github.com/Modernizr/Modernizr.git#^2.8.3
bower cached git://github.com/twbs/bootstrap-sass.git#3.3.1
bower validate 3.3.1 against git://github.com/twbs/bootstrap-sass.git#>=3.3.0
bower cached git://github.com/jquery/jquery.git#2.1.1
bower validate 2.1.1 against git://github.com/jquery/jquery.git#~2.1.1
bower cached git://github.com/jquery/jquery.git#2.1.1
bower validate 2.1.1 against git://github.com/jquery/jquery.git#>= 1.9.0
bower download https://github.com/Modernizr/Modernizr/archive/v2.8.3.tar.gz
bower extract modernizr#^2.8.3 archive.tar.gz
bower invalid-meta modernizr is missing "main" entry in bower.json
bower invalid-meta modernizr is missing "ignore" entry in bower.json
bower resolved git://github.com/Modernizr/Modernizr.git#2.8.3
npm WARN engine jest-cli@0.1.18: wanted: {"node":"0.8.x || 0.10.x"} (current: {"node":"0.11.14","npm":"2.1.11"})
> v8flags@1.0.8 install /Users/susieyy/tmp/yo/node_modules/gulp/node_modules/v8flags
> node fetch.js
bower new version for git://github.com/jquery/jquery.git#~2.1.1
bower resolve git://github.com/jquery/jquery.git#~2.1.1
bower new version for git://github.com/jquery/jquery.git#>= 1.9.0
bower resolve git://github.com/jquery/jquery.git#>= 1.9.0
bower download https://github.com/jquery/jquery/archive/2.1.3.tar.gz
bower download https://github.com/jquery/jquery/archive/2.1.3.tar.gz
bower extract jquery#~2.1.1 archive.tar.gz
bower extract jquery#>= 1.9.0 archive.tar.gz
bower resolved git://github.com/jquery/jquery.git#2.1.3
bower resolved git://github.com/jquery/jquery.git#2.1.3
bower install bootstrap-sass-official#3.3.1
bower install modernizr#2.8.3
bower install jquery#2.1.3
開発用RESTFulAPIサーバへのProxy設定
/api
というPrefixでパスにアクセスした場合に、localhostの3000ポートにプロキシする設定を行います。
gulpでプロキシを行うために、以下のパッケージをインストールします。
$ npm install --save-dev gulp-connect
$ npm install --save-dev proxy-middleware
続いて以下をgulpfile.js
に追記します。
gulpfile.js
var connect = require('gulp-connect');
var proxy = require('proxy-middleware');
var url = require('url');
gulp.task('connect', function() {
connect.server({
root: ['dist'],
port: 9000,
livereload: true,
// /apiにきたリクエストは http://localhost:3000/api にプロキシする。
middleware: function(connect, o) {
return [ (function() {
var options = url.parse('http://localhost:3000/api');
options.route = '/api';
return proxy(options);
})() ];
}
});
});
gulp.task('default', ['connect']);
サーバ起動
localhostの9000ポートでアクセスできるようになります。
$ gulp watch
コード
メインのスクリプトはこんな感じです。
React.jsが宣言的なので、簡素に表記するCoffeeScriptとも相性がよさそうです。
app/scripts/app.coffee
###*
@jsx React.DOM
###
React = window.React = require("react")
Timer = require("./ui/Timer.coffee")
mountNode = document.getElementById("app")
TodoList = React.createClass(
displayName: "TodoList"
render: ->
createItem = (itemText) ->
React.createElement "li", null, itemText
React.createElement "ul", null, @props.items.map(createItem)
)
TodoApp = React.createClass(
displayName: "TodoApp"
getInitialState: ->
items: []
text: ""
onChange: (e) ->
@setState text: e.target.value
return
handleSubmit: (e) ->
e.preventDefault()
nextItems = @state.items.concat([@state.text])
nextText = ""
@setState
items: nextItems
text: nextText
return
render: ->
React.createElement "div", null, React.createElement(TodoList,
items: @state.items
), React.createElement("form",
onSubmit: @handleSubmit
, React.createElement("input",
onChange: @onChange
value: @state.text
), React.createElement("button", null, "Add #" + (@state.items.length + 1))), React.createElement(Timer, null)
)
React.renderComponent React.createElement(TodoApp, null), mountNode
Javascriptの場合はこんな感じです。
app/scripts/app.js
/** @jsx React.DOM */
var React = window.React = require('react'),
Timer = require("./ui/Timer"),
mountNode = document.getElementById("app");
var TodoList = React.createClass({
render: function() {
var createItem = function(itemText) {
return <li>{itemText}</li>;
};
return <ul>{this.props.items.map(createItem)}</ul>;
}
});
var TodoApp = React.createClass({
getInitialState: function() {
return {items: [], text: ''};
},
onChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var nextItems = this.state.items.concat([this.state.text]);
var nextText = '';
this.setState({items: nextItems, text: nextText});
},
render: function() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.onChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
<Timer />
</div>
);
}
});
React.renderComponent(<TodoApp />, mountNode);