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

How to properly make module alias work when building the dist/build folder? #74

Closed
Tracked by #40
arvi opened this issue Sep 25, 2019 · 13 comments
Closed
Tracked by #40

Comments

@arvi
Copy link

arvi commented Sep 25, 2019

I was experimenting on module alias for typescript and it works during dev, but when I try to build it I always get cannot find module because it is pointing to src file even on build. I tried experimenting on different configs and searched all over but can't still get it to work. I thought I should just ask here for advice.

Here is my project structure:

|-project
|---src
|-------index.ts  // (where I put require('module-alias/register');)
|-------app (business logic) // where I start using modules e.g import config from '@config/index.config';
|-----------index.ts
|-----------routes
|-------config
|---dist
|---node_modules
|---.env
|---.eslintrc
|---package.json
|---tsconfig.json
|---yarn.lock

Snippet of my package.json

"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
  "dev": "NODE_ENV=development ts-node-dev --respawn --transpileOnly ./src/index.ts",
  "prod": "yarn run build && node -r ./dist/index.js",
  "build": "NODE_ENV=production yarn run build-ts",
  "build-ts": "tsc -p .",
},  
"_moduleAliases": {
  "@config": "./src/config",
  "@util": "./src/util",
  "@middlewares": "./src/app/middlewares",
  "@routes": "./src/app/routes",
  "@controllers": "./src/app/controllers",
  "@models": "./src/app/models",
  "@helpers": "./src/app/helpers"
},

My tsconfig.json

