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-5-20

    ヘッドレス CMS ってどんなの?ブログの新しい作り方!

    ブログの新しい作り方 みなさん、ブログ書いていますか? 結構多くの方が、これまで一度はブログをやっ…
  2. 2019-5-13

    東京オフィスを移転いたしました

    将来的な展望を踏まえ、東京オフィスを移転いたしました! 株式会社エージェントグローの東京オフィスは…
  3. 2019-5-13

    日本語入力は ATOK がとっても便利なことを知ってもらいたい

    日本語入力、何使ってますか? みなさん、PC での日本語入力ソフトは何を使っていますか? Win…
  4. 2019-5-11

    エージェントグローの社長ブログで気になる記事をピックアップ!【人柄・思考術・会社のアレコレ】

    こんにち、ヒロキです。 今日はエージェントグローの社長ブログから、私がオススメする記事をピックアッ…
  5. 2019-5-9

    2019 年 4 月度・第 33 回福岡社員総会 & 懇親会

    今月もやってまいりました! こんにち、ヒロキです。 今月も福岡の社員総会&懇親会が開催されました…
ページ上部へ戻る

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

「フェアネス方式」は、株式会社エージェントグローが日本国内において出願中の商標です。

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

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