patorashのブログ

方向性はまだない

昔に作ったgemのメンテを開始した

ただの日記です。

gemはいくつか作っているのだけれど、大体がRailsに関連するgemだったり、Railsでよく使われているgemの関連gemだったりする。

どれもあんまり使われているわけでもないのだけれど、自社サービスで使っているので、RubyRailsのバージョンアップに合わせて、ちゃんと動作することくらいはテストしていかないとなぁ~と常々思っていたので、プライベートの時間をそれに使うことにした。本を読んだりもしたいけれど。

とりあえずimyouの動作検証はやっておいた。imyouについてはこちらの記事を。

patorash.hatenablog.com

他にもいくつかあるのだが、ransacker_translatorという6年も放置していたgemがあったので、それをメンテすることにした。ところが、メンテしようにも、なんとテストを書いてなかった。多分当時はGemのテストを書く方法をあんまり知らなくてまぁ動くしいいや、みたいな感じでリリースしていたと思う。我ながらどうしようもない奴だ、と思い、ひとまずテストを書くことにした。

Railsに依存しているgemのテストの書き方については、公式のところにあるっちゃあるのだが、dummyアプリのRailsのバージョンが固定されるため、微妙。 imyouのときはcombustionというgemを使っていた。

github.com

このgemを使うことで、複数のバージョンを切り替えながらテストを書くことができるが、それについては今度また記事を書こうと思う。

一旦、モデルのテストだけ追加して、CircleCIでCIが回るように設定しておいた。バージョンアップする際にはgemをリリースする仕組みを追加しようとした。 ググったら自分のブログがヒットした。前にやってるじゃないか!?

patorash.hatenablog.com

とりあえずこれでいつでも複数バージョンのRailsRubyでテストできるようになったので、ボチボチやっていこうと思う。

SprocketsをやめてWebpackerに移行したのでどうやったか公開する

ようやくSprocketsからWebpackerに移行したので、そのためにやったことをまとめておきます。

移行前の状態

  • Railsのバージョンは6.0系
  • Sprockets4
  • CoffeeScript
  • Sass
  • Bootstrap3を使用
  • yarnは使ってる

筆者(私)はECMAScriptに関してはそこまで詳しくなくて、今後習得していきたいと思っているレベル。

方針

「とにかくWebpackerに移行する」ということを念頭に置き、JavaScriptを完璧にモジュール化する等は目指さない。Webpackerで動けばいい。後でリファクタリングしていくから!

Webpackerをざっくり理解する

Webpackerはwebpackの設定などをほとんど意識することなく、いい感じに使えるようにしてくれるやつです。

webpackをざっくり理解する

じゃあwebpackって何?となるかと思いますが、webpackはフロントエンドに関連するファイル群を(基本的には1つに)まとめる(bundleする)役割を担います。具体的に言うと、JavaScript, CSS, 画像を全部JavaScript内にbundleしてしまうというものです。画像もbase64の文字列データにしてしまいます(後述するloaderを使う等)。

loaderをざっくり理解する

基本的にはwebpackはまとめることしか行いません。しかし、ただまとめただけでは実際のウェブサイトでは使い物になりません。例えばJavaScriptの新しいバージョンの記法(ES2015以上)で実装すると、レガシーなブラウザでは動作しなくなりますし、Sassはcssに変換されません。そのため、webpackにはbundleする途中に処理を追加する仕組みがあります。それがloader(ローダー)です。

あまり詳しくないのですが、Node.jsにはStream.pipeline(パイプライン)という仕様があって、データを処理した後に次のpipelineに渡して更に処理、更に次のpipelineに渡して処理…のようにすることができます。loaderもその仕組みを使っています。

例えば、以下のような設定があるとします。(webpack.config.jsの途中の設定。webpackerを使っていたら登場しません)

