DecompressionStreamでJSを解凍します
#Demoscene #圧縮 #CompressionStream #JavaScript
compekoの話
Why
Demoscene、64KB Intro (64k)
64KB制限
1ファイル
バイナリを別ファイルにするなどは禁止
httpサーブなし
当然gzipは効きません
--allow-file-access-from-files はお情けでオッケー
バカじゃねーの
Related Works
Content-Encoding: なにもしなくてもサーバは勝手に圧縮してくれるしクライアントは解凍してくれる チートか
pnginator・JsExe: JSコードをpngに格納し、末尾にHTMLのペイロードを挿入。canvasを用いて復元。
Our Method
DecompressionStream使えるんじゃね?
https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream
htmlは末尾にバイナリでペイロード入れてもちゃっかり動いてしまうため、JsExeと同じようなことができる
Working Code
code:js
const { readFileSync, writeFileSync } = require( 'fs' );
const { resolve } = require( 'path' );
const zlib = require( 'zlib' );
const inputPath = resolve( process.cwd(), process.argv 2 );
const outputPath = resolve( process.cwd(), process.argv 3 );
const inputFile = readFileSync( inputPath );
const compressed = zlib.deflateSync( inputFile, {
level: 9,
} );
const header = '<script>fetch("#").then(t=>t.blob()).then(t=>new Response(t.slice(156).stream().pipeThrough(new DecompressionStream("deflate"))).text()).then(eval)</script>';
const headerBuffer = Buffer.alloc( header.length );
headerBuffer.write( header );
const concated = Buffer.concat( headerBuffer, compressed );
writeFileSync( outputPath, concated );
localhost ・ --allow-file-access-from-files ともに問題なく動きます
Comparison
Domain by 0b5vr で比較してみた
jsexe: 54,519 bytes
これ: 56,578 bytes
だめじゃん
解散
敗因
PNGOUTがたぶん強い
PNGOUTの公式サイト 見に行ったら、なんか「その辺のソフトより強い圧縮してるよ」とか書いてあるので、たぶん強いんだと思う
zopfliで再戦
zopfli !!
上のソースコードのzlibをnode-zopfliでリプレイス
code:js
const compressed = zopfli.zlibSync( inputFile, {
numiterations: 100,
blocksplitting: true,
} );
Comparison (2)
jsexe: 54,519 bytes
これ: 54,174 bytes
ウオオオオオオオオオオオオ!!!!!!
jsexeの時代、終わり!
Compatibility
https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream より
Firefoxが対応してない
知らね~~~~~~~~!!!!!!
Update 2023-05: Firefoxも対応しました!
Future Works
FirefoxのDecompression Stream対応を祈る
Mozillaさんの気分は "worth prototyping" とのことです 🔗
より短いヘッダコード
全然考えてないのでまだまだ短くなるはず
Brotli
より強い圧縮アルゴリズム
W3Cで検討はされているみたい 🔗
実現した場合、Domainが 47,358 bytes に圧縮されるらしいです
glTFみたいに複数のbufferviewにする?
複数のバイナリをがっちゃんこして、チャンクの一部を参照してコード、他の一部を参照して画像アセット……
現状、画像データや音声データはbase64で文字列として格納されているので、これで多少はサイズが小さくなる可能性がある
いや結局デコード部分のコード含めたらそうでもないかも……?
gist
https://gist.github.com/0b5vr/09ee96ca2efbe5bf9d64dad7220e923b
Update 2022-08-15: fetchcrunch
subzeyさんという方がほぼ同じアプローチの手法 "fetchcrunch" を同時期に別で開発していました
zopfliで固めたjsをDecompressionStreamで読む感じ
https://github.com/subzey/fetchcrunch
one-knob-cyberiaを圧縮してみたところ、同じzopfliのイテレーションでcompekoのほうがわずかに小さかった(4,094 vs. 4,099) 俺の勝ち
ただ、ヘッダを見るともうちょっと差がついても良いんじゃないかなという感じだった たぶんdeflateのバイナリ部分がfetchcrunchのほうがもっと小さい
現状、node-zopfliのzopfliのバージョンはv1.0.2らしいが、fetchcrunchのほうが新しいzopfli(v1.0.3)を使っているっぽいので、その差かもしれんね
https://media.discordapp.net/attachments/534447164043165700/1008701322436038696/unknown.png?width=138&height=262 https://media.discordapp.net/attachments/534447164043165700/1008701322863841360/unknown.png?width=137&height=220
ヘッダの改善
fetchcrunchを読んでみると、compekoのヘッダにいくつか改善点が見つかったので、改善してみた
<script></script> → <svg onload="">
かしこすぎ
fetch("#") → fetch\`#\`
これはシンプルに記法を知らなかった
deflate → deflate-raw
4月時点ではまだ deflate-raw がW3CのUnofficial Draftに乗っかっていなかったが、いつの間に使えるようになっていた
以下、改善版compeko
deflateのヘッダも合わせて6 bytesの削減です やったね
https://gyazo.com/f2800ac4943f1f4de06d73af9eeb7f56