{
  "compilerOptions": {
    "incremental": true,
    "target": "es6",
    "module": "commonjs",
    "declaration": true,
    "sourceMap": true,
    "outDir": "./dist",
    "removeComments": true,
    "strict": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "noUnusedLocals": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "suppressImplicitAnyIndexErrors": true,
    "moduleResolution": "node",
    "baseUrl": "./src",
    "paths": {
      "@config/*": ["config/*"],
      "@util/*": ["util/*"],
      "@middlewares/*": ["app/middlewares/*"],
      "@routes/*": ["app/routes/*"],
      "@controllers/*": ["app/controllers/*"],
      "@models/*": ["app/models/*"],
      "@helpers/*": ["app/helpers/*"]
    },
    "allowSyntheticDefaultImports": true, 
    "esModuleInterop": true 
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

My .eslintrc (not sure if it matters or relevant to this problem)

{
  "extends": ["airbnb-typescript/base"],
  "rules": {
    "import/prefer-default-export": 0
  },
  "settings": {
    "import/resolver": {
      "babel-module": {}
    }
  }
}

Sample of how I use it

import logger from '@util/logger/generic.logger';
import { NextFunction, Request, Response } from 'express';

I hope you can help me point out where I went wrong with my config. Thanks a lot.

@ilearnio
Copy link
Owner

ilearnio commented Sep 25, 2019

@arvi Looks like in your src folder it works because you have aliases setup in tsconfig.json which is correct. So even though Typescript sources require module-alias it never get's executed because TS should be compiled to JS first. After it's compiled to JS into dist this is where module-alias should come to action. So instead of using aliases from tsconfig.json it starts using them from package.json.

To sum up, TS is using aliases from tsconfig.json, JS is using them from package.json.

So in your package.json they should only point to dist:

    "paths": {
      "@config/*": ["dist/config/*"],
      "@util/*": ["dist/util/*"],
      "@middlewares/*": ["dist/app/middlewares/*"],
      "@routes/*": ["dist/app/routes/*"],
      "@controllers/*": ["dist/app/controllers/*"],
      "@models/*": ["dist/app/models/*"],
      "@helpers/*": ["dist/app/helpers/*"]
    },

@arvi
Copy link
Author

arvi commented Sep 25, 2019

Hello @ilearnio . Thanks for your response.

You mean replace src with dist in package.json right? Upon checking package json docu, there is no path property.:

  "_moduleAliases": {
    "@config": "./dist/config",
    "@util": "./dist/util",
    "@middlewares": "./dist/app/middlewares",
    "@routes": "./dist/app/routes",
    "@controllers": "./dist/app/controllers",
    "@models": "./dist/app/models",
    "@helpers": "./dist/app/helpers"
  },

This worked and compiled correctly. The issue now is when I run dev again, it doesn't respect the paths in the tsconfig.json and is showing the dist content instead of the newly updated src content. I also tried tsconfig-paths:

"dev": "NODE_ENV=development ts-node -r tsconfig-paths/register ./src/index.ts | pino-pretty",

But still nope. I can only work on my project in dev when I change back the _moduleAliases in package.json back to src again.

@ilearnio
Copy link
Owner

@arvi It could be because you are using ts-node which compiles TS in background. Since you have compliled dist folder already you may not need ts-node at all. Is it possible to change it so your dev script would run node ./dist/index.js? You can run TS watcher as tsc -w a separate process that will keep dist always up to date

@Kehrlann
Copy link
Collaborator

Interesting discussion !

Another possibility is to look into something like tsconfig-paths. It seems designed for this use-case.

@arvi
Copy link
Author

arvi commented Sep 26, 2019

Hello @Kehrlann,

I also did that as my nth attempt 😄. It doesn't recognize that as well. It points to what is defined in package.json. I'll try what @ilearnio suggested about tsc -w later when I get home and update this thread.

@Kehrlann
Copy link
Collaborator

Kehrlann commented Sep 26, 2019

Here's an example @arvi : https://github.com/Kehrlann/module-alias-74

Edit for more context:
tsconfig-path replaces aliases with the actual physical paths. To do so, it uses your tsconfig.json file, specifically baseUrl and paths. Since your baseUrl is "src", it constructs paths that look like src/.... But your outDir is "dist", those paths are not valid in the compiled app. You need a different config for tsconfig-path, telling it that your baseUrl is "dist" in this case. This is achieved by setting up tsconfig-paths programatically with:

node -r ./prod-paths.js ./dist/index.js

@arvi
Copy link
Author

arvi commented Sep 27, 2019

@Kehrlann ,

Oh my gosh! 😄 I can't thank you enough! It finally worked! Hope you have that answer on my SO question 😅 so I can upvote you haha. But anyways, I have posted your answer in that thread.

Thanks for all your help @ilearnio and @Kehrlann . I appreciate it a lot.

@vschoener
Copy link

Here's an example @arvi : https://github.com/Kehrlann/module-alias-74

Edit for more context:
tsconfig-path replaces aliases with the actual physical paths. To do so, it uses your tsconfig.json file, specifically baseUrl and paths. Since your baseUrl is "src", it constructs paths that look like src/.... But your outDir is "dist", those paths are not valid in the compiled app. You need a different config for tsconfig-path, telling it that your baseUrl is "dist" in this case. This is achieved by setting up tsconfig-paths programatically with:

node -r ./prod-paths.js ./dist/index.js

Hey, I was looking at your solution but as tsconfig-paths is installed in the dev dependencies, when building your code with docker, for example, this package won't be able in a production environment.
What do you think?

@Kehrlann
Copy link
Collaborator

Good point @vschoener ; I should probably include tsconfig-paths in the dependencies. I'll update that, thanks <3

@rebirthtobi
Copy link

For anyone having this issue, this is another way to do this, put it in the entry file at the top before other imports.

import moduleAlias from "module-alias";

if (process.env.NODE_ENV === "production") {
    moduleAlias.addAliases({
        "@shared": `${__dirname}/shared`,
        "@modules": `${__dirname}/modules`,
        "@models": `${__dirname}/models`
    })
} else {
    moduleAlias.addAliases({
        "@shared": `${__dirname}/shared`,
        "@modules": `${__dirname}/modules`,
        "@models": `${__dirname}/models`
    })
}

@Kehrlann
Copy link
Collaborator

Also a good suggestion @rebirthtobi , thank you !

It seems the two paths in your if branch are the same. So in your specific case you probably don't even need an "if".

@PRossetti
Copy link

PRossetti commented Feb 11, 2021

@rebirthtobi Thank you very much! This worked perfectly for me!
Just two clarifications that could be helpful to someone having this same issue.

  1. I had to remove the _moduleAliases from package.json (otherwise wasn't working)
  2. I didn't use the if condition because in the example the same thing is done both in the if and else (like @Kehrlann said)

Just replaced this
import 'module-alias/register';

for this

import moduleAlias from 'module-alias';
moduleAlias.addAliases({
  "@shared": `${__dirname}/shared`,
  "@modules": `${__dirname}/modules`,
  "@models": `${__dirname}/models`
});

@razb-viola
Copy link

razb-viola commented Apr 1, 2024

What happens if the imported module is in a parent directory? For example I have a common module that I use both in frontend and backend.

Directory structure:

project/backend/file.ts (imports like this: ../common/hello)
project/common/hello.ts
project/frontend/file.ts (imports like this: ../common/hello)

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

7 participants