ts-node-dev: paths option(tsconfig.json) is ignored

Settings

tsconfig.json

Other options are omiited for brevity.

{
  "compilerOptions": {
    "baseUrl": "./src"
    "paths": {
      "#/*": ["./*"]
    }
  }
}
command
ts-node-dev --respawn --transpileOnly src/index.ts
src/index.ts
import { foo } from '#/foo'

console.log(`index.ts uses ${foo}`)

Problem

When I edit foo.ts, which index.ts depends on, ts-node-dev does not respawn. I guess it’s because ts-node-dev does not consider the paths option in tsconfig.json.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 33
  • Comments: 18 (4 by maintainers)

Most upvoted comments

For anyone finding this and want a two-liner here it is: npm i -D tsconfig-paths

then in your package.json add a script like this: "dev": "tsnd --respawn -r tsconfig-paths/register *pathtoyourmainfile* "

@whitecolor I would recommend to add a note about this (also mentioning baseUrl, since one really doesn’t have to use paths for things to break) in points of notice in the README.

Because while I love ts-node-dev for the reason you mentioned in this issue, I almost just threw in the towel until I stumbled across your recommendation to use tsconfig-paths. Which is so simple to set up but… why would one even think about looking for it in the first place? At least all I perceived was an inconsistency between tsc and ts-node-dev and assumed it was a bug (hence then looking for it here).

If anyone has issues with this, try the module-alias plugin. So far it’s the cleanest solution I’ve found and it is working all right!

Given a directory structure like the following:

- build/                 // Here is the typescript outDir folder
- src/                   // Here is where we have all the app files
- src/index.ts
- src/app.ts
- package.json

Create a src/aliases.ts file like this:

import moduleAlias from 'module-alias';
import path from 'path';

const IS_DEV = process.env.NODE_ENV === 'development';
const rootPath = path.resolve(__dirname, '..', '..');
const rootPathDev = path.resolve(rootPath, 'src');
const rootPathProd = path.resolve(rootPath, 'build');
moduleAlias.addAliases({
  '@src': IS_DEV ? rootPathDev : rootPathProd,
});

Then, in the src/index.ts file, or in whatever is your entry file, add this as first import:

import './aliases'; // MUST BE FIRST!!

// Now you can use the `@src` for absolute imports
import { app } from '@src/app';

Finally, set tsconfig.json like this (mostly to make typescript and your IDE recognise the path):

{
  "compilerOptions": {
     "outDir": "./build/",
     "baseUrl": ".",
     "paths": {
       /* IMPORTANT: this must be the same of 'src/aliases.ts' */
       "@src/*": ["src/*"]
     },    
}

@whitecolor tsconfig paths does not seem to work with ts-node-dev. It worked well previously with nodemon and ts-node.

Running via npx ts-node-dev --project tsconfig.json -r tsconfig-paths/register src/server/server.ts

My aliased paths do not resolve.

my original answer to fix ts-node paths problem here: https://github.com/TypeStrong/ts-node/issues/138

if ts-node-dev also support to read “ts-node” configuration on tsconfig.json, i think it may help

Workaround

Here is a workaround for folks with the same problem.

Install concurrently (or another preferred alternative) and node-dev (or maybe another like nodemon).

npm uninstall ts-node-dev
npm install concurrently node-dev --save-dev

Then run

concurrently 'tsc -w' 'node-dev --respawn dist/index.js'

This would incrementally compile editted .ts files(tsc -w), and node-dev would respawn by detecting compiled .js files. Obviously, this is logically identical to what ts-node-env does, but now tsc respects the “paths” option.

@whitecolor Thanks for the recommendation again. The main point of this issue is asking if ts-node-dev would repect paths. I think it’s cool and necessary. How do you think?

This is strange “workaround” =)

Should this even work with node.js without special plugins?

TS compiler doesn’t transform require paths like ‘#/foo’, so it should just give the error Cannot find module '#/foo'

There is a module https://github.com/dividab/tsconfig-paths allows to handle tsconfig’s paths option with node.js

For anyone finding this and want a two-liner here it is: npm i -D tsconfig-paths

then in your package.json add a script like this: "dev": "tsnd --respawn -r tsconfig-paths/register *pathtoyourmainfile* "

It doesn’t work for me, but I have a -P path/to/tsconfig.json as well… It might be throwing things off?

Edit: It works with ts-node but not with ts-node-dev …

Edit Edit: got it working with cross-env TS_NODE_PROJECT=‘./src/some/folder/tsconfig.json’ ts-node-dev …

Thanks @microcipcip for directing me to module-alias plugin, since the other comments e.g tsconfig-paths did not work.

I created a src/aliases.ts file like this:

import moduleAlias from 'module-alias'

moduleAlias.addAliases({
  '#v1': __dirname + '/',
  '#v2': __dirname + '/v2'
})

Then, in the src/index.ts file, I added this as first import:

import './aliases'; // MUST BE FIRST!!

