現場のSails.js #1 AltJS編

# 概要
弊社では一部の社内プロジェクトでSails.jsを利用しています。その中で得られたベストプラクティスやらバッドプラクティスをまとめていこうと思います。
第1回はバックエンド側のAltJSの活用から。

# 動作環境
* node.js v0.12.7
* Sails.js v0.11.2
* [npm]sails-hook-babel v5.0.1
* [npm]typescript v1.5.3
* [npm]grunt-tsconfig-update v0.0.1

# AltJSの活用
Sails.jsはnode.jsのフレームワークなので実際の開発ではAltJSを使用しています。TypeScriptで書いたコードを `--target es6` に変換後、`babel` でES5に変換しています。

## TypeScript
### 採用理由
個人的にTypeScriptが好きなのでバックエンド側コードは全面的にTypeScriptを採用しています。TypeScriptを導入するメリットは大きいと考えていて、モジュール間の整合性を厳密に定義出来る点が気に入っています。次回以降でアーキテクチャについては詳しく説明しますが、弊社ではViewController -> ApiController -> Service -> Modelという4層構造でバックエンドを構築しています。各層のAPIを変更したときに、デグレが発生していないかをコンパイル段階で気付けるのは良いですね。

### 設定方法

```
$ npm i typescript grunt-tsconfig-update grunt-shell --save-dev
```

Sails.jsにはGruntが組み込まれているのでこれを活用することにします。とは言えあまり依存したくもないので、tsconfig.jsonを出力するだけのモジュール `grunt-tsconfig-update` と、 `grunt-shell` で直接`tsc`を叩く構成にしています。今見ると改善したくなってきた。。

```js:tasks/config/tsconfig.js
module.exports = function(grunt) {
  grunt.config.set('tsconfig', {
    main: {}
  });

  grunt.loadNpmTasks('grunt-tsconfig-update');
};
```

```js:tasks/config/shell.js
module.exports = function(grunt) {
  grunt.config.set('shell', {
    typescript: {
      command: "npm run ts"
    }
  });

  grunt.loadNpmTasks('grunt-shell');
};
```

```js:tasks/register/buildServer.js
module.exports = function (grunt) {
  grunt.registerTask('build-server', [
    'tsconfig:main',
    'shell:typescript'
  ]);
};
```

```package.json
  "scripts": {
    "ts": "./node_modules/typescript/bin/tsc -p ./"
  }
```

あとはtsconfigを用意して終わりです。

```json:tsconfig.json
{
  "compileOnSave": false,
  "compilerOptions": {
    "target": "es6",
    "declaration": false,
    "sourceMap": false,
    "removeComments": true,
    "noLib": false,
    "preserveConstEnums": false,
    "moduleResolution": "node"
  },
  "filesGlob": [
    "./api/**/*.ts",
    "!./**/*.d.ts",
    "!./node_modules/"
  ],
  "files": [
  ]
}
```

コンパイル

```
$ grunt build-server
```

## Babel
### 採用理由
最初に書いた通り、TypeScriptは`--target es6`で使っています。これはいつでもTypeScriptを捨てられるようにするためです。個人的にはずっとTypeScriptで書きたいのですが、書きたくない人に引き継ぐ可能性があるので。。

### 設定方法
`sails-hook-babel`というnpmを１つ入れるだけで終わりです。これでSails起動時に自動的にbabelで変換してくれます。ただ、このnpmは今のところbabel6対応をしていないため、長期メンテする予定のシステムでは使わない方が良いかもしれません。いつもどおり返還前のコードを置くディレクトリを用意して、普通にbabelを叩いて`api/*`に出力するほうが良いのかも。

```
$ npm i sails-hook-babel --save
```

# 使用しているd.tsファイル

Sails用の型定義ファイルが無かったので自前で作って使っています。ただ公開出来るレベルでは書いていないので。。型定義ファイルにどれだけ作成コストをかけるのかというのは常に悩む。

```ts:sails.d.ts
/// <reference path="./typings/bundle.d.ts" />

declare var sails: Sails.sails;

declare module "sails" {
  import * as express from 'express';

  module s {
    interface Request extends express.Request {
      port: number;
      user: User;
      session: Session;
      sessionID: string;

      allParams(): Object;
      logout();
      file(name: String): any; // TODO file object
    }

    interface Response extends express.Response {
      ok(params: Object | number);
      view(view: string, params?: Object);
      json(params: Object);
    }

    interface Session {
      regenerate(callback: (error) => void);
    }

    interface User {
      id: number;
    }
  }

  export = s;
}

declare module Sails {

  interface sails {
    log: log;
  }

  interface log {
    verbose(...data: any[]);
    error(...data: any[]);
    warn(...data: any[]);
    debug(...data: any[]);
    info(...data: any[]);
    silly(...data: any[]);
  }
}
```

# コードサンプル

実際のコードの抜粋です。雰囲気は伝わるかなと。

```ts:TagController.ts
/// <reference path="../../../sails.d.ts" />
/// <reference path="../../../typings/bundle.d.ts" />

import * as Sails from 'sails';

class TagController {
  'use strict';

  constructor() { }

  find(req: Sails.Request, res: Sails.Response) {
    sails.log.verbose('api::TagController#find');
    sails.log.verbose('params', req.allParams());
    sails.log.verbose(`user=${(req.user ? req.user.id : 'null') }`);

    const page = +req.param('page') || 1;

    TagService.find({
      limit: 30,
      page: page
    }).then((tags) => {

      res.ok(tags);

    }).catch((error) => {
      sails.log.error("api::TagController.find", error);
      res.send(500);
    });
  }
}

export default new TagController();
```

# まとめ
TypeScript良いよね。