Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Absolute import paths cannot be used in production #74

Closed
dislick opened this issue Feb 11, 2019 · 18 comments
Closed

Absolute import paths cannot be used in production #74

dislick opened this issue Feb 11, 2019 · 18 comments

Comments

@dislick
Copy link

dislick commented Feb 11, 2019

Issue

Absolute import paths like import { foo } from 'src/utils/foo' work great with ts-node, but fail when running npm run start:prod.

Error

internal/modules/cjs/loader.js:583
    throw err;
    ^

Error: Cannot find module 'src/utils/foo'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:581:15)
    at Function.Module._resolveFilename (/Users/patrick/r/typescript-starter/node_modules/tsconfig-paths/lib/register.js:75:40)
    at Function.Module._load (internal/modules/cjs/loader.js:507:25)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:22:18)
    at Object.<anonymous> (/Users/patrick/r/typescript-starter/dist/main.js:13:15)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)

Here is a fork of typescript-starter with minimal changes to reproduce the issue. Compare changes.

The start:prod command is already changed to include the tsconfig-paths/register module, from

{
  "start:prod": "node dist/main.js"
}

to

{
  "start:prod": "node -r tsconfig-paths/register dist/main.js"
}

unfortunately without any effect.

@dislick dislick changed the title Relative import paths cannot be used in production Absolute import paths cannot be used in production Feb 11, 2019
@dantman
Copy link

dantman commented Feb 17, 2019

Is this actually a supported way of running NestJS?

If it is, in addition to production it also does not appear to work in tests.

@dislick
Copy link
Author

dislick commented Feb 18, 2019

If it isn't supported we should remove tsconfig-paths from the starter repo. I was not even 100% aware I was using such a feature because of auto-imports in VSCode.

@dantman
Copy link

dantman commented Feb 18, 2019

