Go で作ったプログラムを GopherJS を使ってブラウザで動かした話

先日 CeVIO Creative Studio で出力した MusicXML を MIDI ファイルに変換するプログラムを作った

これは Go 言語を使って作ったのだが、頻繁に使うわけでもないツールをダウンロードして使うのは面倒だし、バージョンアップなどを考えるとブラウザから使えたほうが手軽かもしれないと思ったので、プログラムを更に GopherJS でブラウザ単体で動くようにしてみた
サーバサイドでは一切何もやらないため、GitHub Pages に置くこともできる。


GopherJS は C 言語に対する Emscripten のようなもので、Go 言語で書いたプログラムを JavaScript にコンパイルすることができる。
どの程度そのままで動くのかはここに対応表があり、例えばローカルファイルを触るような操作は node.js でのみ対応だったり、cgo や syscall のように Go 言語で書かれていない部分が関わるものは動かなかったりもする。
Goroutine にも対応しているらしいのだが、今回は使用しなかったため特に触れない。

今回のプログラムは元々はコマンドラインで引数としてファイルを渡すとそれを読み込み、ファイルを作成して終了するといった動作をするもの。
先述の通りローカルファイルへのアクセスはブラウザ上では難しいため、その部分は入出力共に ArrayBuffer で行うことにした。

以下、ここだけ掴んでおけばファイルを受け取ってファイルを吐くツール相当のものが作れるメモ。

1. Go 言語で書いた処理をJavaScript 側から 呼び出すには

今回のイメージとしては、jQuery などと同じようにライブラリとして Go 言語で書いた処理を読み込んでおき、必要なところで JavaScript から呼び出すような使い方をしたい。

そのためにはこの辺を参考にするといい

js.Global.Set を使って登録すると、JavaScript からその名前でアクセスできるようになる。
自分のプログラムではオブジェクトである必要すらなかったので、map ではなく直接関数を登録した。

2. JavaScript から Go 言語にバイナリデータを渡す

やり方は色々あるかも知れないが、先述の通り ArrayBuffer で行うことにしたので、JavaScript から以下のような感じでファイルを <input type="file" id="inputfile"> から読み込むような処理を書いた。
 var file = document.getElementById('inputfile').files[0];
 var r = new FileReader();
 r.onload=function(e){
  YourGoFunc(r.result);
 };
 r.readAsArrayBuffer(file);
これで Go 言語側に第一引数として ArrayBuffer が渡されてくる。
渡されてきたデータはこのコードで xhr.Get("response") を []byte に変換しているのと全く同じ方法で、[]byte に変換できる。

3. Go 言語から JavaScript にバイナリデータを渡す

Go 言語からは js.NewArrayBuffer を使えば []byte から簡単に ArrayBuffer に変換できる。
今回のツールではその ArrayBuffer を元に Blob を作成し、それを FileSaver.js で保存させるようにした。
Safari とかいうブラウザではちゃんとダウンロードできない問題があるらしい。

まとめ

ファイル関連の処理を差し替えた以外には何もしなくても動かすことができた。
Go 言語を使っている場合、データの入出力には io.Reader や io.Writer を使うのが定石のようなものなので、変にファイルに依存せずしっかりやっておけば大変持ち込みやすい。

実際の処理では Shift_JIS の XML を UTF-8 に変換しながら読み込み、MIDI ファイルの出力時には改めて Shift_JIS に変換して保存しているが、XML の読み込みは標準ライブラリがあり、文字コード変換には golang.org/x/text/encoding という準標準ライブラリもあり、いずれも大変完成度が高く Pure Go で書かれており外部ファイルにも依存していないため、何の問題もなく持ち込めたのが良かった。

charset.NewReaderLabel を使用すると XML 読み込み時に必要なことを全部やってくれて全く手間がかからないため今回はこれを利用したが、Big5 など今のところは必要ない文字コード変換周りの処理も読み込まれてしまうので、多少オーバーヘッドはありそうだ。

今回のプログラムは Windows 64bit 環境で go build すると 3.78MB になるが、gopherjs build -m すると js 2.39MB + js.map 68KB と大きさもなかなか健闘している感じになり、js ファイルを更に gzip 圧縮すると 463KB まで縮んだ。
ブラウザからアクセスした場合に転送される容量としては小さくはないが、利便性を考えると個人的には許容範囲内に感じた。

使いはじめるための準備も、Go 言語利用者なら誰でも知っている go get -u github.com/gopherjs/gopherjs だけで良く、簡単で素晴らしい。
今後も機会があれば積極的に利用していきたい。