期待の新MV*(MVW)フレームワークAureliaを、TypeScriptで書いてみたいと思います。
折角なので、Aurelia公式のGet StartedやNavigation Skeletonよりもシンプルなスケルトンをフルスクラッチで作成します。
この解説の成果物はこちらのリポジトリに用意してあります。
もっともフルスクラッチが目標なので、この解説を通して同じものが簡単に作れることを確認していただきたいです。
##追記 2015/07/23
Visual Studio 2015の公開と、Aurelia型定義ファイルのテスト配布開始に伴い記事を更新しました。
なお色々試した結果として、以下の理由からASP.net 5 プロジェクトの採用は推奨しません。
- Aureliaは基本的にjspmを利用するが、新しい依存性管理システムはnpm/bowerにしか対応していないうえ、コマンドプロンプトより手間がかかる。
- Aureliaをbower経由で入手する場合、SystemJS用の依存性設定ファイルは自作となり、更に手間がかかる。
- tsconfigなど一応TypeScriptにも対応しているが、当然TypeScriptプロジェクトに比べて利便性は落ちる。
- そもそも今までコマンドで作っていた各種jsonファイルを手で書けというのは明らかに時代に逆行して(略
#環境
- Visual Studio Community 2015
- TypeScript 1.5(VS2015に同梱)
- Node.js v0.12.5
- Internet Explorer 11
- Web Essentials 2015(任意:HTMLに対してAureliaのインテリセンスが多少効きます)
ライブラリ管理はjspmを用い、Visual Studioの外部(コマンドプロンプトなど)で行います。
#プロジェクト
Visual StudioにてTypeScript HTML アプリケーション プロジェクトを作成します。
プロジェクト > プロパティ > TypeScriptビルド
からECMAScriptバージョンを6に設定しておきます。
暗黙的なanyの許可からチェックを外しておくことをオススメします。
#パッケージマネージャー
gitが必要となりますので、git公式からインストールしておいてください。
npmを使ってjspmをグローバルインストールします。
npm install -g jspm
jspmはnpmのようなパッケージマネージャの1つで、npmデータベースに加えGitHubからもダウンロードを行うことができます。
続いてjspmを用いてプロジェクトの設定ファイルを作成します。
cd <プロジェクトのディレクトリ>
jspm init
<略>
Which ES6 transpiler would you like to use, Traceur or Babel? [traceur]: babel
npm init
と同じような画面に従って項目を埋めていきます。
基本的に全てデフォルトでOKですが、ES6互換性ライブラリのみAureliaが推奨するBabelに切り替えておきます。
必要モジュールを指定するpackage.json(npmと兼用)、モジュールが格納されるjspm_packagesディレクトリ、モジュールのマッピングが記述されるconfig.jsが生成されます。
#ライブラリ
jspmを用いてAureliaのモジュールを導入します。
jspm install aurelia-bootstrapper
最小構成において指定の必要があるモジュールはaurelia-bootstrapper
のみです。
Aureliaには数多くのモジュールがありますが、aurelia-bootstrapper
の依存関係がjspmを通じて必要なものを引っ張ってくれます。
##※jspmのマッピングについて
jspmのマッピングサーバーにAureliaが登録されたようで、githubなどの指定も無く通名だけでインストール出来るようになりました!
jspm経由のインストールを推奨しているAureliaですが、実は命名規則が違っており、悪いことに似通っています。
Aureliaはモジュールにaurelia-***
、jspmは識別子に作者名/リポジトリ名
を用いています。
Aureliaの作者名はaurelia
でリポジトリ名はモジュール名と同じなので、一見ハイフンとスラッシュの違いだけに見えてしまいますが全くの別物であり、当然意を汲む動作はしてくれません。
そのためインストール時に=
繋ぎで関連付けを行っています。
npmのようにgithub:...
だけでインストールしてしまうと手動でconfig.jsを調整することになるので注意してください。
##※型定義ファイルについて
同梱されている型定義ファイルには問題があり、core-js
についてコンパイルエラーが出ます。
本来はcore-js
の型定義ファイルを追加導入すれば解決するのですが、core-js
の型定義ファイルにも問題があるようです。
応急措置として、Aureliaに含まれる.d.tsファイルから、一括置換か何かでcore-js
のimport宣言の行を消去してください。
#index.html
スタートアップページとなるindex.htmlです。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="app.css" type="text/css" />
</head>
<body aurelia-app>
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script>
System.import("aurelia-bootstrapper");
</script>
</body>
</html>
headタグ内でのapp.jsのロードを削除します。読み込みはAureliaが推奨するモジュールローダーのSystemJSに任せるためです。
<body aurelia-app>
bodyタグにaurelia-app
属性を付与し、Aureliaのルートを指定します。
値を指定しない場合、app
モジュールのロードを試みます。
モジュールとは同名のhtmlファイルと(tsファイルがコンパイルされた)jsファイルのペアです。
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script>
System.import("aurelia-bootstrapper");
</script>
そしてbodyタグ内でSystemJSと、jspmが生成したconfig.jsをロードし、SystemJSを利用してaurelia-bootstrapper
をロードします。
忘れずにindex.htmlをスタートページに設定しておきます。
#appモジュール
ルーティングの土台となるapp
モジュールを作ります。
具体的にはルーティングテーブルの設定と対応したコンテンツが入るコンテナを用意します。
app
と名の付くモジュールはaurelia-app
のデフォルト値として指定されているため、最初に読み込まれます。
<template>
<router-view></router-view>
</template>
単純明快、router-view
というコンテンツコンテナがあるだけです。
このコンテナにはrouterモジュールによってコンテンツが自動的に読み込みます。
モジュールのhtmlは全て<template>
タグ内に記述します。
import {Router, RouterConfiguration} from "aurelia-router";
export class App {
configureRouter(config: RouterConfiguration, router: Router) {
config.title = "Aurelia";
config.map([
{ route: ["", "welcome"], name: "welcome", moduleId: "app/welcome", nav: true, title: "Welcome" }
]);
}
}
ルーティングテーブルの作成はここで行います。
import {Router, RouterConfiguration} from "aurelia-router";
ES6のインポート宣言はこのように書きます。import {} from "***"
だけ先に書いてしまうと、インポート対象についてインテリセンスを効かせることができます。
今回Aureliaに同梱が開始されたファイルにはinterfaceが含まれていないため、それほどインテリセンスを活かすことは出来ません。
{ route: ["", "welcome"], name: "welcome", moduleId: "app/welcome", nav: true, title: "Welcome" }
ルートなし(初期状態)またはwelcome
というルートに遷移した際にapp/welcome
モジュールをロードし、タイトルにWelcomeと表示するよう指定しています。
今回のようなシングルページアプリケーションの場合、上記ルーティングはクライアントサイドで行われるため、URLは常に.../index.html
となりルートはindex.html#welcomeのように示されます。
さらにこのルートのルート名をwelcome
とし、ルーターのルート一覧に遷移可能として登録されるようにしています。
index.html、app.html、app.tsの3ファイルはプロジェクトルートに作成しましょう。
#welcomeモジュール
app
モジュール上のコンテナにrouter
モジュール経由で読み込んでもらうapp/welcome
モジュールを作ります。
先の3ファイル以外はappディレクトリに纏めることにします。
welcomeモジュールについてはnavigation skeletonとほぼ同じものです。
<template>
<section class="au-animate">
<h2>${heading}</h2>
<form role="form" submit.delegate="welcome()">
<div class="form-group">
<label for="fn">First Name</label>
<input type="text" value.bind="firstName" class="form-control" id="fn" placeholder="first name">
</div>
<div class="form-group">
<label for="ln">Last Name</label>
<input type="text" value.bind="lastName" class="form-control" id="ln" placeholder="last name">
</div>
<div class="form-group">
<label>Full Name</label>
<p class="help-block">${fullName}</p>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</section>
</template>
${}
の片方向バインディング、<イベント名>.delegate
のイベントハンドラ、<属性名>.bind
の双方向バインディングが確認できます。
非常にスマートで、Angular1の進化系としてはAngular2よりもAureliaの方がしっくり来ます。
export class Welcome {
heading: string = "Welcome to the Aurelia Simple Skeleton!";
firstName: string = "John";
lastName: string = "Doe";
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
welcome() {
alert(`Welcome, ${this.fullName}!`);
}
}
片方向/双方向バインディングされる変数やプロパティと、submit時に呼び出されるイベントハンドラです。
アノテーションを使った方が丁寧ですが、コンパイラにVisual Studioから設定できないフラグを渡す必要があるなど発展途上なので、今回は省略しています。
#実行
Visual Studioからデバッグを実行すると、ルーティングテーブルに指定した<ドメイン名>/
のパターンにマッチするためapp/welcome
モジュールが読み込まれます。
双方向バインディングが正しく機能していることを確認してください。
#まとめ
以上、AureliaのTypeScriptスケルトンをフルスクラッチで作ることができました。
個人的にnavigation skeletonはスケルトンの割に機能が多すぎて、理解しづらいとの思いで挑戦しました。
型定義ファイルの生成について、現在route-recognizer
でテストを行っているようです。かなり良い感じなので、他のモジュールについても期待しています。