0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeScript のパスエイリアスを使ったときに「Cannot find package '@/services' imported from ...」エラーが出る

Last updated at Posted at 2025-05-18

パスエイリアスとは

パスエイリアスとは、TypeScript において import 文のパスを短く可読性の高い文字列に置き換える機能のことです。例を見たほうがわかりやすいと思います。

Before

import { UserService } from '../../../services/UserService';

After

import { UserService } from '@/services/UserService';

ダラダラとした相対パス (../../../) を使わないため、コードの可読性や保守性が向上します。これは tsconfig.json に paths というのを書くことで実現できます。例えば以下のように書きます。

{
    "compilerOptions": {
        "target": "es2020",
        "module": "NodeNext",
        "moduleResolution": "nodenext",
        "rootDir": "./src",
        "outDir": "./dist",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "skipLibCheck": true,
        "sourceMap": true,
        "paths": {
            "@/*": ["./src/*"] // ←ここ
        }
    },
    "include": [
        "./src/**/*.ts"
    ]
}

"@/*": ["./src/*"] のように指定すると、例えば @/hoge と書いたときにルートディレクトリから ./src/hoge と指定したと同じ意味になります。

これはとても便利ですし、だいぶ import がすっきり書けました。

「Cannot find package '@/services' imported from ...」エラー

しかしこれを tsc でビルドして実行しようとしたところ、エラーが発生しました。

Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@/services' imported from /workspace/dist/controllers/apiController.js

発生していた行はここです。

import { UserService } from '@/services/UserService';

このエラーは TypeScript の tsconfig.json で設定したパスエイリアスが、コンパイル後の JavaScript ファイルでは解決されず、Node.js がそのパスを認識できないために発生します。

ちなみにローカルなどで tsx を使って簡易的に Node.js を実行している限りはエラーは出ません。tsx は上手いことパスエイリアスを解決してくれていたのです。

エラーの原因詳細

TypeScript コンパイラ (tsc) は、tsconfig.jsonpaths オプションを型チェックやコード補完には利用しますが、デフォルトではコンパイル後の JavaScript ファイルの import パスを自動的に書き換える機能はありません。

そのため、コンパイル後の JavaScript ファイルには、パスエイリアス (@/services) がそのまま残ってしまい、Node.js がこのパスを標準のモジュール解決アルゴリズムで探しても見つからないため、ERR_MODULE_NOT_FOUND エラーが発生します。

なお、TypeScript のビルドに Vite や Webpack を使っている場合は、それらのツールがパスエイリアスを解決してくれるのであまり気にしなくても良いと思います。
自分は Express で API 開発をしていたので「Vite はフロントエンド用だからちょっと違うし・・・」「Webpack はちょっと設定めんどくさいし・・・」と思ってもっと手軽な方法を探してみました。

いくつか解決方法があります。

tsc-alias を使った解決方法

最もお手軽だと思ったのが tsc-alias を使った解決方法です。
まず tsc-alias をインストールします。

npm install --save-dev tsc-alias

お好みで package.json のビルドスクリプトに組み込みます。

package.json
{
    ...
    "scripts": {
        "build": "tsc && tsc-alias"
    },
    ...
}

ビルドを実行します。これだけでビルド後のスクリプトのパスエイリアスが相対パスに置き換えられます。

npm run build

module-alias を使った解決方法

module-alias を使った解決方法もありました。
こちらはビルドスクリプトをいじらなくても良いのですが、その他の設定がやや複雑です。

インストール

まず module-alias をプロジェクトにインストールします。

npm install --save-dev module-alias

次に、package.json_moduleAliases というセクションを追加し、パスエイリアスを定義します。値はコンパイル後のディレクトリに向ける必要があります。例えば以下のように書きます。

{
  ...(省略)...
  "_moduleAliases": {
    "@": "./dist"
  }
}

ここは tsconfig.jsonpaths 設定に合わせて調整してください。

エントリーポイントの修正

Node.js のエントリーポイントとなるファイル (通常は index.tsserver.ts など) の先頭で module-alias を import します。

import 'module-alias/register.js'

ビルドして実行

お好みで package.json にビルドスクリプトを追加します。

package.json
{
    ...
    "scripts": {
        "build": "tsc"
    },
    ...
}

ビルドします。

npm run build

これでビルド後のスクリプトを実行すると、Node.js が起動時に package.json のエイリアス設定を読み込み、@/services のようなパスエイリアスを正しく解決できるようになります。

個人的には、パスエイリアスの設定を二重で持たなくてはいけない点、エントリーポイントに import を足すのが気持ち悪くて、あまり良いと思えませんでした。

まとめ

TypeScript でパスエイリアスを利用すると、コードの可読性と保守性が向上しますが、tsc 単体ではコンパイル後の JavaScript ファイルでエイリアスが解決されません。追加で tsc-aliasmodule-alias などのライブラリを利用することで「Cannot find package」エラーを回避することができます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?