Introduction
元々、typescript4jというTypeScriptをコンパイルできるJavaライブラリがあることを知り興味が湧いたのだが、
型定義ファイルの参照 /// <reference path="typings/jquery/jquery.d.ts" />
があると
コンパイルに失敗するらしく、調べてみた。
試してみるといくつか条件がありそうだったが、
相対パスで指定した型定義ファイルの位置を特定するのがうまくいかない場合があるようだった。
compiler.compile(new File("/Users/zaneli/ws/typescript4j/example.ts"), new File('output.js'));
のように第一引数に指定するtsファイルパスを絶対パスで指定し、
かつその中で参照されている型定義ファイルも同一ディレクトリに配置されている場合には
コンパイルできることが確認できたが、
それ以外の場合には参照しようとしているファイルパスの解決に失敗しているようだった。
typescript4jは内部でtypescript-compileを使用してTypeScriptファイルをコンパイルしており、
(これ以上あまり真面目に調べなかったけど)typescript-compile側で一手間必要なのかな、と思い
それを使わない形でコンパイルしてみたらどうなるかと試してみたのがきっかけ。
結論から言うと、TypeScript公式のコンパイラに使用されている tsc.js を使用することで
型定義ファイルの件については特に何か回避コードを入れるでもなく解決できたようだ。
Work procedure
TypeScript公式のソースコードをダウンロード(最新のリリースブランチっぽい release-1.0.2 を選択した)し、
bin/ディレクトリの tsc.js をScala(JVM)上で動かすためにRhinoを使用した。
動かしてみたところ、プロパティが無い、undefinedのメソッドが呼べないなどのエラーが色々出たので
tsc.js を読みつつトライ&エラーを繰り返す。
TypeScript.Environment
と TypeScript.IO
というオブジェクトを
WSH(Windows Script Host)の場合、node.jsの場合でそれぞれ作成しているようだったが、
Rhinoで動かしているのでどちらのオブジェクトも作成できず、
これらのオブジェクトのプロパティを参照しようとしてエラーが発生しているようだ。
TypeScript4sEnvironment, TypeScript4sIO というobjectを作り、Scalaのメソッドで補う形を取った。
それをTypeScriptCompiler.jsCodesで設定するようにしている。
実行の都度渡されるコンパイル引数は、本当はTypeScript.IO
のプロパティのひとつだが、
TypeScript4sIO
のこれ以外の値は実行の都度変わるものではないので、
今回はこれのみこんな感じでts4sargs
という変数に設定することにした。
また、コンパイル時にデフォルトライブラリ lib.d.ts が必要だったので、
これもダウンロードしたソースコードから持ってきた。
tsc.js 実行パスを取得し、その同一ディレクトリにある lib.d.ts を使う、という形で参照していたが、
jarに固めた場合これではうまくいかないだろうと思い、強引なやり方だがこんな感じで読み取った lib.d.ts の内容を渡すようにした。
他、色々無理やりだったり実装がイケてなかったりする箇所があるが、
とりあえず当初やりたかった事ができたので一旦 GitHub に上げた。
Todo
TypeScriptCompiler.jsCodes の設定方法に無理やり感があるので、もっとスマートにしたい。
理想としては、jsコードの上書きは
scope.put("TypeScript.Environment", scope, TypeScript4sEnvironment)
scope.put("TypeScript.IO", scope, new TypeScript4sIO(args))
val jsCodes = """
var batch = new TypeScript.BatchCompiler(TypeScript.IO);
batch.batchCompile();"""
cx.evaluateString(scope, jsCodes, "compile.js", 1, null)
これだけで済むように TypeScript4sEnvironment
と TypeScript4sIO
を作り替えたい。
(が、現状上手いやり方が分からなかった…)
コンパイル引数の指定や組み立てが冗長でScalaっぽくない気がするので
もっとスッキリさせたい。どうやったら綺麗になるかなー…。
TypeScript.Environment
と TypeScript.IO
の実装が適当(とりあえず動かして、失敗したら付け足す)なので、ちゃんと tsc.js を読んで必要な物を実装する。
コンパイル時に出しているログとかも Scala の logger を呼び出すようにしたい。
Javaの文字列をJavaScriptの文字列に変換するために、空文字を足すといういかにもな回避コードを入れているが、
Rhinoに変換メソッドみたいなのがあるんじゃないかな、と思っているので探してみたい。
Context.javaToJS(javaString)
とか new NativeString(javaString)
みたいな感じで。
何故こんな変換が必要だったかというと、tsc.jsでString.replace()を呼んでいる箇所があるのだが、
Javaの文字列のままだと replace(char oldChar, char newChar)
か replace(CharSequence target, CharSequence replacement)
か
どっちを呼んでいるのか曖昧で分からんとエラーが出たため。
また、String.length
もJavaの文字列では呼べないため、
length の値で String.substring
を呼んで長さに合わずエラーになる箇所もあり、
現状2箇所でこの回避コードを入れている。
(そもそもJavaからJavaScriptへの変換なんてしなくても対応可能ならそちらを選択したい…)