DockerでRails5 + npm 環境構築からReact + MaterialUiのサンプルコードを書くところまで

(2017.01.18追記)Dockerfile.developmentを修正しました。ローカルモードでインストールしたnpmパッケージをshellから実行できるように、パスを追加しました。

##  はじめに
先日、以下でdocker上に Rails5の開発環境を構築しました。
[10ステップでMac OS XにRails5のDocker開発環境を構築する](http://qiita.com/hiraokashi/items/3f712c90b59845c6a77d)

今度は、上記開発環境に**npm(nodejs)**をインストールし、railsから、browserify-rails経由で**npm**を利用できるようにします。

そうした上で、**npm**経由でインストールした、**Reactjs**とReactjsベースのマテリアルデザインフレームワーク**material-ui**をRailsから利用するところまでやってみようと思います。

[マテリアルデザインとは〜基本概念と実務で使える無料フレームワーク6選](https://ferret-plus.com/3774)
[material-ui](http://www.material-ui.com/#/)


## npm環境構築
ここからは、まず、以下の記事で紹介した手順を完了していることを前提とします
[10ステップでMac OS XにRails5のDocker開発環境を構築する](http://qiita.com/hiraokashi/items/3f712c90b59845c6a77d)

### 1.Dockerfile
webコンテナのDockerfileを以下のように修正し、nodejs, npmをインストールします。

-  修正点1(9行目から17行目): **nvm経由で、nodejsをインストール**します。 apt-getでやると指定したバージョンのnodejsがインストールできないので、このようにします。**npm**はnodejsをインストールすると自動的にインストールされます。

-  修正点2(31行目から32行目): npm のinstallを実行します。26行目にADDしているのは、pakcage.jsonの変更がない場合には実行省略できるようにするためです。

- 修正点3 (2017.01.18追記)(23-24行目)ローカルモードでインストールしたnpmパッケージをshellから実行できるように、パスを追加しました。


```Dockerfile.development
FROM ruby:2.3.1
# Replace shell with bash so we can source files
#RUN rm /bin/sh && ln -sf /bin/bash /bin/sh

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev

ENV NVM_DIR /usr/local/nvm
ENV NODE_VERSION 7.2.0

# Install nvm with node and npm
RUN curl https://raw.githubusercontent.com/creationix/nvm/v0.30.1/install.sh | bash \
    && . $NVM_DIR/nvm.sh \
    && nvm install $NODE_VERSION \
    && nvm alias default $NODE_VERSION \
    && nvm use default

ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
ENV PATH      $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
# ARG命令でdocker-compose.ymlから渡されたAPP_HOMEという引数を参照できるようにします
ARG APP_HOME

ENV PATH      $APP_HOME/node_modules:$PATH
ENV PATH      $APP_HOME/node_modules/.bin:$PATH

RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME

ADD Gemfile $APP_HOME/
ADD Gemfile.lock $APP_HOME/
RUN bundle install -j4
ADD package.json $APP_HOME/
RUN npm install
ADD . $APP_HOME

```

### 2.package.jsonの追加
**package.json**をprojectルートに追加し、必要なパッケージをインストールします。
reactjs用、material-uiのパッケージもインストールします。

```shell-session
$ docker-compose run --rm web init npm
$ docker-compose run --rm web  npm install --save-dev browserify
$ docker-compose run --rm web  npm install --save-dev babelify
$ docker-compose run --rm web  npm install --save-dev browserify-incremental
$ docker-compose run --rm web  npm install --save-dev react
$ docker-compose run --rm web  npm install --save-dev react-dom
$ docker-compose run --rm web  npm install --save-dev react-tap-event-plugin
$ docker-compose run --rm web  npm install --save-dev material-ui
```

### 3.Gemfileにbrowserify-railsの追加

```ruby:Gemfile
gem "browserify-rails"
```

bundle install実行

```shell-session
$ docker-compose run --rm web  bundle install
```
browserify_railsの設定を追加します。

```rb:config/application.rb
    config.browserify_rails.commandline_options = "-t babelify"
    config.less.paths << "#{Rails.root}/node_modules/material-ui/src/less/"
    config.browserify_rails.use_browserifyinc = true

```

### 4.まずここでいったんビルド
ここでまずビルドしてイメージ作成後コンテナを実行してみます。
*localhost:3000*に接続して、今まで通り表示されればOK

```shell-session
$ docker-compose build
$ docker-compose up
```

## Reactjs + material-uiのサンプルコード
react部分についてはgemの[react-rails](https://github.com/reactjs/react-rails)を使う記事が多いですが、material-uiとうまくつなげることができなかったのとフロントのパッケージはnpmで管理しようということで、今回は使いません。
サンプルコードとしては、以下のように[RaisedButton](http://www.material-ui.com/#/components/raised-button)を表示するものを作ります！！！！

### コンポーネント作成

```js:assets/javascripts/components/feelings.js
import React from 'react';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import RaisedButton from 'material-ui/RaisedButton';
import {pinkA100} from 'material-ui/styles/colors';
import baseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
const style = {
  margin: 12,
};
class Feeling extends React.Component {
  constructor(props) {
    super(props)
  }
  render () {
    return (
        <RaisedButton key={this.props.id} style={style} secondary={true} label={this.props.name} />
    )
  }
}
export default class Feelings extends React.Component {
  constructor(props) {
    super(props)
    this.state = {feelings: [{id: 1, name: '喜'}, {id: 2, name: '怒'}, {id: 3, name: '哀'}, {id: 4, name: '楽'}]}
  }
  render () {
    return (
      <MuiThemeProvider muiTheme={getMuiTheme()}>
      <div className='messages'>
        {this.state.feelings.map(function (feeling, i) {
           return <Feeling key={i} id={feeling.id} name={feeling.name}/>
        })}
      </div>
      </MuiThemeProvider>
    )
  }
}

```

```js:assets/javascripts/components.js

import React from 'react';
import ReactDOM from 'react-dom';
import Feelings from './components/feelings';
import injectTapEventPlugin from 'react-tap-event-plugin';

// Needed for React Developer Tools
window.React = React;

injectTapEventPlugin();
ReactDOM.render(<Feelings />, document.getElementById('feelings'));

```

以下のように、コンポーネントの生成は、HTMLがすべてロードされてからやる必要があります。
(gemの**react_rails**を利用するとこの辺はいいようにできるのですが....)

```js:assets/javascripts/application.js
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
$(function(){
  // DOM がロードされてから
  require('./components');
});

```

### top_controller 作成

```shell-session
$ docker-compose run --rm web bundle exec rails g controller top
```
ルーティングを設定します。

```ruby:config/routes.rb
Rails.application.routes.draw do
  root to: 'top#index'
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

```

```shell-session
$ docker-compose build
$ docker-compose up
```

できました！！
![スクリーンショット 2017-01-15 1.05.02 PM.jpg](https://qiita-image-store.s3.amazonaws.com/0/105028/fa2077c8-7a72-d20c-e722-d27d2295c913.jpeg)

material-ui　の使い方は随時整理していきます。