ScalaでタイプセーフにCSSを記述できるScalaCSSを使ってみる

Scala Warriorではユーザが入力したコードを実行する目的でScala.jsを使っているのですが、せっかくScala.jsを使っているのでフロントエンドもできるだけScalaで書いてみようと思い、ScalatagsやScalaCSSなどを試しています。今日はScalaCSSについて紹介してみたいと思います。サンプルコードはScalaCSSのドキュメントから持ってきています。

github.com

ScalaCSSはScalaのコードからCSSを生成するためのライブラリで、CSSをタイプセーフに記述・参照できたり、Scalaコードで動的にスタイルを定義できたりといった便利な機能があります。Scala.jsにも対応しており、大きく分けでスタンドアロンモードとインラインモードの2通りの使い方があります。

スタンドアロンモード

スタンドアロンモードはこんな感じで記述します。

import scalacss.Defaults._

object MyStyles extends StyleSheet.Standalone {
  import dsl._

  "div.std" - (
    margin(12 px, auto),
    textAlign.left,
    cursor.pointer,

    &.hover -
      cursor.zoomIn,

    media.not.handheld.landscape.maxWidth(640 px) -
      width(400 px),

    &("span") -
      color.red
  )

  "h1".firstChild -
    fontWeight.bold

  for (i <- 0 to 3)
    s".indent-$i" -
      paddingLeft(i * 2.ex)
}

通常の外部CSSファイルの代わりに使う感じですね。Scalaコードを記述できるので繰り返しや条件分岐などを使って動的にスタイルを定義することも可能です。定義したCSSは以下のようにして出力することができます。

println(MyStyles.render)

インラインモード

これに対してインラインモードの場合は以下のような感じで定義します。

import scalacss.Defaults._

object MyStyles extends StyleSheet.Inline {
  import dsl._

  val common = mixin(
    backgroundColor.green
  )

  val outer = style(
    common, // Applying our mixin
    margin(12 px, auto),
    textAlign.left,
    cursor.pointer,

    &.hover(
      cursor.zoomIn
    ),

    media.not.handheld.landscape.maxWidth(640 px)(
      width(400 px)
    )
  )

  /** Style requiring an Int when applied. */
  val indent =
    styleF.int(0 to 3)(i => styleS(
      paddingLeft(i * 2.ex)
    ))

  /** Style hooking into Bootstrap. */
  val button = style(
    addClassNames("btn", "btn-default")
  )
}

定義したCSSスタンドアロンモードと同じく以下のようにして出力できます。

println(MyStyles.render)

また、以下のようにしてHTMLタグのclass属性に指定する値を取得することができます。これを使ってclass属性を出力しておくことでクラス名の記述ミスを防いだり、リファクタリング時などに使用箇所を容易に特定することができます。

MyStyles.outer.htmlClass     // Returns "MyStyles-outer"
MyStyles.indent(1).htmlClass // Returns "MyStyles-indent-1"
MyStyles.indent(2).htmlClass // Returns "MyStyles-indent-2"
MyStyles.button.htmlClass    // Returns "btn btn-default"

ScalaCSSの便利機能

Scalaコードならではの便利機能がいろいろあります。たとえばSCSSのmixinのようなスタイルの使い回しは以下のように記述できます。

val button = style(margin(8 px, auto), ...)
val userTitle = style(marginLeft(4 ex), ...)

val userButton = style(button, userTitle, ...)

合成した場合にスタイルの定義が重複している場合は以下のように警告を出力することができるようです。

[CSS WARNING] .MyStyles-navbar -- {margin-left: 6px} conflicts with {margin: 12px}
[CSS WARNING] .MyStyles-button -- {cursor: zoom-in} conflicts with {cursor: pointer}

また、他のCSSフレームワークと組み合わせて使用する場合などは以下のようにしてScalaCSSのインラインモードで定義するスタイルにclass属性の値を追加しておくことができます。こうしておくとhtmlClassで出力する値に追加したクラス名も含まれるようになります。

val button = style(
  addClassNames("btn", "btn-default"), // Bootstrap classes
  textAlign.center                     // Optional customisation
)

個人的にはsbt-webで定義したスタイルを外部CSSファイルに出力してくれるようなsbtプラグインがあるといいかもと思いました。

まとめ

ScalaCSSはScalaCSSを記述するDSLとしてはそれなりに使いやすいライブラリです。

が、フロントエンドのツールチェーンに組み込めなかったり、そもそもフロントエンジニアがScalaを書くのか?といった問題があり、現実的には一般的な開発体制ではなかなか使いにくいのではないかと思います。Scala.jsにしても(そもそもScala.js自体使うのかという問題はさておき)、フロントエンドの開発に使うというよりは、サーバサイドのエンジニアがフロントエンジニアにインターフェースを提供するために使うのがベストプラクティスと言われるようになってきています。

そういうことも考えるとScalaCSSに限らずScalaのフロントエンドソリューションは(GitBucketやScala Warriorのように)少人数のScalaプログラマでサーバサイドからフロントエンドまで開発している特殊なプロジェクト以外では使うモチベーションを見出すのが難しいかもしれません。