Ruby on Railsアプリケーションにおいて、一定以上の規模でフロントエンドを開発する際、アセットをSprocketsでなくwebpackなどのビルドツールを用いて切り分ける手法が主流となってきています。

今回から2回に分けて、webpackを用いたフロントエンド開発環境構築についてまとめてみます。 今回はフロントまわりの設定で、次回はサーバまわりについて書く予定です。

開発環境の要件

今回は、次の要件を満たすような環境を構築していきます。

  • app/assetsは使わない(削除する)
  • JavaScript、SCSS、画像をビルドしてビューから参照できるようにする
  • ファイル更新時に自動リロードを行なう
  • コマンド1つでサーバの起動とビルド監視を行なう
  • 本番環境ではダイジェストを付与する(キャッシュ対策)

動作環境

今回の記事は次の各バージョンで動作を確認しています。

  • Ruby on Rails v5.0.0.1
  • webpack v1.13.2

ディレクトリ構成

ビルド対象となるアセットは、次のようにclient/assets下に配置します。 これらをビルドし、webpackによりpublic/assets下に出力します。

$ tree -L 2 client
client
└── assets
    ├── images
    ├── javascripts
    └── stylesheets

手順

それでは、環境を構築する方法を順番に書いていきます。

1. ディレクトリを準備する

まず、app/assetsを削除します。 また、フロントエンドのソースを置くディレクトリを作成します。

$ rm -rf app/assets
$ mkdir client
$ mkdir client/assets
$ mkdir client/assets/images client/assets/javascripts client/assets/stylesheets

2. パッケージをインストールする

次に、必要となるNode.jsパッケージをインストールします。

$ npm init -y
$ npm install --save-dev webpack
$ npm install --save-dev webpack-manifest-plugin webpack-dev-server
$ npm install --save-dev babel-loader babel-preset-es2015
$ npm install --save-dev css-loader sass-loader extract-text-webpack-plugin node-sass
$ npm install --save-dev file-loader

3. JavaScriptをビルドする

まず、必要最低限の設定を行ないます。 以下の設定により、ビルド対象のJavaScriptをビルドし、指定した出力先に出力します。

また、entryオブジェクトで指定したキーがoutput.filenameにおける[name]になります。

publicPathはビルド対象(JavaScriptなど)の相対パスの起点となるパスで、ビルド時にこれに置き換わります。 本番環境でCDNを利用する場合などは、環境に応じてこの値を変えればよいです。

const webpack = require('webpack');
const path = require('path');

const env = process.env.NODE_ENV || 'development';
const fileName = (env == 'development') ? '[name]' : '[name]-[hash]';

module.exports = {
  // 起点となるディレクトリ
  context: path.join(__dirname, './client/assets'),

  // ビルド対象となるファイル
  entry: {
    application: './javascripts/application.js'
  },

  // ビルド先のファイル
  output: {
    path: path.join(__dirname, 'public/assets'),
    filename: `${fileName}.js`,
    publicPath: '/assets/'
  }
};

4. ローダーを設定する

ローダーとは、ビルド時にビルド対象にいろんな処理をほどこすwebpackの仕組みです。 たとえば、ES6やAltJSをトランスパイルしたりすることができます。

ここでは、次の処理を行ないます。

  • ES6をトランスパイルする
  • JavaScript内で参照しているSCSS/CSSを別ファイルに切り出す
    • 標準ではheadタグ内にインラインで出力される
  • 画像を切り出す
    • 標準ではビルド対象外
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  ...

  module: {
    loaders: [
      // JavaScriptをBabelでトランスパイルする
      {
        test: /\.js$/,
        loader: 'babel',
        query: {
          presets: ['es2015']
        }
      },
      // SCSS/CSSを別ファイルに切り出す
      {
        test: /\.s?css$/,
        loader: ExtractTextPlugin.extract('css!sass')
      },
      // 画像を別ファイルに切り出す
      {
        test: /\.(gif|jpg|png)$/,
        loader: 'file',
        query: {
          name: `${fileName}.[ext]`
        }
      }
    ]
  }
};

5. プラグインを設定する

プラグインは、外部ライブラリを利用してビルド時に処理を加える仕組みです。 プラグインでは、次のことを行ないます。

  • CSSの出力について設定する
  • 自動リロードを有効にする
  • 本番環境のキャッシュ対策としてハッシュ値を生成する
module.exports = {
  :

  plugins: [
    // CSSを切り出す
    new ExtractTextPlugin(`${fileName}.css`),

    // JavaScript/CSS変更時に自動でリロードする
    new webpack.HotModuleReplacementPlugin(),

    // 本番環境のキャッシュ対策としてハッシュ値を生成する
    new ManifestPlugin({
      fileName: 'webpack.manifest.json'
    })
  ],
};

その他の設定

以上が主な設定となります。 それ以外の細かい設定を次に示します。

1. 拡張子を省略する

JavaScript内でJavaScriptやSCSSを参照する際、拡張子を省略できると便利です。 たとえば、import 'foo'と記述することでfoo.jsfoo.scssを参照できるようになります。

これはresolveで定義します。

module.exports = {
  :

  resolve: {
    extensions: ['', '.js', '.scss']
  }
}

2. 自動リロードを設定する

開発環境では、ビルドの監視にwebpack-dev-serverを使います。 これは、ビルド対象内のファイルが変更されたら、ブラウザも自動でリロードしてくれる便利な機能です。

SCSSをインポートしておけば、SCSS変更時にもリロードしてくれます。

module.exports = {
  :

  devServer: {
    contentBase: 'public/assets',
    inline: true,
    hot: true
  }
};

ビルドする

ビルドは次のコマンドで行ないます。

ファイルを生成する通常のビルドはwebpackコマンド、ファイルの変更を監視→自動リロードはwebpack-dev-serverコマンドを利用します。

$ $(npm bin)/webpack -d
$ $(npm bin)/webpack-dev-server

ここで、webpack-dev-serverは出力をメモリ上に展開するため、ファイルは作成されないことに注意が必要です。

参考記事

おわりに

以上がフロント側の設定となります。 次回の記事ではサーバ側の設定を説明しますので、あわせてご覧ください。