WebOS Goodies

WebOS の未来を模索する、ゲームプログラマあがりの Web 開発者のブログ。

WebOS Goodies へようこそ! WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
Subscribe       

Closure Stylesheets で CSS を最適化する (2)

前回に続いて、本日も Google Closure Stylesheets (以下 GSS)についてです。前回は GSS を単体で使用した時の機能をひと通り説明したので、今回は Closure Compiler, Closure Templates, Closure Library といった他の Closure Tools と組み合わせて、 CSS のクラス名を短縮する方法をご紹介します。

Google+ などの CSS ファイルを覗いたことがある方はご存知かと思いますが、それらの CSS クラス名は「a-j」とか「c-i-j-ua」なんていう数文字のアルファベットをハイフンで繋いだものになっています。これがまさに GSS によるクラス名短縮の結果です。 GSS を使えば、我々もこうした CSS クラス名の短縮を簡単に利用できます。

また、多少の工夫は必要になるものの、他のフレームワーク(Rails とか)上でクラス名短縮を使うことも不可能ではありません。そのあたりの情報も最後にまとめていますので、ぜひお役立てください。

準備

今回は Closure Compiler と Closure Templates を利用するので、それらを先にインストールしましょう。 GSS のインストールについては前回を参照してください。

まずは Closure Compiler ですが、以下のページにある closure-latest.zip をダウンロード・展開して、 compiler.jar を適当なディレクトリにコピーしてください。

http://code.google.com/p/closure-compiler/download...

Closure Templates も同様に、以下のページから closure-templates-for-javascript-latest.zip をダウンロード・展開してください。そして SoyToJsSrcCompiler.jar, soyutils_usegoog.js, soyutils.js の 3 つのファイルを適当なディレクトリにコピーしてください。

http://code.google.com/p/closure-templates/downloa...

これで Closure Compiler と Closure Templates がインストールできました。

Closure Library と組み合わせる

まずは Closure Tools の JavaScript フレームワークである Closure Library を利用したプロジェクトで、 CSS クラス名を短縮する方法から。ある程度実用的なサンプルでチュートリアルっぽくしようとも思ったんですが、それだとどうしても焦点がボケてしまうので、単純な HelloWorld でいきます。

サンプルを作る

というわけで、まずは Hello World を表示するサンプルを作りましょう。ここは手短にいきますので、 Closure Library についてご存じない方は、こちらの記事も参照されるとよいかと思います。

では、最初に適当な作業用ディレクトリを作って、 Subversion で Closure Library をチェックアウトします。

svn checkout http://closure-library.googlecode.com/svn/trunk/ closure-library

index.html を以下の内容で作成します。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>GSS のテスト</title>
  <link rel="stylesheet" type="text/css" href="gss_sample.gss">
</head>
<body>
  <script src="closure-library/closure/goog/base.js"></script>
  <script src="deps.js"></script>
  <script>
    goog.require('gsssample.App');
  </script>
</body>
</html>

スタイルシートを記述した gss_sample.gss を作ります。 .gss は GSS 向けに記述された CSS ファイルの拡張子です。 GSS の拡張機能などを使うとブラウザで直接読むことはできないのですが、今回は CSS 互換の記述しかしていないので、テスト的に index.html から直読みしています。

.message {
  color: red;
  font-size: 24px;
  font-weight: bold;
}

JavaScript を記述します。 scripts というディレクトリを作り、 scripts/gss_sample.js を以下の内容で作成してください。

goog.provide('gsssample.App');
goog.require('goog.dom');

gsssample.App = function() {
  var el = goog.dom.createDom(
    'div', goog.getCssName('message'), 'Hello World!');
  goog.dom.append(document.body, el);
}
goog.addSingletonGetter(gsssample.App);

gsssample.App.getInstance();

CSS クラス名を直接記述する代わりに goog.getCssName() を通しているのがポイントです。この関数が短縮前のクラス名を短縮後のものに置き換えてくれます。さらに Closure Compiler で最適化すると goog.getCssName() の呼び出し自体が短縮クラス名のリテラルで置き換えらるので、オーバーヘッドはゼロです。

実は goog.getCssName() は以前から Closure Library に実装されていたのですが、これといった利点がなかったので、使っていない方が多いかと思います。しかし、今後は GSS の恩恵を受けるためにも、必ずこの関数を通すようにしましょう。

さて、ここまできたら、最後に deps.js を生成して終わりです。

python closure-library/closure/bin/build/depswriter.py \
  --root_with_prefix="scripts ../../../scripts" --output_file=deps.js