Also remove baseUrl from the tsconfig. I found this issue because I was going to open an issue asking for baseUrl to be removed because it causes vscode to make src/ imports. Which apparently is an intended feature of baseUrl. But not supported by the jest config to my knowledge (I'm pretty certain that import failures in jest for src/ imports is how I found they were being inserted).

@kamilmysliwiec
Copy link
Member

You should never use such absolute imports src/utils/foo in your app because eventually, your code will very likely end up in a different directory (for example, dist). However, baseUrl is required in order to enable tsconfig paths which basic setup is shipped together with the starter project. Nonetheless, we don't force anybody to use them, even though it's a recommended way (it's always up to you).

@dantman
Copy link

dantman commented Feb 18, 2019

However, baseUrl is required in order to enable tsconfig paths which basic setup is shipped together with the starter project. Nonetheless, we don't force anybody to use them, even though it's a recommended way (it's always up to you).

Is there any way tsconfig paths can be enabled without baseUrl? Or can we at least warn new users of the side effects.

Because baseUrl explicitly enables those src/ absolute imports. And as a result development tools output those absolute paths because tsconfig has told them you want them to be output.

@kamilmysliwiec
Copy link
Member

@dantman Unfortunately, no. I have been struggling with the same issue as well and this is actually unbearable in the long run. I hope that IDEs will provide better integration with TS options soon.

@dantman
Copy link

dantman commented Feb 21, 2019

@kamilmysliwiec Since you're having this issue to (in vscode I presume), could you try setting "javascript.preferences.importModuleSpecifier": "relative" and see if it does anything.

@kamilmysliwiec
Copy link
Member

Thanks @dantman. However, I believe that it will disable typescript-paths feature which, on the other hand, are very useful.

@dantman
Copy link

dantman commented Feb 22, 2019

@kamilmysliwiec Can you confirm if that is the case, I don't have any typescript-paths to test. If it is then I'll try to get the other bug reopened.

@muyu66
Copy link

muyu66 commented Apr 1, 2019

try create a index.js in root path

require("ts-node/register"); require("./src/main");

then, node -r tsconfig-paths/register index.js

@korniychuk
Copy link

korniychuk commented Aug 20, 2019

The problem is that we don't have src directory in the dist after compilation.

This is the reason why imports like import ... from 'src/...' don't work in case compiling via tsc(when you making a build) or tsc-watch (when you running the app in dev mode).

There are several solutions:

  1. Add rootDir to your tsconfig.json to avoid omitting src directory during compilation.
    1.1. tsconfig.json:

    {
      "compilerOptions": {
        "baseUrl": "./",
      }
    }

    1.2 Add src to scripts in the package.json:

    "start:dev": "tsc-watch -p tsconfig.build.json --onSuccess \"node dist/src/main.js\"",

    Notice: imports like require('../ormconfig.json') from the root of project will not work. It looks like not a problem because these imports is a bad practice because in this case dist loses independence.

  2. Provide mapping without src in dist natively.
    2.1. Add NODE_PATH=dist prefix to your scripts in package.json.

    "start": "ts-node -r tsconfig-paths/register src/main.ts",
    "start:dev": "NODE_PATH=dist tsc-watch -p tsconfig.build.json --onSuccess \"node dist/main.js\"",
    "start:prod": "NODE_PATH=dist node dist/main.js",

    Notice: don't add NODE_PATH to ts-node ... commands.
    2.2. Replace "baseUrl": "./", with "baseUrl": "./src", in the your tsconfig.json
    2.3. When you import a file from the root omit src/.
    Example: import { ... } from 'utils/foo'; instead of src/utils/foo.

  3. Provide mapping without src in dist using module-alias package.
    3.1. Install the package. npm i --save module-alias or yarn add module-alias.
    3.2. Add next code to the end of package.json

     "_moduleAliases": {
       "@app": "./dist"
    }
    

    3.3. Add paths to compileOptions in the tsconfig.json

      "paths": {
        "@app/*": ["./src/*"]
      },
    

    3.4. The package will break execution via ts-node. Therefore we need to udpate start command in the package.json

    "start": "IS_TS_NODE=true ts-node -r tsconfig-paths/register src/main.ts",
    

    3.5. Add next code to the top of src/main.ts file:

    if (!process.env.IS_TS_NODE) {
      // tslint:disable-next-line:no-var-requires
      require('module-alias/register');
    }
    

    3.6. Write your imports like import { ... } from '@app/utils/foo'; instead of src/utils/foo.

    Notice: You can define multiple aliases ;-) For example, @utils for src/utils

PS: I prefer the third solution.

Warning: Previously proposed solution is a bad solution!

try create a index.js in root path
require("ts-node/register"); require("./src/main");
then, node -r tsconfig-paths/register index.js

Because this is the same to ts-node -r tsconfig-paths/register src/main.ts, but via a hack.
In this case, we use ts-node to compile TS to JS on the fly. This is good for development reasons, however fatal for production because of broken performance.

Notes:
tsconfig-paths issues:

@AndrewShwets
Copy link

AndrewShwets commented Sep 27, 2019

@korniychuk Thank you so much! Third solution is awesome.

@korniychuk
Copy link

korniychuk commented Oct 20, 2019

The problem is that we don't have src directory in the dist after compilation.
...
3. Provide mapping without src in dist using module-alias package.
3.1. Install the package. npm i --save module-alias or yarn add module-alias.
3.2. Add next code to the end of package.json

 "_moduleAliases": {
   "@app": "./dist"
}

3.3. Add paths to compileOptions in the tsconfig.json

  "paths": {
    "@app/*": ["./src/*"]
  },

3.4. The package will break execution via ts-node. Therefore we need to udpate start command in the package.json

"start": "IS_TS_NODE=true ts-node -r tsconfig-paths/register src/main.ts",

3.5. Add next code to the top of src/main.ts file:

if (!process.env.IS_TS_NODE) {
  // tslint:disable-next-line:no-var-requires
  require('module-alias/register');
}

3.6. Write your imports like import { ... } from '@app/utils/foo'; instead of src/utils/foo.
Notice: You can define multiple aliases ;-) For example, @utils for src/utils

PS: I prefer the third solution.

The third solution can be improved.
We can:

  • avoid tsconfig-paths/register dependency
  • avoid aliases duplication in 3 places (tsconfig.json, jest config, module-alias config).
  • avoid IS_TS_NODE variable

To do this we need to write a simple script for jest and module-alias to load paths directly from tsconfig.json.

The full solution you can find in this fork of the original starter:
https://github.com/korniychuk/nestjs-starter

@microcipcip
Copy link

microcipcip commented Nov 16, 2019

I've finally got it working without having to specify multiple aliases, and it works in production, here are the steps for my future self:

  1. Set the following parameters to tsconfig.json
{
  "compilerOptions": {
    //...other
    "outDir": "./dist",
    "baseUrl": ".",
    "paths": {
      "@src/*": ["src/*"]
    },
    "esModuleInterop": true
  }
}
  1. Install module-alias:
npm i module-alias
npm i -D @types/module-alias
  1. Create the file aliases.ts on ./src/config/aliases.ts with this code:
import moduleAlias from 'module-alias';
import path from 'path';

const rootPath = path.resolve(__dirname, '..', '..', 'dist');
moduleAlias.addAliases({
  '@src': rootPath,
});
  1. Import the newly created file on ./src/main.ts, note that this has to be the FIRST import:
import './config/aliases';
import { NestFactory } from '@nestjs/core';
// etc
  1. For the jest config (untested):
/* eslint-disable */
const { pathsToModuleNameMapper } = require('ts-jest/utils');
const { compilerOptions } = require('./tsconfig');

module.exports = {
  preset: 'ts-jest',
  rootDir: '.',
  testEnvironment: 'node',
  moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
    prefix: '<rootDir>/',
  }),
};

