14

I couldn't figure out why ts-node isn't resolving the alias when esm is enabled

I made a tiny project trying to isolate the issue as much as possible

package.json

{
  "type": "module"
}

tsconfig.json

{
  "compilerOptions": {
    "module": "es2020",                                
    "baseUrl": "./",                                  
    "paths": {
      "$lib/*": [
        "src/lib/*"
      ]
    },
  },
  "ts-node": {
    "esm": true
  }
}

test.ts

import { testFn } from "$lib/module"

testFn()

lib/module.ts

export function testFn () {
  console.log("Test function")
}

command

ts-node -r tsconfig-paths/register src/test.ts

Here's a minimal repo

Yunnosch
  • 26,130
  • 9
  • 42
  • 54
unloco
  • 6,928
  • 2
  • 47
  • 58
  • 2
    Any update on this? I'm having the same problem – DDana Apr 11 '22 at 11:55
  • @DDana Check the first line in my question, I linked to a solution – unloco Apr 12 '22 at 12:02
  • I tried it but unfortunately did not succeed. I wrote a comment there. If you can unblock me would really appreciate it https://github.com/TypeStrong/ts-node/discussions/1450#discussioncomment-2560012 – DDana Apr 13 '22 at 14:11
  • 3
    Just putting here OPs own answer [https://github.com/TypeStrong/ts-node/discussions/1450#discussioncomment-1806115](https://github.com/TypeStrong/ts-node/discussions/1450#discussioncomment-1806115) – Preda7or Apr 24 '22 at 16:55
  • @Preda7or If you'd like to summarize or explain the solution, I will accept it as the solution. Cheers – unloco Apr 24 '22 at 19:24

3 Answers3

6

Solution from: https://github.com/TypeStrong/ts-node/discussions/1450#discussion-3563207

At the moment, the ESM loader does not handle TypeScript path mappings. To make it work you can use the following custom loader:

// loader.js
import {
  resolve as resolveTs,
  getFormat,
  transformSource,
} from "ts-node/esm";
import * as tsConfigPaths from "tsconfig-paths"

export { getFormat, transformSource };

const { absoluteBaseUrl, paths } = tsConfigPaths.loadConfig()
const matchPath = tsConfigPaths.createMatchPath(absoluteBaseUrl, paths)

export function resolve(specifier, context, defaultResolver) {
  const mappedSpecifier = matchPath(specifier)
  if (mappedSpecifier) {
    specifier = `${mappedSpecifier}.js`
  }
  return resolveTs(specifier, context, defaultResolver);
}

Then use the loader with: node --loader loader.js index.ts

Caveat: This only works for module specifiers without an extension. For example, import /foo/bar works, but import /foo/bar.js and import /foo/bar.ts do not.

Remember to install these packages as well:

"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.2",
kollein
  • 328
  • 3
  • 10
  • 2
    don't forget to rexport `load` as well from `ts-node/esm` or else you'll get `TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"` – RyanDay Mar 13 '23 at 05:28
  • Can we make this work with paths that end with `.js`? I suspect, this has something to do with `\`${mappedSpecifier}.js\`` – Parzh from Ukraine Jun 20 '23 at 12:17
3

I suggest to use esno esno src/test.ts

David
  • 1,094
  • 9
  • 9
1

You can use @bleed-believer/path-alias to execute your ESM project (requires ts-node installed in your project):

npm i --save @bleed-believer/path-alias

To execute your source files with ts-node:

node --no-warnings --loader @bleed-believer/path-alias/esm ./src/test.ts

Assuming "outDir": "./dist", to execute your transpiled code directly with node (bypassing ts-node):

node --no-warnings --loader @bleed-believer/path-alias/esm ./dist/test.js

If you don't want to use the library to execute your transpiled project, you can use swc (this transpiler resolves the path aliases), or you can use tsc-alias after transpile your project with tsc.

SleepWritten
  • 334
  • 2
  • 5