// Now you can use the `#v1` for absolute imports
import { app } from '#v1/app';

Inside my tsconfig.json I added this so my IDE can recognise the path:

    "baseUrl": "." /* Base directory to resolve non-absolute module names. */,
    "paths": {
      "#v1/*": ["./src/*"],
      "#v2/*": ["./src/v2/*"]
    } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */,

I have a dev script inside my package.json file

"dev": "ts-node-dev --respawn --transpile-only ./src",

If anyone has issues with this, try the module-alias plugin. So far it’s the cleanest solution I’ve found and it is working all right!

Given a directory structure like the following:

- build/                 // Here is the typescript outDir folder
- src/                   // Here is where we have all the app files
- src/index.ts
- src/app.ts
- package.json

Create a src/aliases.ts file like this:

import moduleAlias from 'module-alias';
import path from 'path';

const IS_DEV = process.env.NODE_ENV === 'development';
const rootPath = path.resolve(__dirname, '..', '..');
const rootPathDev = path.resolve(rootPath, 'src');
const rootPathProd = path.resolve(rootPath, 'build');
moduleAlias.addAliases({
  '@src': IS_DEV ? rootPathDev : rootPathProd,
});

Then, in the src/index.ts file, or in whatever is your entry file, add this as first import:

import './aliases'; // MUST BE FIRST!!

// Now you can use the `@src` for absolute imports
import { app } from '@src/app';

Finally, set tsconfig.json like this (mostly to make typescript and your IDE recognise the path):

{
  "compilerOptions": {
     "outDir": "./build/",
     "baseUrl": ".",
     "paths": {
       /* IMPORTANT: this must be the same of 'src/aliases.ts' */
       "@src/*": ["src/*"]
     },    
}

This is a task for another package, as I said you may use https://github.com/dividab/tsconfig-paths to achieve that if needed, setup is quite transparent.

Thanks @soupman99 it works like charm

@whitecolor

Thank you for the recommendation 😃 I’ve been using link-module-alias, which automatically creates symlinks in node_modules (And by this aspect, it has some other benefits, especially on plain node project, by the way). Whereas tsconfig-paths works on runtime, link-module-alias should be executed (only once) before running node.

TS compiler doesn’t transform require paths like ‘#/foo’, so it should just give the error Cannot find module '#/foo'

The compiler doesn’t transform but compiles it “as-is” (import foo from '#/foo' -> const foo = require('#/foo')), without an error or warning.

Should this even work with node.js without special plugins?

For example, I set a symlink node_modules/# -> dist through link-module-alias. So #/foo is valid on compiled js.

So, I let ts compiler check types by looking “paths”, and node find modules by symlinks. Maybe you can read https://dev.to/larswaechter/path-aliases-with-typescript-in-nodejs-4353 if you want.

What I suggest is, of course, different from tsc’s behavior. Though ts compiler compiles modules aliases as-is, ts-node-dev should dynamically stitch ts files by looking “baseUrl” and “paths”. This only requires reading tsconfig.json and doing some regex, so I guess It’d be not a big deal.

Sometimes, project structure gets deeper and wider, so using absolute path provides significant benefit. If ts-node-dev doesn’t care them, people using aliases have to find other ways like the “workaround” I suggested. And what we should consider is, people negatively feel to fragmented setting. They prefer setting as consistent as possible through projects, no matter big or small. If ts-node-dev doesn’t support, then they might just avoid using it at all even on projects without aliases.

P.S.

This is strange “workaround” =)

“strange”…? Hmm, why? Well, I personally think it’s also a quite good approach. There would be pros and cons.

pros

  • Able to choose tools we prefer. concurrently, npm-run-all, parallelshell, node-dev, nodemon, forever, pm2 or whatever.
  • tsc --watch is stable, so straight-forward and beginner-friendly.
  • Able to compose various options of different tools by ourselves. It can be more fine-grained than options ts-node-dev provides.
  • More closer to production-like condition. Node process would directly and statically consume the entire file system source code is in. (While this logcally doesn’t neccessarily mean ts-node-dev has a disadvantage.)
  • Incremental compilation can be enabled. (I know ts-node-dev supports “dynamic” watching compilation, but don’t know if it supports “static” incremental compilation. But tsc --watch --incremental supports both ones.)

cons

  • node process could start before compilation(tsc -w) is completed, which would cause an initial error message printed. (However, it’s actually not a problem as it would be restarted automatically just after .ts modules are all compiled.)
  • tsc compiles even when ‘modules entrypoint (e.g. index.ts) does not depend on’ (e.g. test files) changes. (But this is very unlikey because there’s generally no reason to place such a file without “excluding” it on tsconfig.)
  • Maybe it looks not so “neat” ?

It works smoothly. jjangga0214/ts-boilerplate is a working example. You can clone it, run yarn/npm dev, and then edit .ts files. Then it would automatically restart the node process seamlessly.