I created an ESM-only package that expose all the files instead of defining exports
in 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"
}
}
I get a file structure like this:
├── 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
In my main project I have this 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
}
}
After installing my package, I try to import it like this:
import { Logger } from '@org/runtime/logger';
ESlint, my IDE and tsc work without any warning/error on that import statement. I can lint and build with no issues. Then, when I run the transpiled code I get the error
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"?
If I add index.js
to my import statement, I can run the project without issues. How can I make TSC catch these issues during build-time? Or how can I make the IDE aware of this issue?
I would prefer to avoid depending on eslint, so I can catch this when I build the project.
What I ended up doing was adding this to my package.json
"exports": {
"./*.js": {
"types": "./*.d.ts",
"require": "./*.js",
"import": "./*.js"
}
}
Now I can import any file, and I don't need to maintain the exports field.
import { Logger } from "@org/runtime/logger/index.js"