Skip to content

[EPIC] Full TS-style resolution hooks #1514

Description

@cspotcode

Note: if you're here following a link from our docs about experimentalResolver, you know that our docs for this feature are currently quite limited. Feel free to experiment and ask questions in Discussions or on Discord. This epic tracks the development work to finish all the resolver features and eventually promote them out of experimental status.

Current status

Describes state of our main branch, may not be published to npm yet

  • All file extensions are supported, both CommonJS and ECMAScript modules
    • Use module: NodeNext, esm: true, and experimentalResolver: true. Everything works: cjs, cts, mjs, mts, with or without file extensions in import specifiers, in CommonJS or ESM files

Motivation

In TS, you import the emitted output path, and the compiler resolves this to the input source. They might have different extensions or be in different directories.

In ts-node, we should do the same.

Creating an epic to link together all the related tickets and tasks to make this happen.


Related tickets in no particular order:

How this works in TS

It's been a while since I read the TS code.

I believe that TS creates a mapping from all source files to their emit paths. It can do this because it starts with a glob of all source files, then computes the output path for each in a loop.

Aside: Does this work with files not included in a files array? Does it trigger inclusion of those files in compilation? If yes, that would violate my understanding above.

How it'll work in ts-node

ts-node must do the opposite of TS: we start from a runtime require() or import request, then work backwards to a source TS file. We don't know if such a source file exists; we might be importing a non-compiled .js file.

Mappings to keep in mind:

  • package.json exports
  • package.json main
  • node_modules search
  • Searching all matching file extensions
  • tsconfig.json outDir and rootDir correspondence
  • tsconfig.json paths
  • tsconfig.json baseUrl
  • tsconfig.json rootDirs

Supported extensions are affected by:

  • resolveJsonModule

  • allowJs

  • jsx

  • TS >=4.5 supports mts, cts, mjs, cjs

  • moduleResolution or module?

  • break down this list by (a) those needed when resolving relative specifiers (b) those needed when resolving non-relative specifiers, such as foo

Questions

"Resolve to source path" vs "pretend emit exists on disk"

When resolving from ./dist/foo.js to ./src/foo.ts we have 2x options:

  • A) Tell node that the path is ./dist/foo.js and give it the compiled output of ./src/foo.ts
    • Resolve hook resolves to ./dist/foo.js
    • Load hook understands ./dist -> ./src mapping
    • Sourcemap must map from ./dist to ./src
  • B) Tell node that the path is ./src/foo.ts and give it the compiled output of ./src/foo.ts
    • Resolve hook resolves to ./src/foo.ts
    • Load hook does not need to understand any mappings
    • Sourcemap must map to same filename

ts-node today does (B) but does not do most of the advanced resolution we'd be implementing in this epic.

(B) is closer to deno and probably what people want. __filename and import.meta.url will refer to the actual TS file being executed. This is technically slightly different than pre-compilation execution. However, my gut tells me users will be happier with an intuitive __filename and won't mind the slight difference with pre-compilation.

Note: resolve hook is equivalent to _resolveFilename, load hook is equivalent to compile() hook

ignore with resolver

If src/index.ts is ignored and dist/index.js resolves to src/index.ts, should the resolver do that mapping?

Activity

  1. changed the title [-][EPIC] Full TS-style resolution hooking[/-] [+][EPIC] Full TS-style resolution hookds[/+] on Oct 15, 2021
  2. changed the title [-][EPIC] Full TS-style resolution hookds[/-] [+][EPIC] Full TS-style resolution hooks[/+] on Oct 15, 2021
  3. added
    researchNeeds design work, investigation, or prototyping. Implementation uncertain.
    on Oct 15, 2021
  4. pinned this issue on Oct 15, 2021
  5. cainrus commented on Feb 18, 2022

    @cainrus

    If src/index.ts is ignored and dist/index.js resolves to src/index.ts, should the resolver do that mapping?

    Sounds like an edge case. Lib can throw an error with any unclear behavior for the first feature-version.

  6. unpinned this issue on May 31, 2022
  7. hitsthings commented on Feb 1, 2023

    @hitsthings

    Stumbled across this while looking for support for the --conditions flag in tsnode.

    Just wanted to mention that the list of "Mappings to keep in mind" might also need to consider the "imports" field in package.json. I had trouble using it with paths outside the package. E.g. "#other": "../sibling-package/index.js". Either TypeScript or ts-node didn't like it (unfortunately I changed approaches before figuring out exactly what went wrong there).

  8. cainrus commented on Feb 1, 2023

    @cainrus

    Just wanted to mention that the list of "Mappings to keep in mind" might also need to consider the "imports" field in package.json. I had trouble using it with paths outside the package. E.g. "#other": "../sibling-package/index.js". Either TypeScript or ts-node didn't like it (unfortunately I changed approaches before figuring out exactly what went wrong there).

    Do other tools also suffer from the same issue of varied dependency mapping across different configuration files (such as package.json mappings, tsconfig, jsconfig, swc config, etc.)? Has anyone developed any external loaders to solve this problem?

  9. JavaScriptBach commented on Jun 28, 2024

    @JavaScriptBach

    Is this still being worked on? I work on a large Typescript mono-repo and I can't have libraries shared by both ts-node and Node until this is implemented, because we use bare imports.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    researchNeeds design work, investigation, or prototyping. Implementation uncertain.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions