エラーのシナリオ
スパゲッティのように絡み合った相対パスの壁を見つめることほど、コーディングの意欲を削ぐものはありません。私はプロジェクトのリファクタリングを行い、import { helper } from '../../../../utils/helper.js' をよりクリーンな記述に置き換えようとしていました。現在の Node.js は、package.json の "imports" フィールドを介した Subpath Imports をネイティブにサポートしており、# で始まるエイリアスを使用できます。
理屈の上ではすべて正しく見えました。しかし、アプリを起動した瞬間、次のような特定のエラーを吐いてクラッシュしたのです。
Error [ERR_PACKAGE_IMPORT_NOT_DEFINED]: Package import specifier "#utils" is not defined in package "package.json" imported from /app/src/index.js
at new NodeError (node:internal/errors:399:5)
at throwImportNotDefined (node:internal/modules/esm/resolve:408:9)
なぜこのエラーが発生するのか?
Node.js は、# で始まるインポートを見つけたものの、設定内に一致するルールが見つからない場合にこのエラーを発生させます。構文自体は Subpath Imports として認識されていますが、解決ロジックが行き止まりに達してしまっています。
通常、原因は以下の4つの問題のいずれかです。
package.json内で"imports"フィールドが欠落しているか、配置場所が間違っている。- キーにタイポ(入力ミス)がある(例:
"#util"を定義しているのに"#utils"を呼び出している)。 - パスマッピングが必須の
./プレフィックスで始まっていない。 package.jsonに末尾のカンマなどの構文エラーがあり、Node.js がフィールド全体を無視している。
解決策:package.json の修正
この問題を解決するには、厳密にフォーマットされた "imports" オブジェクトが必要です。このフィールドは、package.json の "name" や "version" と並んで、トップレベルに配置する必要があります。
1. 直接的なファイルマッピング
特定のエイリアスを単一のファイルにマッピングしたい場合は、これを使用します。
{
"name": "my-app",
"type": "module",
"imports": {
"#utils": "./src/utils/index.js"
}
}
2. 動的なパターンマッピング
ほとんどのプロジェクトでは、フォルダ全体をマッピングしたいはずです。これを行うには、マッピングの両側で * ワイルドカードを使用する必要があります。
{
"imports": {
"#utils/*": "./src/utils/*.js"
}
}
これで、import { log } from '#utils/logger' は正しく ./src/utils/logger.js を指すようになります。その * がないと、Node.js はサブファイルをどのように解決すればよいか判断できません。
覚えておくべき重要なルール
Node.js は、これらのフィールドの構造に関して非常に厳格であることで知られています。わずかなミスがアプリの破損につながります。
ハッシュ(#)プレフィックスは必須
TypeScript の paths や Webpack のエイリアスとは異なり、imports フィールドのすべてのキーは 必ず # で始まる必要があります。これにより、@company/package のようなスコープ付き npm パッケージとの衝突を防いでいます。
パスは相対パスである必要がある
ターゲットの値は ./ で始まる必要があります。これは Node.js に対し、パスがパッケージのルートからの相対パスであることを伝えます。"src/lib.js" と記述すると失敗します。代わりに "./src/lib.js" を使用してください。
JSON の構文エラーに注意
package.json が無効な場合、Node.js は単にその行をスキップするだけでなく、imports フィールド全体を読み込めなくなる可能性があります。私は以前、インポートのデバッグに45分費やした挙句、ファイルの全く別のセクションでダブルクォーテーションが1つ抜けているのを見つけたことがあります。
安全を期すために、バリデーターでファイルを確認してください。私は、標準的なエディタが見落としがちな目に見えない構文エラーや余計なカンマを見つけるために、ToolCraft の JSON Formatter & Validator を使用しています。
応用編:条件付きインポート
Subpath Imports が強力なのは、環境に基づいてファイルを切り替えられる点にあります。これは、重いバンドラーを使用せずに Node.js とブラウザのロジックを使い分けるのに最適です。
{
"imports": {
"#db-client": {
"node": "./src/db/server-side.js",
"default": "./src/db/client-side.js"
}
}
}
修正を確認する方法
設定を更新したら、ただ動くことを祈るのではなく、以下のクイックチェックを実行してください。
- バージョンを確認する:
node -vを実行します。Subpath Imports を完全にサポートするには、Node.js 14.13.0 以降が必要です。 - CLI テストを実行する: サーバー全体を起動しなくても、解決を確認できます。
node -e "import('#utils/logger').then(m => console.log('Success!'))"
- **開発サーバーを再起動する:** `nodemon` や `ts-node` などのツールは、`package.json` をキャッシュすることがあります。修正後もエラーが消えない場合は、ターミナルを完全に再起動すると、ゴーストエラーが解消されることがよくあります。
## クイックチェックリスト
- `"imports"` 内のすべてのキーが `#` で始まっていますか?
- すべての値が `./` で始まっていますか?
- `"imports"` フィールドは `package.json` のルートに配置されていますか?
- ディレクトリをマッピングする場合、両側に `*` ワイルドカードを含めましたか?
- JSON の構文は 100% 正しいですか?