module: {
  rules: [
    {
      test: /\.scss$/,
      exclude: /node_modules/,
      use: [
        {
          loader: MiniCssExtractPlugin.loader,
        },
        {
          loader: 'css-loader',
          options: {
            // 0 => no loaders (default);
            // 1 => postcss-loader;
            // 2 => postcss-loader, sass-loader
            importLoaders: 2
          },
        },
        {
          loader: "sass-loader",
        },
      ]
    }
  ]
}

これは、以下のような処理を行う設定です。

  1. test:で、正規表現に該当するファイル(今回は拡張子が.scss)を対象とすることを宣言
  2. exclude:で、除外する設定を行う(ライブラリは既に変換済のため)
  3. use:で、対象のファイルに使うloaderを配列で指定する。その際、loaderが適用される順番は配列の逆順であるので注意する。
  4. sass-loaderでsassをcssに変換する。pipelineで次のloaderに渡される。
  5. css-loaderでcssJavaScriptで扱える形に変換する。pipelineで次のloaderに渡される。
  6. MiniCssExtrctPlugin.loderでminifyする(余計な空白・改行を取り除く)
  7. 次のローダーがないので終了する。

で、Webpackerは何をしているのか?

webpackでは、webpack.config.jsに上記のようなローダーの設定などを行わなければなりません。 Webpackerでは、この辺りが隠蔽されていて、最初からいい感じに処理してくれるようになっています。webpackerの設定から動的にwebpack.config.jsを生成するようなイメージです。 メリットとしては、モダンなフロントエンドがすぐに使えるので楽!

しかし、webpackerが何をやっているのかがぱっと見でわからないので、モダンなフロントエンドよくわからない、webpack怖い、というふうになってしまいます。そこがwebpackerのデメリットかなと思います。

また、開発者自身が新たにloaderを追加したいこともあります。Webpackerではloaderの差込も行うことはできますが、webpack.config.jsを書くわけではないので、そこがまたわかりにくさを助長しているように思えます。 Webpackerをある程度使いこなそうと思ったら、webpackに関する理解もしないといけません。

私はwebpackに関しては、Kindleで読めるwebpack実践入門でざっくり理解しました。

patorash.hatenablog.com

500円で買えますし、Kindle Unlimitedユーザならば無料で読めますのでおすすめしておきます。

webpackerの導入

まずはwebpackerを入れます。入れ方はgithubのreadmeに書いてありますが、一応載せておきます。

github.com

Gemfileに追加します。

# Gemfile
gem 'webpacker', '~> 5.x'

そしてインストール。ついでにerb-loaderも入れます。gem js-routesを使っている場合はerb-loaderもここで入れときましょう。

bundle install
bundle exec rails webpacker:install
bundle exec rails webpacker:install:erb

