SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

freee、マジ価値開発の現場から

Railsアプリケーションにおけるフロントエンド環境のモダン化

freee、マジ価値開発の現場から 第3回


  • X ポスト
  • このエントリーをはてなブックマークに追加

 freeeの価値基準の一つである、ユーザーにとって「本質的(マジ)で価値ある」ものを届けるということ。本連載ではそれに向かって、日々挑戦を続ける開発現場の事例をお伝えします。本記事では、freeeでのフロントエンド環境の改善への取り組みである、Ruby on Railsアプリケーションにおけるフロントエンド環境のモダン化について紹介します。Rails5.0以前の標準的なフロントエンドのビルド基盤であるSprocketsアセットパイプラインがもたらした問題について解説するとともに、その仕組みに依存して成長してきたプロダクトがいかにしてSprocketsへの依存を断ち切ったかについて説明します。

  • X ポスト
  • このエントリーをはてなブックマークに追加

対象読者

 Ruby on Rails 5.0以前のアプリケーションのフロントエンド開発で、npmによるモジュール管理やwebpackなどによるビルドを行いたいのにそれがなかなか実現できずに困っている方。またRailsを使っていなくとも、グローバル変数に依存したアーキテクチャを改善したい方。

技術トレンドの変化と共に成長したプロダクト

 人事労務管理システムである「人事労務freee」は2014年から開発されています。当時のフロントエンド分野は技術トレンドの変化が非常に目まぐるしく、大ざっぱな印象としては、Backbone.jsが成熟しReactが流行の兆しを見せ始めた頃だったかと思います。

 こうした時勢のまっただ中で、人事労務freeeというプロダクトは誕生し、成長してきました。それを表すように、このプロダクトにはさまざまな言語やライブラリやアーキテクチャで書かれたソースコードが存在しています。常に新しい技術を取り入れることでユーザへ、より大きな価値を少しでも早く届けることができないかを模索してきた結果でもあります。

 しかし、新しい技術を取り入れる試みが徐々に難しくなってきました。原因はビルド環境の旧式化です。Ruby on Rails(5.0以前)には「Sprockets」というモジュールに、アセットパイプラインと呼ばれる仕組みが備わっており、これを用いるとCoffeeScriptからJavaScriptへの変換や、複数のJavaScriptファイルの結合が容易に実現できます。

 フロントエンド黎明期から提供されていたこの仕組みは、Railsの「レール」の一部として浸透していました。freeeのプロダクトも当然のようにこのレールに乗って開発されていました。ところが、このSprocketsこそが、後にビルド環境の旧式化という問題の原因を作り、ビルド環境を刷新する上でのブロッカーとなる存在となってしまいました。

Sprocketsがもたらすグローバル変数依存アーキテクチャ

 Sprocketsのアセットパイプラインで問題になるのは、グローバル変数依存のアーキテクチャを作ってしまうことです。

 Sprocketsの機能として、「Sprocketsディレクティブ」と呼ばれるものがあります。これは//= require <FILE_PATH>の記述をJavaScriptファイル内にすることで、指定されたファイルがサーバで結合されレスポンスとして返される、というものです。

 これを使うとJavaScriptファイルが分割できるので、役割ごとにファイルを分けたくなりますね。例えばhello関数を定義するファイルgreets.jsと、hello関数を利用するmain.jsを分ける、といったことができます。

greets.js
function hello() {...}
main.js
//= require greets
hello()
結合結果
function hello() {...}
hello()

 しかし greets.js を読み込み忘れた場合はどうでしょうか。当然、hello関数は定義されていないので呼び出しエラーとなります。

greets.js
function hello() {...}
main.js
hello()
結合結果
hello() // ReferenceError: hello is not defined

 つまり「先に読み込んだファイルで関数を定義」し、「後で読み込んだファイルで呼び出す」といった関係を死守しなくてはなりません。

 ファイルが少ないうちはさほど混乱することはありませんが、プロダクトが成長したらどうでしょうか。次に載せるいくつかのコードリストは、ある時点でのfreeeのプロダクトに存在した、実際のファイルの中身です。

application.js
//= require ./common.js
//= require ./lib


//= require_tree ../templates


//= require ./payroll/sub_router
//= require_tree ./payroll/mixin
//= require_tree ./payroll/components
//= require_tree ./payroll/components/modal
//= require_tree ./payroll/components/code_select
//= require ./payroll/components_init


//= require ./payroll/model
//= require ./payroll/payroll_model
//= require ./payroll/payroll_collection
//= require ./models/company_payroll_statement
//= require_tree ./models
//= require_tree ./collections
//= require ./components/shared/text_input
//= require ./components/shared/time_string_input
//= require ./components/shared/field_with_validation
//= require ./components/shared/select
//= require_tree ./components
//= require ./components_init


//= require ./payroll/payroll_modal
//= require ./payroll/payroll_view
//= require ./views/yearend/employee_base
//= require_tree ./views
//= require ./payroll/payroll_page
//= require ./pages/settings/base
//= require_tree ./pages


//= stub ./routers/employees_v2
//= stub ./routers/paid_holiday
//= stub ./routers/onboardings
//= stub ./routers/monthly_standard_remuneration_reports
//= require_tree ./routers
//= require ./for_legacy


//= require ./bootstrap


$(document).ready(function() {
  window.router = new freee.GLOBAL.routers.Base({
    role: window.userRole,
    movableRange: freee.data.get('movableRange')
  });
  Backbone.history.start();
});
common.js
//= require freee-js-framework/for_payroll.js
//= require ./session.js
//= require ./heartbeat
//= require ./initialize.js
lib.js
//= require ./freee/mvc/vue_view
//= require ./freee/mvc/vue_component
//= require ./freee/ui/modal_vueview

 3ファイルのみの引用でやめておきますが、ファイル読み込みはさらに連鎖し、結果として1000を超えるファイルが読み込まれることになります。この結果、「どこかのファイル」にある、views.Employeesmodels.Settingsといったグローバル変数に定義された関数を、「どこかのファイル」で使用するという、大変複雑な状況になってしまっていました。

 もはや人の脳のキャパシティを越えています。コードを実行するのは機械ですが、コードを書くのは人間です!

 このように、Sprocketsがもたらすグローバル変数依存のアーキテクチャは、ファイルの読み込み漏れや読み込み順の誤りだけでなく、関数名の重複や循環参照など、さまざまなリスクを抱えてしまいやすいのです。

Sprocketsが扱える外部ライブラリ形式はgemだけ

 さらに別の問題として、Sprocketsディレクティブで外部ライブラリを読み込むことができるのは、Rubyのパッケージ形式であるgemに限られ、npmのパッケージを読み込むことができません。例えば、jQueryをライブラリとして読み込む場合は、jQueryをgem形式にラップしたjquery-railsといったgemをインストールし、JavaScriptファイル内で//= require jqueryとすることでjQueryがグローバル変数$にアサインされ使えるようになる、という仕組みです。

 つまり、npmであればすぐに使えるパッケージでも、Sprocketsではgem化されたものが必要です。ややマイナーなパッケージの場合は、gem化されたライブラリが見つからなかったり、バージョンが最新版に追従していなかったり、途中でバージョンアップが放棄されていたりと、余計な問題をもたらします。

会員登録無料すると、続きをお読みいただけます

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

次のページ
Sprocketsの切り崩し方

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
freee、マジ価値開発の現場から連載記事一覧

もっと読む

この記事の著者

加藤 慧(freee.K.K.)(カトウ ケイ)

 SIerを退職し2017年1月にfreeeに入社。人事労務freee開発チームでテックリードとして主にフロントエンドのアーキテクチャ設計や実装をしています。 GitHub

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/10633 2018/03/22 16:42

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング