HerokuにRails + React + webpackのプロジェクトをデプロイする

HerokuにRails + React + webpackのプロジェクトをデプロイする

完全個人的に使うWEBアプリを作ったのでその際にHerokuを使ったのですが、結構ハマるポイントが多かったのでまとめてみました。

Ads

今回やりたいこと

  • webpackを使ってbuildする
  • develop(開発環境)とproduction(heroku)でbuildを切り分ける

というのもdevelopとproductionで叩きにいくAPIが違うのでしょうがないですね。あとはredux-loggerなどのフロントで使うモジュールもdevelopだけにしたいものあります。

大体こんな感じでherokuにbuildしたいなと思います。ただ、冒頭でも述べたようにかなりハマりました。あとbuildにすっごい時間がかかります。herokuの無料枠なのでしょうがないんですが、初回アクセス時に大体timeoutしちゃいます。今後の課題はtimeout問題の解消ですね。

Herokuを使ったことない方はこちらで確認してくださいね。
Heroku初心者がHello, Herokuをしてみる – Qiita

まずはディレクトリ構成

普通にrailsで作られる構成にフロント用のディレクトリとしてclientがあります。あとはwebpack.configやら各種ファイルがちょっと増えている感じです。

├── Gemfile
├── Gemfile.lock
├── Procfile
├── README.md
├── Rakefile
├── app
│   ├── assets/
│   ├── channels/
│   ├── controllers/
│   ├── helpers/
│   ├── jobs/
│   ├── mailers/
│   ├── models/
│   └── views/
├── bin
├── client
│   ├── config/
│   └── src/
├── config
├── config.ru
├── db
├── lib
│   ├── assets/
│   └── tasks/
├── log/
├── node_modules/
├── package.json
├── procfile.dev
├── public/
├── test/
├── tmp/
├── vendor/
├── webpack.config.js
└── webpack.production.config.js

/clientはjsファイル群が入っています。その中のconfigディレクトリ内で

Config.js(開発用)

const users = require('./Users')

module.exports = {
  api : {
    root : 'http://localhost:5000/api/v1/',
    users : users
  }
}

Config.production.js(heroku用)

const users = require('./Users')

module.exports = {
  api : {
    root : 'https://hogehoge.com/api/v1/',
    users : users
  }
}

rootの部分で切り分けるようにしています。ちなみに、usersの中身はというとRESTに合わせてそれぞれ叩きに行くとこやmethodなどを定義しています。先ほどのrootとusers.jsのpathでdevelopならhttp://localhost:5000/api/v1/users/users:idを叩きにいくという感じです。この辺はReactのお話になるので今回は割愛します。

users.js

module.exports = {
  userGet: {
    config: {
      method: 'get',
      headers: {
        'Content-Type': 'application/json'
      },
    },
    path: 'users/:userId'
  },
  userPut: {
    config: {
      method: 'put',
      headers: {
        'Content-Type': 'application/json'
      }
    },
    path: 'users/:userId'
  }
}

developとproductionで叩くAPIが違うので切り分ける必要が必ず出てきます。では、buildするときになんとかして切り替えないといけないんですよね。

package.jsonはこちら

ここでのポイントは
– プロジェクトの直下にないとnpmコマンドが使えない
– devDependenciesはinstallされない

プロジェクトの直下にないとnpmコマンドが使えない

元々は僕はclient/に置いてdevelopで開発していました。これは全く問題なくできたのですが、いざherokuにあげて見るとnpmコマンドがないと怒られます。
どうやらherokuだとプロジェクトの直下じゃないとnpm や nodeのインストールがでいないようです。(おそらくclientでできるようにする方法もありそうだけど、今回はやっていません。)

devDependenciesはinstallされない

これはまーそうですよね。jsファイルないで使っているものでdevDependenciesに入っているとerrorになるのでしっかり確認してからherokuにデプロイするのがいいです。
僕はちょっとハマりました。

scriptsについて

devとbuildを作りました。そこでwebpack.configを切り分けています。

package.json

