LoginSignup
26
28

More than 5 years have passed since last update.

Reactとか最近のフロントエンド界隈の技術をRailsでシンプルに使う

Last updated at Posted at 2016-07-22

問題点

  • フロントエンド(webpack,react+redux,etc...)とバックエンド(Rails)の住み分け・分離手法と協業
  • AssetPipelineとwebpackのタスクの重複(minify,ダイジェスト付与)

前提&要望

  • あくまでRails動作環境内での話。
  • Rails動作環境内でフロントエンドとバックエンドを完全に分離したい。
  • Railsのレールには乗り、AssetPipelineを使用しないのではなく、使用するけど何もしないようにしたい。

解決案

フロントエンドで使用するライブラリは全てnpmで管理。

  • react-railsなどのフロントエンドライブラリをラップしているgemは使用しない。

webpackからはproxyでRailsに流す

  • フロントエンドの開発をしてる時はバックエンドからはAPIしか使用しないので事足りるはず。

例:

webback.config.js
  ...
  devServer: {
    contentBase: "./public",
    colors: true,
    historyApiFallback: true,
    inline: true,
    proxy: {
      '/api/*': {
        target: 'http://localhost:3000'
      },
    }
  }
}
index.js
function getUsersApi(page) {
  return fetch(`/api/v1/users?page=${page}`) // <- こんな感じで普通にRailsのAPIを叩けるようになる
         .then(response => response.json())
         .then(json => json.posts)
}

webpackで、開発用と本番用のjs・CSSファイルをビルドし、Railsに読み込ませる。

  • 開発用を用意するのは、バックエンド開発者がRails側で開発する時に使用したり、Railsにのせた時に実際に動作するかローカルで確認する時に使用する為。
  • 開発用と本番用のjs・CSSファイルは、実際は画像ファイルやフォントファイルのパス(publicPath)と、Minifyされているか否かの違いだけ。
  • デプロイ時、AssetPipelineに通すけど、application.css,application.js自身にダイジェスト付与されるだけで、中身は何もされない。既にwebpackでMinify、ダイジェスト付与済みのファイルだし、画像・フォントはassets配下ではなく、public/配下に配置されているから。
  • S3を使用している場合、webpackで生成したファイルは、capistrano-s3でpublic/配下を、assets配下は、asset_syncを使用して配置すると楽(アップロードしたファイルは溜まる一方なので注意)。

例:

1 ビルド用のwebpack設定ファイルを用意

webpack.build.common.config.js(共通設定)
var webpack = require('webpack');
var autoprefixer = require('autoprefixer');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var dotenv = require('dotenv');
var myEnv = dotenv.config();

module.exports = {
  devtool: 'eval-source-map',

  entry: [
    __dirname + "/src/javascripts/index.js",
    __dirname + "/src/stylesheets/main.scss",
  ],

  postcss: [
    autoprefixer({"browsers": ["last 2 versions", "iOS >= 8", "Android >= 4.1"]})
  ],

  sassLoader: {
    includePaths: []
  },

  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: __dirname + '/src/index.html'
    }),
    new webpack.DefinePlugin({
      'process.env': Object.assign({
        'NODE_ENV': JSON.stringify(process.env.NODE_ENV),
      }, myEnv)
    }),
  ]
}
webpack.build.config.js(開発用)
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var path = require('path');

var common = require('./webpack.common.config');
var publicPath = process.env.NODE_ENV == 'production' ? 'production URL or CDN URL' : '../'; // <- ここで開発と本番のパスの切り替えしてる

module.exports = {
  devtool: common.devtool,

  entry: common.entry,

  output: {
    path: path.join(__dirname, "build", process.env.NODE_ENV),
    publicPath: publicPath,
    filename: "bundle.js",
  },

  module: {
    loaders: [
      {
        test: /\.json$/,
        loader: 'json'
      },
      {
        test: /\.js?$/,
        exclude: /node_modules/,
        loader: 'babel'
      },
      {
        test: /\.(css|scss)$/,
        loader: ExtractTextPlugin.extract('css!sass', {'publicPath': publicPath})
      },
      {
        test: /\.(jpe?g|png|gif)$/,
        loader: 'url-loader?limit=1&name=images/[name]-[hash].[ext]'
      },
      {
        test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'url-loader?limit=1&name=fonts/[name]-[hash].[ext]&mimetype=application/font-woff'
      },
      {
        test: /\.(ttf|eot|svg|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'url-loader?limit=1&name=fonts/[name]-[hash].[ext]'
      }
    ]
  },

  postcss: common.postcss,

  sassLoader: common.sassLoader,

  plugins: common.plugins.concat([
    new ExtractTextPlugin('stylesheets/style.css')
  ]),
}
webpack.build.production.config.js(本番用)
var webpack = require('webpack');

var buildConfig = require('./webpack.build.config');

delete buildConfig.devtool;

buildConfig.plugins.push(
  new webpack.optimize.OccurenceOrderPlugin(),
  new webpack.optimize.UglifyJsPlugin()
);

module.exports = buildConfig;

2 ビルド実行

$ NODE_ENV=development webpack --config webpack.build.config.js --progress --colors
$ NODE_ENV=production webpack --config webpack.build.production.config.js --progress --colors

3 build/配下にdevlopments・production2つの環境のビルドされたファイル群があるので、それらをRails環境下に配置する。Gulpでwatchしてコピーするタスクを実行してると楽。

$ cp ./build/development/bundle.js path/to/rails/app/assets/javascripts/development.js
$ cp ./build/development/stylesheets/style.css path/to/rails/app/assets/stylesheets/development.css
$ cp ./build/development/images/* path/to/rails/public/images/
$ cp ./build/development/fonts/* path/to/rails/public/fonts/
$ cp ./build/production/bundle.js path/to/rails/app/assets/javascripts/bundle.js
$ cp ./build/production/stylesheets/style.css path/to/rails/app/assets/stylesheets/style.css
$ cp ./build/production/images/* path/to/rails/public/images/
$ cp ./build/production/fonts/* path/to/rails/public/fonts/
gulpfile.js
var gulp = require('gulp');
var rename = require('gulp-rename');
var watch = require('gulp-watch');
var print = require('gulp-print');

gulp.task('default', function() {

  gulp.src('build/development/images/**/*')
    .pipe(watch('build/development/images/**/*'))
    .pipe(gulp.dest('path/to/rails/public/images'))
    .pipe(print(function(filepath) { return 'Copied: ' + filepath; }));

  gulp.src('build/production/images/**/*')
    .pipe(watch('build/production/images/**/*'))
    .pipe(gulp.dest('path/to/rails/public/images'))
    .pipe(print(function(filepath) { return 'Copied: ' + filepath; }));

  ...

});

4 js・CSSファイルを環境毎に切り替えるように変更

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html lang="ja">
  <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">
    ...
    <%= stylesheet_link_tag Rails.env.production? ? "application" : Rails.env, media: 'all' %>
    <%= csrf_meta_tags %>
  </head>
  <body>
    <div id="root"></div><!-- react render dom -->
    <%= yield %>
    <%= javascript_include_tag Rails.env.production? ? "application" : Rails.env %>
  </body>
</html>
app/assets/javascripts/application.js
//= require bundle
app/assets/stylesheets/application.css
/*
 *= require style
 */

ゼニガメGET! :turtle::thumbsup:

26
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
28