◆ uglifyjs が優秀
◆ TICKER は中で uglifyjs 使った上で packer 的な圧縮かけてる
◆ packer は js 構文を解釈してないみたいでセミコロン補われない
◆ google-closure-compiler はメモリ食いすぎて大きいファイルは無理
  ◆ ADVANCED はコードが壊れる 

前回の続きです

zip ファイルの状態だと

pdf.js → 85KB
pdf.worker.js → 296KB


とまあちょっと大きめなライブラリってくらいです

でも 解凍してみたら

pdf.js → 366KB
pdf.worker.js → 1.37MB


サイズ大きすぎ!



これが原因か pdf.js をロードしてからはページロードが重いなぁと思うようになりました

さすがに pdf.js をデバッグ実行でみることはないでしょうし minify 版をロードするようにしよう  としたのですが
そういえば .min.js がないです

だいたいのライブラリにはついてそうなものですけど pdf.js の pre-build 版にはついてません

minify ツール

無いなら自分で作るしかないので 適当に minify ツールを調べてみました

やっぱり有名なのは Google の closure compiler
YUI は昔はよく聞いたけど最近の日本語の比較ページではほとんど載っていません
海外の minify ツール紹介ページならけっこう見かけました

それと よくも悪くもない普通くらいと思っていた uglifyjs2 はかなり有名なトップクラスぽい感じになってます

昔は定番だった packer は一応まだ比較として出される程度にはあるようです

あと初めて聞いたのですが TICKER というのがあるみたい
http://qiita.com/akira_/items/c47b57de4f338fff95ad

圧縮率が他よりかなり優れてるとか

とりあえず
  • google closure compiler
  • uglifyjs2
  • packer
  • TICKER

を使ってみます

TICKER

1MB 超えてるファイルなので出来る限り小さくしたいのでまずは TICKER を調べてみました

と言ってもオープンソースじゃないみたいで公式サイトにファイルをドロップするしか方法がないみたい
http://solufa.io/ticker/

ツールをダウンロードすることもできません

solufa を minify するために作られたもの らしいので solufa の方のリポジトリに混ざってないか調べてみたのですが ヒットなしです
というか solufa と言うもの自体 公式サイトのデザインはいい感じですが リポジトリの Star や Issues がほぼないし README もないし 更新も数ヶ月されてないし かなりマイナーどころみたい
開発者が日本人みたいなので仕方ないかも?


なんにしろ TICKER は web 上でサーバにアップロードするしかないようです

使ってみる

pdf.js は OSS だし アップロードして困るものでもないので とりあえず使ってみました

すると


エラーになった!

ファイルサイズが大きすぎるみたい
pdf.js の方は無事成功したのですが pdf.worker.js の方はダメでした


とりあえず minify できた pdf.js のサイズを見てみると 99.47KB
かなり減ってます

