C#コードをJavaScriptに変換してくれるDuoCode。まだベータ版ではあるが、サンプルコードを基にその能力を調べてみた。
本連載は、Insider.NET編集部のスタッフが「これ気になるな〜」と思った技術的な話題などを、実際に手を動かして試し、その情報を読者と共有することをコンセプトとしている。
ちなみに筆者は、長らく担当してきた@ITのInsider.NET編集長を今年から卒業し、今は一編集スタッフとしてInsider.NETに関わっている。現在の新編集長は、すでに2年ほどInsider.NETで活躍してきた「かわさき しんじ」氏だ。かわさき氏は、本連載を含め、Insider.NETの新たな成長に向けていろいろと考えているようなので、ぜひご期待いただきたい。なお私自身は、WebやIoTやスマホ開発技術など幅広い最新技術情報を取り扱う新興Webメディアの「Build Insider」で2年近く編集長をしているので、「そのサイト、知らない・聞いたことがない」という方は、この記事を機に、ぜひ情報収集先リストの端に加えていただけると幸いである。
さて少し脱線し過ぎたが、ところであなたは、Webアプリなどで使うJavaScriptコードを、C#コードから変換すると、便利だと思うだろうか?
先月中旬ごろだっただろうか。「“DuoCode”というC#→JavaScriptコンパイラーが出てくるよ」という情報をキャッチした。本稿では、このDuoCodeを試し、そのリポートをコンパクトにまとめる。
DuoCodeのことを聞いてすぐに、Java→JavaScriptのコード変換ができる“Google Web Toolkit”(以下、GWT)のことを思い出した。その登場は2006年でもうすぐ9年になる。登場から数年後に聞いた開発者の評価は、「純粋にJavaScriptを書くよりも、最適化された良いコードが生成される」というものだった。「コード変換の有効性」に半信半疑だった筆者にとっては、がぜん興味が湧いたことは言うまでもない。
しかし結局、筆者は手を出さなかった。そもそも筆者はC#好き人間だったので(というか2007年からずっとMicrosoft MVP for C#である。※今年から「C#」カテゴリは「.NET」カテゴリに統合された)、あえてJavaコードを書きたくもなく、むしろJavaScriptコードを直接書く方が実務面では良かったからだ。そうこうするうちに、CoffeeScriptの次にTypeScriptまでも登場し、GWTを使う気は一切なくなった。
そんな心持ちになったところに、「DuoCodeでC#→JavaScript変換しようよ」という話である。当然、「ええっ?! 何で今頃? 何のために?」と思ってしまった……。しかし「先入観にとらわれず、とにかく触るだけ触ってから、考えてみよう」と、ベータ版の招待に申し込んでみた(下記のリンク先の[EMAIL]欄にメールアドレスを入力して[Subscribe]ボタンをクリックすると、招待に申し込める。どれくらい待たされるのかは不明だが、2週間ぐらいはかかると思う。ちなみにベータ版は2015年6月30日まで使える。製品版登場時には有償になる模様)。
上の公式サイトを見ると、英語で「あなたのC#コードとスキルを、DuoCodeでWebに持っていこう」というキャッチコピーが書かれている。確かにこれまでに積み上げてきたC#の既存資産をJavaScript化できるのは大きいかもしれない。しかしどれくらいの精度で移植できるのかは気になるところだ。
次に、「Microsoftの“Roslyn”の力で実現している、C#→JavaScriptコンパイラー」という記載がある。この変換エンジンが実現できたのは、どうやら次期コンパイラーの“Roslyn”が登場したからのようだ。“Roslyn”によってマイクロソフト以外でもコンパイル関連サービスが柔軟に実現できるようになるとされていたが、早くもそんなサービスが登場してきたということだ。あらためて“Roslyn”登場のメリットが感じられた。
その他、公式ページから読み取れる、DuoCodeの特徴を箇条書きで示しておこう。
これだけ見ると「すごい」と言わざるを得ない。いやまだ信用しないぞ。早速、DuoCodeをインストールして試していこう。
図2のような招待メールが届けば、DuoCodeを試用できる。そのメール上の[Download Now]ボタンをクリックして、インストーラー(本稿の例では「duocode0.3.878.0beta.exe」ファイル)をダウンロードする。
インストーラーを実行すると、図3のようなウィザードが表示される。特に難しいところもなく、簡単にインストールできる。
インストールが完了したら、Visual Studio(※2012/2013/2015 CTP 5)を起動して[新しいプロジェクト]ダイアログを表示し、図4のように左側のツリーから[Visual C#]−[DuoCode]を選択しよう。
図4の右側を見ると、以下の六つのプロジェクトテンプレートがインストールされている。
本稿では、最も基本的と思われる「HelloDuoCode (HTML Application)」テンプレートで新規プロジェクトを作成してみよう。
図5はプロジェクト作成後の[ソリューション エクスプローラー]の中身だ。以下のようなファイルが生成されている。
それぞれのファイル内容を見ていく前に、ひな型の状態での実行結果を見ておこう。図6のようになる。
console.cssファイルとweb.configファイルの中身は、プログラム内容そのものではないので説明を割愛する。index.htmlファイルから順に見ていこう。
<!DOCTYPE html>
<html>
<head>
<title>Hello DuoCode</title>
<link href="console.css" rel="stylesheet" type="text/css">
<!-- アセンブリ参照(依存関係順に並べる) -->
<script type="text/javascript" src="scripts/mscorlib.js"></script>
<script type="text/javascript" src="scripts/HelloDuoCode.js"></script>
</head>
<body onload="HelloDuoCode.Program.Run()">
<h1>Hello DuoCode</h1>
<div id="content"></div>
<div id="duocode-console">
<font color="darkblue">
This is a sample of a project written in C# and compiled to JavaScript using DuoCode.
Press F12 to see the source code of the project.
Enable source-maps in your browser and you'll be able to debug the C# code directly.
You can also debug it from Visual Studio 2015.
</font>
<hr />
</div>
</body>
</html>
気になるのは、「アセンブリ参照」と書かれているところだ。その記述を読むと、「scripts」フォルダー内に「mscorlib.js」と「HelloDuoCode.js」の二つのファイルが存在するのが分かる(※これらは[ソリューション エクスプローラー]で[すべてのファイルを表示]ボタンをクリックすると表示される)。実際にフォルダー内を見ると、図7のようになっていた。
このうちの.jsファイルは、DuoCodeによりC#コードからJavaScriptコードに変換された後のファイルだ。各ファイルは、以下のような役割を持つ。
前掲の[ソリューション エクスプローラー](図5)を見ると、[参照設定]に「mscorlib」というアセンブリが追加されている。このファイルの実体は、(Windows 8.1(64bit)上にインストールした筆者の環境では)「C:\Program Files (x86)\DuoCode\mscorlib.dll」にあり、これが[ローカル コピー]される設定になっている。つまりこれが、上の「scripts」フォルダーに出力されていると考えられる。
そのmscorlibアセンブリをVisual Studioの[オブジェクト ブラウザー]で参照したのが図8である。
上の方には「DuoCodeほにゃらら」という名前空間もあるが、その下のほとんどは「Systemほにゃらら」という.NET Framework標準ライブラリと互換性のある名前空間になっている。一番下の「System.Web」名前空間の中には「HttpUtility」クラスがあり、右側を見ると「UrlDecode(String)」メソッドと「UrlEncode(String)」メソッドが含まれているようである。このように「.NETのライブラリ」といっても、かなり制限された一部のメソッドだけが使えるようだ(※.NETライブラリの「サブセット」のようなものだが、DuoCodeの公式ページではそのようには説明されていないので、「部分的に互換性のあるクラス群」と表現すればよいのだろうか)。
なお、DuoCodeが提供するクラス群は、今後、きちんとしたAPIリファレンスが提供されるものと思われるが、現時点では[オブジェクト ブラウザー]を使うなどして、使用可能なクラスやメソッドなどを調べながら使う必要があるだろう。
再び、リスト1を参照してほしい。<body>タグのonloadイベント属性に「HelloDuoCode.Program.Run()」というJavaScriptコードが記載されている。ここでC#で記述したプログラムのコードが呼び出されている。そこで次に、Program.csファイルの中身を見てみよう。
using System;
using DuoCode.Dom;
//using static DuoCode.Dom.Global; // C# 6.0の「using static」を使うには、このコメントを外す
namespace HelloDuoCode
{
static class Program
{
public class Greeter
{
private readonly HTMLElement element;
private readonly HTMLElement span;
private int timerToken;
public Greeter(HTMLElement el)
{
element = el;
span = Global.document.createElement("span");
element.appendChild(span);
Tick();
}
public void Start()
{
timerToken = Global.window.setInterval((Action)Tick, 500);
}
public void Stop()
{
Global.window.clearTimeout(timerToken);
}
private void Tick()
{
span.innerHTML = string.Format("The time is: {0}", DateTime.Now); // try to put a breakpoint here
}
}
static void Run() // HTML <body>タグonloadイベント属性に指定したエントリポイント(index.htmlファイルを参照)
{
System.Console.WriteLine("Hello DuoCode");
var el = Global.document.getElementById("content");
var greeter = new Greeter(el);
greeter.Start();
}
}
}
前述のJavaScriptコードから、リスト2のRunメソッドが呼び出されることになる。
そのRunメソッドの中で、まずSystem.Console.WriteLineメソッドが呼ばれている。このコードが呼ばれると、メソッド引数の文字列が(リスト1の)<div id="duocode-console">要素の一番下に(テキストではなく)HTMLコードとして追加される(例えば「System.Console.WriteLine("<b>Hello DuoCode</b>")」のように<b>タグを追加すれば、HTMLページ上で太字になる)。
次にGlobal.document.getElementById("content")メソッドで、(リスト1の)<div id="content">の要素を取得している。HTML&JavaScript開発経験があれば分かると思うが、JavaScriptを使って操作するDOMのdocument.getElementByIdメソッドに相当するものである。このようにGlobalクラス(DuoCode.Dom名前空間)を使えば、DOMも操作できる。
さらにその下にあるGreeterクラスは独自に実装したクラスである。このように自作のクラスを使って、さまざまな処理が実現できることが分かる。このGreeterクラス内は、C#とJavaScript開発経験があれば難しくないと思うので、説明を割愛する。
最後に「変換後のコードはメンテナンスしやすい」という特徴が本当なのかを確認しておこう。
//
// HelloDuoCode, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
//
// Generated by DuoCode Compiler 0.3.878.0 BETA
//
(function HelloDuoCode() {
"use strict";
var $asm = {
fullName: "HelloDuoCode",
anonymousTypes: [],
types: [],
$getAttrs: function() {
return [new System.Reflection.AssemblyTitleAttribute.ctor("HelloDuoCode"), new System.Reflection.AssemblyDescriptionAttribute.ctor(""),
new System.Reflection.AssemblyConfigurationAttribute.ctor(""), new System.Reflection.AssemblyCompanyAttribute.ctor(""),
new System.Reflection.AssemblyProductAttribute.ctor("HelloDuoCode"), new System.Reflection.AssemblyCopyrightAttribute.ctor("Copyright \xA9 2015"),
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("0.3.878.0 BETA")];
}
};
var $g = (typeof(global) !== "undefined" ? global : window);
var HelloDuoCode = $g.HelloDuoCode = $g.HelloDuoCode || {};
var $d = DuoCode.Runtime;
$d.$assemblies["HelloDuoCode"] = $asm;
HelloDuoCode.Program = $d.declare("HelloDuoCode.Program", System.Object, 0, $asm, function($t, $p) {
$t.Run = function Program_Run() {
System.Console.WriteLine$10("Hello DuoCode");
System.Console.WriteLine$10("<p>Hello DuoCode2</p>");
var el = document.getElementById("content");
var greeter = new HelloDuoCode.Program.Greeter.ctor(el);
greeter.Start();
};
});
HelloDuoCode.Program.Greeter = $d.declare("Greeter", System.Object, 0, HelloDuoCode.Program, function($t, $p) {
$t.$ator = function() {
this.element = null;
this.span = null;
this.timerToken = 0;
};
$t.ctor = function Greeter(el) {
$t.$baseType.ctor.call(this);
this.element = el;
this.span = document.createElement("span");
this.element.appendChild(this.span);
this.Tick();
};
$t.ctor.prototype = $p;
$p.Start = function Greeter_Start() {
this.timerToken = window.setInterval($d.delegate(this.Tick, this), 500);
};
$p.Stop = function Greeter_Stop() {
window.clearTimeout(this.timerToken);
};
$p.Tick = function Greeter_Tick() {
this.span.innerHTML = String.Format("The time is: {0}", $d.array(System.Object, [System.DateTime().get_Now()])); // try to put a breakpoint here
};
});
return $asm;
})();
//# sourceMappingURL=HelloDuoCode.js.map
読者の皆さんは、どう感じるだろうか。筆者の感想としては、「このコードを編集する」となった場合、「できなくはない」が「できればしたくない」といったところだ。
DuoCodeは、Visual Studioなどのビルドで活用されるMSBuildタスクに対応しているだけでなく、C#のcsc.exeコマンドと同じような「dcc.exe」というコマンドライン用のコンパイラーツールが用意されている(※インストール先の例:「C:\Program Files (x86)\DuoCode\dcc.exe」)。
以上、DuoCodeの基本機能を試した。他にもサンプルがいくつかあるが、それなりに文章が長くなったので、本稿はここで終わりとする。気になる方は、ぜひ自分で試してみてほしい。
で、筆者が「DuoCodeを使いたい」と思ったかどうかだが、依然として「微妙」な気持ちだ。かなり悩む。やっぱり「TypeScriptにしておくべきではないか」などと思ってしまう。
確かに.NETのクラスライブラリと共通のクラスが存在するのは魅力的だ。しかし全てのメソッドが使えるわけではなく、かなり機能が制限されているというのは、実用上でどれくらいデメリットになるのかが気に掛かる。
もちろんTypeScript開発よりも、DuoCode開発の開発生産性が大幅に高いのであれば、選択候補に挙げたいとは思うのだが……。あと、有償製品になりそうなので、その金額も懸念材料だ。
いずれにしてもDuoCodeはまだベータ版なので、決定的な評価を下すには早すぎるだろう。取りあえず今後も、その動向に注視していきたい。
Copyright© Digital Advantage Corp. All Rights Reserved.