index.html をブラウザで開いて、赤文字で「Hello World」と表示されるのを確認してください。

Closure Stylesheets を実行

それでは、ようやく本題です。 GSS を使って CSS を最適化しましょう。以下のコマンドを実行してください(closure-stylesheets.jar のパスは適宜補ってください)。

java -jar closure-stylesheets.jar \
  --output-file gss_sample.css --output-renaming-map renaming_map.js \
  --output-renaming-map-format CLOSURE_UNCOMPILED \
  --rename CLOSURE gss_sample.gss

オプションとして指定している --output-renaming-map は CSS クラス名の短縮前と短縮後のマッピング情報の出力ファイル名です。 --output-renaming-map-format はそのマッピング情報の形式の指定で、 CLOSURE_UNCOMPILED は Closure Compiler で最適化する前の Closure Library 向けという意味です。 --rename は CSS クラス名の短縮方法の指定で、 CLOSURE は Closure Library 標準の短縮方法です。他に NONE (短縮しない)と DEBUG (後述)が指定できます。

上記のコマンドを実行すると、 gss_sample.css と renaming_map.js という 2 つのファイルが生成されます。まず gss_sample.css の内容を見ると、以下のようになっているはずです。

.a{color:red;font-size:24px;font-weight:bold}

CSS が最適化され、クラス名も短縮されて「.a」になっているのがわかりますね。

次に renaming_map.js を見ると、こうなっています。

CLOSURE_CSS_NAME_MAPPING = {
  "message": "a"
};

Closure Library の base.js は、 CLOSURE_CSS_NAME_MAPPING というグローバル変数を(もし定義されていれば) getCssName() で使う変換テーブルとして使用します。したがって、 base.js の前にこの renaming_map.js を読みこめば、 getCssName() の返り値が短縮後のクラス名になるというわけです。読み込む CSS ファイルの変更とあわせて、 index.html を以下のように書き換えます。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>GSS のテスト</title>
  <link rel="stylesheet" type="text/css" href="gss_sample.css">
</head>
<body>
  <script src="renaming_map.js"></script>
  <script src="closure-library/closure/goog/base.js"></script>
  <script src="deps.js"></script>
  <script>
    goog.require('gsssample.App');
  </script>
</body>
</html>

index.html をブラウザで開くと、 CSS クラス名が短縮され、かつ正しいスタイルが適用されているのが確認できるでしょう。

Closure Compiler で最適化する

前述のとおり、 Closure Compiler を使うと短縮クラス名への変換をコンパイル時に行い、 getCssName() の呼び出しを省略できます。そのためには、まず Closure Compiler 用に renaming_map.js を生成しなおします。

java -jar closure-stylesheets.jar \
  --output-file gss_sample.css --output-renaming-map renaming_map.js \
  --output-renaming-map-format CLOSURE_COMPILED \
  --rename CLOSURE gss_sample.gss

オプション --output-renaming-map-format の値を CLOSURE_COMPILED に変えていることに注意してください。こうすることで、 renaming_map.js の内容が以下のように変わります。

goog.setCssNameMapping({
  "message": "a"
});

名前短縮用のマッピングデータをグローバル変数に代入する代わりに、 goog.setCssNameMapping() に渡しています。このコードを Closure Compiler のコンパイルに含めることで、コンパイル時にクラス名を変換してくれます。

これで準備が整ったので、 Closure Compiler でスクリプトを最適化します。 Closure Library では closurebuilder.py 経由で Closure Compiler を呼び出すことになっているので、 -f オプションで renaming_map.js を読み込むように指示するのがよいでしょう。 --compilation_level には ADVANCED_OPTIMIZATIONS か SIMPLE_OPTIMIZATIONS を指定してください。

python closure-library/closure/bin/build/closurebuilder.py \
  --root=closure-library --root=scripts -n gsssample.App \
  -o compiled --output_file=built.js -c compiler.jar \
  -f "--compilation_level=ADVANCED_OPTIMIZATIONS" -f "--js=renaming_map.js"

これですべての(最適化された)コードを含む built.js が生成されました。 built.js には base.js, deps.js そしてもちろん renaming_map.js の情報も含まれているので、 index.html からそれらの読み込みを削除し、 built.js だけを読み込むように変更します。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>GSS のテスト</title>
  <link rel="stylesheet" type="text/css" href="gss_sample.css">
</head>
<body>
  <script src="built.js"></script>
</body>
</html>

