[esm] Cannot import from external packages with an `exports` field that contains a wildcard and an extension
Search Terms
esm, external, exports, wildcard, extension
Expected Behavior
If a package defines an exports field
// foo_pkg/package.json
"exports": {
"./*.js": "./dist/*.js"
}
And a consuming package imports:
// src/import_foo.ts
import { foo } from "foo_pkg/foo.js";
console.log(foo);
The program should compile and execute with ts-node-esm.
Actual Behavior
An ERR_PACKAGE_PATH_NOT_EXPORTED exception is raised.
Steps to reproduce the problem
I have made a branch on a repo to reproduce this issue.
Minimal reproduction
git clone --single-branch --branch ts_node_import_err https://github.com/jm4rtinez/ts_node_sandbox.git
npm install
npx ts-node-esm ./src/import_foo.ts
If the compiled form of the program is run with Node, no error occurs:
node ./dist/import_foo.js
Specifications
- ts-node version: 10.9.1
- node version: 19.2.0
- TypeScript version: 4.9.4
- tsconfig.json, if you're using one:
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "NodeNext",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": true,
"outDir": "dist"
},
"include": ["src"]
}
- package.json:
{
"name": "ts-node-sandbox",
"version": "1.0.0",
"description": "",
"type": "module",
"scripts": {},
"author": "",
"license": "ISC",
"devDependencies": {
"ts-node": "^10.9.1",
"typescript": "~4.9"
},
"dependencies": {
"foo_pkg": "file:foo_pkg-1.0.0.tgz"
}
}
- Operating system and version: Windows 10 Pro Version 10.0.19045 Build 19045
- If Windows, are you using WSL or WSL2?: WSL2 Ubuntu 20.04.5 LTS
I just ran in to this, as well. exports isn't that new 🙃
turns out! I only had to change moduleResolution in my tsconfig.json to nodenext. yay
Hitting this too and I have moduleResolution set to NodeNext.
Removing the extension worked though. So the exports field is now "./*": "./src/*" instead of "./*.js": "./src/*.js"
The current implementation of the import resolution is from Node v15.3, and it basically assumes exports would either end with / or *. Export keys ending in an extension basically get ignored by the loop that tries to match them to the specifier to be resolved.
Since the file is a verbatim copy of Node's version I assume this matched the behaviour of Node at v15.3, but v16.9 updated it and all currently supported up to date Node versions now follow a different resolution logic.
As an example in some of my packages I like to enable folder paths to be resolved to index.js while still allow specific files to be imported too.
{
"exports": {
".": "./build/index.js",
"./*.js": "./build/*.js",
"./*": "./build/*/index.js",
"./*/": "./build/*/index.js"
},
}
I'd expect import * from 'mypackage/some-path/some-file.js' to be resolved to mypackage/build/some-path/some-file.js, however ts-node tries to load mypackage/build/some-path/some-file.js/index.js instead, because it cannot understand the 2nd mapping and uses the 3rd instead.
This exports config works fine in node.js, when not used with ts-node.
I've found the missing puzzle piece: https://github.com/nodejs/node/pull/39635
node-internal-modules-esm-resolve-v15.3.0.js should be updated to match at least Node v16.9's version, improved by the PR linked above. The feature was also backported to v14.19, but not v15. Using files from v15 seems to be an unfortunate choice, as LTS releases would have offered easier upgrade path for these files.
face this issue too.