Cách sửa lỗi ERR_PACKAGE_PATH_NOT_EXPORTED trong Node.js

intermediate💚 Node.js2026-04-29| Node.js (v12.17.0+, v14.x, v16.x và mới hơn), npm/pnpm/yarn, mọi hệ điều hành (Linux, macOS, Windows)

Error Message

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './lib/utils' is not defined by "exports" in node_modules/some-package/package.json
#nodejs#esm#npm#gói#exports#import

Cách khắc phục nhanh trong 60 giây

Bạn đã bao giờ thử import một tiện ích cụ thể từ một thư viện phụ thuộc (dependency) nhưng lại bị Node.js chặn đứng chưa? Lỗi này xảy ra do tác giả của package đã sử dụng trường "exports" để khóa các tệp nội bộ của họ. Nếu một tệp không nằm trong danh sách "công khai", Node.js đơn giản là sẽ không cho phép bạn chạm vào nó.

Hãy thử các bước sau để tiếp tục công việc:

  • Kiểm tra tài liệu: Có thể bạn đang cố gắng truy cập một tệp nội bộ không dành cho mục đích sử dụng công khai. Hãy tìm cách chính thức để truy cập tính năng đó.
  • Rút ngắn đường dẫn import: Thay vì import { helper } from 'my-lib/dist/utils', hãy thử cách viết gọn hơn import { helper } from 'my-lib/utils' hoặc chỉ cần import { helper } from 'my-lib'.
  • Kiểm tra cập nhật: Những người duy trì (maintainer) thường ẩn các đường dẫn con khi họ chuyển sang ESM (ECMAScript Modules). Nếu bạn vừa cập nhật package, hãy kiểm tra lịch sử phiên bản để xem có thay đổi mang tính phá vỡ (breaking changes) nào không.

Tại sao Node.js lại chặn lệnh import của bạn?

Trước phiên bản Node.js 12.17.0, bạn có thể truy cập vào bất kỳ npm package nào và lấy bất cứ thứ gì mình muốn. Nó giống như việc bước vào một nhà hàng và đi thẳng vào bếp để lấy một chiếc nĩa. Mặc dù tiện lợi, nhưng điều đó có nghĩa là các nhà phát triển không thể thay đổi cấu trúc thư mục của họ mà không làm hỏng hàng ngàn ứng dụng đang sử dụng.

Node.js đã khắc phục điều này bằng cơ chế Đóng gói Package (Package Encapsulation). Bằng cách thêm khối "exports" vào package.json, các tác giả giờ đây có thể xác định chính xác tệp nào là một phần của API công khai. Nó hoạt động giống như một người bảo vệ cổng. Nếu một tệp không có trong danh sách, nó sẽ không tồn tại đối với thế giới bên ngoài.

// Bên trong node_modules/some-package/package.json
{
  "name": "some-package",
  "exports": {
    ".": "./index.js",
    "./feature": "./src/feature.js"
  }
}

Trong kịch bản này, việc cố gắng import some-package/src/feature.js trực tiếp sẽ thất bại với lỗi ERR_PACKAGE_PATH_NOT_EXPORTED. Thay vào đó, bạn phải sử dụng bí danh some-package/feature.

Ba cách để giải quyết vấn đề

1. Sử dụng các điểm truy cập (Entry Points) đã xác định

Mở tệp package.json của thư viện bên trong thư mục node_modules của bạn. Cuộn xuống phần "exports". Các khóa (keys) này là những cách hợp lệ duy nhất để import mã nguồn.

Nếu package định nghĩa:

"exports": {
  ".": "./dist/main.js",
  "./utils": "./dist/utils.js"
}

Đừng cố gắng trỏ trực tiếp vào thư mục dist nữa. Hãy thay đổi mã của bạn từ thế này:

import { formatDate } from 'some-package/dist/utils.js'; // Lệnh này sẽ gây lỗi

Sang phiên bản gọn gàng này:

import { formatDate } from 'some-package/utils'; // Cách này hoạt động hoàn hảo

2. Công khai các đường dẫn con (Dành cho tác giả Package)

Nếu bạn là chủ sở hữu của package đang gây ra rắc rối này, bạn cần đưa các đường dẫn còn thiếu vào danh sách cho phép (whitelist). Bạn không nhất thiết phải liệt kê từng tệp một. Hãy sử dụng ký tự đại diện (wildcards) để công khai toàn bộ thư mục cùng một lúc:

{
  "exports": {
    ".": "./index.js",
    "./lib/*": "./lib/*.js" 
  }
}

Điều này cho phép người dùng của bạn import bất kỳ tệp JS nào bên trong thư mục lib. Đây là một sự cân bằng tuyệt vời giữa tính bảo mật và sự linh hoạt.

3. Bản vá "khẩn cấp"

Đôi khi bạn đang trong tình huống cấp bách và không thể đợi tác giả thư viện hợp nhất (merge) một pull request. Trong những trường hợp đó, hãy sử dụng patch-package để sửa lỗi cục bộ.

  • Chỉnh sửa thủ công tệp package.json bên trong node_modules/the-package/.
  • Thêm đường dẫn còn thiếu vào đối tượng "exports".
  • Chạy lệnh npx patch-package the-package.

Cách này tạo ra một bản sửa lỗi vĩnh viễn trong thư mục patches/, bản vá này sẽ ở lại với dự án của bạn ngay cả sau khi bạn cài đặt lại các dependency.

Cách kiểm tra bản sửa lỗi của bạn

Một lệnh terminal nhanh gọn có thể giúp bạn tiết kiệm rất nhiều thời gian gỡ lỗi. Chạy dòng lệnh sau để xem Node có thể phân giải (resolve) đường dẫn mà không cần chạy toàn bộ ứng dụng hay không:

node --input-type=module -e "import('some-package/utils').then(() => console.log('Resolved!')).catch(console.error)"

Nếu bạn thấy "Resolved!" thay vì một stack trace, đường dẫn import của bạn đã chính thức hợp lệ.

Tránh rắc rối này trong tương lai

Các lỗi cấu hình rất dễ xảy ra. Chỉ cần thiếu một dấu phẩy trong package.json cũng có thể khiến Node bỏ qua toàn bộ khối exports của bạn. Tôi thường kiểm tra cấu hình của mình qua một Công cụ kiểm tra JSON (JSON Validator) trước khi xuất bản để phát hiện những lỗi cú pháp vô hình đó. Nó nhanh hơn nhiều so với việc phải gỡ lỗi một bản build CI bị thất bại sau đó.

Ngoài ra, hãy lưu ý rằng ESM rất khắt khe về phần mở rộng tệp. Nếu exports của bạn trỏ đến một tệp không có phần mở rộng .js hoặc .mjs, Node có thể đưa ra một lỗi khác nhưng cũng khó chịu không kém. Luôn ghi rõ ràng các đường dẫn để trình tải module (module loader) luôn hoạt động ổn định.

Đọc thêm

Related Error Notes