このようにして、 Closure Library で使用するスタイルシートを最適化できます。今回は使っていませんが、 Closure Library に含まれる GUI コンポーネントは当然 getCssName() を使っているので、それらのスタイルシートも含めて GSS で最適化し、ひとつのファイルにまとめることが可能です。 Closure Library を使えば、 GSS の恩恵を最大限に受けることができるわけです。

Closure Templates と組み合わせる

次は Closute Templates を使ってみます。 Closure Templates は Closure Tools に含まれるテンプレートエンジンで、テンプレートを高速に展開する JavaScript のコードを生成します(Java コードも生成できますが、たぶん現時点では GSS の短縮クラス名には対応できないかと思います)。基本的には独立したツールですが、 CSS クラス名の短縮については Closure Library と組み合わせることが前提になっているようです。そこで、ここでは上で作成した Hello World! をテンプレート化する作業を題材にして、 GSS との連携方法を解説することにします。

上記の Closure Library 向けの作業がすでに終わっていると仮定して、その続きをやっていきます。まずは Closure Templates のソースファイルである SOY ファイルを作成します。以下の内容を template.soy として保存してください。

{namespace gsssample.template}

/**
 * Hello World! を表示
 */
{template .hello}
<div class="{css message}">
  Hello World!
</div>
{/template}

CSS クラス名を記述する部分を「{css CSSクラス名}」とすることで、その部分に短縮名が展開されます。このファイルを以下のコマンドで JavaScript に変換します。

java -jar SoyToJsSrcCompiler.jar \
  --shouldProvideRequireSoyNamespaces --cssHandlingScheme GOOG \
  --outputPathFormat scripts/template.soy.js template.soy

いろいろオプションを指定していますが、おまじないだと思ってください。 GSS のクラス名短縮機能を使う場合、「--shouldProvideRequireSoyNamespaces --cssHandlingScheme GOOG」はほぼ必ず指定するはずです。

生成された scripts/template.js は以下のようになります。

// This file was automatically generated from template.soy.
// Please don't edit this file by hand.

goog.provide('gsssample.template');
goog.require('soy');
goog.require('soy.StringBuilder');

gsssample.template.hello = function(opt_data, opt_sb) {
  var output = opt_sb || new soy.StringBuilder();
  output.append('<div class="', goog.getCssName('message'), '">Hello World!</div>');
  return opt_sb ? '' : output.toString();
};

CSS クラス名の部分が getCssName() の呼び出しになっていることがわかりますね。このように Closure Templates が Closure Library 互換の JavaScript を生成してくれるので、 CSS クラス名置換の仕組みがそのまま使えるわけです。

次は、 scripts/gss_sample.js の処理を書き換えます。 gsssample.template を require しているのと、 goog.dom.createDom() で DOM 要素を作成する代わりにテンプレートをレンダリングしているのが変更点です。

goog.provide('gsssample.App');
goog.require('goog.dom');
goog.require('goog.soy');
goog.require('gsssample.template');

gsssample.App = function() {
  goog.dom.append(
    document.body,
    goog.soy.renderAsElement(gsssample.template.hello));
}
goog.addSingletonGetter(gsssample.App);

gsssample.App.getInstance();

もうひとつ SOY テンプレートをレンダリングするのに必要なのが、 Closure Templates に付属している soyutils_usegoog.js です。これを scripts フォルダの中にコピーしておいてください。

これで準備ができたので、 Closure Compiler (closurebuilder.py) でコンパイルします。手順はさきほどの Closure Library のみの場合と同じです。

python closure-library/closure/bin/build/closurebuilder.py \
  --root=closure-library --root=scripts -n gsssample.App \
  -o compiled --output_file=built.js -c compiler.jar \
  -f "--compilation_level=ADVANCED_OPTIMIZATIONS" -f "--js=renaming_map.js"

その後、 index.html を表示すれば、これまでどおり「Hello World!」が表示されるはずです。サンプルの結果が代わり映えしなくて申し訳ないですが…(涙)

なお、ここでは一気に Closure Compiler による最適化を行なってしまいましたが、 Closure Library のみの場合と同様にビルド前に CSS クラス名の短縮を適用することも可能です。 Closure Templates は単に Closure Library 向けの JavaScript コードを吐き出しているだけなので、 Closure Library 単体の時とやっていることは変わりません。

Closure Tools 以外と連携する

ここまで見てきたとおり、 GSS のクラス名短縮機能は Closure Library と緊密に連携するようになっています。しかし、これだけ便利な機能なので他のライブラリでも使いたいと思いますよね。また、 Closure Library を使う場合でも HTML はサーバーサイドで生成したいことがあるでしょう。

