2011年9月6日

Waf と YUI Compressor で静的ファイルを圧縮する

Waf を使って JavaScript と CSS を探し、YUI Compressor の引数に渡すことで 静的ファイルを圧縮します。

Apache Ant で YUI Compressor を使う方法は、次の英語の記事で網羅的に説明されています。

  • Building Web Applications With Apache Ant - Julien Lecomte's Blog
    • Apache Ant
    • Build types - 開発用、検証用、製品用に分けてビルドする
    • Concatenate your JavaScript and CSS files - 順番を意識してファイル内容を結合する
    • Preprocess your JavaScript files using CPP - C のプリプロセッサを使って条件付きビルドを実現する
    • Minify your JavaScript and CSS files - yuicompressor.jar を使って JavaScript と CSS を圧縮する
    • Work around caching issues - 変更のないファイルをキャッシュするためのテクニック
    • Deploy your application - サーバーにファイルを転送し、サービスを再起動する
    • Conclusion - みんながビルドプロセスを考慮すべき

この記事では yuicompressor.jar の使い方の部分だけを取り上げます。 なお、Python 関連では buildout も使えます。

インストール

まずはいくつかのファイルをインストールします。 virtualenv を使って Waf を利用可能にし、JAR ファイルを配置します。 PyPI に yuicompressor 2.4.6.1 がありますので pip で JAR ファイルもインストールできます。

$ virtualenv --distribute yuicompress-test
$ cd $_
$ source bin/activate
$ curl http://waf.googlecode.com/files/waf-1.6.7 >bin/waf
$ chmod +x bin/waf
$ pip install yuicompressor
$ find . -name yuicompressor\* -type f
./bin/yuicompressor
./lib/python2.7/site-packages/yuicompressor/yuicompressor.jar

bin/yuicompressor というコマンドがインストールされます。 これは yuicompressor.jar ファイルへのラッパースクリプトになっています。 事前条件として、実行環境には Java がインストールされている必要があります。

入出力ファイルの確認

static というディレクトリにふたつのファイル - script.js, style.css - を置きます。 これらのファイルを圧縮し、ビルドディレクトリに出力します。 このとき、圧縮した結果は拡張子の前に "-min" を付けたファイルに出力します。

サンプルの準備

まずはサンプルファイルを準備します。

$ mkdir static
$ touch $_/script.js $_/style.css
$ ls static
script.js  style.css

ant_glob() と change_ext()

ant_glob() を使って、拡張子が .js.css に一致するファイルを探します。 結果はリストとして取得できますので、ループを回してファイルを処理します。 このとき、 change_ext() を使ってファイル名を変更します。

wscript:

APPNAME = 'yuicompress-test'
VERSION = '1.0.0'

top = '.'
out = '_build'

def configure(ctx):
    ctx.find_program('yuicompressor')

def build(bld):
    assets = bld.path.ant_glob(['**/*.js', '**/*.css'])
    for src in assets:
        dst = src.change_ext('-min' + src.suffix())
        print "%s\n\t-> %s" % (src.abspath(), dst.abspath())

実行すると次のようになります。

$ waf configure build
Setting top to                           : /private/tmp/yuicompress-test
Setting out to                           : /private/tmp/yuicompress-test/_build
Checking for program yuicompressor       : /private/tmp/yuicompress-test/bin/yuicompressor
'configure' finished successfully (0.004s)
Waf: Entering directory `/private/tmp/yuicompress-test/_build'
/private/tmp/yuicompress-test/static/script.js
        -> /private/tmp/yuicompress-test/_build/static/script-min.js
/private/tmp/yuicompress-test/static/style.css
        -> /private/tmp/yuicompress-test/_build/static/style-min.css
Waf: Leaving directory `/private/tmp/yuicompress-test/_build'
'build' finished successfully (0.059s)

ループを回すのではなく、拡張子に対してルールを設定することもできます。 この方が依存関係の解析が早くなるから、、、か確証はありませんが、きっと高速なはずです。

YUI Compressor の実行

yuicompressor コマンドを引数なしで実行すると、使い方が表示されます。

$ yuicompressor

Usage: java -jar yuicompressor-x.y.z.jar [options] [input file]

Global Options
  -h, --help                Displays this information
  --type <js|css>           Specifies the type of the input file
  --charset <charset>       Read the input file using <charset>
  --line-break <column>     Insert a line break after the specified column number
  -v, --verbose             Display informational messages and warnings
  -o <file>                 Place the output into <file>. Defaults to stdout.
                            Multiple files can be processed using the following syntax:
                            java -jar yuicompressor.jar -o '.css$:-min.css' *.css
                            java -jar yuicompressor.jar -o '.js$:-min.js' *.js

JavaScript Options
  --nomunge                 Minify only, do not obfuscate
  --preserve-semi           Preserve all semicolons
  --disable-optimizations   Disable all micro optimizations

If no input file is specified, it defaults to stdin. In this case, the 'type'
option is required. Otherwise, the 'type' option is required only if the input
file extension is neither 'js' nor 'css'.

最後の部分を日本語に訳してみると次のような感じでしょうか。

入力ファイルが指定されないと標準入力から読み取ります。 この場合は、 'type' オプションは必須です。 そうでなければ、入力ファイルの拡張子が 'js' でも 'css' でもない場合に限り、 'type' オプションは必須です。

ということで拡張子を気にする必要はありませんので、コマンドラインから次のように実行します。

$ yuicompressor static/script.js >_build/static/script-min.js
$ yuicompressor static/style.css >_build/static/style-min.css

もしくは、 -o オプションを使って出力先を指定します。

$ yuicompressor -o _build/static/script-min.js static/script.js
$ yuicompressor -o _build/static/style-min.css static/style.css

-o オプションにはマクロのような機能もありますので、 複数ファイルを一括で指定することもできるそうです。

Waf で実行させる

wscript に独自のタスクを定義できますので、 先ほどのルールを参考にしてタスククラスを書きます。

wscript にまとめると、次のようになります。

APPNAME = 'yuicompress-test'
VERSION = '1.0.0'

top = '.'
out = '_build'


def configure(ctx):
    ctx.find_program('yuicompressor')


from waflib.Task import Task

class yuicompressor(Task):
    def run(self):
        return self.exec_command('yuicompressor %s >%s' % (
                self.inputs[0].abspath(), self.outputs[0].abspath()
            ))


def build(bld):
    assets = bld.path.ant_glob(['**/*.js', '**/*.css'])
    for src in assets:
        dst = src.change_ext('-min' + src.suffix())
        t = yuicompressor(env=bld.env)
        t.set_inputs(src)
        t.set_outputs(dst)
        bld.add_to_group(t)


適当なスクリプトとスタイルシートを書いてから実行してみます。

$ waf distclean configure build
'distclean' finished successfully (0.002s)
Setting top to                           : /private/tmp/yuicompress-test
Setting out to                           : /private/tmp/yuicompress-test/_build
Checking for program yuicompressor       : /private/tmp/yuicompress-test/bin/yuicompressor
'configure' finished successfully (0.004s)
Waf: Entering directory `/private/tmp/yuicompress-test/_build'
[1/2] yuicompressor: static/script.js -> _build/static/script-min.js
[2/2] yuicompressor: static/style.css -> _build/static/style-min.css
Waf: Leaving directory `/private/tmp/yuicompress-test/_build'
'build' finished successfully (0.811s)

生成されたファイルを比較してみると、次のようになります。

$ ls -l static/ _build/static/ |
    awk 'NF == 9 { printf "%-20s -> %5d (bytes)\n", $9, $5; }' |
    sort -r
style.css            ->  1621 (bytes)
style-min.css        ->  1422 (bytes)
script.js            ->   357 (bytes)
script-min.js        ->   215 (bytes)

注意

YUI Compressor を実行すると、解読できない構文エラーが報告されることがあります。 ソースコードのエラーかもしれませんが、YUI Compressor 側のバグかもしれませんので注意が必要です。

簡単に空白などを取り除きたい場合は、 JSMin の方が分かりやすいかもしれませんね。

終わりに

Waf を使って JavaScript と CSS を圧縮しました。 Ant でいいんじゃないの?と思った場合は、Ant の方が良いでしょうね。 Waf を使うメリットは Python ですべてを記述できることです。 Ant だと XML でルールを書きますが、独自のタスクを定義する場合には Java のクラスを実装する必要があります。 この辺の好きずきかな、と思いました。

文脈は異なっても、処理系のことはあまり気にせず文法などで選びましょう、 という流れは今後も加速するはずですから、色々と知っておいた方が良いのかな、というのが基本となる動機です。

Heroku for Java :

"A common deployment infrastructure reduces language choice to just a question of syntax and libraries."

ソフトウェアを動かすためのインフラが汎化されると、 プログラミング言語の選択は構文とライブラリを選ぶだけの問題になります。 (Java だから特定のベンダーサポートがあって ... という話は意味をなさない)

なお、 wscript のタスクに依存関係を持たせる方法はこちらにも書いています。

コメントを投稿