JSの圧縮
JSの圧縮ツールにJsExeというのがある。ちょっとダウンロードしようとしたが出来なかった。
JsExeは持っていたが、どうせならばで作ってみた。
参考にした記事は
yomotsu.net
正月早々に、俺は何でJSでバイナリなんて弄っているんだって、ぼやきながら、ずぶずぶにハマっていた。で、出来てみれば、大したことのない行数で済んでしまうんだな。こんな感じ。python3で書いてます。
from PIL import Image import numpy import base64 from io import BytesIO def js2img(js): js = numpy.array(list(js.encode()), dtype='uint8').reshape((1, len(js))) img = Image.fromarray(js) buffer = BytesIO() img.save(buffer, "png") return buffer.getvalue() def asciiHtml(js, filename='asciiDemo.html'): f = open(filename, 'w') f.write( "<canvas id=c><img src='data:image/png;base64," + base64.b64encode(js2img(js)).decode() + "' onload=C=c.getContext('2d');for($=_='';C.drawImage(this,-$,0),X=C.getImageData(0,0,1,1).data[0],$++<this.width;_+=String.fromCharCode(X));(1,eval)(_)>" ) f.close() def binaryHtml(js, filename='binaryDemo.html'): f = open(filename, 'bw') f.write( js2img(js) + b"<canvas id=c><img src=# onload=C=c.getContext('2d');for($=_='';C.drawImage(this,-$,0),X=C.getImageData(0,0,1,1).data[0],$++<this.width;_+=String.fromCharCode(X));(1,eval)(_)>" ) f.close() def stdHtml(js, filename='stdDemo.html'): f = open(filename, 'w') f.write( "<body><script>" + js + "</script></body>" ) f.close() js = ''' var canvas = document.createElement("canvas"); canvas.style.position = "fixed"; canvas.style.cursor = "none"; canvas.style.left = canvas.style.top = 0; canvas.width = window.innerWidth; canvas.height = window.innerHeight; document.body.appendChild(canvas); var gl = canvas.getContext("webgl2") || canvas.getContext("experimental-webgl2"); var compileShader = function(prog, src, type){ var sh = gl.createShader(type); gl.shaderSource(sh, src); gl.compileShader(sh); gl.attachShader(prog, sh); gl.deleteShader(sh); }; vs = `#version 300 es void main() { gl_Position = vec4(ivec2(gl_VertexID&1,gl_VertexID>>1)*2-1,0,1); } ` fs = `#version 300 es precision mediump float; uniform vec2 resolution; uniform float time; out vec4 O; #define R(p,a,t) mix(a*dot(p,a),p,cos(t))+sin(t)*cross(p,a) #define H(h) (cos((h)*6.3+vec3(0,23,21))*.5+.5) void main(){ vec3 p,c=vec3(0), d=normalize(vec3((gl_FragCoord.xy-.5*resolution.xy)/resolution.y,1)); float i=0.,s,e,g=0.,t=time; for(;i++<99.;){ p=g*d;; p.z-=3.; p=R(p,vec3(.577),t*.3); s=3.; for(int i=0;i++<8;p*=e) p=vec3(1,3.+sin(t)*.3,2)-abs(p-vec3(1,2,1.5+sin(t)*.2)), s*=e=9./clamp(dot(p,p),.8,9.); g+=e=abs(p.y/s-.001)+1e-3; c+=mix(vec3(1),H(length(p*.2+.5)),.6)*.0015/i/e; } c*=c; O=vec4(c,1); } ` var p = gl.createProgram(); compileShader(p, vs, gl.VERTEX_SHADER); compileShader(p, fs, gl.FRAGMENT_SHADER); gl.linkProgram(p); gl.useProgram(p); gl.uniform2f(gl.getUniformLocation(p, "resolution"), canvas.width, canvas.height); var zero = Date.now(); (function () { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.uniform1f(gl.getUniformLocation(p, "time"), (Date.now() - zero) * 0.001); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); requestAnimationFrame(arguments.callee); })(); ''' stdHtml(js) asciiHtml(js) binaryHtml(js)
これで出来たbinaryDemo.html
が圧縮の対象。
stdDemo.html 1,986 バイト asciiDemo.html 2,005 バイト binaryDemo.html 1,530 バイト
こんな感じの圧縮状況。
binaryDemo.html はバイナリなので、通常では見れない。localhostを立てて見ます。完全にdemoparty仕様です。
python3を使ってlocalhostを立ててみるには
python -m http.server
これでOK。
http://localhost:8000/
にアクセスしてください。
asciiDemo.html はbase64を使った奴。サーバー上じゃなくても見れます。しかしJSのコードがbase64に変換されるので、まあ読めなくなりますね。
stdDemo.html は普通のパターンな奴。
ちょっと気がかりな点でperformance.now()
を使うとバイナリhtmlが表示出来なかった。他にも落とし穴的な関数があるかもしれない。
簡単なトリミング
JSの空白とか改行をトリミングする関数。shader用に使っていたのを、ちょっと弄ったので、まだ実績が無い。とりあえず使えそうなので載せておく。想定外なエラーが出る可能性有りです。
import re def trim(src): src = re.compile(r'/\*.*?\*/', re.DOTALL).sub("", src) src = re.sub(r"//.*", "", src) src = re.sub(r"\t", " ", src) src = re.sub(r" +", " ", src) src = re.sub(r" *\n *", "\n", src) src = re.sub(r"\n+", "\n", src) src = re.sub(r"^\n", "", src) ##### line = src.split("\n") for i in range(len(line)): s = line[i] if re.search("#", s) != None: line[i] = "\n" + line[i] + "\n" else: s = re.sub(r" *\+ *" ,"+", s) s = re.sub(r" *\- *" ,"-", s) s = re.sub(r" *\* *" ,"*", s) s = re.sub(r" */ *" ,"/", s) s = re.sub(r" *= *" ,"=", s) s = re.sub(r" *< *" ,"<", s) s = re.sub(r" *> *" ,">", s) s = re.sub(r" *& *" ,"&", s) s = re.sub(r" *\| *" ,"|", s) s = re.sub(r" *\( *" ,"(", s) s = re.sub(r" *\) *" ,")", s) s = re.sub(r" *\[ *" ,"[", s) s = re.sub(r" *\] *" ,"]", s) s = re.sub(r" *{ *" ,"{", s) s = re.sub(r" *} *" ,"}", s) s = re.sub(r" *; *" ,";", s) s = re.sub(r" *, *" ,",", s) line[i] = s src = "".join(line) src = re.sub(r"\n+","\n", src) return src
あとがき
JsExeはpngのバイナリを壊して更にminifyしてます。そこまではやってません。8bitグレースケールのpngを使っているので、大体良い線にはいっていると思います。
今年はコードを隠してみようかなってって考えてます。今まではコードを見て見て感が強くて、良いトリックを思いつくと、それで満足しちゃうところあって絵がおろそかになってました。そんな事もあって年の頭に、面倒な事をやってました。
追記
以前にJsExeを使って圧縮したファイルのサイズをみたところ、半分くらいに圧縮されているようでした。まだまだ奥がありそうです。pngの圧縮も出来るようなので、その辺りの技術を使っているのかもしれません。いづれ手を出すかも。まあ素直にJsExeを使わせて貰えって話もあるけど、そういうハックも面白かったりするので困ったものです。
後日の為に情報を残しておきます。
ハフマン符号化