このブログでは、記事中のプログラムの文法ハイライトに、 Syntax Highlighter and Code Colorizer for WordPress というプラグインを使用しています。 JavaScript と CSS で、プログラムを見易くしてくれる素敵なプラグインなのですが、 JavaScript ファイルだけで 1 度に 18 ものファイルを読むのが気になるところです。

外部のリソースが増えると、当然 HTTP リクエストも増え、ページ全体の表示時間に影響が出ます。

というわけで、以下のようなスクリプトを用意しました。

使い方

まずは、上記 Ruby スクリプトを syntax-highlighter-minimizer.rb といった適当な名前で保存します。

このスクリプトの動作には、 JSMin という Rubygems が必要なので、インストールします。

$ gem install jsmin --no-rdoc --no-ri

スクリプトは以下のように使用します。

$ ruby syntax-highlighter-minimizer.rb /path/to/syntax-highlighter-and-code-prettifier/script

プログラムの引数として、 sh***.js といったファイルの含まれるディレクトリのパスを渡して実行します。すると、ディレクトリ内に以下のようなファイルが保存されます。

  • syntax-highlighter-all.js … sh***.js をすべて結合したもの
  • syntax-highlighter-all-min.js … ***.js をすべて結合し、難読化したもの

仕上げとして、 JavaScript ファイルの読み込み部分を書き換え、ファイルを結合・難読化したものと置き換えます。

./wp-content/plugins/syntax-highlighter-and-code-prettifier/syntax-highlighter.php

18 もあった JavaScript ファイルがひとつにまとまりました !

難読化とは

文字通りに言えば、プログラムを読みにくくすることですが、実際はソースコードの圧縮が目的です。インデント等を極力少なくすることで、圧縮されます。

どれぐらいの効果があるか

以下のようなページを作って測定してみました。

  • default.html … 元の 18 の JavaScript が読み込まれる
  • all.html … 18 の JavaScript を 1 つのファイルに結合したものが読み込まれる
  • all-min.html … 1 つのファイルに結合したのち、難読化したものが読み込まれる

いずれも、 script タグにより JavaScript が読み込まれるだけのシンプルなページです。 FireFox で Ctrl + F5 の強制更新で、 onload イベントの発生までの時間を計測しています。

default.html all.html all-min.html
1 379ms 140ms 180ms
2 375ms 148ms 168ms
3 556ms 131ms 133ms
4 365ms 135ms 124ms
5 370ms 146ms 137ms
6 363ms 128ms 133ms
7 370ms 257ms 132ms
8 397ms 122ms 133ms
9 411ms 127ms 136ms
10 362ms 135ms 135ms
Avg. 394.8ms 146.9ms 141.1ms

難読化による効果は誤差程度に止まりましたが、 JavaScript ファイルをまとめることは、絶大な効果があるようです。今回の例でいえば、 2.8 倍近い高速化が実現できました。

ただし、実際のウェブサイトは、 JavaScript 以外にもたくさんの要素を含み、キャッシュ等の要因も絡むため、ここまでのインパクトは無いと思いますが、ページの表示時間が気になるときは、是非検討してみるべきでしょう。

今後の予定

今回は特定のプラグインに標的を絞りましたが、 WordPress といった CMS を使用する場合、プラグインで JavaScript や CSS の数がもの凄いことになっていた ! というのはよくあると思います。それらを 1 つ 1 つ潰していくことで、案外バカにできない効果が期待できそうです。

あと、 JavaScript ファイルを減らそう ! とかいいながら、この記事には GitHub の Gist が 2 つも貼り付けられています。これらは JavaScript の外部リソースによるものなので、ページ全体の読み込み時間に影響します。ページの読み込み時に読み込むのではなく、「このコードは読んでみたいな」と思ってクリックするまで、読み込みを遅延させる、という仕組みもアリかもしれません。

参考文献

[エラー: asin:487311361X というアイテムは見つかりませんでした]

Yahoo ! のフロントエンド最適化チームによるノウハウ集で、 JavaScript の結合・難読化についてはほとんどこの書籍に書いてあります。

