Skip to content

Support exports and imports subpath (package.json) pointing to generated .js files #1974

Open
@InExtremaRes

Description

@InExtremaRes

Desired Behavior

Consider a source file src/package-a/index.ts that will be transpiled by tsc to build/package-a/index.js, and a package.json that looks like this:

{
  "type": "module",
  "imports": {
    "#package-a": "./build/package-a/index.js"
  }
}

Now TypeScript allows me to import import {...} from #package-a in any other file. Even when I am importing the generated .js file (that maybe don't exists yet), TypeScript knows that file will be generated from the .ts in src and correctly type-check that file, uses it for editor support, etc.

ts-node, however, doesn't know how to resolve the file and fails with

node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:366
    throw new ERR_MODULE_NOT_FOUND(
[...]

The same occurs for subpath exports: TypeScript resolves to the source file that will generate that .js but ts-node doesn't.

I would expect ts-node to follow TypeScript semantics here and resolve to the corresponding .ts file as the language server does.

Is this request related to a problem?

Using subpath imports (since v14, v12 in experimental) is a common practice to alleviate the burden of nested, relative paths, as well to provide "internal packages" and many other cases. TypeScript, with a well configured tsconfig.json and package.json, just works.

The issue is, you can't point to the .ts file in the "imports" field of the package.json to make ts-node works since that field will also be read by node at runtime to resolve the import, so it must point to the generated .js. This issue makes it really difficult to correctly use this Node feature.

Alternatives you've considered

My first idea was to use conditional imports so the package.json could look something like this:

{
  "type": "module",
  "imports": {
    "#package-a": {
      "ts-node": "./src/package-a/index.ts",
      "default": "./build/package-a/index.js"
    }
  }
}

However I can't figure out how to use a custom condition for ts-node (I'm using ts-node-esm but don't think that's relevant). I have tested with NODE_OPTIONS="-C ts-node" npx ts-node-esm thing but got the same error. A possible workaround is to allow custom conditions and the last pattern should work, but I still think the original example should be supported by ts-node out of the box (or at least via some config).

Additional context

I think the issue was commented on #1007, but that's a very long thread to follow a possible resolution there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions