TypeScript の paths はパスを解決してくれないので注意すべし!

  • 2019/3/11
  • TypeScript の paths はパスを解決してくれないので注意すべし! はコメントを受け付けていません。

こんにちは、江嵜です。

みなさん、 TypeScript 書いてますか?

私は JavaScript のゆるふわ感が大好きなんですが、
やっぱりそこそこの大きさのものを開発しようと思うと
型とかあった方が安心で、TypeScript を使おうかなって気になります。

で、今回はそんな TypeScript を使う上で注意すべき小ネタ的お話を 1 点。
(文中のコードは一部全角にしているので実行される場合は注意してくださいね)

TypeScript の paths って設定使ってますか?

TypeScript はコンパイルのルールを色々設定することができます。
tsconfig.json ファイルですね!
多分一度 TypeScript を書かれた方なら触ったことがあるのではと思います。

色々な設定ができるのですが、今回はその中でも
paths という設定項目について、見ていきます。

…と!その前に、今回の環境を準備

さて、すぐにでも paths のお話をしてもよいのですが、
ここは検証のための環境を簡単に作っておきましょう。

とりあえず、こんなディレクトリ構成で…

test
├ node_modules
├ index.ts
├ package.json
└ tsconfig.json

package.json はこんな感じ。
グローバルインストールがイヤなので、
npm install --save-dev typescript で typescript だけ入れておきます。

{
  ”name”: ”test”,
  ”version”: ”1.0.0”,
  ”description”: ””,
  ”main”: ”index.js”,
  ”scripts”: {
    ”build”: ”tsc -p tsconfig.json”
  },
  ”author”: ”ezaki”,
  ”license”: ”No License”,
  ”devDependencies”: {
    ”typescript”: ”^3.3.3333”
  }
}

tsconfig.json はこう。
これも基本的な設定だけで特に何ってことはないですね。
ちなみに今回はサーバーサイドで使うことを想定しているので、
target は ES5 じゃなくてもいいかなという事で ESNext で。

{
  ”compilerOptions”: {
    ”module”: ”CommonJS”,
    ”target”: ”ESNext”,
    ”noImplicitAny”: true,
    ”rootDirs”: [
      ”.”
    ],
    ”moduleResolution”: ”Node”,
    ”sourceMap”: true,
    ”baseUrl”: ”.”
  },
  ”include”: [
    ”./**/*”
  ],
  ”exclude”: [
    ”./node_modules”
  ]
}

index.ts はとりあえずこう。

const hello = (name: string): void => {
  console.log(`Hello! ${name}`);
};

hello(’AG’);

JavaScript に、ただ型情報を与えただけですね。

では、コンパイルしてみましょう。
package.json に build コマンドを作っておいたので、

npm run build

でいいですね。

こんな感じで、ビルドできます。
<img src=”https://www.agent-grow.com/self20percent/wp-content/uploads/2019/03/0d40a5e4a645fc6b96e767d64ac0878e.png” alt=”” width=”284” height=”77” class=”alignnone size-full wp-image-13306” />

ディレクトリ内はこんな感じ

test
├ node_modules
├ index.ts
├ index.js
├ index.js.map
├ package.json
└ tsconfig.json

index.jsindex.js.map が生成されましたね。
これを node コマンドで実行。

node index.js

””

はい、想定通り実行されました。

paths を使ってみる

ここまでは普通ですからね!
では、本題の paths を使ってみましょう。

この paths というのは、公式ドキュメントの説明としては

List of path mapping entries for module names to locations relative to the baseUrl.

baseUrl からのモジュール名への相対パスマッピングということで。

これだけだと良くわからんので実例を。

例えばこんな構成だったとして…

test
├ node_modules
├ index.ts
├ package.json
├ tsconfig.json
└ a
  └ b
    └ c
      └ d
        └ name.ts

index.ts は name.ts に依存しているとしましょう。

name.ts はこんな感じ

export const MyName = ’AG’;

MyName を export してるだけですね。

これに合わせて、 index.ts も修正しましょう。

import { MyName } from ’./a/b/c/d/name’;

const hello = (name: string): void => {
  console.log(`Hello! ${name}`);
};

hello(MyName);

先程同様、ビルドして実行してみると、同じように ”Hello! AG” が出力されますよね。

良かった良かった…なのですが、

import { MyName } from ’./a/b/c/d/name’;

この部分長くないですか?

今回みたいなケースならまだいいのですが、
lib みたいなディレクトリが浅い階層にあって、
色々な所から

import { MyName } from ’../../../../../../lib’;

みたいにすごくさかのぼって参照しないといけないとか…
イヤですよね!私はイヤです!

ということで、これを解決してくれるのが、この paths です。

こんな感じで paths の設定を追加すれば、
import の対象をルールに従って読み替えてくれます。

{
  ”compilerOptions”: {
    ”module”: ”CommonJS”,
    ”target”: ”ESNext”,
    ”noImplicitAny”: true,
    ”rootDirs”: [
      ”.”
    ],
    ”moduleResolution”: ”Node”,
    ”sourceMap”: true,
    ”baseUrl”: ”.”,
    ”paths”: {
      ”@/*”: [”./a/b/c/d/*”],
      ”@”: [”./a/b/c/d”]
    }
  },
  ”include”: [
    ”./**/*”
  ],
  ”exclude”: [
    ”./node_modules”
  ]
}

今回、 @/* を設定しました。
* の部分は任意の文字なので、ちょうど @/./a/b/c/d/ と差し変わる形ですね。

では、これに合わせて、 index.ts も修正してみます。

import { MyName } from ’@/name’;

const hello = (name: string): void =&gt; {
  console.log(`Hello! ${name}`);
};

hello(MyName);

スッキリしましたね!

こんな感じで、よく使うパスを特別に設定しておくことで、
カンタンに import を書くことができるようになります。

ちなみに、 paths の設定を外して動かしてみると…

””

「@/name というモジュールが見つからんよ」
とエラーを出してくれます。
TypeScript はこういう感じで、存在しないものを設定したときに
きちんとエラーを出してくれるというのがいいところですね。

でもこれ…実行できない?

ワーイヤッターベンリー!なんですが、
ここで残念なお知らせを一つ…

これ、実行できないんです。

試しに実行してみると…

””

あれ? @/name モジュールが無いと言われてますね…

生成された index.js を見てみると…

”use strict”;
Object.defineProperty(exports, ”__esModule”, { value: true });
const name_1 = require(”@/name”);
const hello = (name) =&gt; {
    console.log(`Hello! ${name}`);
};
hello(name_1.MyName);
//# sourceMappingURL=index.js.map

require(”@/name”) と、パスが @/ のまま入っているではないですか!
解決してくれたんじゃないんかい!

…と、思いますが、これが TypeScript の仕様です。
ちなみに、今後も typescript 側で解決はしてくれるようにはならなさそうです。

この辺りの経緯は typescript の GitHub issue で
https://github.com/Microsoft/TypeScript/issues/10866
こんな感じで議論されていましたが、最近、議論をロックされました。

最終的な結論としては、
「import のパス解決は TypeScript の責務じゃないから!」
という事みたいです。

確かに、 TypeScript としてはファイルがあることを確認してくれればいいわけで、
JavaScript にコンパイルした後のパス解決は TypeScript のやることではないと。
ド正論でしたね…

では、どうするのか、というと、
「webpack とか、そういうパス解決させるためのヤツにやらせなさい」
という事みたいです。

ということで、そうしましょうか。

npm install --save-dev webpack webpack-cli ts-loader
webpack と typescript を webpack から使うための loader ををインストールして、
webpack.config.js をこんな感じでルートディレクトリに作っときましょう。

const path = require(’path’);

module.exports = {
  mode: ’development’,
  entry: path.resolve(__dirname, ’index.ts’),
  output: {
    path: path.resolve(__dirname),
    filename: ’main.js’,
  },
  resolve: {
    extensions: [’.ts’],
    alias: {
      ’@’: path.resolve(__dirname, ’a/b/c/d’),
    },
  },
  target: ’node’,
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: {
          loader: ’ts-loader’,
          options: {
            configFile: path.resolve(__dirname, ’tsconfig.json’),
          },
        },
      },
    ],
  },
};

後は package.json の ”build” の所を、
”tsc -p tsconfig.json” から ”webpack” に変えて…

””

生成された main.js を実行してみれば、
きちんとビルドも実行もできていますね!

まとめ

TypeScript に paths というオプションがあるので、
なんとなく解決までやってくれそうなイメージですが、
TypeScript の責務を考えればそこまでやらないというのも分かる話ですね。

ちなみに私は
「どこかに解決までしてくれるオプションとかあるんじゃねぇの!」
と思って 1 時間程無駄にしました。ご報告まで。

Takato Ezaki

Takato Ezaki小中高の塾講師からエンジニア

投稿者プロフィール

福岡で Web 系のエンジニアをしています。

中高の理科教師免許を取り、起業に 2 年間トライした後エンジニアの道へ入りました。

化学反応の中では Belousov-Zhabotinsky 反応が大好きです。

この著者の最新の記事

関連記事

コメントは利用できません。

募集中!(o゜▽゜)o

エンジャパン
求む、社長!
follow us in feedly

コッチもヨロシク!





最近のネタ!

  1. 2019-8-23

    リーダーに興味がある人向け勉強会〜今月もやります&前回レポ〜

    こんにちは! さんどですよ(∩´∀`)∩ 前回開催からしばらく講師陣の都合がつかず開催できていなか…
  2. 2019-8-22

    第四回リアル脱出ゲーム部~人狼村からの脱出~

    はじめに 令和初リアル脱出ゲーム部の活動が8/3(土)にありました! というわけで活動報告をしま…
  3. 2019-8-19

    Web で物理アニメーション・その3 「簡単な物理法則に従うアニメーションを作ろう」

    前回の回答 前回の回答から行きましょう。 前回の宿題は 以下のように、近づいて右下に抜けてい…
  4. 2019-8-15

    2019年7月度社員総会&懇親会@Nagoya

    愛知は、わざいかぬっかど! 大田です。 さて!(笑) 今回は7/10(水)に行われた、名古屋社員総…
  5. 2019-8-13

    2019年7月度社員総会&懇親会@東京

    はじめに 今年もやってきました!みんな大好き、夏季休暇の季節! 40度近い気温が毎日どこかで計測さ…
ページ上部へ戻る

当サイトに掲載されているコンテンツ(文書、画像等)は、許可なく複製・転用等する事を禁じます。

「フェアネス方式®」(登録6150741)は、日本国内における株式会社エージェントグローの登録商標です。

当サイトでは最低限必要と考えられる場合において、会社名/サービス名/商品名などを記載している場合があります。
これらはあくまでも説明の必要性に応じて用いているものであり、各社の権利等を侵害を目的とするものではございません。
不適切と考えられる場合には、当社お問い合わせフォームよりご連絡ください。

当サイトでは®や™などの表記を省略させていただいております。