これで様々なファイルが自動生成されます。babel.config.jsとかpostcss.config.jsも作成されますが、編集しません。 今後、編集するファイルは以下になります。

  • ./app/javascript/*
  • ./config/webpack/*
  • ./config/webpacker.yml

もし開発環境としてDockerを使っていて、webpack-dev-serverもDocker経由で起動する場合は、フロントエンドのファイルの変更検知ができない可能性があるので、ポーリングで検知するようにしておいてください。

patorash.hatenablog.com

SassをWebpackerに委ねる

まずは、CoffeeScriptはSprocketsのままで、SassをWebpackerに移行しようとしました。

scssをapplication.jsで読み込む

webpackのエントリーポイントである、/app/javascript/packs/application.jsで、/app/javascript/stylesheets/application.scssを読み込みます。

import '../stylesheets/application.scss'

これで、application.scssはwebpackのbundleの対象になりました。

css用のヘルパーメソッドを変更する

View側のCSSの読込をstylesheet_include_tagから、stylesheet_pack_tagに変更しました。

vendor以下をwebpackの対象にする

7年くらい前から開発しているRailsアプリなので、未だにvendor/assetsの中にJSやCSSがあったりします。 それらをwebpackでbundleする対象にします。

webpacker.ymlの、resolved_pathを修正します。

default: &default
  # 略

  # Additional paths webpack should lookup modules
  # ['app/assets', 'engine/foo/app/assets']
  resolved_paths: ['vendor/assets/javascripts', 'vendor/assets/stylesheets', 'vendor/assets/images']

ファイルを移動する

/app/assets/stylesheetsから、scssファイルをごっそり/app/javascripts/stylesheetsに移動させます。 もちろん、移動させただけだと動きません。

import文を変更する

Sprocketsの頃は、以下のように読み込んでいました。

@import 'bootstrap-sass';

これが、wepbackerだと以下のようになります。

@import '~bootstrap-sass';

node_modules以下のcssを読み込む場合は~が先頭に必要になるで注意しましょう。

resolve-url-loaderを導入する

いつも悩まされるのが、ライブラリのフォントへのパスを通すやつです。何回同じことやってるんだろう?という気持ちになります。 手元にあった作業ログによると、

  • $icon-font-pathを"~bootstrap-sass/assets/fonts/bootstrap/";にしたこと
  • resolve-url-loaderを入れたこと
  • bootstrap-sass-asset-helperを使わないようにしたこと

によって、Bootstrap3のglyphiconの表示に成功したようです。 bootstrap-sass-asset-helperは、sassにfont_pathなどのメソッドを定義して使うやつだったかと思います。もう不要です。

では、$icon-font-pathを設定しておきます。

$icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
@import '~bootstrap-sass';

次に、resolve-url-loaderを入れましょう!

yarn add -D resolve-url-loader

/config/webpack/environment.jsを編集します。sass-loaderを取得し、その前にresolve-url-loaderを差し込みます。

const { environment } = require('@rails/webpacker')

// resolve-url-loader must be used before sass-loader
const sass_loader = environment.loaders.get('sass')
sass_loader.use.splice(-1, 0, {
  loader: 'resolve-url-loader'
});

module.exports = environment

これで、フォントへのパスは通るようになりました。

import-glob-loaderを導入する

Sprocketsの場合は、自作のscssファイルの読込は@import "modules/*";等で一気に読み込むことができましたが、webpackだけではそれができませんので、import-glob-loaderを入れます。

yarn add -D import-glob-loader

そして、先ほどと同様にloaderをwebpackerに設定します。

/config/webpack/environment.jsを編集します。sass-loaderを取得し、その前にresolve-url-loaderを差し込みます。

const { environment } = require('@rails/webpacker')

// resolve-url-loader must be used before sass-loader
const sass_loader = environment.loaders.get('sass')
sass_loader.use.splice(-1, 0, {
  loader: 'resolve-url-loader'
});
sass_loader.use.push('import-glob-loader') // <= 追加

module.exports = environment

これで、Sprocketsの頃と同様に@import "modules/*";等で一気に読み込めるようになりました。

画像をWebpackerに委ねる

次に、画像もWebpackerに任せようと思います。設定はscssのときと似ていますが、webpackに画像をbundleさせるように認識させなければなりません。 これはrails webpaker:installで作られた/app/javascript/packs/application.jsの時点で、コメントアウトされているものがあります。 このコメントアウトを外します。

// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
const images = require.context('../images', true)
const imagePath = (name) => images(name, true)

これで、画像はwebpackで扱えるようになりました。

image_tagをimage_pack_tagへ変更する

このままだと画像が読み込めないので、image_tagメソッドをimage_pack_tagメソッドにしていきます。私は雑に一斉置換しました。 しかし、それだと外部から参照している画像(例えばAWS S3にある画像)やDBにバイナリで保存している画像を表示する際に、「manifest.jsにない」と怒られてエラーになります。そういう部分は再びimage_tagに戻しましょう。

favicon_link_tagをfavicon_pack_tagへ変更

地味ですがこれも忘れずに…。

image_pathやimage_urlを変更していく

image_path, image_urlメソッドで画像のパスやURLが取れなくなるので、それらを直しておきます。asset_pack_path, asset_pack_urlメソッドを使うようになるのですが、パス指定が'media/images/'が先に付くようになります。

# before
image_path('logo.png')
image_url('logo.png')

# after
asset_pack_path('media/images/logo.png')
asset_pack_url('media/images/logo.png')

JavaScriptで画像を参照している場合

これはCoffeeScriptをやめた後の話にはなるのですが、画像の話なのでここでしておきます。 GoogleMap等でピンを立てるための画像を読み込んでいた場合、Sprocketsの頃はcoffee.erbに拡張子を変更して、image_urlヘルパーメソッドを使って画像のパスを取っていましたが、webpackでは、画像をimportできます。あとはimgタグのsrcにその変数を渡すだけです。

// before
var MapIcon = "<%= image_url("map_icon.png"); %>";

// after
import MapIcon from '../images/map_icon.png'

CoffeeScriptをやめる

当初はCoffeeScriptのまま移行しようと思っていたのですが、なかなか思うように行かなかったのでJavaScriptに変換することにしました。参考にした記事が以下の記事の前編・後編なのですが、Sprocketsを使ってたときのようにWebpackを使わないことという文言をみて、踏み切ることにしました。読んでおくことをオススメします。

techracho.bpsinc.jp

decaffeinateを使う

こちらの記事を参考にして、decaffeinateを使ってCoffeeScriptファイルをJSファイルにトランスパイルしました。

kohtaro24.hatenablog.jp

まずはdecaffeinateをインストール。

npm install -g decaffeinate

そして、雑に/app/assets/javascriptsから/app/javascript/srcに移動させてからトランスパイルしました。 また、/vendor/assets/javascriptsもトランスパイルの対象にすることをお忘れなく。

結構いい感じにトランスパイルはしてくれるのですが、もちろんそれだけでは動きません。ES2015以降、JSは基本的にモジュールとして扱わなくてはならないので、importしたりclassをexportしたりしなければなりませんし、CoffeeScriptの持つ暗黙のreturnへの対応なども必要です。これはもう地道に直していきました。

デカフェ後にハマったところを紹介

クラスを継承したらコンストラクタで先にsuperを呼ばないとthisが使えない

参考情報はこちら。

qiita.com

このアプリではMVVMにknockout.jsを使っているのですが、バインディングする処理を基底クラスのコンストラクタでやっていました。 そのため、継承後はプロパティを追加で定義してからsuperを呼び出していました。それができないとわかったので、基底クラス含めて結構書き直して継承後のクラスでバインディングさせるようにしました。

CoffeeScriptのswitch文の変換でbreakが差し込まれなかった

CoffeeScriptのswitch文だと、breakが必要ありません。Rubyのcase式のように使えるわけです。 ところが、トランスパイル後のコードにもbreakが入っていなくて、ずっと想定していない動作をしていました。CoffeeScriptに慣れすぎていたため、なかなかbreakがないことに気づけませんでした。

GemになっているJS便利ツールを使えるようにする

みなさんもgem js-routesは結構使っているんじゃないでしょうか?このアプリも使っています。今更パスを直していくのも辛いので、erb-loaderを使って解決します!

/app/javascript/src/js-routes.js.erbを作ります。

<%= JsRoutes.generate %>

そして、これを/app/javascript/packs/application.jsで読み込んでwindow.Routesに定義します。

// 追加
import Routes from '../src/js-routes.js.erb';
window.Routes = Routes;

これで使えるようになりました。参考情報はこちら。

github.com

JSライブラリをグローバル変数に定義する

ProvidePluginでは対応できない課題

webpackとjQueryを調べるとwebpackのProvidePluginを使って解決しました、という話をよく見かけるのですが、ダメなケースがありました。ProvidePluginは、jQueryに限らずですが、指定した文字列で各ファイルで動的にimportしてくれることで擬似的にグローバル変数ぽく使える、と私は理解しています。

webpackでJSを使うだけならば、全然これでいいのですが、Viewでjs.erbを使っているときに、エラーが起きました。こんなやつです。

// $がなくて落ちる!
$('#foo').html("<%= j(render('list')) %>");

私がほしいのは擬似的なグローバル変数ではなく、本物のグローバル変数に定義されたjQueryなわけです!

expose-loaderを使う

そこで、expose-loaderを使います。

github.com

expose-loaderを使えば、今までと同様にライブラリをグローバル変数に割り当てることができるので上記のような問題が解決します。 このアプリではjQueryだけでなく、underscore.jsやknockout.jsも使っていたので、それらもexpose-loaderで読み込ませるようにしました。

いよいよ、webpackerにローダーを定義していきます。といっても大したことはありません。/app/config/webpack/loaders/erb.jsを参考に、expose-loaderの設定を書くだけです。 /app/config/webpack/loaders/expose.jsを作ります。ただし、exportしたいものが複数あるので、module.exportsではなく、exportsを使いました。

exports.jquery = {
  test: require.resolve('jquery'),
  use: [{
    loader: 'expose-loader',
    options: {
      exposes: [
        { globalName: '$', override: true },
        { globalName: 'jQuery',override: true },
      ],
    },
  }]
}

exports.underscore = {
  test: require.resolve('underscore'),
  use: [{
    loader: 'expose-loader',
    options: {
      exposes: [
        { globalName: '_',override: true },
      ],
    },
  }]
}

exports.knockout = {
  test: require.resolve('knockout'),
  use: [{
    loader: 'expose-loader',
    options: {
      exposes: [
        { globalName: 'ko', override: true },
      ],
    },
  }]
}

これを、/config/webpack/environment.jsで読み込みます。

const { environment } = require('@rails/webpacker')
const erb = require('./loaders/erb')
const expose = require('./loaders/expose')
environment.loaders.prepend('erb', erb)
environment.loaders.prepend('expose-jquery', expose.jquery)
environment.loaders.prepend('expose-underscore', expose.underscore)
environment.loaders.prepend('expose-knockout', expose.knockout)

// 略
module.exports = environment

これで、Sprocketsの時と同様にjQueryやunderscore.jsをグローバル変数に持つことができるようになりました。

jsonを読み込む

jsonファイルの読込をするために、今までは一度画面を表示してからAjaxで取得させてから使っていたのですが、webpackだったらjson-loaderが使えるので導入しました。

こちらも、json-loaderの設定を作っていきます。/app/config/webpack/loaders/json.jsを作成します。

module.exports = {
  type: 'javascript/auto',
  test: /\.json$/,
  use: [{
    loader: 'json-loader',
  }]
}

これを、/config/webpack/environment.jsで読み込みます。

const { environment } = require('@rails/webpacker')
// 略

const json = require('./loaders/json')
environment.loaders.prepend('json', json)

// 略
module.exports = environment

これで、jsonを簡単にロードできるようになりました。リクエスト数が減ってよかったです。

const data = require('data.json');

レガシーブラウザ対応もwebpackerに任せる

CoffeeScript時代はpolyfillを個別に入れていたのですが、Webpackerにすると自動でbabelとcore-jsが入ります。なので、core-jsにpolyfillを任せて、他のpolyfillを削除しました。

core-jsを使うには、/app/javascript/packs/application.jsで以下を追加します。先頭のほうに追加しておいたほうがいいでしょう。

import "core-js/stable";
import "regenerator-runtime/runtime";

Eventクラスのpolyfillはcore-jsにはない

しかし、罠がありました😱core-jsにはEventクラスのpolyfillが含まれていませんでした。 IEはEventクラスでconstructorが未実装だったりと難があります…。

developer.mozilla.org

調査したところ、core-jsはECMAScriptのpolyfillは対応するけれど、それ以外(つまりはIEのみの対応と思われる)は、やらないとのこと…。

github.com

そのため、個別にevents-polyfillを入れました。

github.com

events-polyfillをインストールします。

yarn add events-polyfill

/app/javascript/packs/application.jsを編集します。

import "core-js/stable";
import "regenerator-runtime/runtime";
import 'events-polyfill';

ViewからCoffeeScriptを削除

slimを使っているので、coffee:で検索して、内部をJavaScriptに直しました。これは大したことはないですね。

CircleCIでRspecを実行する前にwebpacker:compileする

CircleCIでも明示的にbundle exec rails webpacker:compileしておいたほうがいいです。一応、やっていなくてもCapybaraが初回アクセス時に動的にコンパイルしてくれるのですが、量が多くてあまりに遅いとなんか不安定だなと感じました。

ステージングへのリリース時にトラブル

これで開発環境の動作検証でも問題なく、CircleCIのテストも通りました。/app/assetsをバッサリと削除して、Gemfileからもsprockets関連のgemを削除して、漸くSprocketsとはオサラバーだわ〜と思っていた矢先、ステージングにデプロイしたら悲劇が待っていました。

assets/config/manifest.jsは削除できない

なんと、sprockets-railsに依存しているgemがあったのです。js-routesとgraphiql-rails等。これらがsprocketsを使う過程でmanifest.jsがないというエラーでHerokuへのデプロイが失敗しました。そのため、/app/assets/config/manifest.jsは復活させました。その他は削除済です。また、ただ復活させただけだと、linkなどの記述が残っていてsprocketsがファイルを探しにいってエラーになるため、manifest.jsのファイルの中身は空にしておく必要があります。

uglifierを削除したのに設定が残っていた

/config/environments/production.rb に、config.assets.js_compressor = :uglifierが残っていたため、assets:precompileで落ちました。uglifierはもう削除したので、コメントアウトしました。

これで、ステージング環境へのリリースに成功し、動作検証もパスしました。

まとめ

誰か褒めてほしい。

f:id:patorash:20200702165147p:plain

そして、本記事がSprocketsからWebpackerに移行するのをためらっている人のお役に立てば幸いです。

WSL2のUbuntuに環境構築の続き

ただのメモです。

  1. postgresql-client, postgresql-contrib, libpq-devのインストールなど。
  2. pyenvのインストール
  3. Python 2系のインストール
  4. Python 3系のインストール

PythonをインストールしようとしてWARNIGが出たのでメモ。 obel.hatenablog.jp

その後、pythonのインストールをし足り等。

pyenv global 3.8.3 2.7.18

Pythonを入れたのは、yarn installをしたらnode-sassのところでコケてて、エラーメッセージでPythonをいれたほうがよさそうだったから。

WSL2のUbuntuに環境構築中

これはただの日記というかメモです。

WSL2のUbuntuに開発環境を作ってみようと思って作業中。

やったことをリストにします。

  1. zshのセットアップ
  2. zplugを使ってみる
  3. goenvのインストール
  4. goのインストール
  5. ghqのインストール
  6. rbenvのインストール
  7. rubyのインストール
  8. bundlerのインストール
  9. bundle config --globalの設定
  10. pecoのインストール
  11. .zshrcにpecoを使った関数の定義
  12. nodenvのインストール
  13. nodeのインストール
  14. yarnのインストール

Macでもzshがデフォルトのシェルになったし、レンタルサーバだとbashzshが多いので、ちょっとこれからはzshにしていこうかなと思っている。fish便利なんだけどなー。

zshのザックリとした内容が知りたかったので、ちょうど特集記事のあった今月のソフトウェアデザインをKindleで買って読んだ。

oh-my-zshは入れずに、zplugで個別に入れるようにしてみている。 とはいえ、oh-my-zshプラグインが使えるので便利。

webpack実践入門を読んだ

先日子供を車に乗せるときに背中を痛めてしまってしんどいです😥 寝返りを打つのも痛いし、咳・クシャミしても痛い状態です。どうも「ぎっくり背中」というらしいです。

出かけるのもしんどかったため、家でゴロゴロしつつ、この本を読んでみました。

ちょうどRailsプロジェクトをwepbacker化している最中だったり、社内プロジェクトでwebpackを使っていたりしてハマることが多かったので、ザっとわかっておこうと思いました。 この本はKindle Unlimitedに入っていたら無料で読めますし、もし入ってなくても500円で読めます。あとQiitaにも記事があるみたいですが、本のほうが読みやすいかなと感じたのでKindleで読みました。

  • webpackとは何か?
  • webpackを動かしてみる
  • ローダーについて
  • プラグインについて
  • その他の機能について
  • 最適化について

くらいのボリュームで、サンプルコードに関しては丁寧すぎるくらいの記述でわかりやすかったです。ローダーの設定の仕方や、よく使うプラグインについて、そして環境ごとに設定を使い分ける際の方法や最適化の方法など、これらが分かれば後は自走して他の機能を取り入れていけるだろうという絶妙な感じでした。

最適化やファイルの分割については、ちょうど知りたいと思っていたところだったので、プロジェクトに導入していこうと思います。

Docker上でWebpackerがファイル変更を検知できない件に対応した

担当しているRailsプロジェクトのフロントエンド環境をSprocketsからWebpackerに移行しようとしていて、ここ最近辛い毎日です。

とりあえず、scssの移行は終わったので、今はCoffeeScriptのコードをできるだけそのままでWebpackerに乗っけようとしているのですが、エントリーポイントのファイルを修正しても全然再コンパイルが走らなくて毎回webpack-dev-serverを再起動するというめちゃくちゃ辛いことをしていました。時間かかりすぎる…。

いい加減、原因を探ろうと調査しだしたのですが、私の開発環境は全てDockerに移行しています。

patorash.hatenablog.com

webpack-dev-severもDockerで起動するようにしてあったのでRailsのコンテナとwebpack-dev-serverのコンテナがうまく連携できてないのが原因なのかなと思い、ググった記事を参考にコンテナを作り直したりとか、色々していました。 ローカル環境でwebpack-dev-serverを起動したら、ファイル変更の検知が効いたのに、Dockerコンテナ経由だと効かなかったので、ファイルの変更の検知をポーリングできないものか…と思ったらwebpackにそういう設定ありました。

webpack.js.org

じゃあそれをwebpackerでどうやって設定すればいいか?というところですが、config/webpacker.ymlで設定できます。

webpacker.ymlwatch_optionsにpollを追加します!pollはミリ秒を設定するので1秒毎にポーリングするよう、1000を設定してみました。

development:
  # 略
  dev_server:
    # 略
    watch_options:
      ignored: '**/node_modules/**'
      aggregate_timeout: 200
      poll: 1000

これで、変更があった分のファイルのみ再コンパイルが実行されるようになりました。速い!!🚀

まだ全然Sprocketsからの移行は終わってないのですが、やる気がみなぎってきました。

7年開発しているプロジェクトをRails6にアップグレードした

Railsのアップグレード作業は検証含めて非常に面倒なもので、結構遅れがちなのですが、どこかで気合い入れてやらなければなーと思い、エイヤッとやってやりました。Rails3からやってるプロジェクトなので負債もまだまだ多いですが、とりあえずメジャーバージョンアップさえしておけば、他の技術も徐々に対応できるはず…。今回のアップグレードは5.2系から6.0系へのアップグレードです。

今回のアップグレードでやったこと

トレンドに追従できていないのは全然わかっていますが、頑張って追いかけるためにやれることはやっていきます😀

  • Sprocketsを3から4に変更した
  • sass-railsを6に変更した
  • therubyracerをmini_racerに変更した
  • Rails6をインストールしてbin/rails app:updateで差分を反映した
  • belongs_toにoptional: trueを付けていった
  • app/views/以下にある*.js.coffeeのファイルを*.js.erbに変更してCoffeeScriptからJavaScriptに修正した
  • Rails6からのローダーであるzeitwerkに対応するため、bin/rails zeitwerk:checkを行い、クラス名を修正したり等を行なった
  • ActionDispatch::HostAuthorizationを有効にするためにconfig.hostsに設定を行なった
  • credentials機能をバックポートしていたので、それを削除した

個別に解説するのもあれなので、ざっくりと書いていきます。

Sprocketsを3から4に変更した

まず雑にRails6系にしてbundle update railsをしたら、色々と怒られまして、その中に入っていたと思います。これはSprockets3系に依存していたgemがあったためでした。たしか、css_spritterだったかなと思います。これはIE9が1ファイルに付き4096までしかセレクタ定義を認識できないため、それ以上の定義を使おうと思ったらファイル分割するしかない、というやつですね。

これは昔、私がqiitaに記事を書いてます。

qiita.com

もうIE9のサポートは打ち切ったので、これを削除し、Sprocketsのバージョンを4系にしました。 この辺りでsass-railsを6にするのもなんか時間かかった気がしますが、何が原因だったか忘れました。

therubyracerをmini_racerにした

mini_racerの存在に気づいたのがつい最近というレベルで知らなかったのですが、以下の記事は3年前の記事です。これといってtherubyracerに依存しているようなものはないだろう、と思って変更しました。テストも通っているので大丈夫でしょう。

note.com

belongs_toにoptional: trueを付けていった

これはもう以前のバージョンの時点でやっておくべきだったことですが、放置していたので対応しました。belongs_toをつけると関連先がデフォルトで必須になる件です。config.load_modules 6.0に設定して、頑張って直しました。

views以下のjs.coffeeをjs.erbに修正した

これは、assets以下のほうはjs.coffeeで動くのですが、views以下のほうがjs.coffeeを認識しなくなっていました。Ajaxでアクセスされた際に返すViewが以前は自動的にjs.coffeeになっていたのだけれど、ファイルの存在を認識してくれないからhtml側のViewを返してしまってエラーになっていました。

このままで動く方法を探そうとしたのですが、いまいちわからず…。Railsがcoffeeをサポートしなくなったからなんでしょうか?直し方がわからなかったので、拡張子のjs.coffeeをjs.erbに変換して、ファイルの内容もCoffeeScriptからJavaScriptに直したら、普通にAjaxで認識してくれるようになりました。

Ajaxで返す際のCoffeeScriptでは、大したことをしていなかったのでJSへの修正も簡単、かつファイル数も少なかったため、手動で直して終わり、としました。

zeitwerk対応

bin/rails zeitwerk:checkを行い、クラス名の修正等を行ないました。

zeitwerkはフォルダ構造とファイル名からnamespaceやクラス名を推測してロードを試みるため、多少の修正が必要でしたが、先ほどのコマンドで「こう直してほしい」と言われるので、大したことはありませんでした。一斉置換で対応可能でした。

ActionDispatch::HostAuthorization対応

これは、DNSバインディング攻撃から保護するために追加されたミドルウェアの設定です。

config/application.rbやconfig/environments/production.rbなどで設定しておくと、設定されたドメイン以外でのアクセスを受けたらエラーになってくれます。

いろんなサブドメインで検証することもあるので、config/application.rbで以下のようにしました。(example.comはプロダクトのドメインに置き換えてください) .example.comとすることで、サブドメインワイルドカードにすることができます。

config.hosts.push 'example.com', '.example.com'

設定はこれでいいのですが、インテグレーションテストを実行すると、この設定が仇となってエラーになります。

そこで、config/environments/test.rbの最後で、クリアしておきます。

config.hosts.clear

これで、テストも無事実行されるようになりました。

credentials機能をバックポートしていたので、それを削除した

これは以前に記事にした通り、Rails6系のcredentialsの機能を5.2にバックポートしていたのですが、正式に6系にしたため、不要になりましたので、削除しました。

patorash.hatenablog.com

削除した後に動作検証をして、問題ないことを確認しました。

これからやりたいこと

あくまでも私見ですが…

  • CoffeeScriptをやめる
  • Sprockets4からWebpackerに移行する
  • bootstrap3から4に移行する
  • knockout.jsからstimulusに移行する
  • turbolinksを導入する

周回遅れ感がありますが、保守をしつつ、機能追加をしつつ、アップグレードしていくのは大変なのです😢若者がプロジェクトに入ってくれることになっているのですが、若者に今更CoffeeScriptとかやってもらうのは厳しいと思うので、本格的にアサインする前にこれらの一部でも倒しておきたい所存…。やっていくしかない。