その他にも様々なテクニックが紹介されていますが、その中でも特に敷居が低いのが、この JavaScript についての最適化です。

是非試してみてはいかがでしょうか。

, ,

JavaScript ではよくある、こういう書き方。

一応説明すると、これは実行時に外側の無名関数を実行します。外側の無名関数は返り値として、内側の無名関数を返し、変数 counter には内側の無名関数が代入されます。

これの何が嬉しいのか。

  • counter 関数が内部に持っている変数 i は隠蔽されており、外から変更できない。
  • グローバル空間の汚染は counter 1 つだけ。

といったところでしょうか。とにかく、 JavaScript 脳の人はこういう感じのコードをよく書いている気がします。 JavaScript にはオブジェクト指向によくある protected や private といったアクセスレベルを制御する機能が無いため、これの応用で内部の変数やメソッドを隠蔽することが多いです。 Prototype や jQuery でもそういった使い方がされています。

このように、 JavaScript では無名関数の中にスコープを閉じ込めることができ、これをクロージャと呼びます。

で、最近久しぶりに勉強している Perl 。 Plack のコードを読んでいる中で、 Perl にも無名関数とクロージャがあるということを知ったので、試してみました。

上部のおまじない的なものを除けば、やってることは本質的に何も変わりません。

ついでに Ruby も。これもやっていることは特に変わらないと思います。

Python にも無名関数 (ラムダ) はあるので、同じようなことができそうな気がしたのですが、うまくいかず、代わりにこんな書き方に。

これはクロージャではなく、ジェネレータという機能を使っています。内部の変数が隠蔽されており、名前空間の汚染も 1 つだけですが、最初は関数だった counter にジェネレータをぶち込むというのは、お行儀的にはよくないかもしれません。

で、 PHP でも 5.3 からは無名関数・クロージャが使えるようになったので、試してみたのですが、以下の JavaScript 的な記述では parse error となりました …

以下のように書き換えると動きますが、これでは名前空間の汚染が 2 つです。

そして、何かもう方向を見失っている例。

変数の名前空間汚染は 1 つだけど、クラス名の名前空間まで汚染してしまっている上、返り値が文字列になっています。 (__toString はそのように定義しないといけないため)

そして、一応数値を返すようにしたものが以下。

なんか、 JavaScript と Perl の話だったはずが、暗に PHP を Dis る結果になってしまったので、今日はこの辺で。

, , , , , ,

通勤電車の中で思いついたコードを、昼休みを利用して書いてみました。動いたので公開。ただし、バッドノウハウなので要注意。

解説

まず、クロージャを利用して、外からは見えない privateNameSpace という名前空間を作ります。この privateNameSpace 自体は、言わば private static なものなので、Dog オブジェクトが個別に持つものではなく、Dog クラスに紐づく (ように見える) 空間です。

これを、各インスタンス毎に分けて使えるよう、Dog コンストラクタで objectId というメンバ (これは public) を作っています。objectId は、ミリ秒単位の時間と、乱数を文字列として結合したもので、およそユニークであることが期待されています。とはいえ、理論上は衝突の可能性もあり得るのですが・・・。

この objectId をキーに、privateNameSpace の中に、連想配列オブジェクトを作れば、インスタンスごとに private な名前空間を持つことができる、というわけです。

2 番目のコードを動かしてみると、何となくそれらしく動いていることが確認できます。

ただ、大きな問題は、objectId を乗っ取ることができてしまうということです。3 番目のコードのようにすることで、もともとは pochi オブジェクトだったものが、あたかも taro オブジェクトであるかのように振舞うようになってしまいます。

まとめ

上記の問題の他にも、メンバの取得のための記述が煩雑なところも大きな問題と言えます。もう少し抽象化できれば書きやすくなるかもしれませんが、いずれにせよ、実用的な方法では無いでしょう。

似たようなことを実現するためのノウハウはネット上にいろいろ転がっているようなので、そちらも読んでみたいと思った次第です。

あとで読む

,