Cách khắc phục lỗi ERR_PACKAGE_IMPORT_NOT_DEFINED trong Node.js Subpath Imports

intermediate💚 Node.js2026-06-16| Node.js v14.13.0+, v16.x, v18.x, v20.x+; Windows, macOS, Linux.

Error Message

Error [ERR_PACKAGE_IMPORT_NOT_DEFINED]: Package import specifier "#utils" is not defined in package "package.json" imported from /app/src/index.js
#esm#package-json#imports#subpath#nodejs

Kịch bản lỗi

Không gì làm mất hứng thú lập trình bằng việc phải nhìn vào một "đống mì Ý" các đường dẫn tương đối. Tôi đang tái cấu trúc (refactoring) một dự án để thay thế import { helper } from '../../../../utils/helper.js' bằng thứ gì đó gọn gàng hơn. Node.js hiện đã hỗ trợ mặc định Subpath Imports thông qua trường "imports" trong package.json, cho phép bạn sử dụng các bí danh (alias) bắt đầu bằng dấu #.

Mọi thứ trông có vẻ ổn trên lý thuyết. Tuy nhiên, ngay khi tôi khởi chạy ứng dụng, nó đã bị sập với lỗi cụ thể này:

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)

Tại sao điều này lại xảy ra?

Node.js kích hoạt lỗi này khi nó thấy một lệnh import bắt đầu bằng # nhưng không tìm thấy quy tắc tương ứng trong cấu hình của bạn. Nó nhận diện cú pháp đó là một subpath import, nhưng logic phân giải (resolution logic) lại đi vào ngõ cụt.

Thông thường, nguyên nhân nằm ở một trong bốn vấn đề sau:

  • Trường "imports" bị thiếu hoặc đặt sai vị trí trong package.json.
  • Bạn gõ sai tên khóa (ví dụ: định nghĩa "#util" nhưng lại gọi "#utils").
  • Đường dẫn ánh xạ (mapping) không bắt đầu bằng tiền tố bắt buộc ./.
  • Tệp package.json của bạn có lỗi cú pháp, chẳng hạn như dư dấu phẩy ở cuối, khiến Node.js bỏ qua toàn bộ trường này.

Giải pháp: Chỉnh sửa package.json

Để khắc phục, bạn cần một đối tượng "imports" được định dạng nghiêm ngặt. Trường này phải nằm ở cấp cao nhất trong tệp package.json của bạn, cùng cấp với "name""version".

1. Ánh xạ tệp trực tiếp

Sử dụng cách này nếu bạn muốn ánh xạ một alias cụ thể tới một tệp duy nhất:

{
  "name": "my-app",
  "type": "module",
  "imports": {
    "#utils": "./src/utils/index.js"
  }
}

2. Ánh xạ theo mẫu động (Dynamic Pattern Mapping)

Trong hầu hết các dự án, bạn sẽ muốn ánh xạ toàn bộ một thư mục. Để làm điều này, bạn phải sử dụng ký tự đại diện * (wildcard) ở cả hai phía của phép ánh xạ:

{
  "imports": {
    "#utils/*": "./src/utils/*.js"
  }
}

Giờ đây, import { log } from '#utils/logger' sẽ trỏ chính xác đến ./src/utils/logger.js. Nếu không có dấu * đó, Node.js sẽ không biết cách phân giải các tệp con.

Các quy tắc thiết yếu cần ghi nhớ

Node.js nổi tiếng là khắt khe về cách cấu trúc các trường này. Một sai sót nhỏ cũng có thể khiến ứng dụng ngừng hoạt động.

Tiền tố dấu thăng (#) là bắt buộc

Không giống như đường dẫn trong TypeScript hay alias trong Webpack, mọi khóa trong trường imports phải bắt đầu bằng dấu #. Điều này đảm bảo chúng không bị xung đột với các gói npm có phạm vi (scoped package) như @company/package.

Đường dẫn phải là đường dẫn tương đối

Giá trị đích phải bắt đầu bằng ./. Nó cho Node biết rằng đường dẫn này tương đối so với thư mục gốc của package. Sử dụng "src/lib.js" sẽ bị lỗi; hãy sử dụng "./src/lib.js" thay thế.

Cẩn thận với lỗi cú pháp JSON

Nếu package.json không hợp lệ, Node.js không chỉ bỏ qua dòng bị lỗi mà có thể không đọc được toàn bộ trường imports. Tôi đã từng mất 45 phút để gỡ lỗi một lệnh import chỉ để phát hiện ra mình thiếu một dấu ngoặc kép ở một phần hoàn toàn khác trong tệp.

Để đảm bảo an toàn, hãy kiểm tra tệp của bạn qua một công cụ xác thực (validator). Tôi thường sử dụng JSON Formatter & Validator tại ToolCraft để phát hiện các lỗi cú pháp ẩn hoặc các dấu phẩy thừa mà các trình soạn thảo thông thường đôi khi bỏ sót.

Sử dụng nâng cao: Conditional Imports

Subpath imports rất mạnh mẽ vì chúng có thể hoán đổi các tệp dựa trên môi trường. Điều này rất tuyệt vời để xử lý logic giữa Node.js và trình duyệt (Browser) mà không cần đến các bộ đóng gói (bundler) nặng nề.

{
  "imports": {
    "#db-client": {
      "node": "./src/db/server-side.js",
      "default": "./src/db/client-side.js"
    }
  }
}

Cách xác minh bản sửa lỗi

Sau khi cập nhật cấu hình, đừng chỉ hy vọng nó sẽ hoạt động. Hãy thực hiện các bước kiểm tra nhanh sau:

  • Kiểm tra phiên bản: Chạy node -v. Bạn cần tối thiểu phiên bản Node.js 14.13.0 để hỗ trợ đầy đủ subpath import.
  • Chạy thử nghiệm qua CLI: Bạn có thể xác minh việc phân giải đường dẫn mà không cần khởi động toàn bộ máy chủ:

node -e "import('#utils/logger').then(m => console.log('Success!'))"

  
  - **Khởi động lại Dev Server:** Các công cụ như `nodemon` hoặc `ts-node` đôi khi lưu bộ nhớ đệm (cache) cho `package.json`. Nếu lỗi vẫn tiếp diễn sau khi đã sửa, việc khởi động lại hoàn toàn terminal thường sẽ xóa sạch các lỗi "ma" này.

## Danh sách kiểm tra nhanh

  - Có phải mọi khóa trong `"imports"` đều bắt đầu bằng `#` không?
  - Có phải mọi giá trị đều bắt đầu bằng `./` không?
  - Trường `"imports"` có nằm ở thư mục gốc của `package.json` không?
  - Nếu ánh xạ một thư mục, bạn đã bao gồm ký tự đại diện `*` ở cả hai phía chưa?
  - Cú pháp JSON của bạn có hợp lệ 100% không?

Related Error Notes