duocode - C# to JS

今までキワモノだぁ!と思っていたdocodeをちょっと試してみたので、忘れないように書き残しておきます。
主にマルチプラットフォームに耐えうるか調べてみます。

  • duocode

http://duoco.de/
doccode それはC#JavaScriptに変換してくれる素敵なツール。VSコンパイラの機能で行っているようです。
30日間無料があるのでそれで使用感をつかみたいと思います。

インストール

インストールは公式にそって大まかに進めてください。
ただし、以下の2点が大まかな説明からは抜けているので、個別にインストールしないとコンパイルできません。。

  • iis express のインストール

https://www.microsoft.com/ja-JP/download/details.aspx?id=48264
webアプリのプロジェクト作成に必要になります。もちろんiisが入っていれば不要でしょう。

  • node watcher

nodeアプリのデバッグに必要なようです。
実行時に「無いのでこのコマンドでインストールしてね」と出るので省略します。
自分の環境では壮大にインストールエラーになりましたが、その後デバッグ可能になりました。コワイです。きっと機能不完全だと思います。
今回は使わないのでパスします。

プロジェクト

大きく分けて3種類のプロジェクトが選択可能になります。

  1. webアプリ(html)
  2. クラスモジュール
  3. nodeJSアプリ

感触をつかむには最初はwebアプリがいいでしょう。

webアプリデバッグの注意点

さっそく売りの1つのVS上でのデバッグを試してみたくなりますよね。
サンプルにも「ここにブレークポイントを置いてみよう!」なんてコメントもあります。わくわくしますね。
設置して実行すると、あ…豪快に通過しますね…ダメじゃないですか。


理由はリモートターゲット(webなのでブラウザ)によって対応してないみたいです。
そんな…Edgeちゃんここでもダメなの?IEだとちゃんと止まるようですね。しかしめっちゃ重い……。
Edgeの場合はブラウザ上でデバッグすれば問題ありませんでした。吐き出されたJSにSourceMapコードがついてるので、対応ブラウザでちゃんと元のC#ファイルの箇所を示してくれるようです。
うん。

クラスモジュールの作成

慣れてきた(?)ところで、最小構成とマルチプラットフォームの程度が知りたいのでクラスモジュールを試します。
この機能はC#の一連のクラスモジュールをそのままJSのモジュールに変換します。デフォルトでは自動的に一個のファイルの形に結合して落とし込んでくれます。
新規プロジェクトを選ぶと、空のクラスが作成されます。適当に書いて弄ってコンパイル
script\ フォルダに返還後のJSファイルなどが出力されます。

ClassLibrary1.dll     // C#クラスモジュール
ClassLibrary1.js      // 出力JS
ClassLibrary1.js.map  // JS SourcaMap debug
ClassLibrary1.pdb     // VS SourceMap debug
mscorlib.d.ts         // type script 定義ファイル
mscorlib.js           // .net 基礎ライブラリのJS版
mscorlib.min.js       // mscorlib.js最適化版。改行と空行を無くしたもの

mscorlib と一緒に出力・使用されるのが duocode の特徴です。
動作させるには mscorlib.js/mscorlib.min.js と ClassLibrary1.js の2つを使用します。
webや組み込みであれば

<script src="mscorlib.js" />
<script src="ClassLibrary1.js" />

という感じで2つのソースを連結すれば動作します。
またnodeJSなら

require("mscorlib.js");

を ClassLibrary1.js の先頭に追加すれば動作します。


なお、変換されたJS(ClassLibrary1.js)はこんな感じになっております。

(function ClassLibrary1() {
"use strict";
var $asm = {
    fullName: "ClassLibrary1",
    anonymousTypes: [],
    types: [],
    getAttrs: function() { return [new System.Reflection.AssemblyTitleAttribute.ctor("ClassLibrary1"), new System.Reflection.AssemblyDescriptionAttribute.ctor(""), new System.Reflection.AssemblyConfigurationAttribute.ctor(""), new System.Reflection.AssemblyCompanyAttribute.ctor(""), new System.Reflection.AssemblyProductAttribute.ctor("ClassLibrary1"), new System.Reflection.AssemblyCopyrightAttribute.ctor("Copyright \xA9  2017"), new System.Reflection.AssemblyTrademarkAttribute.ctor(""), new System.Reflection.AssemblyCultureAttribute.ctor(""), new System.Reflection.AssemblyVersionAttribute.ctor("1.0.0.0"), new System.Reflection.AssemblyFileVersionAttribute.ctor("1.0.0.0"), new DuoCode.Runtime.CompilerAttribute.ctor("3.0.1654.0")]; }
};
var $g = (typeof(global) !== "undefined" ? global : (typeof(window) !== "undefined" ? window : self));
var ClassLibrary1 = $g.ClassLibrary1 = $g.ClassLibrary1 || {};
var $d = DuoCode.Runtime;
$d.$assemblies["ClassLibrary1"] = $asm;
ClassLibrary1.Class1 = $d.declare("ClassLibrary1.Class1", 0, $asm);
$d.define(ClassLibrary1.Class1, null, function($t, $p) {
    $t.ctor = function Class1() {
        $t.$baseType.ctor.call(this);
        console.log("constructor :)");
    };
});
return $asm;
})();
//# sourceMappingURL=ClassLibrary1.js.map

