オフラインファースト的なGUIアプリケーションをScala.jsで書く話 / Scala.jsについて

はじめに

オフラインファーストへの要求

近年、オフラインファーストというか、「オフラインのときにも普通につかえて、オンラインになったら同期する」みたいなことに対する要求が高まっているように感じます。

その場合は、ローカルにもきちんと永続データを持っておき、オンラインのときにバックエンドと通信をしながらバックエンドのデータと同期していく、というスタイルを考えるのが自然だと思います。

また、普通のJSアプリケーションであっても、「サーバーに投げる際に失敗したデータはローカルでメモリ上にもっておいてリトライしたい」などの要求もあるでしょう。

さらに、ここでモバイルアプリも視野に入っているとなると、どうしても「オッRealm Mobile Platformか!?」という感じが出てきますが、Realm Mobile Platformにロックインされるのと引き換えに開発の速を選ぶのか、それとも、というのは判断の別れるところでしょう。

そこで、一旦オフラインファーストなGUIアプリケーションをどのように設計するべきかの素振りを、ブラウザプラットフォーム上でしてみることにしました。これはわたしが単に慣れているプラットフォームだから、という理由です。

複雑なロジックは表現力の高い言語で書きたいという要求

オフラインファーストなアプリケーションを書いていく場合、「いつ同期するのか」「同期のストラテジはどうするのか」「同期中のデータはどう管理するのか」「同期に失敗した場合は?」「サーバーから降ってくる同期用データをどうハンドリングするか」など、かなり複雑なことをモデル層以下でハンドリングしなければならないということが予想されます。

また、そもそもモデル層はなるべくリッチで設計上の制約がない言語で書いて、ドメインの設計やアプリケーションの設計に集中したいという要求があります。

今回は、「どうせ素振りなのだから」ということでScala.jsに手を染めてみました。ちなみにリポジトリShinpeim/Scala.jsTodoExampleにあります。

本記事では、この素振りによって得られた知見をまとめます。全3,4回くらいになる予定です。

初回はScala.jsについてです。

Scala.jsについて

JSとの協調、懸念点

Scala.jsを使ってみるにあたって

  • JSのライブラリ資産ってScala.js側からは使えるの?
  • Scalaのライブラリ資産ってそのまま使えるの?

というところがまず懸念点として上がります。また、やはりJSが得意とする部分(たとえばVue.jsのシングルファイルコンポーネントでUIを組み立てていくこととか、Routingとか、そういうブラウザと密結合している部分)についてはJSで書けたほうが余分なハマりが少なそうという感じもあります。

そこで、今回は

  • index.js(エントリポイント)とUI定義であるVue.jsのシングルファイルコンポーネント(*.vueってやつ)だけJSで書く
  • そのほかはすべてScala.jsで書き、sbtがwatchしてJSにコンパイル
  • webpackがそのファイルもwatchして、がっちゃんする

というスタイルを取ることにしました。

このスタイルの懸念点としては、

  • Scala.jsが吐くファイルサイズがでかくなるけどそれをwebpackでがっちゃんって実用に耐えるの?
  • そもそもScala.jsで吐いたJSってES6 importとかCommonJS requireで扱えるの?

というあたりがあるので、そのあたりの検証も行いました。

以下、その懸念点に対する回答です。

JSのライブラリ資産ってScala側からは使えるの?

結論から言うと、使おうと思えば使えますが、「そのまま」は使えません。

  • すでにJSライブラリをScala.jsで動かすためのファサードが提供されている場合はそれ使う
  • 自分でファサード書く
  • 型検査なしにScala.js側からがんばってJSのオブジェクトなどを操作して使う

というみっつの選択肢があります。

詳しくは公式のドキュメント - JavaScript libraries for Scala.jsを参照してください。

ただ、今回はJSのほうが得意な部分は素直にJSで書く、というスタイルを選択しているため、jQueryだとかVue.jsだとかをScala.js側から触る必要はないので、JSのライブラリ資産をScala側から利用するシーンはありませんでした。

Scalaのライブラリってそのまま使える?

結論から言うと、「Scala.jsコンパチなライブラリならそのまま使える」ということのようです。

公式のドキュメント Compatible Scala librariesを御覧ください。ScalazとかCatsとかShaplessとかあって強い……

joda.timeを利用しようとしたらうまくfastOpt(最後にjs吐く部分の操作だと思って)できなくて、「どういうライブラリなら"Scala.jsコンパチ"って言えるんだ?」ってことはよくわかってなくて、まだ調べきれてません。

Scala.jsが吐くファイルサイズがでかくなるけどそれをwebpackでがっちゃんって実用に耐えるの?

  • でかいとwebpackがbundle.js吐くのに時間かかって開発のサイクルが遅くなったりしない?
    • 開発時はminifyなどをスキップする、Scala.jsが吐くjsファイルはバベったりローダー噛ませたりしないでそのままimportすると、現実的な速度でサイクル回せるなあという感じがする
  • で、最終的なサイズはどうなの、実用できそう?
    • 今回のリポジトリの規模で、minifyなしで4.08MB、minifyありで2.16MB。でかいライブラリに依存したりするとかなり厳しいかなという気がするが、まだちょっと未知数という感じがする。

そもそもScala.jsで吐いたJSってES6 importとかCommonJS requireで扱えるの?

扱える。see Export Scala.js APIs to JavaScript - Exports with modules

Scala.jsの書き味はどうだった?

最高の体験だった……。静的型付けによるIDEの賢いリファクタ……、堅牢で柔軟な言語仕様……、なんか矛盾があればsbtさんが怒ってくれる……、traitを利用したDI……。ほしいものがここにあった……ここは地上の楽園か……?

また、Scalaが表現力の高い言語なので、結果として設計やロジックに集中することができて、JSでがんばって書きながら設計してたときよりもより良い設計が導けた気がする。

次回予告というか今後語られる予定の話

  • Vue.jsのシングルファイルコンポーネントの世界とScala.jsの世界をつなぐ窓口をどのように設計したか(それはつまりプレゼンテーション層とドメイン層をつなぐ窓口の設計である)について
  • オフラインファーストを視野に入れた際のドメイン層の設計について

-> 書きました

オフラインファースト的なGUIアプリケーションをScala.jsで書く話 / Vue.jsによるUI層とScala.jsによるモデル層のコミュニケーション - 猫型の蓄音機は 1 分間に 45 回にゃあと鳴く