文件明明在包里,Node.js 为什么报 ERR_PACKAGE_PATH_NOT_EXPORTED?
碰到 ERR_PACKAGE_PATH_NOT_EXPORTED,先看消费方实际装到的 package.json,检查 exports 有没有声明报错里的子路径。exports 定义了哪些包名入口能被解析。文件存在只能说明它被安装到了磁盘。
1. 同一个文件,公开前后怎么解析
保存为 package_exports_lab.py,运行时需 Python 和 Node.js。脚本只写临时目录,不使用 npm registry。
import json
from pathlib import Path
import subprocess
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmp:
root = Path(tmp)
package = root / "node_modules" / "demo-exports"
package.mkdir(parents=True)
config = {"name": "demo-exports", "type": "commonjs",
"exports": {".": "./index.cjs"}}
manifest = package / "package.json"
(package / "index.cjs").write_text("module.exports = 'root';", encoding="utf-8")
(package / "internal.cjs").write_text("module.exports = 'helper';", encoding="utf-8")
manifest.write_text(json.dumps(config), encoding="utf-8")
def node(mode, script):
result = subprocess.run(
["node", "--input-type=" + mode, "-e", script],
cwd=root, capture_output=True, text=True, check=True,
)
return result.stdout.strip()
assert node("commonjs", "console.log(require('demo-exports'))") == "root"
assert node("commonjs", "console.log(require('./node_modules/demo-exports/internal.cjs'))") == "helper"
for mode, operation in (
("commonjs", "require('demo-exports/internal.cjs')"),
("module", "await import('demo-exports/internal.cjs')"),
):
result = node(mode, f"try {{ {operation} }} catch (err) {{ console.log(err.code) }}")
assert result == "ERR_PACKAGE_PATH_NOT_EXPORTED"
print(mode, result)
config["exports"]["./helper"] = "./internal.cjs"
manifest.write_text(json.dumps(config), encoding="utf-8")
assert node("commonjs", "console.log(require('demo-exports/helper'))") == "helper"
assert node("module", "import h from 'demo-exports/helper'; console.log(h)") == "helper"
print("demo-exports/helper works in both loaders")
commonjs ERR_PACKAGE_PATH_NOT_EXPORTED
module ERR_PACKAGE_PATH_NOT_EXPORTED
demo-exports/helper works in both loaders
2. 按维护者和消费方各自的入口修复
消费方先找包的文档,确认同一功能是否从根入口或其他公开子路径导出。代码依赖旧版深层路径时,查清版本边界,再把调用迁到受支持的入口。直接拼安装路径虽然在样本中可行,但会依赖目录布局,升级或切换包管理器后容易失效。
维护者给旧包增加 exports 时,要盘点已有的公开入口。旧版曾承诺 pkg/feature 和 pkg/feature.js 的话,需要明确保留对应键,或在迁移说明里指出路径变更。发布前用打包产物建一个干净的消费方项目,分别运行实际支持的 import 与 require。两种加载器可能因条件导出命中不同文件,本例特意让它们共用一个 CJS 目标来隔离子路径因素。
小结
把包名和子路径当作发布接口。复现时应从消费方导入,直接执行包内文件无法验证公开入口。
数据来源与检索时间
| 来源 | URL | 检索日期 | 对应内容 |
|---|---|---|---|
| Node.js 24.19.0 Packages | https://nodejs.org/download/release/v24.19.0/docs/api/packages.html#package-entry-points | 2026-11-13 | exports 的入口优先级、封装及兼容性 |
| Node.js 24.19.0 Subpath exports | https://nodejs.org/download/release/v24.19.0/docs/api/packages.html#subpath-exports | 2026-11-13 | 显式公开子路径及相关错误 |
核验说明
- Windows、Python 3.11.9、Node.js 24.19.0 实测 CommonJS 和 ESM。实验使用本地临时包,未测试 bundler 或发布流程。