TypeScriptの出力と決定的に違うのはすべてが mscorlib に依存したコードで出力されることです。細かい部分も.netをエミュレートしているような印象が強いです。よく見る.ctorなんかconstrucotrですね。
このソースを保守管理するの現実的なレベルではありません。ソースマップがあるとはいえ実行時エラーはかなり厳しい宇宙言語的な内容を返すでしょう。
デバッグや開発は基本的にC#で完結する環境を作って行い、多言語との受け渡しとしてDLLみたいな(もう中身は弄らないぞ!)感じで扱うなら問題無さそうです。とはいえ、DLLに比べれば追跡もエラー箇所特定も初期状態で恵まれた環境なのは確かです。

mscorlib.jsを許せるかどうか

このファイルの存在が duocode を使用した際の最大の課題点です。
.netの基礎部分を記したもので、duocodeで変換を行う限りこれに依存します。クラスの根幹部分もこれに依存しているため事実上分離は不可能です。サイズが mscorlib.js(864K) / mscorlib.min.js(471K)と結構あります。今後も増えるでしょう。容量と初回起動時のコストで評価が分かれると思います。個人的には規模の割に十分軽いと思いますが。
また、外部から使用する際は.netエミュレータの方言があるので、一層自前インターフェイスクラスで包んであげたりする必要があるでしょう。ただし、TypeScriptを使用するのであればこの辺の問題はパス出来そうです(後記)。
吐き出されたコードによるオブジェクト(クラス)の展開は、ちゃんとグローバルに構造を展開してくれているので、プログラムや他のJSから容易に引っ張ってこれる点はとても気が利いています。グローバル汚染と言われるかも知れませんが…。

所感

C#移植とおもって使うと肩すかしを食らいますが、言語変換としてみればとてもよく出来てると思います。デバッグまで付いてきますし。
単なるwebページに使うにはちょっと大がかりかなーというのは確かですが、C#におけるPCL(ポータブル・クラス・ライブラリ)の一種として使用するなら十分に運用の可能性があると思われます。
C#上で環境を構築、テストを行い、その一部モジュール(ただし環境依存性が無いこと)を色んなところで使いたいなら協力だと思います。
サンプルのような1からwebアプリを作るのは他の色んなJSライブラリを入れることを考えると……ちょっと現実的じゃない気がします。すべてC#で組む勢いならいいですが、結局既存のJSライブラリとの連携がどうにもならないので…。
nodeJSアプリについても同様の不安が残ります。データベースや各種HTTPのシェイクハンドなども言語依存性が高いですし…。


あくまでC#による一部資産を生かすための手段の1つ以上のことは望まないほうが幸せかな?と言った印象です。
おっと忘れてましたが、売りのVS上でのデバッグについては、思った以上に素直に動きませんでしたので残念な印象です。まだ産まれたばかりですし今後に期待しましょう。

おまけ、ChakraCoreで動かす場合

基本的には1つのファイルに結合すれば問題ないのですが、一点だけエラーが発生します。

var $g = (typeof(global) !== "undefined" ? global : (typeof(window) !== "undefined" ? window : self));

グローバルオブジェクトの取得で失敗します。検索する3つの変数がどれもデフォルトでは存在しません。まぁそうですよね。
global、windowsをプログラム側で用意してやれば動作します。selfでも大丈夫だと思いますが、他のオブジェクトのプロパティでも使われているのでどうかなーといったところです(参照優先順位的にだいじょうぶなのは確認済みです)

JsGetGlobalObject(globalref);
JsGetPropertyIdFromName("global",idref)
JsSetProperty(globalre,fidref,globalref); // (global).global = global

そろそろglobal参照を定義しませんか?JSさん…

おまけ、TypeScript出力

VSプロジェクト設定から選ぶことができま…あれ?declare?

/// <reference path="./mscorlib.d.ts" />

declare module ClassLibrary1 {
    // ClassLibrary1.Class1
    export interface Class1 extends System.Object {
    }
    export interface Class1TypeFunc extends TypeFunction {
        (): Class1TypeFunc;
        prototype: Class1;
        new (): Class1;
        ctor: { new (): Class1; };
    }
    var Class1: Class1TypeFunc;
}

TypeScriptの定義ファイル(d.ts)が出力されるだけでした。今まで通りJSも出力しとくから最後に勝手に繋げて使ってね!と言うことみたいです。
クラスに関する取り扱いはTypeScriptを通した方が断然楽だと思うので、変換したものにTypeScriptでインターフェイスを書いて一層噛ましてあげるとプログラムとの連携がスムーズになりそうです。全部プログラムで済まそうとするときっと大変です。

mscorlibの対応状況

Microsoftのバックアップもあるので大丈夫かと思うのですが、今のところStringBuilderなどの主要クラス、プロパティもちゃんと実装されておりました。
ただ、ローケルとかあのあたりは避けるべきでしょう。DateFormatあたりは調べておいた方が良さそうです。文字コード変換なども諦めて自前実装しましょう。と、このあたりはマルチプラットフォーム前提だとしょうがないですね。どの言語でも似たようなものです。