次の方法で共有


.style0 { color: #f26522; border-bottom-color:#f26522;margin-right:12px; } .style1 { color: #f26522; } .style2 { clear:both;height:0px; } .style3 { height:0px;clear:both; } .style4 { vertical-align:top; } .style5 { vertical-align:top; } .style6 { vertical-align:top; } .style7 { vertical-align:top; } .style8 { vertical-align:top; } .style9 { vertical-align:top; } .style10 { vertical-align:top; } .style11 { vertical-align:top; } .style12 { vertical-align:top; } .style13 { vertical-align:top; }

scriptjunkie{}

TypeScript: JavaScript アプリに生産性と管理の容易性を追加する — パート 2

Shayne Boyer | 2013 年 2 月 4 日

TypeScript に関するこのシリーズの初回 (英語) では、クラス、モジュール、タイプ セーフ性、矢印関数 (ラムダ) といった基礎に加えて、大規模な JavaScript アプリケーションの開発や整理にこのような機能がいかに役立つかを取り上げました。

今回は、既存の JavaScript ライブラリの使用、コマンドライン ツールを使用した独自の定義ファイル (*.d.ts) の作成、Node.js REST サービスのビルドでこのような定義ファイルを使用する方法など、TypeScript の高度なトピックについて説明します。

JavaScript ライブラリを扱う

現実には、開発者がプロジェクトに利用できそうな優れた .js ライブラリがたくさんあります。読者の開発コンピューターの周囲にも、いつでも含めることができる JavaScript スニペットや再利用可能なコードがいくつかあるでしょう。しかし、TypeScript を利用して、既存のライブラリにタイプ セーフ性やツールをすべて取り入れるにはどうすればよいでしょうか。オプションは複数あり、どのオプションも機能します。どれを採用するかは状況次第です。

すべての JavaScript は有効な TypeScript なので、このような既存の JavaScript を単純に TypeScript (.ts) ファイルに配置できます。ただし、配置はできますが、それだけのことです。このアプローチから始めることをお勧めしますが、配置したライブラリを参照するときにツールの効果が現れるように、関数、パラメーター、およびオブジェクトに型指定を追加します。次の例は、計算結果を返すシンプルな JavaScript モジュールです。

function calc (ticket) {
  return ticket.itemOne + ticket.itemTwo;
}

パート 1 で触れたように、TypeScript コンパイラの型インターフェイスを利用して、ツールによって返される型が数値であることが伝えられます。コンパイラでは関数に渡されるオブジェクトの詳細は認識されないため、開発者がこの部分に少し手を加えます。

パラメーターにシンプルな型の注釈を追加すれば、TypeScript のコンパイラは、オブジェクトの形状に関する追加の情報を使用してツールの効果を高めます。

interface stub {
  itemOne : number;
  itemTwo : number;
}
function calc (ticket : stub) {
  return ticket.itemOne + ticket.itemTwo;
}

注釈を追加する (この場合は、インターフェイスを追加してパラメーター オブジェクトの形状を説明する) ことで、calc.ts という名前でこのコードを保存し、他の TypeScript コードで参照するための定義ファイルを作成して使用できます。

定義ファイルの作成

TypeScript ツールをインストールすると、ライブラリの定義ファイルの作成に非常に有効なユーティリティとしてコマンドライン コンパイラが利用できるようになります。コマンド プロンプトを開き、次のコマンドを入力して calc.d.ts ファイルを作成します。

tsc c:\calc.ts –declarations

コンパイラによって、定義ファイルの calc.d.ts と JavaScript ファイルの calc.js という 2 つのファイルが作成されます。その後、calc 機能を必要とする TypeScript ファイルでは、ファイル冒頭に次のコードを追加して、エディターへの参照を追加するだけです。

/// <reference path="calc.d.ts”>

これは明らかに単純な例ですが、この手順は大規模ファイルでも簡単に行えます。さいわい、github.com/borisyankov/DefinitelyTyped (英語) には、JQuery、Moustache、Express、Angular などの著名な JavaScript ライブラリに対するコミュニティのコレクションがあります。このコレクションは増え続けており、ほぼ毎日更新されています。まずはこちらを確認して、TypeScript 用のお気に入りの JavaScript 定義ファイルを見つけてください。

モジュール

パート 1 でモジュールについて取り上げましたが、コードの再利用や既存のライブラリに関連することから、今回は CommonJS と RequireJS のサポートについて説明します。CommonJS は、モジュールの同期読み込み機能を提供し、コードを整理できるようにします。AMD (非同期読み込みモデル) もサポートします。

通常の JavaScript ファイル内の calc のサンプルにサポートを追加する場合は、calc.js ファイルの一番下に次のコード行を追加するだけです。

exports.calc = calc;

その結果、他の JavaScript ファイルでこの関数をインポートできます。Node.js アプリケーション (app.js) の場合は、require 関数を使用して次のように記述します。

require(‘calc.js’);

TypeScript コンパイラが必要な exports 属性、module 属性、および require 属性を提供する JavaScript を作成できるようにするには、TypeScript クラス内に calc 関数をラップし、MyMath という名前の TypeScript モジュールに収めます。

export module MyMath {
  export interface stub {
    itemOne : number;
    itemTwo : number;
    }
    export class Calculator {
    calc (ticket : stub) {
      return ticket.itemOne + ticket.itemTwo;
      }
    }
}

これにより、次の JavaScript が作成され、app.js ファイルで MyMath モジュールをインポートして使用できるようになります。

(function (MyMath) {
  var Calculator = (function () {
    function Calculator() { }
    Calculator.prototype.calc = function (ticket) {
      return ticket.itemOne + ticket.itemTwo;
    };
      return Calculator;
  })();
  MyMath.Calculator = Calculator;   
})(exports.MyMath || (exports.MyMath = {}));
var MyMath = exports.MyMath;

app.js では、次のように require 関数を使用することで、calc 関数を使用できます。

var math = require(‘calc.js’);
var add = new math.MyMath.calc();
var result = add.calc({itemOne: 10, itemTwo: 2});
res.send(result);

アンビエント宣言

アンビエント宣言により、定義ファイル (*.d.ts) がないプロジェクトで使用する JavaScript ライブラリ用に変数を定義できます。図 1 では Knockout.js を使用して、バインド用に viewModel を作成しています。

Using Knockout.js to Create a viewModel
図 1. Knockout.js を使用して viewModel を作成する

図 1 では、コンパイラが "ko" 変数を認識しないため、エラーが発生していることに注目してください。前回説明したように、github リポジトリの DefinitelyType から定義ファイルを取得して、ファイル冒頭にインライン参照を作成するとエラーを解消できますが、利用できる定義ファイルがない場合は、次のように "any" 型のアンビエント宣言を追加します。

declare var ko;

このようにするとエラーが解消され、コーディングを続けることができます。ただ問題点として、ツールで IntelliSense が使用できません。

すべてを 1 つに組み立てる

TypeScript が 2012 年 10 月にリリースされて以来、テクノロジ専門家の見解を掲載する多くのブログ記事が公開され、これに関連するコースも pluralsight.com で公開されています。しかし、この言語をプロジェクトで使用するにはどうすればよいでしょう。

そこで、TypeScript を Node.js と組み合わせて使用して、講演者の一覧を取得する REST API をビルドしてみます。今回の例では、Express.js と Node.js の定義ファイルを参照し、TypeScript を直接使用するモデル オブジェクトをコーディングしたら、require 関数を使用してこのモジュールを参照するデモを行います。

Node.js

Node.js になじみがない方は、http://nodejs.org (英語) で詳細情報や例を確認してください。この Web サイトでは、Node.js について次のように述べています。「Node.js は、高速かつスケーラブルなネットワーク アプリケーションを簡単に構築できるようにするために、Chrome の JavaScript ランタイム上に構築されたプラットフォームです。Node.js では、複数のデバイスに分散して実行されるデータ集中型リアルタイム アプリケーションに最適な、I/O の軽量化および効率化を行うイベント ドリブン型の非ブロッキング I/O モデルを使用します」

つまり、Node.js は、JavaScript をサーバー上で実行できるようにし、コンテンツにサービスを提供するプライマリ言語として使用できるようにします。これは、PHP、Ruby、.NET といった言語やフレームワークとは対象的です。

設定するには、この Web サイトから Node.js をダウンロードしてインストールします。インストールには Node Package Manager (npm) コマンドライン ツールが含まれており、残りのプロジェクトの設定を作成する際に使用します。

次に、コマンド プロンプトを開き、次の一連のコマンドを実行することで、ScriptJunkie フォルダーを作成し、Express (ノードの Web アプリケーション フレームワーク http://expressjs.com、英語) を使用して Web サイトを設定します。

npm install –g express Node Package Manager からダウンロードを行って、Express フレームワークをコンピューターのグローバルにインストールする
mkdir c:\ScriptJunkie ディレクトリを作成する
cd c:\ScriptJunkie ディレクトリを変更する
express ScriptJunkie フォルダーに Express を設定する
npm install ScriptJunkie フォルダーに Express の依存関係をダウンロードしてインストールする

コマンド プロンプトで「node app」と入力してインストールが完了したことを確認します。"Express is listening on port 3000" (Express はポート 3000 でリッスンしています) というメッセージが表示されます (図 2 参照)。好みのブラウザーを開き、https://localhost:3000 に移動すると、図 3 に示すテスト ページが表示されます。

Completed Installation of Node.js
図 2. Node.js のインストール完了

The Test Page
図 3. テスト ページ

今回開発環境に選んだのは Visual Studio ですが、JavaScript 開発では SublimeText もよく使われます。どちらの開発環境でも TypeScript が適切にサポートされます。プロジェクトに適した開発環境を選びます。

ScriptJunkie フォルダーを開き、Definitions という新しいフォルダーを作成します。Definitions フォルダーには、コードで参照する node.d.ts 定義ファイルと express.d.ts 定義ファイルを保存します。どちらの定義ファイルも gitHub (https://github.com/borisyankov/DefinitelyTyped、英語) にあります。2 つのファイルをダウンロードして、Definitions フォルダーに保存します。

次に、実行する Node.js 用の新しいアプリケーション ソースとしての役割を果たす、server.ts ファイルを作成します。server.ts ファイルは、フォルダーのルートに保存します。

まず、GitHub の DefinitelyTyped リポジトリからダウンロードした express.d.ts 定義ファイルに参照を追加します。

///<reference path="definitions/express.d.ts"/>

次に、AMD により express モジュール、http モジュール、および path モジュールをインポートし、require 関数で express の他の依存関係を追加し、express サーバーのために app 変数を宣言する必要があります。

import express = module('express');
import http = module('http');
import path = module('path');
var routes = require('./routes')
  , user = require('./routes/user');
var app = <express.ServerApplication> express();

ここで、express() を express.ServerApplication としてキャストするキャスト ステートメントに注目します。これにより、IntelliSense またはツールによって適切なヒントが提供しされます。express.d.ts 定義ファイルとこのわずかなコードで、TypeScript により、Express.js ライブラリ向けに優れた IntelliSense サポートを利用できるようになり、続くコンパイルで直接的かつ自然な JavaScript が提供されるようになります (図 4 参照)。

Express.js IntelliSense Support from Typescript and the Express.d.ts File
図 4. Typescript および Express.d.ts ファイルの Express.js IntelliSense サポート (クリックして拡大)

http モジュールでも、関数やメソッドの IntelliSense によってパラメーターの型を示すヘルプが表示されます。 (図 5 参照)。

IntelliSense for the http Module
図 5. http モジュールの IntelliSense (クリックして拡大)

これは、TypeScript が JavaScript 開発者にもたらす貴重な支援です (特に、大規模外部ライブラリを使用して作業しているときに役立ちます)。route、port、および createServer の構成を追加した完全なファイルを次に示します。

///<reference path="definitions/express.d.ts"/>
import express = module('express');
import http = module('http');
import path = module('path');
 var routes = require('./routes')
  , user = require('./routes/user');
var app = <express.ServerApplication> express();
app.configure(function(){
  app.set('port', process.env.PORT || 3000);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));
});
app.configure('development', function(){
  app.use(express.errorHandler());
});
app.get('/', routes.index);
http.createServer(app).listen(app.get('port'), function(){
  console.log("Express server listening on port " + 
    app.get('port'));
});

一見すると、このファイルは、モジュールの読み込みと定義ファイルの参照を除けば、プロジェクトに含まれる app.js ファイルの JavaScript とそれほど大差ありません。しかし、ここでのメリットは、コーディング時に表示されるヒントです。

データ オブジェクトの追加

データの保存には優れた方法が必要になるため、配列の代わりに、TypeScript で簡単なキーと値のコレクションに基づくオブジェクトまたはクラスをコーディングします。これにより、講演者のコレクションに対してキーを使用して検索を行うというわかりやすい方法が利用できるようになります。

まず、プロジェクトのルートに Collections というファイルを追加して、export というモジュールから作成を始めます。

export module collections {
}

次に、item という個別の item クラスを追加し、key パラメーターが string 型で value パラメーターが any 型という 2 つのパラメーターを受け取るコンストラクターを追加します。

export class item {
constructor (public key: string, public value: any){  
  }
}

最後に、コンストラクターを用意し、基盤構造の配列を操作する add 関数、get 関数、all 関数、および remove 関数を定義することで、KeyValueCollection クラスをビルドします。

export class keyValueCollection {
  private collection = {};
  private keys : string[];
  public count : number;
  constructor(){
    this.count = 0;
    this.keys = new string[];
  };
    add(item : collections.item){
    // add the key to the keys array
    this.keys.push(item.key);
    // add the item to the collection
    this.collection[item.key] = item.value;
    //increment the count property
    this.count = this.count + 1;
  }
  get (key : string) : collections.item {
    var val : any = this.collection[key];
    if (val != null) {
      return val;
    }
    return null;
  }
  private _get(key: string) : collections.item {
    var val : any = this.collection[key];
    if (val != null) {
      return new collections.item(key, val);
    }
    return null;
  }
  all () : collections.item[] {
    var values = new Array();
    for (var i = 0; i < this.keys.length;i++){
      values.push(this._get(this.keys[i]).value);
    }
    return values;
  };
  remove(key:string) : void {
    if (delete this.collection[key]){
      this.count = this.count - 1;
      var i = this.keys.indexOf(key);
      this.keys.splice(i);
    }
  }
}

モジュールとクラスの両方の先頭に "export" キーワードを付けて、CommonJS サポート向けにこれらをコンパイルするように設定します。

コレクション クラスの作業が完了したら、次はシンプルな speaker クラスです。このクラスは、作成している API から取得するデータを保存します。Speaker.ts というファイルを作成し、次の TypeScript コードをファイルに追加して保存します。

export class speaker{
  constructor( public name : string,
    public twitter : string,
    public url? : string = 'http://scriptjunkie.com') {
  }
}

モデル オブジェクトを利用できるようになったので、server.ts ファイルを開き、collections の TypeScript および speaker の TypeScript の import ステートメントを追加します。これでタイプ セーフ性の情報も利用できるようになります。

 

import express = module('express');
import http = module('http');
import path = module('path');
import dataStore = module('collections');
import dataModel = module('speaker');

start() という関数内に app.createServer 呼び出しをラップし、ファイルの一番下に直接実行する関数を追加します。ここで、次に追加することになる API ルートでの get 呼び出しで使用する、講演者情報の収集を開始します。

function start() {
  http.createServer(app).listen(app.get('port'), function () {
    console.log("Express server listening on port " + app.get('port'));
  });
}
// declare the collection variable for storage
var mySpeakers = new dataStore.collections.keyValueCollection();
(function () {
  console.log('loading data...');
  mySpeakers.add(new dataStore.collections.item('spboyer',
    new dataModel.speaker('Shayne Boyer', 
    '@spboyer', 'http://tattoocoder.com')));
  mySpeakers.add(new dataStore.collections.item('john_papa',
    new dataModel.speaker('John Papa', 
    '@john_papa', 'http://johnpapa.net')));
  // leave the url parameter blank and use the default value as defined in the class
  mySpeakers.add(new dataStore.collections.item('danwahlin',
    new dataModel.speaker('Dan Wahlin', 
    '@DanWahlin')));
  console.log('speakers added: ' + mySpeakers.count.toString());
  console.log('starting...');
  // start the server
  start();
})();

ファイルを保存したら、コマンド プロンプトに戻り、node コマンドを実行して (図 6 参照) サーバーを開始し、console.log に正しく書き込まれることを確認します。

Node Server Showing Output of Speakers Loading
図 6. 講演者情報の読み込み結果を示す node サーバー

get ルートの追加

現時点では、データと、https://localhost:3000 で利用できる最初のルート ページしかありません。しかし、TypeScript を使用してここで構築しようとしているのは、講演者情報を取得するための REST API です。そのためには、2 つの新しい get ルート (1 つは全員の講演者情報を取得するための get ルート、もう 1 つはキーを使用して特定の講演者情報を取得するための get ルート) が必要です。

server.ts ファイルを変更して、次のコードを索引用に app.get ルートの下に追加します。

// establishes a GET route to return all speakers
app.get('/speakers', function (req, res) {
  res.send(200, mySpeakers.all());
});
// get a specific speaker by key, if not found send a 404 - Not Found Message
app.get('/speakers/:key', function (req, res) {
  var speaker = mySpeakers.get(req.params.key);
  if (speaker == null) {
    res.send(404)
  } else {
    res.send(200, speaker);
  }
});

1 つ目のルートでは、TypeScript コレクションの mySpeakers.all() 関数を使用することで、全講演者情報が JSON 形式で返されます。2 つ目のルートでは特定の講演者情報を取得しますが、講演者情報が見つからない場合は、ご想像どおり "not found" (見つかりません) という適切な HTTP メッセージが表示されます。

もう一度 "node server" コマンドを使用してサーバーを起動したら、ブラウザーでエンドポイントをクリックして結果を確認できます。まず、https://localhost:3000/speakers を閲覧して、全講演者情報の JSON 結果を確認します。次に、"/spboyer" を追加して特定の講演者情報を取得します。存在しないキー (たとえば "/foo") を使用すると、404-NOT FOUND (404 見つかりません) メッセージが表示されます。

https://localhost:3000/speakers のすべての結果

[
{
  name: "Shayne Boyer",
  twitter: "@spboyer",
  url: "http://tattoocoder.com"
},
{
  name: "John Papa",
  twitter: "@john_papa",
  url: "http://johnpapa.net"
},
{
  name: "Dan Wahlin",
  twitter: "@DanWahlin",
  url: "http://scriptjunkie.com"
}
]

https://localhost:3000/speakers/spboyer の単一の結果

{
  name: "Shayne Boyer",
  twitter: "@spboyer",
  url: "http://tattoocoder.com"
}

まとめ

今回のプロセスでは、最後まで *.js ファイルを開くことはなく、*.js ファイルにコードを追加してプロジェクトを作成することもありませんでした。開発はすべて Visual Studio 2012 で行い、*.ts ファイルを保存するときに TypeScript コンパイラを実行する Web Essentials プラグインを使用しました。Sublime Text などのエディターでカスタム コンパイラを設定して Visual Studio 2012 と同様の方法で操作するには、オンライン ドキュメントの例を確認してください。

執筆者紹介

Shayne Boyer は、Telerik MVP、Nokia Developer Champion、Microsoft MCP、INETA の講演者であり、フロリダ州オーランドのソリューション アーキテクトでもあります。過去 15 年間に渡って、マイクロソフトベースのソリューションの開発に携わっています。ここ 10 年間は、大規模な Web アプリケーションの生産性およびパフォーマンスに力を注いでいます。余暇には、Orlando Windows Phone and Windows 8 User Group を運営したり、http://www.tattoocoder.com (英語) で最新テクノロジに関するブログを執筆しています。