Angular2のBetaがリリースされたので、Angular2 & TypeScript で、サーバサイドとクライアントサイドのコードを共通化(isomorphic)できるかを試していました。
いくつか、ハマった点があったので整理しておきたいと思います。
TypeScript の module タイプの違い
Angular2のチュートリアルでは、tsconfig.jsonのmoduleは、systemになっており、systemjsを利用してモジュールをロードする方式になっています。Browser上で動かすには、systemjsで上手く動きます。
export * from './model/user';
export * from './util/validator';
System.register(['./model/user', './util/validator'], function(exports_1) {
function exportStar_1(m) {
var exports = {};
for(var n in m) {
if (n !== "default") exports[n] = m[n];
}
exports_1(exports);
}
return {
setters:[
function (user_1_1) {
exportStar_1(user_1_1);
},
function (validator_1_1) {
exportStar_1(validator_1_1);
}],
execute: function() {
}
}
});
//# sourceMappingURL=model.js.map
しかし、NodeJSで動かそうとすると、systemjsの設定を書かなければならず、面倒でした。NodeJSで動かすならcommonjsの方が楽です。
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
__export(require('./model/user'));
__export(require('./util/validator'));
//# sourceMappingURL=model.js.map
commonjsであれば、NodeJSでそのまま実行することができます。
<解決方法> tsconfig.json を 2つ用意する
NodeJSでsystemjsの設定を頑張れば、できるかもしれないですが、面倒だったので諦めて、サーバサイドとクライアントサイドのTypeScriptのコンパイルを分けることで、moduleの読み込み方法の違いによる問題を解決しました。
フォルダを下記のように分け、clientフォルダにもtsconfig.jsonを作ります
├── client :クライアントのソース
│ └── tsconfig.json :クライアント用のtsconfig.jcon
├── server :サーバのソース
├── share :共通のソース
└── tsconfig.json :サーバ用のtsconfig.json
サーバ用のtsconfig.jsonでは、excludeにclientを入れ、トランスパイル対象から外します。
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false,
"outDir": "dist/server"
},
"exclude": [
"node_modules",
"client",
"dist"
]
}
クライアント用のtsconfig.jsonはclientフォルダに置くのでそのままにします。
{
"compilerOptions": {
"target": "es5",
"module": "system",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false,
"outDir": "../dist/client"
},
"exclude": [
"node_modules"
]
}
これで、コマンドラインから、tsc -w
と tsc -p ./client -w
を起動すると、同じTypeScriptのファイルをクライアントでもサーバでも動かすことができます。
concurrently で複数のコマンドを同時に起動する
ちなみに、複数のコマンドを実行するのは面倒なので、concurrentlyを使うと便利です。
{
"name": "my-project",
"version": "1.0.0",
"description": "",
"scripts": {
"tsc:client": "tsc -w -p ./client/",
"tsc:server": "tsc -w",
"start": "concurrent \"npm run tsc:server\" \"npm run tsc:client\" "
},
"dependencies": {
},
"devDependencies": {
"concurrently": "^1.0.0",
"livereload": "^0.4.0",
"typescript": "^1.7.3"
},
"license": "ISC"
}
とpackage.jsonを作っておくと、npm start
で2のコンパイラーを一つのコマンドで動かすことができます。
作ったモノ:
Angular2 + Typescript + Express + MongoDB のサンプルプロジェクト※
※ ただし、angular/universal が動かせず、サーバサイドレンダリングはできていません。