Angular2 & TypeScript で サーバサイドとクライアントサイドのコードを共通化する


Angular2のBetaがリリースされたので、Angular2 & TypeScript で、サーバサイドとクライアントサイドのコードを共通化(isomorphic)できるかを試していました。
いくつか、ハマった点があったので整理しておきたいと思います。

## TypeScript の module タイプの違い
Angular2のチュートリアルでは、tsconfig.jsonのmoduleは、systemになっており、systemjsを利用してモジュールをロードする方式になっています。Browser上で動かすには、systemjsで上手く動きます。

```typescript:トランスパイル前のTypeScript
export * from './model/user';
export * from './util/validator';
```

```typescript:systemでトランスパイルされたTypeScript
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の方が楽です。

```typescript:commonjsでトランスパイルされたTypeScript
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を入れ、トランスパイル対象から外します。

```json:サーバサイド用のtsconfig.json(プロジェクト直下に配置)
{
    "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フォルダに置くのでそのままにします。

```json:クライアントサイド用の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を使うと便利です。

```package.json
{
  "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 のサンプルプロジェクト※](https://github.com/takeshi/my-mean2) 

<small> ※ ただし、angular/universal が動かせず、サーバサイドレンダリングはできていません。
</small>