Done! Basically the idea here is that with module-alias we can change the import url on runtime, so we can effectively resolve for a different path from dev mode to prod mode.

Now we can import with absolute paths, for example with @src/auth/auth.repository.ts

@pxmage
Copy link

pxmage commented Nov 19, 2019

@microcipcip Thanks! You just saved my ass XD.

@felipegouveiae
Copy link

@kamilmysliwiec Since you're having this issue to (in vscode I presume), could you try setting "javascript.preferences.importModuleSpecifier": "relative" and see if it does anything.

This one worked for me perfectly.

deduced added a commit to deduced/reddit-clone that referenced this issue Sep 23, 2020
VSCode is making absolute imports (src/...) during autoimport, which breaks teh watch. The imports should be relative as compilation goes into ./dist. BaseUrl is required if using paths, but since that's not currently being used, easiest solution is to comment out baseurl. If paths will be used, then an alternative should be chosen.

See nestjs/typescript-starter#74 for more information.
laireyx added a commit to ploffer11/WebRTC-Group-Chatting that referenced this issue Apr 29, 2023
With baseUrl set to "./", vscode autocompletes import path as absolute.
It's a bad practice, see nestjs/typescript-starter#74 for more info.
Moreover, using absolute path conflicts with module resolution in jest.
laireyx added a commit to ploffer11/WebRTC-Group-Chatting that referenced this issue Apr 29, 2023
With baseUrl set to "./", vscode autocompletes import path as absolute.
It's a bad practice, see nestjs/typescript-starter#74 for more info.
Moreover, using absolute path conflicts with module resolution in jest.
@ninthsun91
Copy link

@microcipcip
Thanks, this finally worked. But I still don't get it why some Nestjs projects work fine without any external library installed such as module_alias, and some Nestjs projects fail to resolve alias path.

@microcipcip
Copy link

microcipcip commented Jun 26, 2023

@microcipcip Thanks, this finally worked. But I still don't get it why some Nestjs projects work fine without any external library installed such as module_alias, and some Nestjs projects fail to resolve alias path.

If for some projects works it may be that they have solved it with a slightly different configuration like point 2.0 of this answer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants