Rista Tech Blog

株式会社リスタの技術?ブログ

RailsでWebpackerを導入してCoffeeScriptからES2015(ES6)に移行しました

f:id:c5meg1012:20170927120314j:plain

エンジニアの望月(@c5meru)です。
この前の日曜は、HTML5カンファレンスにボランティアスタッフとして参加してきました。
いくつか聴衆としても聴くことができてとても楽しかったのですが、ずっと立ちっぱなしだったので今すごく腰が痛いです…運動不足…(´・ω・`)

さて、弊社では表題の通り、JOBLISTのCoffeeScriptのコードをES2015(ES6)に移行しました。

移行の経緯

移行前はRailsで、こんな感じのフロントエンドでした。

  • JSのライブラリはvender下に直接置くか、gemで管理
  • CoffeeScript
  • jQueryベースの開発

そこで、より効率的で高品質なフロントエンド開発を目指すにあたって、社内で以下のような点を検討しました。

  • npmや、yarnを用いたパッケージ管理
  • CoffeeScriptからES2015(ES6)に移行
  • React.js、Vue.jsなどのフレームワークの導入

その中で、yarnによるパッケージ管理と、ES2015(ES6)への移行については、

  • Rails 5.1から導入されたWebpacker
  • CoffeeScriptからES2015(ES6)へ変換するツール (decaffeinate)

を使うと簡単にできることがわかりました。

JOBLISTはサービス、管理画面を含め、いくつかの機能に分かれていて、それぞれにapplication.jsがあります。
この中で比較的小さな機能を試験的に移行してみたところ、既存のasset pipelineのコードも問題なく並行して動かせることもわかりました。

こういう状況の中で、『ES2015(ES6)』と『CoffeeScript』を比較・検討したところ、「どちらも効率的な開発を行うのに十分な機能があるが、今後の将来性を考えるとES2015(ES6)のほうが有望そうであり、そんなに大した手間もかからないし移行してしまおう」ということになったのでした。

移行の流れ

大まかな流れとしましては、

  1. yarn, webpacker導入
  2. Asset PipelineからWebpack(rails/webpacker)への移行
  3. CoffeeScriptからES2015(ES6)の変換
  4. 移行後のエラー検知のためbugsnag-jsを導入
  5. 本番デプロイ

となっています。
次項より、詳しく説明していきます。

1. yarn, webpacker導入

1-1. 開発環境へのインストール

まずはyarnをインストールします。
yarnを動かすためにnode.jsが必要なので、一緒にインストールします。

$ brew install node
$ brew install yarn

続いて、webpackerを導入していきます。
まず、Gemfileにwebpackerを追加します。

source 'https://rubygems.org'
gem 'rails', '5.1.3'
gem 'webpacker' # 追加

次に、bundle install で gemをインストールします。

$ bundle install

webpackerで、webpackのインストールを行います。

$ bundle exec rails webpacker:install

これでwebpack環境ができました。

1-2. bin/webpack、bin/webpack-dev-server

この後JavaScriptのファイルをES2015(ES6)に移行していくのですが、このままではES2015(ES6)をコンパイルする際に毎回、

$ bin/webpack

のコマンドを実行しないといけないので、その手間が不要になるよう、Rails server とは別に webpack-dev-server を立ち上げておきます。
新しくターミナルのウインドウを開いて、このコマンドを実行しておきましょう。

$ bin/webpack-dev-server

2. Asset PipelineからWebpack(rails/webpacker)への移行

2-1. webpackerのエントリポイントの作成

app/assets/javascripts/にあったファイルをapp/javascript/に移動していきます。 エントリポイントとなるapplication.jsはapp/javascript/packsに移動します。

弊社の場合は以下のように、複数のapplication.jsがあったので、

app/assets/javascripts/
  ├ common/
  │  └ util.coffee
  │
  ├ pc/
  │  ├ a.coffee
  │  ├ b.coffee
  │  └ application.js
  │
  └mobile/
     ├ a.coffee
     ├ b.coffee
     └ application.js

それぞれをapplication_pc.jsapplication_mobile.jsなどにリネームして、以下のように配置しました。

app/javascript/
  ├ packs/
  │  ├ application_pc.js
  │  └ application_mobile.js
  │ 
  ├ common/
  │  └ util.js
  │
  ├ pc/
  │  ├ a.js
  │  └ b.js
  │
  └ mobile/
     ├ c.js
     └ d.js

次項では、それぞれのファイルの中身について説明します。

2-2. application.jsの変更と、npmライブラリへの移行

app/javascript/packs直下に移動したapplication.js、以下のような中身になっていると思います。

//= require jquery
//= require jquery-ui/widgets/dialog
//= require jquery_ujs

//= require_tree ../common
//= require_tree .

これを、node.jsのrequireを使って以下のように書き換えます。

// jQueryはライブラリによって「$」だったり「jQuery」だったりするため
global.$ = global.jQuery = require('jquery')
require('jquery-ui/ui/widgets/dialog')
require('jquery-ujs')

require('common/util')

require('pc/a')
require('pc/b')

こちらを置き換える際に、これまでgemのライブラリを用いていた部分については、npmライブラリに移行していきます。
ですので、先ほどの例を移行するのであれば、jqueryjquery-uijquery_ujsの部分をnpmライブラリに移行する必要があります。

npmライブラリを使うためには、冒頭でインストールしたyarnを用いて、ライブラリのインストールを行います。

$ yarn add jquery

# 複数インストール
$ yarn add jquery jquery-ui jquery-ujs

2-3. インクルードタグの変更

webpackでビルドしたJavaScriptをインクルードするために、これまで javascript_include_tag で読み込んでいたところを javascript_pack_tagというヘルパーメソッドに変更します。

弊社はhamlを使っているので、

= javascript_include_tag "pc/application"

こちらの部分を、以下のように変更します。

= javascript_pack_tag "application_pc"

3. CoffeeScriptからES2015(ES6)の変換

3-1. 変換に使ったツール

CoffeeScriptからES2015(ES6)へ変換するツールは、

の2種類が使えそうなものでした。
検討の結果、より最近までコミットされてメンテナンスされていたdecaffeinateを用いることとしました。

3-2. decaffeinateの実行方法と変換サンプル、エラー対応

decaffeinateをインストールします。

$ yarn global add decaffeinate

実際に変換するには decaffeinate コマンドを使います。

$ decaffeinate hogehoge.coffee

実行すると同じ階層に、拡張子が.jsになったファイルが生成されます。
変換するファイルをディレクトリ単位で指定すると、該当ディレクトリの直下のCoffeeScriptファイルが変換されます。

それでは、以下のCoffeeScriptを変換してみましょう。

$ ->
  $modal = $('.modal')
  $toggle = $('.toggle')

  $toggle.on 'click', =>
    if $modal.dialog('isOpen')
      $modal.dialog('close')
    else
      $modal.dialog('open')

decaffeinate コマンドで変換します。

$ decaffeinate modal.coffee

変換された中身はこんな感じになります。

/*
 * decaffeinate suggestions:
 * DS102: Remove unnecessary code created because of implicit returns
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */
$(function() {
  const $modal = $('.modal');
  const $toggle = $('.toggle');

  return $toggle.on('click', () => {
    if ($modal.dialog('isOpen')) {
      return $modal.dialog('close');
    } else {
      return $modal.dialog('open');
    }
  });
});

追加されたコメントと修正が必要な事項について説明します。

decaffeinate はまだまだ変換の精度が甘いところがあるので、変換後のJavaScriptファイルにはコメントアウトで警告が書かれることがあります。
こちらの警告と、警告に対する解説を見ながら、動作確認をしていく必要があります。

今回は以下の警告について対応しました。

Remove unnecessary code created because of implicit returns

CoffeeScriptではrubyと同じようにブロックの最後の式に暗黙のreturnが付与されます。 なので、変換後のJavaScriptには、先ほどの変換サンプルのように不要な return が入ってしまいます。

これについては実際にコードを確認しながら出来る限り削除しました。

Avoid top-level this

CoffeeScriptでは@をつけることでグローバルスコープのclass、関数を定義することが出来ますが、その@がthisに変換されています。 以下のようなfunctionが、

@functionName = () ->
  hogehoge()
  fugafuga()

以下のように変換されて、想定通りの動作をしなくなってしまうのです。

this.functionName = function() {
  hogehoge();
  return fugafuga();
};

今回の対応としては、ひとまず this.を外して

functionName = function() {
  hogehoge();
  return fugafuga();
};

以上のように対応をしました。

Rewrite code to no longer use __guard__

CoffeeScriptの hoge?.isHoge()? のような、? の解釈がうまくいかないようでした。
こちらにしたがって、修正対応をしました。

Remove unnecessary use of Array.from

配列をループするコードでは、配列がArray.from()の中に入って変換されます。
しかし、Array.fromはSafariで対応していないので、こちらにしたがって外側のArray.from()を削除して対応しました。

4. 移行後のエラー検知のためbugsnag-jsを導入

変換したJavaScriptの動作を全て確認するのは難しかったので、エラー検知のためにbugsnag-jsを導入しました。
もともと弊社は、RailsのException検知にBugsnagを使っています。
導入により変換後のJavaScriptでエラーが発生した場合はSlackに通知がきて、以下のようにエラー発生箇所を確認出来るようになりました。

f:id:c5meg1012:20170927100204p:plain

詳細な導入手順は別の記事で、改めてご紹介します。

5. 本番デプロイ

開発環境で準備ができたら、いよいよ本番デプロイです。

5-1. サーバ(Ubuntu 16.04)設定

以下のように、node.jsを6にバージョンアップしました。

curl -sS https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
echo "deb https://deb.nodesource.com/node_6.x xenial main" | tee /etc/apt/sources.list.d/nodesource.list
apt-get update
apt-get install nodejs

また、yarnのインストールも行いました。

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
apt-get update
apt-get install yarn

5-2. デプロイ設定

弊社では、デプロイにcapistranoを使っています。
capistranoの設定は、以下の3つを行いました。

  • Gemfileにgem 'capistrano-yarn'を追加してbundle install
  • Capfileにrequire 'capistrano/yarn'を追加
  • config/deploy.rbに以下を追加
set :yarn_flags, "--prefer-offline --production"
set :yarn_roles, :app

以上で、デプロイコマンド実行時にyarnによるパッケージインストールとwebpackが実行されます。
移行の手順はこれでおしまいです。お疲れさまでした。

参考:
- CoffeeScriptからES2015(ES6)へ移行しました

さいごに

ということで、長くなりましたが、CoffeeScriptのコードをES2015(ES6)に移行する手順をざっくりと説明しました。
JavaScript系のgemはどんどんメンテされなくなってきていますので、ようやくnpmに移行することができて、ほっとしています。

今後の予定としては、

  • webpackのconfigを調整する
  • decaffeinateの変換で、修正しきれなかった警告をつぶしていく

など、他にも動かしていくうちに上がった課題に取り組んでいきます。
まだまだやることがいっぱいです!

さて、やることはいっぱいなのですが、弊社では現在

  • Webサービス(Rails)
  • iOSアプリ(Swift)
  • Androidアプリ(Kotlin)

の3つがありまして、エンジニアの人手がまったく足りていません!助けてください!(´;ω;`)
特にアプリは構築してプレスリリースしたばかりなので、やったことがダイレクトにユーザーに届く環境だと思います!

↓詳しくはこちら!↓

rista.job-list.net

rista.job-list.net

rista.job-list.net