TypeScript

TypeScript 2.0のModule Resolution Enhancementsについて

More than 1 year has passed since last update.

@Quramy です。先日TypeScript2.0 betaがリリースされました。

1.xからの変更点についても、@vvakame 先生が TypeScript 2.0 Beta 変更点 にしっかり纏めてくれています。すごい。

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

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


paths, baseUrl

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

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

- (project root)

|- modules/
| my_module.ts
| config.js
| main.ts
| tsconfig.json

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


tsconfig.json

{

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

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


main.ts

import * as MyModule from "my-module";



いつ使うのか?

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

jspm install rxjs

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


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に参照が通ります。


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を生成するスクリプトを作っておくと良いでしょう。大分雑ですが、下記のようなイメージです。


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 の例でも"generated" ディレクトリを指定していることからも、自動生成したソースを、人が書いたソースとは別のディレクトリに配置しているケース、というユースケースが考えられそうです。

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

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


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 に下記のように書いておき、


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ディレクトリに配置しているところがポイントです。

(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を出力したい場合は下記のように実行するだけです。

tcm --out generated src


まとめ

というわけで、今回のエントリでは paths, baseUrl, rootDirs というTypeScript 2.0から導入されたモジュール解決系のオプションについて書いてきました。

pathsrootDirs を駆使すれば、かなり複雑なディレクトリ構成のプロジェクトを作ることも可能となったわですが、いたずらに複雑なプロジェクトを組んだ所で誰得?ってなるのは目に見えています。ご利用は計画的に、といったところでしょうか。

それでは、また。