{
  "private": "true",
  "name": "project",
  "version": "1.0.0",
  "engines": {
    "node": "v6.9.4",
    "npm": "3.10.9"
  },
  "scripts": {
    "dev": "webpack -w --config webpack.config.js",
    "build": "./node_modules/webpack/bin/webpack.js -p --config webpack.production.config.js --progress --profile --colors"
  },
  "devDependencies": {
    "babel-eslint": "^7.1.1",
    "eslint": "^3.2.2",
    "eslint-plugin-react": "^6.0.0",
    "stylelint": "7.6.0",
    "stylelint-config-standard": "^15.0.0"
  },
  "dependencies": {
    "babel-core": "^6.22.1",
    "babel-loader": "^6.2.10",
    "classnames": "^2.2.3",
    "isomorphic-fetch": "^2.2.0",
    "js-cookie": "^2.1.2",
    "lodash": "^4.12.0",
    "lodash.merge": "^4.3.5",
    "moment": "^2.17.1",
    "react": "^15.4.2",
    "react-day-picker": "^4.0.0",
    "react-dom": "^15.4.2",
    "react-redux": "^5.0.2",
    "react-router": "2.6.1",
    "react-router-redux": "^4.0.5",
    "recharts": "^0.20.8",
    "redux": "^3.6.0",
    "redux-form": "6.5.0",
    "redux-router": "^2.1.2",
    "redux-thunk": "^2.2.0",
    "whatwg-fetch": "^1.0.0",
    "webpack": "^2.2.1",
    "babel-plugin-transform-class-properties": "^6.23.0",
    "babel-preset-es2015": "^6.22.0",
    "babel-preset-react": "^6.22.0",
    "redux-logger": "^2.6.1"
  }
}

webpack.config

こちらは開発用とheroku用で2ファイルとしてプロジェクトの直下に作っています。基本的には最小でjsのビルドができるようになっている構成ですがいくつかポイントがあります。

outputについて

ここはビルド後のjsファイルの場所とファイル名を指定しますが、今回はrailsのルールに合わせてます。ちなみに、/app/assets/javascriptsの中身はGitで管理しなくてもいいです。なので.gitignoreなどで除外しておくのがいいでしょう。

output: {
  path: './app/assets/javascripts',
  filename: 'application.js'
},

externalsについて

先ほどのConfig.jsを出し分けている部分です。それぞれのファイルでConfigに入るファイルが違いますね。

develop

externals : {
  'Config' : JSON.stringify(require('./client/config/Config.js'))
},

production

externals : {
  'Config' : JSON.stringify(require('./client/config/Config.production.js'))
},

webpack.config.js(開発用)

var webpack = require("webpack");

