You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义ESM包导入遇ERR_UNSUPPORTED_DIR_IMPORT,如何让TSC构建时检测?

问题描述

我创建了一个仅支持ESM的包,未在package.json中定义exports字段,直接暴露所有文件。包的package.json配置如下:

{
  "name": "@org/runtime",
  "version": "0.0.0-development",
  "description": "Runtime library",
  "module": "index.js",
  "type": "module",
  "scripts": {
    "b": "pnpm build",
    "prebuild": "rm -rf dist",
    "build": "tsc --project tsconfig.build.json && pnpm build:copy-dts && tsc-alias && cp package.json dist/",
    "build:copy-dts": "copyfiles -V -u 1 src/**/*.d.ts src/*.d.ts dist",
    "lint": "eslint .",
    "test": "vitest",
    "test:ci": "vitest --coverage",
    "release": "semantic-release"
  },
  "devDependencies": {
    "tsc-alias": "^1.8.10",
    "typescript": "^5.5.3"
  },
  "dependencies": {},
  "engines": {
    "node": ">=22"
  }
}

构建后的文件结构:

├── background-job
│   ├── index.d.ts
│   └── index.js
├── logger
│   ├── formatter
│   │   ├── console-formatter.d.ts
│   │   ├── console-formatter.js
│   │   ├── formatter.d.ts
│   │   ├── formatter.js
│   │   ├── index.d.ts
│   │   ├── index.js
│   │   ├── json-formatter.d.ts
│   │   └── json-formatter.js
│   ├── index.d.ts
│   ├── index.js
│   ├── shared.d.ts
│   └── shared.js
├── package.json
├── request-context
│   ├── fastify.d.ts
│   ├── fastify.js
│   ├── index.d.ts
│   ├── index.js
│   ├── plugin.d.ts
│   └── plugin.js
├── result.d.ts
├── result.js
├── tests
│   ├── setup.d.ts
│   └── setup.js
├── types.d.ts
├── validations
│   ├── is-truthy.d.ts
│   └── is-truthy.js
└── validator
    ├── index.d.ts
    ├── index.js
    ├── schema-extensions.d.ts
    ├── schema-extensions.js
    └── yup.d.ts

主项目的tsconfig.json配置:

{
  "compilerOptions": {
    "target": "ES2023",
    "module": "NodeNext",
    "esModuleInterop": true,
    "moduleResolution": "NodeNext",
    "lib": [
      "ESNext"
    ],
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "rootDir": "src",
    "baseUrl": ".",
    "typeRoots": [
      "node_modules/@types"
    ],
    "types": [
      "node"
    ],
    "declaration": false,
    "sourceMap": false,
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  },
  "include": [
    "src/**/*.ts",
    "node_modules/@types/node/globals.d.ts",
    "node_modules/vitest/globals.d.ts",
    "node_modules/.pnpm/vitest@2.0.5_@types+node@20.16.1/node_modules/vitest/globals.d.ts"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ],
  "tsc-alias": {
    "resolveFullPaths": true,
    "verbose": true
  }
}

安装包后,尝试导入:

import { Logger } from '@org/runtime/logger';

ESLint、IDE和tsc均无警告或错误,能正常完成lint和构建,但运行转译后的代码时出现错误:

Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '/my-project/node_modules/@org/runtime/logger' is not supported resolving ES modules imported from /my-project/index.js
Did you mean to import "@org/runtime/logger/index.js"?

添加index.js后缀后可正常运行,希望不依赖ESLint,让TSC在构建阶段或IDE提前识别该问题,请问该如何处理?

解决方案

1. 给包添加exports字段(推荐)

在包的package.json中定义exports,明确指定各个子路径的入口,统一TypeScript和Node.js的解析规则,消除目录导入的歧义。

修改包的package.json,添加如下内容:

"exports": {
  ".": "./index.js",
  "./logger": "./logger/index.js",
  "./logger/*": "./logger/*",
  "./background-job": "./background-job/index.js",
  "./request-context": "./request-context/index.js",
  "./validations/*": "./validations/*",
  "./validator": "./validator/index.js",
  "./result": "./result.js",
  "./types": "./types.d.ts"
}
  • 配置后,@org/runtime/logger会被明确映射到logger/index.js,TypeScript编译时会自动生成带正确后缀的导入语句。
  • IDE会根据exports定义提供准确的路径提示,避免错误的目录导入写法。

2. 配置TypeScript启用严格模块解析

在主项目的tsconfig.json的compilerOptions中,启用严格模块解析规则,让TSC和IDE检查导入路径是否符合ESM规范:

"compilerOptions": {
  // 保留原有配置
  "strictModuleResolution": true,
  "moduleSuffixes": [".js", ".ts"]
}
  • strictModuleResolution会强制TSC按照Node.js的ESM解析逻辑检查路径,不符合规范的导入会直接报错。
  • moduleSuffixes指定TSC优先识别的文件后缀,帮助IDE自动补全正确的导入路径。

3. 优化包的TypeScript构建配置

在包的tsconfig.build.json中添加以下配置,生成更准确的类型文件,提升IDE和主项目TSC的识别能力:

"compilerOptions": {
  // 保留原有配置
  "declaration": true,
  "declarationMap": true,
  "moduleResolution": "NodeNext",
  "strictModuleResolution": true
}
  • declarationMap会生成类型映射文件,让IDE可以直接关联到包的源码路径,提供更精准的导入提示。

4. 临时方案:直接导入带后缀的文件

如果暂时无法修改包的配置,可以在代码中直接导入带完整后缀的文件:

import { Logger } from '@org/runtime/logger/index.js';

这种方式虽能解决运行错误,但不够优雅,仅作为临时过渡方案。


内容的提问来源于stack exchange,提问作者Jose Truyol

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 00:28:09