packer 系で
eval(function(t,i,c,k,e,r){~~
という元の形を維持しないタイプでした

google closure compiler

TICKER がダメなので次は closure compiler を試してみます
npm -g install google-closure-compiler-js
google-closure-compiler-js --compilationLevel=SIMPLE pdf.worker.js > pdf.worker.min.js





FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory

メモリ不足!!


タスクマネージャを見てると 最初は 200MB 前後をうろうろしていたのに急に 1GB くらいにすごい勢いで メモリ使用量が上がっていきます

設定いじってみる

パソコン的には 4GB くらい使ってくれても全然かまわないので 最大量を調整しようと思います
調べてみると v8 オプションの
--max_old_space_size と --max_executable_size がメモリサイズみたい

google-closure-compiler-js に対してこのオプションをつけても不正なオプションと言われます
Error: java.lang.RuntimeException: Unhandled flag: max_old_space_size

なんで Java なんだろう
Java のランタイムをそのまま JavaScript に移植してる?

とりあえず node の方の引数にしないとダメなので内部的に nodejs を呼び出しているところにつけます

私は Windows 環境なので google-closure-compiler-js.cmd ファイルを編集します
このファイルの場所はいろいろありますが

nodejs 通常のもので -g でインストールしていれば AppData\Roaming\npm の中にあるはずです
nodist で -g だと nodist のフォルダの中の bin とかその辺です

-g がないときはカレントディレクトリの node_modules\.bin の中です


ファイルはこんなのでした
@IF EXIST "%~dp0\node.exe" (
  "%~dp0\node.exe"  "%~dp0\node_modules\google-closure-compiler-js\cmd.js" %*
) ELSE (
  @SETLOCAL
  @SET PATHEXT=%PATHEXT:;.JS;=;%
  node  "%~dp0\node_modules\google-closure-compiler-js\cmd.js" %*
)

こういう感じに設定
"%~dp0\node.exe" --max_old_space_size=2000 --max_executable_size=2000 "%~dp0\node_modules\google-closure-compiler-js\cmd.js" %*

node.exe が cmd ファイルと同じところにないのになぜか if 文が true のほうが実行されてました

closure compiler じゃダメみたい

設定はしたのですが 2000 (2GB) です
ググってみると 8192 とか指定していて いかにも 4GB や 8GB が設定できそうだったのですが 2 GiB を超えた指定をすると動きません

もう少しググってるとこんなページが
http://stackoverflow.com/questions/38587346/max-old-space-size-set-to-4gb-actually-limits-to-2gb

V8 のデフォルトでは 32bit 環境だと 512MB で 64bit 環境だと 1GB
--max-old-space-size を指定すれば最大で 32bit 環境は 1GB まで 64bit 環境では 1.7GB まであげられるようです

4GB や 8GB に変更してる人はどうなってるでしょう
nodejs のバージョンは新しいの使ってるはずなのですけど


気になったので 別のパソコンでも試してみると

これまで 使っていたパソコンでは 2GiB 以上を指定すると 実行直後にエラーになっていました
ですが 別のパソコンでは 4000 でも普通に読み込みが行われています
ただタスクマネージャで見る限りでは 4GB どころか 3GB に達する前にメモリ不足で落ちます

v8 options

max_old_space_size がメモリ最大量のようなので max_executable_size ってなんだったんだろう

気になったので少し調べてみます
node --v8-options

これで v8 オプションがでるらしい




(前略)
  --max_old_space_size (max size of the old space (in Mbytes))
        type: int  default: 0
  --initial_old_space_size (initial old space size (in Mbytes))
        type: int  default: 0
  --max_executable_size (max size of executable memory (in Mbytes))
(後略)

ふむふむ
なるほど

全く説明してないですね



ググってみると 2012 年の記事ですがこういうのがありました
http://erikcorry.blogspot.jp/2012/11/memory-management-flags-in-v8.html
--max_new_space_size (in kBytes)
The new space is where objects are normally created. With any luck they die young and GC in this space is very fast for dead objects, so it's a nice optimization. The new space normally sizes itself automatically, and you can't increase the max size without recompiling V8, so there is not much need to tune this one. If you want to keep pauses short you can pass a value of 1024 or less to this flag, and the pauses associated with new-space GCs may be reduced from up to 30ms to more like 0-2ms. Throughput may suffer.

--max_old_space_size (in Mbytes)
This defaults to 700Mbytes on 32 bit and 1400Mbytes on 64 bit. If you want to allow V8 to grow more than this then you can set it higher. The maximum is not known. If you set this very high, then V8 will save some time (it's a space-speed tradeoff) by using more memory. File a bug if you have a use case where
a) memory use rises in an unbounded way with a high value for this flag
and
b) the program runs without out-of-memory when you set the limit lower

--max_executable_size (in Mbytes)
We normally limit this for security reasons. It's a defence-in-depth defence against heap spraying attacks. On the client, don't increase it unless you have truly gargantuan programs. On the server side, heap spraying attacks are not normally feasible so this is not so important.

いきなりみたことない --max_new_space_size というのがあります
node --v8-options ではみつからないので node では設定できないか 消えたものかな

内容は
通常のオブジェクトが作られる場所で よく GC が回収してくれていい感じに最適化される場所  みたい?
サイズは自動決定で v8 の再コンパイルしないと最大値を増やせないようなので node のオプションにはいらないのかな


--max_old_space_size は 32 bit は 700MB で 64bit は 1400MB とさっきと微妙にサイズが違います
もっと増やせるようで最大値は不明ぽいです
さっきのページとは違っていて結局どうなのかよくわかりません

old ってなんだろうと思ってたのですが new と対応してたようです
new が GC ですぐ消される方みたいなのでこっちはあまり GC されず残り続けるメモリ空間だと思います
たぶん


--max_executable_size はヒープスプレー攻撃から守るためのセキュリティのための制限で この値は基本増やさないほうがいいみたい
といってもクライアントサイドでの問題なので node とかサーバサイドなら気にしなくていいものだとか
結局 max_old_space_size と一緒にこれも設定しないとメモリ使える量は増えないのかはわかりませんでした

packer

話がずれてきたので minify に戻ります
上の方に書いた Qiita のページでは packer もそれなりに圧縮効率が高いので 試してみます

といっても ほとんどの minify ツールって スペースや改行を消したり 変数名を短くしたり程度なのに packer は元の形を維持せず暗号化みたいなことになっています

文字数を減らすのを重視していて JavaScript で処理した結果 もともとのソースコードの文字列ができて それを eval で実行するものです
処理と言っても テキスト操作するだけなので一瞬です
昔のネットワーク速度で大きなファイルを通信する時間に比べれば ほんの僅かな処理時間だと思います


npm にあったので
npm -g install packer

とやってみたらコンパイルエラーになりました
ただダウンロードするだけじゃなくてコンパイル必要なタイプみたいです

Windows 環境だと面倒なので 別の方法を探します


公式サイトには .net のアプリケーションがありました
http://dean.edwards.name/download/

.NET 1.1 向けってすごく古いです

使ってみると ロードしたのをテキストボックスに表示するタイプ
大丈夫なのかなぁ と思いつつやってみると 意外にも問題なく動きました

考えてみると ブラウザならともかく Windows ソフトでなら 1MB 程度で落ちることはそうあるものじゃないですよね


ただ重大な問題がありました

動かない!!

原因はわからないですが よくわからないエラーでした
eval してるので devtools にも詳しいことがでてなかったです

調べるのも面倒なので とりあえず スキップします

uglifyjs2

1番人気らしい期待の uglifyjs です
2 ですがコマンドは 2 なしです
npm -g install uglify-js
uglifyjs -c -m -o pdf.worker.js pdf.worker.min.js
WARN: Side effects in initialization of unused variable pageStripingInformation [_pdf.worker.js:10947,12]
WARN: Side effects in initialization of unused variable segments [_pdf.worker.js:10997,8]
WARN: Dropping unused function parseJbig2 [_pdf.worker.js:10981,11]
WARN: Side effects in initialization of unused variable scanLength [_pdf.worker.js:11936,16]
WARN: Side effects in initialization of unused variable rcon [_pdf.worker.js:23360,6]
WARN: Side effects in initialization of unused variable wy [_pdf.worker.js:26426,18]
WARN: Side effects in initialization of unused variable num [_pdf.worker.js:26754,16]
WARN: Side effects in initialization of unused variable version [_pdf.worker.js:29222,12]
WARN: Side effects in initialization of unused variable length [_pdf.worker.js:29284,12]
WARN: Side effects in initialization of unused variable language [_pdf.worker.js:29285,12]
WARN: Side effects in initialization of unused variable dummy [_pdf.worker.js:42353,12]
WARN: Dropping unused variable pdfjsVersion [_pdf.worker.js:31,4]
WARN: Dropping unused variable pdfjsBuild [_pdf.worker.js:32,4]
WARN: Side effects in initialization of unused variable pdfjsFilePath [_pdf.worker.js:34,6]

とりあえず -c -m のオプションをつければいいみたい
いろいろ警告でていますが 問題なく動きました

ちゃんと JavaScript として解析して 使ってない変数を捨てたりしてるんですねー

結果をまとめると

pdf.js (366.42KB)
--(ticker)--> 99.47KB
--(uglifyjs2)--> 144.44KB
--(closure-compiler)--> 150.49KB

pdf.worker.js (1.37MB)
--(uglifyjs2)--> 582.70KB
--(other)--> n/a


唯一 pdf.worker.js を動く形で minify できたのは uglifyjs2 だけでした
人気ナンバーワンは伊達ではないですね

変換内容を見てみる

ながーい pdf.js のは見たくないので 短いコードを作って minify 書けてみました

1

とりあえずこれを minify します
!function(){
    var obj = {}
    obj.abcd = 1
    console.log(obj["abcd"])
}()

packer


まずは pdf.worker.js で minify できたけど動かなかった packer
eval(function(p,a,c,k,e,d){e=function(c){return(c<a?"":e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1;};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p;}('!4(){3 0={}0.2=1 6.5(0["2"])}()',7,7,'obj||abcd|var|function|log|console'.split('|'),0,{}))

長くなってます
元が短いときは minify じゃないですね

最終的に eval される文字列はこうなりました
!function(){var obj={}obj.abcd=1 console.log(obj["abcd"])}()

セミコロンがない!

単純にホワイトスペースを除去したテキストを作ってるだけで構文解析とかしてないみたいで minify するときに必要なセミコロンがおぎなわれていません

セミコロンなし派と相性が悪いだけでなく 実際多くのコードが動かないと思います
というのもセミコロン書く派の人でも 書き忘れた箇所があってもほぼ動くので 100% セミコロンが正しく打たれているコードは意外と少ないです
ネットでコード見てても このコードセミコロン抜けてると思うことがそこそこ見かけます


これが今ではほとんど使われない理由のひとつでもありそうです

google-closure-compiler


次は google-closure-compiler
>google-closure-compiler-js --compilationLevel=SIMPLE test.js
!function(){console.log(1)}();

おぉっ

すごくシンプルになりました
さすが SIMPLE

valueOf とか toString 書き換えたり プロキシしたりとか色々やってるとちょっと怖いですが google 製だしその辺はきっと大丈夫だよね

さらに圧縮率の高い ADVANCED もあるので試してみます
>google-closure-compiler-js --compilationLevel=ADVANCED test.js
console.log(1);!0;

関数すら呼び出されません
インライン展開されています

「!0」 は 「!functin(){}()」 の結果が true になるのでそこを合わせるためです

TICKER


次に TICKER
eval(function(t,i,c,k,e,r){e=String;if(!''.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])t=t.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return t}('!3(){4 0={};0.2=1,5.6(0.2)}();',7,7,'c||abcd|function|var|console|log'.split('|'),0,{}))

packer 同様 元よりは長いです
でも packer に比べると 短いです

eval される文字列はこれです
!function(){var c={};c.abcd=1,console.log(c.abcd)}();

一般的な minify 済みです

packer はこうでした
!function(){var obj={}obj.abcd=1 console.log(obj["abcd"])}()

minify 済みに packer の改良型の圧縮をするので圧縮率が高いようです

uglifyjs


最後に uglifyjs
!function(){var c={};c.abcd=1,console.log(c.abcd)}();

あれ? さっきも見たような……

TICKER と一緒です

もしかして TICKER って内部で uglifyjs 適用してから独自の圧縮してる??

これは短いからたまたまの可能性もあります

なので pdf.js を圧縮したもの比較してみました
すると
Promise.prototype["catch"]
Promise.prototype.catch

になるような違いはあったものの ほぼ完全に一緒でした
この僅かな違いはたぶん uglifyjs のオプションで変更できるものだと思います


もしかして ソース出さずにオンラインのみなのも 圧縮率一番と言いつつもそのほとんどを uglifyjs に任せてるのを隠したいからだったり?

でも公式サイトに minify と TICKER の比較が並んでるので uglifyjs の minify が minify のことで そこから TICKER でさらにこれだけ減らせるよ と言ってるようにも考えられます


まぁ TICKER の圧縮率の謎もわかりましたし アップロードが嫌なら uglifyjs のあとで packer かければ セミコロン問題もないしそこそこ圧縮されて良いんじゃないでしょうか

2

変数名の圧縮って怖いのでちょっと複雑にしたものを試してみます

コードはこれ
var u = function(n){
    var obj = {}
    obj.t = {abcd: 33}
    obj[n]["abcd"]++
    return obj
}
console.log(u("t"))


まずは closure-compiler の SIMPLE
>google-closure-compiler-js --compilationLevel=SIMPLE test.js
var u=function(b){var a={t:{abcd:33}};a[b].abcd++;return a};console.log(u("t"));

結果も問題なしです

次に ADVANCED
>google-closure-compiler-js --compilationLevel=ADVANCED test.js
console.log(function(b){var a={t:{a:33}};a[b].abcd++;return a}("t"));

んん?

abcd が a になってます

なのに同じキーを参照する別のところは abcd のままです

もちろんこれじゃ動きません

ADVANCED は使わない方がよさそう


最後に uglifyjs
var u=function(a){var c={};return c.t={abcd:33},c[a].abcd++,c};console.log(u("t"));

こっちも問題なし

closure-compiler よりちょっと長いです
pdf.js のときは uglifyjs のほうが圧縮率よかったので コード次第みたいです

ただ こっちはメモリ消費がそれほどないので こっちのほうがよさそう

まとめ

使える順
uglifyjs2 → google-closure-compiler(SIMPLE)

今回試したものでは その他は問題あるのが多いです

TICKER は内部で uglifyjs を使ってます