module.exports = {
  entry: {
    app: './client/src/index.js'
  },

  output: {
    path: './app/assets/javascripts',
    filename: 'application.js'
  },
  externals : {
    'Config' : JSON.stringify(require('./client/config/Config.js'))
  },
  devtool: 'source-map',
  module: {
    loaders: [
      { test: /\.(js|jsx)$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({ENV: JSON.stringify("DEV")}),
  ],
};

webpack.production.config.js(heroku用)

var webpack = require("webpack");

module.exports = {
  entry: {
    app: './client/src/index.js'
  },

  output: {
    path: './app/assets/javascripts',
    filename: 'application.js'
  },
  externals : {
    'Config' : JSON.stringify(require('./client/config/Config.production.js'))
  },
  devtool: 'source-map',
  module: {
    loaders: [
      { test: /\.(js|jsx)$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({ENV: JSON.stringify("PROD")}),
  ],
};

ちなみに、実際にjs側でConfigを呼び出す場合は

import {api as API} from 'Config'

でできます。

Procfileについて

僕はあんまり詳しくないけどHerokuではよく使うみたい。要はいくつかあるビルド用のコマンドを一括でできるようになるみたいです。
ちなみにforemanというのをgemでインストールする必要があります。foreman で アプリケーションを動かす。 – Qiita

例えば今回の場合ならRailsのサーバーの起動とwebpackの起動が必要です。

$ rails s
$ npm start

どっちも起動していないといけないので大変です。

それをプロジェクト直下にProcfileをつくります。
例)

rails: rails s
web: npm run start

そしたら

$ foreman start

でどっちも起動します。すっごい便利!!

では実際のファイルはというと以下のようにしました。heroku用はなんかnpmがないですね。これは後述するのですが、herokuだとprecompileというのがあってそのタイミングでビルドしないとjsファイルがnot foundになっていますんです。ということでrails sだけです。

Procfile.dev(開発用)

rails: rails s
web: npm run dev

Procfile(heroku)

rails: rails s

ここまでで大体フロント側の設定はできました。ちなみに、仮に
Procfile(heroku)

rails: rails s
web: npm run build

ってしてherokuにデプロイしてもjsファイルが作られないどころかnpmコマンドがないって怒られます。

buildpack

herokuのconfigにbuildpack使うよって登録します。
Ruby以外の言語を一緒に使う場合はherokuへのdeploy前にheroku-buildpack-multiを登録する必要があります。今回の場合ならnodeを使いますよね。

$ heroku create app-test --buildpack https://github.com/heroku/heroku-buildpack-multi.git

次にプロジェクト直下に
.buildpacks

https://github.com/heroku/heroku-buildpack-nodejs#v83
https://github.com/heroku/heroku-buildpack-ruby.git

これでnpm insatallができるようになります。ただプロジェクト直下じゃないとnpm installとかのコマンドが使えないようです。
なので,
webpack.configやpackage.jsonなどは今回はプロジェクト直下にしています。

っさ、これでnpmコマンドを使っても怒られなくなりました。何ですけど、実はこれでもまだダメだったんです。

precompileにフックさせる

precompile問題が勃発しました。

というのは実はapp/assets/javasctiptsapp/assets/stylesheets配下のファイルを以下のようなコマンドで事前にコンパイルしておくんですね。というかherokuだとデプロイして自動でやっています。

bundle exec rake assets:precompile

んで、問題っていうのがProcfileで実行されるであろうコマンドはそのprecompileのあとなんです。なので、まだapp/assets/javasctiptsに何にもファイルできていないのにprecompileしてるんですよね。たぶん。

なので今回はprecompileの前にビルドできるようにフックさせました。

lib/task/before_precompile.rake

task :build_frontend do
  cd "client" do
    sh "npm install"
    sh "npm run build"
  end
end

Rake::Task["assets:precompile"].enhance(%i(build_frontend))

デフォルトではapplication.jsとapplication.cssしか対象になっていないため、config/initializers/assets.rbに設定を追加し、全ての JavaScript と CSS ファイルを対象にするようにします。
多分これは今回の場合だとやらなくても大丈夫かも。複数jsファイル等がある場合はやったほうがいいですね。
config/initializers/assets.rb

Rails.application.config.assets.precompile << /(^[^_\/]|\/[^_])[^\/]*(\.js|\.css)$/

これでやっとデプロイできます。

CSSやJSが読み込めない場合

僕はこれにもハマったんですが、ビルドはできているっぽいのになかなか変更が反映されないなどのケースがあるようです。

config/environments/production.rb 内で config.assets.compile = trueにします。

# config.assets.css_compressor = :sass

# Do not fallback to assets pipeline if a precompiled asset is missed.
-  config.assets.compile = false
+  config.assets.compile = true

# Generate digests for assets URLs.
config.assets.digest = true

参考

オススメの本



ReactによるコンポーネントベースのWebフロントエンド開発の入門書。Reactでは小さくて管理が容易なコンポーネントを組み合わせて、大きくて強力なアプリケーションを作成できます。本書の前半は入門編で、簡単なサンプルを使いながらReactの基本やJSXについて学びます。後半は、実際のアプリケーション開発に必要なものや開発を助けてくれるツールについての解説です。具体的には、JavaScriptのパッケージングツール(Browserify)、ユニットテスト(Jest)、構文チェック(ESLint)、型チェック(Flow)、データフローの最適化(Flux)、イミュータブルなデータ(immutableライブラリ)などを取り上げます。対象読者は、ES2015(ES6)の基本をマスターしているフロントエンド開発者。



WebサービスやWebアプリケーションを作りたいけれど、難しくて諦めていたような人へ。本書は「Ruby」というプログラミング言語と、このRubyでWebアプリケーションを作るためのフレームワーク「Ruby

いいなと思ったらシェアお願いします

Ads
ページの先頭へ