TypeScript 2.0のModule Resolution Enhancementsについて

@Quramy です。先日TypeScript2.0 betaがリリースされました。
1.xからの変更点についても、@vvakame 先生が [TypeScript 2.0 Beta 変更点](http://qiita.com/vvakame/items/ae239f3d6f6f08f7c719) にしっかり纏めてくれています。すごい。

このまとめで端折られているのが、"Module resolution enhancements" というテーマです。今日はこのテーマについてメモを書いておこうと思います。

具体的には、tsconfig.jsonの`compilerOptions` に `baseUrl`, `paths`, `rootDirs`というキーが追加されました。どのような機能なのか、それぞれ見ていきましょう。

## paths, baseUrl

`paths` は、モジュール名とそのモジュールの.tsファイルの配置場所のマッピングをtscに教えてあげるための機能です。`paths`を利用する場合、`baseUrl`もセットで指定しなくてはなりません。

例えば、下記のようなフォルダ構成のときに、

```text
- (project root)
 |- modules/
  |  my_module.ts
 |  config.js
 |  main.ts                           
 |  tsconfig.json
```

`baseUrl`, `paths`を以下のように書いたとしましょう。

```json:tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "baseUrl": "modules",
    "paths": {
      "my-module": [ "my_module.ts" ]
    }
  }
}
```

"my-module" をimportすることが出来るようになります。

```ts:main.ts
import * as MyModule from "my-module";
```

### いつ使うのか？

ぱっと思い付くのは、JSPM + SystemJS を利用しているときでしょうか(というよりも、[TypeScript + jspm 触って消耗した話](http://qiita.com/Quramy/items/33cf5991ad8a9b385024) に散々愚痴ったので、この話は結構前から目を光らせていたのです)。

```sh
jspm install rxjs
```

とすると、JSPMが自動的にSystemJSの設定ファイルとして、下記のconfig.jsを生成してくれるため、煩わしいモジュール名と実体のマッピングを管理する手間から開放されます。

```javascript:config.js
System.config({
  baseURL: "/",
  defaultJSExtensions: true,
  transpiler: false,
  paths: {
    "github:*": "jspm_packages/github/*",
    "npm:*": "jspm_packages/npm/*"
  },

  map: {
    "rxjs": "npm:rxjs@5.0.0-beta.10",
    "github:jspm/nodelibs-assert@0.1.0": {
      "assert": "npm:assert@1.4.1"
    },
    /* 以下略 */
  }
});
```

ところで、rxjsのd.tsは jspm_packages/npm/rxjs@5.0.0-beta.10/Rx.d.ts に配置されています。このため、TypeScript 1.xでは、JSPMでinstallしたrxjsを参照する術がありませんでした。

そこで `paths` です。件のrxjsの場合、下記のようにpathsに書くと、JSPMでinstallしたrxjsのRx.d.tsに参照が通ります。

```json:tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2015",
    "noImplicitAny": false,
    "sourceMap": false,
    "baseUrl": "",
    "paths": {
      "rxjs": [
        "jspm_packages/npm/rxjs@5.0.0-beta.10"
      ]
    }
  },
  "exclude": [
    "node_modules",
    "jspm_packages"
  ]
}
```

とはいえ、手動で`paths`をメンテしていたら、折角JSPMを導入した意味が無くなってしまうので、JSPMのconfig.jsからtsconfig.jsonを生成するスクリプトを作っておくと良いでしょう。大分雑ですが、下記のようなイメージです。

```javascript:jspm2tsconfig.js
require('./jspm_packages/system.src'); // SystemJS
require('./config'); // jspm config file

const tsconfig = {
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2015",
    "noImplicitAny": false,
    "sourceMap": false
  },
  "exclude": [
    "node_modules",
    "jspm_packages"
  ]
};

const baseUrl = SystemJS.baseURL.replace(/^file:\/\/\//, "");

const paths = {};
Object.keys(SystemJS.map).forEach(key => {
  const value = SystemJS.map[key];

  // SystemJS.pathsを読んで、解決すべきだけど、手抜き
  const flatten = value.replace(/npm:/, "jspm_packages/npm/");
  paths[key] = [flatten];
});

const compilerOptions = Object.assign({}, tsconfig.compilerOptions, {baseUrl, paths});
console.log(JSON.stringify(Object.assign({}, tsconfig, {compilerOptions}), null, 2));
```

## rootDirs
`rootDirs` は複数のディレクトリに存在しているソースコードを、仮想的に1つのディレクトリ上に存在しているかのように見せかけることの出来る機能です。

### いつ使うのか?

[virtual-directories-with-rootdirs](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#virtual-directories-with-rootdirs) の例でも"generated" ディレクトリを指定していることからも、自動生成したソースを、人が書いたソースとは別のディレクトリに配置しているケース、というユースケースが考えられそうです。

例えば、僕は [typed-css-modules](https://github.com/Quramy/typed-css-modules) というツールを公開しています。CSS Modules形式のcssからcss.d.tsを生成して、TypeScriptのソースコード(例えば.tsxとかです)から利用すれば、補完とか効いて嬉しいよね！っていうツールです(詳細は[TypeScript + React JSX + CSS Modules で実現するタイプセーフなWeb開発](http://qiita.com/Quramy/items/a5d8967cdbd1b8575130)を読んでください)

具体的には、下記のようなソースを書く訳です。

```ts:src/Button.tsx
import * as React from "react";
import * as styles from "./Button.css";

export function Button() {
    return (
        <button className={styles.root} >Click me</button>
    );
}
```

tscは`"button.css"`というCSS ModulesをButton.css.d.tsファイルを通して認識するのですが、自動生成という都合上、Button.css.d.tsを別のディレクトリに配置しておいた方が都合が良いケースも考えられます。
そこで、`rootDirs` に下記のように書いておき、


```json:tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2015",
    "noImplicitAny": false,
    "sourceMap": false,
    "jsx": "react",
    "outDir": "built",
    "rootDirs": ["src", "generated"]
  },
  "exclude": [ "node_modules" ]
}
```

ディレクトリ構成は下記のようにします。Button.css.d.tsはgeneratedディレクトリに配置しているところがポイントです。

```text
(your project root)
- src/
    | Button.css
    | Button.tsx
- generated /
    | Button.css.d.ts
```

`rootDirs` のおかげで Button.tsxとButton.css.d.tsは同一ディレクトリに配置されているように認識されるため、`import * as styles from "./Button.css";` は問題なく動作します。

ちなみに、typed-css-modulesでは`--out` オプションを用意しているので、上記の構成に従って.css.d.tsを出力したい場合は下記のように実行するだけです。

```sh
tcm --out generated src
```

## まとめ
というわけで、今回のエントリでは `paths`, `baseUrl`, `rootDirs` というTypeScript 2.0から導入されたモジュール解決系のオプションについて書いてきました。
`paths` と `rootDirs` を駆使すれば、かなり複雑なディレクトリ構成のプロジェクトを作ることも可能となったわですが、いたずらに複雑なプロジェクトを組んだ所で誰得？ってなるのは目に見えています。ご利用は計画的に、といったところでしょうか。

それでは、また。