今年2月からScalaのチームに異動し、Scala入門している。
2月中はゆっくりコップ本を読んでいたのだけど、やっぱり手を動かさないと自信が付かないので、何かツールを作る事にした。
ついでにGitHub PagesもScala.jsやScalaCSSで実装してみた。
つくったツール
!!! 実用性は度外視しています !!!
レポジトリをまるごとコピーしてくれるやつ。
ブランチを移動する度にコンパイルで時間かかるのを回避するため、ブランチ毎にコピーしたらいいのでは、という発想(上手く行ってるかはわからない……)。
手で cp -r
するのが一番はやいし、ツールを作るにしても普段だったらJSで書くとこだけど、今回はせっかくなのでScalaでCLIツールを作ってみた。
使い方
$ brew tap fand/clonepool $ brew tap fand/clonepool
clonepool -h
でかっこいいヘルプが出る。
仕組み
内部では motemen/ghq と peco/peco を利用している。
$ clonepool rails/rails my-branch
と入力すると以下の処理が走る。
$ ghq get rails/rails
により、$(ghq root)/github.com/rails/rails
にレポジトリがクローンされる~/.clonepool/github.com/rails/rails/my-branch
にコピーを作成my-branch
ブランチにチェックアウト
引数なしで $ clonepool
とすると ~/.clonepool
以下のディレクトリを一覧できる。
cd $(clonepool | peco)
とすると絞り込みつつ移動できて便利。
実装について
ほとんど scala.sys.process.Process を利用してシェルコマンドを実行しているだけ。
Gitレポジトリの操作ということで、最初はJGitを利用していたんだけど、シェルコマンドを利用したほうが楽なことがわかったのでやめてしまった。
Processの実行は scala.util.control.Exception.allCatch でラップしている。
allCatch {}
は、ブロックを評価した結果をSomeで返し、例外が出たらNoneを返してくれる。
個人的には、CLIツールを実装するときは気軽に死にたい。
JSだと、大部分ををPromiseベースで実装して、一番外側で全部catchするというのをよくやっている。
今回は getOrThrow
という関数を作って、気軽に死ねるようにした。
https://github.com/fand/clonepool/blob/master/src/main/scala/example/Util.scala#L9
CLIツールを作るためのライブラリは SCOPT や CLIST などがあるが、あんまり好みじゃなかったので今回は利用していない。
かっこいいヘルプは ansi-interpolater を利用して実装した。
homebrewでインストールできるように
Scalaのライブラリは、maven centralに登録したり、オレオレmavenレポジトリを作って公開するらしいけど、CLIツールを配布できるか良くわからなかった。
conscript を使えば簡単に配布できるらしいけど、利用者もconscriptをインストールする必要があってなんか面倒。
sbtはhomebrewで配布されているので、今回はsbtを真似てみることにした。
今回つくったformulaはこんな感じ。
https://github.com/fand/homebrew-clonepool/blob/master/clonepool.rb
sbtのformulaと大体同じ。
jarファイルをlibexecに配置し、bin/clonepoolに java -jar clonepool.jar
するシェルスクリプトを置いている。
jarファイルはsbt-assemblyというsbtプラグインを利用している。
$ sbt assembly
を実行すると勝手にjarが出来て便利。
GitHub Pagesについて
http://fand.github.io/clonepool
今回の目的はScala入門、ということでGitHub PagesもScala.js + ScalaCSS + Reactで構成した。
Octocatをクリック(またはタップ)すると回転します。
Scala.js
AltJSの一種。Scalaで書かれたコードをJSに変換する。
一昔前に「機能もヤバいしファイルサイズもデカ過ぎてヤバい」って話題になってたけど、今では大分マシになってる。
今回のコードはgzip圧縮済みで122 KBだ。
sbt fastOptJS
または sbt fullOptJS
でコンパイルする。
~fastOptJS
とするとファイル変更をwatchしてくれる。
fullOptJS
を実行すると70秒かかる……。
この前Webpack用のローダーが公開されたりしてたけど、今回はsbtで開発した。
https://github.com/mrdziuban/scalajs-loader
Scala.js自体の内容に関しては特に言うことなくて、ふつうにScalaって感じ。
ただ、あるクラスの子オブジェクト一覧を取得したくて scala.reflect.runtime を使おうとしたけど、なんか上手くいかなかったな、そういえば。
scalajs-react
Scalaは現代的な言語なのでReactも書けます
Scala.jsからReactを利用するためのライブラリ。
JSXの代わりに、 <.div()
とか ^.src := "hoge"
みたいな感じでDOMを記述する。
import japgolly.scalajs.react.vdom.html_<^._ <.ol( ^.id := "my-list", ^.lang := "en", ^.margin := 8.px, <.li("Item 1"), <.li("Item 2"))
(ドキュメントより抜粋)
記法自体は慣れたらそんなに難しくない。
しかし、コンポーネント自体とは別にBackendというクラスを書く必要があったり、独自の概念がたくさんあって大変。
何より実装がほとんど読めないのがしんどい……。
ScalaCSS
scalajs-reactと同じ作者が開発している。
Super type-safe CSS
とのこと。???????
例えば、widthなどのピクセルを指定する場所に無効な文字列を書くと、型が合わないといって怒ってくれる。
ScalaCSSはStandalone APIとInline APIの2種類のAPIを持つ。
単なるAltCSSとして使う時は Standalone APIを、CSS in JSを行なうときはInline APIを利用する。
今回は、コンポーネント毎のスタイルはInline APIで生成し、ページ全体で共通のスタイルはStandalone APIを利用して記述した。
この辺はtakezoeさんの記事で解説されてる。
CSS in JSライブラリとしては、生成されるクラス名が .MyComponent--button
のように予想しやすいクラス名になってしまうのが難点。
(多くのCSS in JSライブラリでは、クラス名をランダムな文字列にすることで、意図しないクラス名の衝突を回避している)
他にも、コンパイル時間が長かったり、対応していないCSSプロパティが多いので、得られる恩恵も割に合わない気がする……。 が、これも慣れてしまえば、書くの自体はそんなに難しくない。
最終的なコンポーネントの定義は以下のようになった。
package io.github.fand.clonepool.docs.components import japgolly.scalajs.react._, vdom.html_<^._ import scalacss.Defaults._ import scalacss.ScalaCssReact._ object Link { val component = ScalaComponent.builder[Tuple2[String, String]]("Link") .render_P(t => <.a( LinkStyle.link, ^.href := t._2, t._1 ) ) .build } object LinkStyle extends StyleSheet.Inline { import dsl._ val link = style( color(c"#EFF"), pointerEvents := "auto" ) }
その他
Sketchでロゴをつくったり、 http://realfavicongenerator.net/ でfavicon生成したりした
今回学んだこと
今度はGolang + GopherJS入門しようかな〜〜