Edited at

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

More than 3 years have passed since last update.


問題点


  • フロントエンド(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: