メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

Webpackerへの移行を機にフロントエンド開発を改善

こんにちは。メドピアでエンジニアをしている村上(pipopotamasu · GitHub)です。 普段はRailsを触っていますが、時々フロントエンド周りの開発もしています。 今回はメドピアの環境におけるWebpacker導入とフロントエンド周りの改善をテーマに記事を書きます。

目次

  1. なぜWebpacker(Webpack)を導入するのか?
  2. フロントエンド改善計画
  3. Webpackerの導入で気をつけたこと

github.com

なぜWebpacker(Webpack)を導入するのか?

Webpackerを導入する背景として、主に2つの課題がありました。

  • JavaScriptのビルド時間が長い

  • パッケージのバージョン管理ができない

JavaScriptのビルド時間が長い

元々メドピアのフロントエンド開発においてBrowserifyというbundlerを使用していました。 しかし、これにはビルド時間が長いという弱点があります。 特に以下の2つの場面でその長さが目につきます。

  • CI, デプロイ, 環境構築時に走るassets:precompileのビルド

  • 開発時のJavaScriptのビルド

現状だと前者はビルドだけで10分近くかかってしまい(JavaScriptだけでなくcssなどの他のアセットのビルドも時間がかかるという理由もありますが)、 後者はBrowserifyと一緒に使っているbabel-plugin-transform-runtimeの実行に時間がかかり、JavaScriptの開発中は3秒ほどの時間がかかります。

依存パッケージのバージョン管理ができない

メドピアの環境のnpmのバージョンが3系であるため、パッケージのバージョンを固定するpackage-lock.json(npm5系から登場)がありません。 そのため、RailsのGemfile.lockのように依存パッケージのバージョンが管理できないという問題があります。


これら2つの課題はWebpackerを導入することで一気に解決されます。

JavaScriptのビルド時間が長い → ビルド時間の短縮

BrowserifyよりWebpackの方がビルドが早くなることのベンチマークは、以下の記事を参考にさせていただきました。

perkframework.com

Browserifyとの比較がとてもわかりやすく掲載されています。 唯一、全くの0からのビルド(Fresh build)がBrowserifyが速度的に上回っていますが、そのようなビルドをするのは初回の環境構築時くらいなのでほとんど気にする必要はないでしょう。

依存パッケージのバージョン管理ができない → yarnによりバージョン管理可能に

Webpacker導入により(というよりWebpackerが依存するyarnというパッケージ管理システムにより)、上記のpackage-lock.json, Gemfile.lockのようにyarn.lockで依存パッケージのバージョン管理ができるようになります。

他にもJavaScript以外のアセットファイルのビルドができたりエコシステムが充実しているという点はBrowserifyからWebpackへの乗り換えで大きな利点でもあります。

このような背景からメドピアではWebpackerを導入することとなりました。

フロントエンド改善計画

しかし、いきなり全てのビルドをBrowserifyからWebpackerに置き換えるということはできません。 今まで書いてきたJavaScriptのコード量が多いため、それをWebpacker用の領域(app/javascripts)に移し替えるのにテストを含め時間がかかるからです。 またせっかく移行するなら、同時にフロントエンドをもっと改善していくチャンスでもあります。 具体的な改善点としては...

  • CommonJSだった部分をES Modulesに置き換える

  • Vue.jsでデータバインディングのみでしか使用していなかったところを、単一ファイルコンポーネントも活用する

  • 単一ファイルコンポーネントのLintを導入する

  • 最新版パッケージへ継続的なUpdate体制の確立

などがあります。 ここで、それぞれの改善点の詳細なポイントについてみていきましょう。

CommonJSだった部分をES Modulesに置き換える

ES Modules(以下esm)に置き換えるメリットとしては個人的には大きく以下2つだと思います。

  1. 実行前にモジュール読み込みのエラーを検知できる

  2. ツールを使うことでコードの最適化ができるようになる

実行前にモジュール読み込みのエラーを検知できる

esmはCommonJSと違い静的構文であるため、コードの実行前に構文解析が走ります。 これにより、開発者はより早い段階で間違いに気づくことができます。

# CommonJS
## module.js
function hoge() {
  return 1 + 1;
}
exports.module = hoge;

## main.js
const fuga = require('./module').fuga
console.log(fuga); // undefined


# esm
## module.js
export function hoge() {
  return 1 + 1;
}

## main.js
import { fuga } from '.module' // <= ここでSyntax Errorが発生


ツールを使うことでコードの最適化ができるようになる

esmを使うことで、コードの最適化ができるようになります。 例えばWebpackにはTree Shakingという機能があります。 ESモジュール形式で書かれたコードをbundleして一つのファイルにする時に、exportしているけどどこからもimportされていない、使われていないコードを削除する機能のことです。

https://webpack.js.org/guides/tree-shaking/

Edgeでもexport/import時にコードの最適化がされるようです。

Previewing ES6 Modules and more from ES2015, ES2016 and beyond - Microsoft Edge Dev BlogMicrosoft Edge Dev Blog

またCommonJSはいわゆるサードパーティなのに対し、esmはECMAScriptで定義されるJavaScriptの標準であるということも置き換えの理由です。

単一ファイルコンポーネントの活用

メドピアではJavaScriptのフレームワークとしてVue.jsを使用しています。Vue.jsの機能として、Vue.jsを適用させるテンプレート(HTML)とJavaScriptを同じファイル内に記述する単一ファイルコンポーネントというものがあります。 単一ファイルコンポーネントを使用するメリットはいくつかありますが、最も大きな理由は可読性の向上です。 可読性の向上については3つのポイントがあります。

  1. シンタックスハイライト

  2. 同一ファイル内にテンプレートとテンプレートに適用するJavaScriptを書くことができる

  3. テンプレート内でES6が使用できる

シンタックスハイライト

単一ファイルコンポーネントを使用しない場合、HTMLファイル内にVue.jsのコードを書く必要があります。 メドピアではViewテンプレートにHamlを採用しているため、Hamlファイル内にVue.jsのコードを書いています。 しかし、Hamlファイル内にVue.jsのコードを書いてもシンタックスハイライトがHamlのコードにしか適用されないため非常に見辛いです。

[単一ファイルコンポーネントのテンプレート] f:id:ec0156hx39:20171027113801p:plain

[Haml内に書いたテンプレート] f:id:ec0156hx39:20171027123647p:plain

上のように、単一ファイルコンポーネント内のテンプレートは見やすくハイライトされ(※お使いのエディタでハイライトのプラグインを入れる必要があります)、一方Haml内に書いたテンプレートはHamlのシンタックスしかハイライトされないため見づらくなってしまいます。

同一ファイル内にテンプレートとテンプレートに適用するJavaScriptを書くことができる

単一ファイルコンポーネントの最大の特徴です。 1ファイル内にHTML, JavaScript(CSSも)が記述できるのでどのJavaScriptがどのHTMLに適用されているかを容易に知ることができます。

f:id:ec0156hx39:20171027124642p:plain

テンプレート内でES6が使用できる

Hamlファイル内ではES6のJavaScriptのコードがトランスパイルされないので古いブラウザのサポートをする必要がある時は使用することができません。 ES6を使いたい場面(特にv-bind時の文字列テンプレートの使用)で使えないことにより、可読性が落ちる場合があります。 以下はinputタグに動的なclassをつける時の例になります。

[単一ファイルコンポーネントのテンプレート] f:id:ec0156hx39:20171027134313p:plain

[Haml内に書いたテンプレート] f:id:ec0156hx39:20171027134321p:plain

単一ファイルコンポーネントのLintを導入する

単一ファイルコンポーネントはHTMLやJavaScriptのLinterが使用できないため、新たに専用のLinterが必要です。 これの導入により、コードの質の担保・コーディングルールの統一を実現します。

最新版パッケージへ継続的なUpdate体制の確立

パッケージは定期的にアップデートしないと差分が大きくなり、いざアップデートしようとすると大怪我をする恐れがあります。 幸いにメドピアではRubyのGemを定期的にアップデートする体制ができているため、それと合わせてパッケージをアップデートしていく体制にしていきたいです。



などなどWebpacker導入を機に、よりJavaScriptの開発をモダンにしていきたい野望があります。

そこで、その野望を実現するために移行計画を立てました。

  1. Webpackerの導入
  2. 一部BrowserifyでビルドしているコードをWebpackerに移植すると同時に単一ファイルコンポーネントのLintを導入する
  3. 徐々にBrowserifyでビルドしているコードをWebpackerに移していくかつCommonJSをES Modulesに置き換え
  4. Webpackerへの完全移行とともにBrowserifyのアンインストール
  5. yarn upgrade体制の確立

Webpackerの導入で気をつけたこと

最後にメドピアにおけるWebpackerの導入で気をつけた部分を共有します。

Docker用の設定

メドピアでは開発環境にDockerを利用しています。 しかし、デフォルトのWebpackerの設定ではhostがlocalhostに設定されているため、ホストOSのブラウザからDocker上の開発環境にアクセスできないため以下のような設定が必要です。

https://github.com/rails/webpacker#development


# webpacker.yml

  dev_server:
     host: localhost # <= ここを0.0.0.0に変更
     port: 3035
     hmr: false
     https: false

config/webpack/environment.jsの拡張

Webpackerの2系ではplugin, loader, aliasの設定はconfig/webpack/shared.jsに記述すればよかったのですが、3系からshared.jsが廃止され、この辺りの設定がnode_modules以下の@rails/webpackerに格納されています。 もちろんalias等の設定を直接node_modules以下に追記することはできません。そのため、今回はwebpack-mergeを使ってconfig/webpack/environment.jsにそれらの設定しました。

# environment.js

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

module.exports = merge(environment.toWebpackConfig(), {
  resolve: {
    alias: {
      vue: 'vue/dist/vue.js'
    },
  },
});

今回はaliasだけですが、plugin, loaderなどの設定もここに追加していけばwebpackの設定がうまいことできそうです。

終わりに

まだまだモダンな環境への移行は道半ばですが、今後も変化の激しいフロントエンドの技術に追従できるように環境の改善及び技術力の向上に努めていきたいと考えています。 その一貫として、メドピアでは毎週火曜日の19:30〜Vue.jsのもくもく会を開催しています。 是非ともご参加あれ!

またRailsエンジニア、フロントエンドエンジニアを絶賛募集中ですので少しでも興味を持った方は一度メドピアに遊びに来てください!


是非読者になってください(☝︎ ՞ਊ ՞)☝︎


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html