Rails-Webpacker-riotを使う前提の環境構築メモ

Rails5.1で導入が予定されている[Webpacker](https://github.com/rails/webpacker)を使ってみたくて、いろいろ調べたことをまとめます。
以下9つの手順を行った環境を[github](https://github.com/higeaaa/rails-webpacker)で公開しました。お役に立てるかわかりませんが、ご参考まで。


# 環境
Rails 5.0.1
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]


# Step.1 `rails new`する
```shell-session
rails new rails-webpacker-riot
cd rails-webpacker-riot

```

# Step.2 gemファイルに`webpacker`と書いて`bundle install`
rails serverとwebpackのバンドル開始を1つのコマンドでできるようになるforemanも入れてみる

```rb:Gemfile

gem 'webpacker'
gem 'foreman'

```

```shell-session
bundle install

```

# Step.3 `rails webpacker:install`でwebpacker関連のフォルダやファイルが作成される

```shell-session

rails webpacker:install

```

# Step.4 必要なJSパッケージを`bin/yarn`で追加する
パッケージはyarnで管理される。step.3 で`bin/yarn`が実行され、次のパッケージがインストールされる

```json:vendor/package.json
{
  "devDependencies": {
    "babel-core": "^6.22.1",
    "babel-loader": "^6.2.10",
    "babel-preset-latest": "^6.22.0",
    "coffee-loader": "^0.7.2",
    "coffee-script": "^1.12.3",
    "path-complete-extname": "^0.1.0",
    "rails-erb-loader": "^3.2.0",
    "webpack": "beta",
    "webpack-dev-server": "beta",
    "webpack-merge": "^2.6.1"
  }
}

```

自分で追加したいパッケージがあれば、`bin/yarn add -D`で追加していく。

```shell-session
bin/yarn add font-awesome font-awesome-webpack2 riot riotjs-loader node-sass sass-loader file-loader url-loader promise-polyfill less babel-preset-es2015-riot bootstrap-sass -D

```

```json:vendor/package.json
{
  "devDependencies": {
    "babel-core": "^6.22.1",
    "babel-loader": "^6.2.10",
    "babel-preset-es2015-riot": "^1.1.0",
    "babel-preset-latest": "^6.22.0",
    "bootstrap-sass": "^3.3.7",
    "coffee-loader": "^0.7.2",
    "coffee-script": "^1.12.3",
    "file-loader": "^0.10.0",
    "font-awesome": "^4.7.0",
    "font-awesome-webpack2": "^0.0.6",
    "less": "^2.7.2",
    "node-sass": "^4.5.0",
    "path-complete-extname": "^0.1.0",
    "promise-polyfill": "^6.0.2",
    "rails-erb-loader": "^3.2.0",
    "riot": "^3.2.1",
    "riotjs-loader": "^4.0.0",
    "sass-loader": "^5.0.1",
    "url-loader": "^0.5.7",
    "webpack": "beta",
    "webpack-dev-server": "beta",
    "webpack-merge": "^2.6.1"
  }
}

```

なお、globalにyarnを入れておく必要があるのか、Step.3で合わせてインストールされるのか、よくわからなかった。

# Step.5 foremanのProcfileをroot配下に作る
`foreman start`でProcfileにある２つのコマンドを実行してくれる。

```Proc:Procfile
webpack: bin/webpack-dev-server
rails: rails s
```

# Step.6 webpack.config関連の設定
webpack.config.js 相当の設定は、config/webpackに格納される。shared, production, developmentとあるので、それぞれ以下の通り設定した。

- shared.jsのruleに、各パッケージのloaderを設定
- production.js, development.jsに、webpack.ProvidePluginを設定

```js:config/webpack/shared.js
// Note: You must restart bin/webpack-watcher for changes to take effect

var path = require('path')
var glob = require('glob')
var extname = require('path-complete-extname')

module.exports = {
  entry: glob.sync(path.join('..', 'app', 'javascript', 'packs', '*.js*')).reduce(
    function(map, entry) {
      var basename = path.basename(entry, extname(entry))
      map[basename] = entry
      return map
    }, {}
  ),

  output: { filename: '[name].js', path: path.resolve('..', 'public', 'packs') },

  module: {
    rules: [
      { test: /\.coffee(.erb)?$/, loader: "coffee-loader" },
      {
        test: /\.js(.erb)?$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: [
            [ 'latest', { 'es2015-riot': { 'modules': false } } ]
          ]
        }
      },
      {
        test: /\.erb$/,
        enforce: 'pre',
        loader: 'rails-erb-loader',
        options: {
          runner: '../bin/rails runner'
        }
      },

      // config for riot
      {
        test: /\.tag$/,
        loader: "riotjs-loader"
      },

      // config for font-awesome-webpack2
      {
        test: /\.woff(2)?(\?v=[a-z0-9]\.[a-z0-9]\.[a-z0-9])?$/,
        loader: 'url-loader?limit=100000'
      },
      {
        test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: "file-loader"
      },
      
      // config for using SCSS.
      {
        test: /\.scss$/,
        enforce: 'pre',
        loader: ['style-loader', 'css-loader', 'sass-loader']
      }
    ]
  },

  plugins: [ ],

  resolve: {
    extensions: [ '.js', '.coffee' ],
    modules: [
      path.resolve('../app/javascript'),
      path.resolve('../vendor/node_modules')
    ]
  },

  resolveLoader: {
    modules: [ path.resolve('../vendor/node_modules') ]
  }
}

```

```js:config/webpack/development.js
// Note: You must restart bin/webpack-watcher for changes to take effect

var path    = require('path')
var webpack = require('webpack')
var merge   = require('webpack-merge')

var config = require('./shared.js')

module.exports = merge(config, {
  devtool: 'sourcemap',

  stats: {
    errorDetails: true
  },

  output: {
    pathinfo: true
  },

  plugins: [
    new webpack.LoaderOptionsPlugin({
      debug: true
    }),
    // config for riot.
    new webpack.ProvidePlugin({ riot: 'riot' }),
  ]
})
```

```js:config/webpack/production.js
// Note: You must restart bin/webpack-watcher for changes to take effect

var path    = require('path')
var webpack = require('webpack')
var merge   = require('webpack-merge')

var config = require('./shared.js')

module.exports = merge(config, {
  output: { filename: "[name]-[hash].js" },

  plugins: [
    new webpack.LoaderOptionsPlugin({
      minimize: true
    }),
    new webpack.ProvidePlugin({ riot: 'riot' })
  ]
})

```

# Step.7 app/javascript配下のこと

## tagファイルの置き場所
app/javascript配下にフォルダを作り、index.jsを作る。また、このフォルダの中にtagファイルを格納する。今回はmainというフォルダ名にした。

```js:app/javascript/main/index.js
require('./style.scss')
```

```scss:app/javascript/main/style.scss
// for use bootstrap-fonts
$icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
@import "~bootstrap-sass/assets/stylesheets/_bootstrap.scss";
```

## packs配下のこと
application.jsで使うパッケージをrequireする。
また、上記で作ったmainフォルダもrequireする。requireされるフォルダにindex.jsがないとうまくコンパイルされない。

```js:app/javascript/packs/application.js
require('font-awesome-webpack2')
require('bootstrap-sass')
require('main')

```
# Step.8 config/environment/development.rb
`rails webpack:install`のときに一番上に書き込まれているこれをコメントアウトする

```rb:config/environment/development.rb
Rails.application.configure do
  # Make javascript_pack_tag load assets from webpack-dev-server.
  config.x.webpacker[:dev_server_host] = "http://localhost:8080"
...
```

# Step.9 herokuへのpushに失敗する問題への対応
対応せずにherokuへpushすると、`bin/yarn`が実行されずに`webpacker:compile`が実行されてしまい、webpackがないため、エラーとなってしまう。

[ここ](https://github.com/rails/webpacker/issues/45)で議論されていて、多分rails5.1では問題なくなっているのだろうけど、忘れずに書いておく。

対応１：buildpacksでnodejsを最初に行うよう設定

```shell-session
$ heroku buildpacks:set heroku/nodejs
```

対応２：rootに偽物のpackage.jsonとyarn.lockを作る
対応１をうまくこなすため、２つのファイルを作る。
package.jsonでenginesにnodeを指定。enginesでyarnを入れてもいいようですが、私はdependenciesでも大丈夫でした。


```json:package.json
{
  "name": "rails-riot-webpacker",
  "description": "\"dummy package.json for heroku/nodejs buildpack support.\"",
  "engines":{
    "node": "6.5.0"
  },
  "dependencies": {
    "yarn": "0.19.1"
  }
}


```

```js:yarn.lock
# Dummy yarn.lock to force heroku/nodejs to install yarn
```

対応３：Rakeタスクの追加
[ここ](https://github.com/gauravtiwari/rails-webpacker/blob/master/lib/tasks/assets.rake)のをそのまま引用した。

```rb:lib/tasks/xxx.rb
Rake::Task['assets:precompile']
  .clear_prerequisites
  .enhance(['assets:compile_environment'])

namespace :assets do
  task compile_environment: [:yarn, :webpack] do
    Rake::Task['assets:environment'].invoke
  end

  desc 'Install node deps via yarn'

  task :yarn do
    sh 'bundle exec ./bin/yarn'
  end

  desc 'Compile assets with webpack'

  task :webpack do
    sh 'bundle exec rails webpacker:compile'
  end
end
```

# 最後に
役に立つかわかりませんが、step9まで実施した内容を[ここ](https://github.com/higeaaa/rails-webpacker)に公開しました。