そうした用途も考えてか、 GSS は rename_map.js を単純な JSON 形式で出力できるようになっています。それを読み込んでテンプレートエンジンで適切に展開すれば、さまざまなシステムでクラス名の短縮が利用できます。ここでは Rails との連携を例にして、その方法をご紹介します。手抜きなので Ruby 1.9 前提です。

まずは、 Rails の新しいアプリを作成して、適当にコントローラを追加します。さらに RAILS_ROOT/gss ディレクトリを作成し、そこに上で使った gss_sample.gss をコピーして最適化しておきます。最適化結果の .css はビューに読み込まれるように RAILS_ROOT/public/stylesheets に出力しておいてください。

rails new gsssample
cd gsssample
rails generate controller Hello world
mkdir gss
cp /path/to/gss_sample.gss gss
cd gss
java -jar closure-stylesheets.jar \
  --output-file ../public/stylesheets/gss_sample.css \
  --output-renaming-map renaming_map.json \
  --output-renaming-map-format JSON --rename CLOSURE gss_sample.gss

次に、 CSS クラス名を短縮名に置き換えるための CSSNameShortener クラスの定義を RAILS_ROOT/lib/cssnameshortener.rb に保存します。かなり手抜きですがご勘弁ください。

class CSSNameShortener
  def initialize(name_map)
    @name_map = JSON.parse(IO.read(name_map))
  end

  def [](name)
    name.split('-').map {|n| @name_map[n] || n }.join('-')
  end
end

コントローラで上記ファイルを読み込み、インスタンスを作成してビューに渡します。

require 'cssnameshortener.rb'

class HelloController < ApplicationController
  def world
    fname = File.join(Rails.root.to_s, 'gss/renaming_map.json')
    @css = CSSNameShortener.new(fname)
  end
end

ビューを書き換えます。

<h1 class="<%= @css['message'] %>">Hello World!</h1>

これで終了。サーバーを起動して http//localhost:3000/hello/world にアクセスすれば、これまでどおりの赤い「Hello World!」が表示されるはずです。

このように、 JSON を読み込んで CSS クラス名を置き換える部分さえ実装すれば、 Closure Tools 以外でも CSS クラス名の短縮を利用できます。注意点は、ハイフンが含まれるクラス名は分割してから処理しなければいけないこと。 Closure Library の getCssName() がそういう仕様なのです。

その他の機能

最後に、ここまでで説明できなかった機能を 2 つほどご紹介。

デバッグ機能

GSS の --rename オプションに DEBUG を指定すると、クラス名を短縮する代わりに、終端にアンダーバーを付加した名前に変換します。

java -jar closure-stylesheets.jar \
  --output-file gss_sample.css --output-renaming-map renaming_map.js \
  --output-renaming-map-format JSON --rename DEBUG gss_sample.gss

この結果 (gss_sample.css) はこうなります。

.message_{color:red;font-size:24px;font-weight:bold}

こうすることで、例えば getCssName() を通すのを忘れていてスタイルが正しく適用されていない場合に、問題のクラス名を素早く特定できます。開発中はこちらのモードを使っておくのが良いかもしれません。

特定のクラス名を短縮しない

一部のクラス名については短縮せずそのままにしておきたいこともあるでしょう。そんなときは、 --excluded-classes-from-renaming オプションにそのクラス名を指定します。

java -jar closure-stylesheets.jar \
  --output-file gss_sample.css --output-renaming-map renaming_map.js \
  --output-renaming-map-format JSON --rename CLOSURE \
  --excluded-classes-from-renaming message \
  gss_sample.gss

短縮しないクラス名が複数ある場合は、 --excluded-classes-from-renaming オプションをその数だけ指定します。これはちょっと面倒ですね…。正規表現とかで指定できるといいんだけど。

以上、前回と 2 回にわたって Closure Stylesheets をご紹介してきました。個人的には CSS 最適化ツールの決定版かなと思っています(なんせ最近は Closure Library しか使ってないもので…)。なんといっても、ファイルサイズを気にせずに長い CSS クラス名を使いまくれるというのはいいですね。ミックスインなどの拡張機能と相まって、 CSS のメンテナンス性を大きく向上できます。皆さんもぜひご活用ください!

関連記事

この記事にコメントする

Recommendations
Books
「Closure Library」の入門書です。
詳しくはこちらの記事をどうぞ!
Categories
Recent Articles