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

[FEATURE] absolute->relative module path transformation #15479

Closed
Kitanotori opened this issue Apr 30, 2017 · 87 comments
Closed

[FEATURE] absolute->relative module path transformation #15479

Kitanotori opened this issue Apr 30, 2017 · 87 comments
Labels
Out of Scope Suggestion

Comments

@Kitanotori
Copy link

@Kitanotori Kitanotori commented Apr 30, 2017

Problem

tsc does not support transforming absolute module paths into relative paths for modules located outside node_modules. In client side apps, this is often not an issue because people tend to use Webpack or similar tool for transformations and bundling, but for TypeScript apps targeting Node.js, this is an issue because there is usually no need for complex transformations and bundling, and adding additional build steps and tools on top of tsc only for path transformation is cumbersome.

Example input (es2015 style):

import { myModule } from 'myModuleRoot/a/b/my_module';

Example output (CommonJS style):

const myModule = require('./a/b/my_module');

My personal opinion is that relative module paths (import { ... } from '../../xxx/yyy';) are an abomination and make it difficult to figure out and change application structure. The possibility of using absolute paths would also be a major benefit of using TypeScript for Node.js apps.

Solution

Compiler options for tsc similar to Webpack's resolve.modules.

Could this be achieved for example with existing baseUrl and paths options and adding a new --rewriteAbsolute option?

Related

#5039
#12954

@aluanhaddad
Copy link
Contributor

@aluanhaddad aluanhaddad commented May 5, 2017

My personal opinion is that relative module paths (import { ... } from '../../xxx/yyy';) are an abomination and make it difficult to figure out and change application structure.

While I heartily agree with your sentiment, I think rewriting such imports would open up a can of worms that would ultimately break or complicate an insanely large number of tools and workflows that rely on the emitted JavaScript using the same module specifiers. Even under a flag, I think it will introduce a lot of complexity.

I hate relative paths that go up, it is just awful for maintainability, but my two cents is that this needs to be done on the NodeJS side, not the transpiler side. Of course it's extremely unlikely that will ever happen...

@ikokostya
Copy link
Contributor

@ikokostya ikokostya commented May 5, 2017

This issue can be solved if add node_modules directory with symlink to src directory:

project_root/
  node_modules/  <-- external modules here
  src/
     node_modules/  <-- keep this folder in git
        src -> ../src  <-- symlink to src
     a/
        b/
           c.ts
     d.ts
  tsconfig.json

In this case you can use the following import in c.ts:

import * as d from 'src/d';

instead

import * as d from '../../d';

All will work in typescript and commonjs. You just need to add exclude field in tsconfig.json:

{
    "exclude": [
        "src/node_modules"
    ]
}

@Kitanotori
Copy link
Author

@Kitanotori Kitanotori commented May 6, 2017

@aluanhaddad I've been using path rewrite (Webpack) on client-side since I began writing React apps. How would it cause problems on the server side if it doesn't cause problems on the client side? Editors already support setting module roots, and linters (ESLint, TSLint) seem to be fine also.

To the maintainers of TypeScript: Please add support for custom module roots so that we can get rid of the awful and unmaintainable import paths.

@Kitanotori
Copy link
Author

@Kitanotori Kitanotori commented May 6, 2017

@ikokostya Many tools have special treatment for node_modules, and it is always treated as a folder for external code. Putting app code into node_modules may solve the import path problem, but introduces potential other problems. It is also ugly solution that shouldn't, in my opinion, be recommended to anyone.

@ikokostya
Copy link
Contributor

@ikokostya ikokostya commented May 6, 2017

Many tools have special treatment for node_modules, and it is always treated as a folder for external code.

Could you provide example of such tools? In my example all external code are placed in node_modules in project_root.

Putting app code into node_modules may solve the import path problem, but introduces potential other problems.

Which problems?

It is also ugly solution that shouldn't, in my opinion, be recommended to anyone.

Maybe you should read this

And why it's ugly? It uses standard way for loading from node_modules.

@aluanhaddad
Copy link
Contributor

@aluanhaddad aluanhaddad commented May 7, 2017

@Kitanotori perhaps I misunderstood your suggestion, TypeScript supports this feature with the --baseUrl flag.
My point was that NodeJS doesn't support it and that TypeScript should not attempt to provide the future on top of NodeJS by rewriting paths in output code.

@ikokostya

Could you provide example of such tools?

There are too many to count but TypeScript is definitely an example of such a tool.

I think putting application code in a node_modules folder is a very ill-advised hack.

@Kitanotori
Copy link
Author

@Kitanotori Kitanotori commented May 7, 2017

@aluanhaddad --baseUrl setting enables to transpile the app, but what's the point of being able to transpile successfully if you can't execute it? ts-node does not seem to support --baseUrl, so I think it is inaccurate to say that TypeScript supports custom absolute paths.

@ikokostya Thanks for the links. It seems that setting NODE_PATH in the startup script is the best way currently. I think I will go with that approach.

@aluanhaddad
Copy link
Contributor

@aluanhaddad aluanhaddad commented May 9, 2017

@Kitanotori

@aluanhaddad --baseUrl setting enables to transpile the app, but what's the point of being able to transpile successfully if you can't execute it? ts-node does not seem to support --baseUrl, so I think it is inaccurate to say that TypeScript supports custom absolute paths.

The point is that it does execute perfectly in environments that support that. RequireJS, SystemJS, and even Webpack support setting a base URL.

What I'm trying to say is that the issue is on the NodeJS side. TypeScript provides base URL configuration to integrate with and take advantage of those other tools.

@RyanCavanaugh RyanCavanaugh added Out of Scope Suggestion Too Complex labels May 9, 2017
@RyanCavanaugh
Copy link
Member

@RyanCavanaugh RyanCavanaugh commented May 9, 2017

Our general take on this is that you should write the import path that works at runtime, and set your TS flags to satisfy the compiler's module resolution step, rather than writing the import that works out-of-the-box for TS and then trying to have some other step "fix" the paths to what works at runtime.

We have about a billion flags that affect module resolutions already (baseUrl, path mapping, rootDir, outDir, etc). Trying to rewrite import paths "correctly" under all these schemes would be an endless nightmare.

@Kitanotori
Copy link
Author

@Kitanotori Kitanotori commented May 9, 2017

@RyanCavanaugh Sorry to hear that. If Webpack team was able to deliver such feature, I thought TypeScript team could also - considering that TypeScript even has major corporate backing. It's a shame that people are forced to add another build step on top of tsc for such a widely needed feature.

@morlay
Copy link

@morlay morlay commented May 10, 2017

Still hope TypeScript could support this.
Or Just make it easy to create a plugin, so we can build something like babel-plugin-module-resolver to make it work. (ref: #11441)

@azarus
Copy link

@azarus azarus commented Jun 4, 2017

So anyone got any solution or a really nice workaround without babel to make this happen ?

Edit
I've ended up with a custom transform script using gulp & tsify & gulp-typescript
https://gist.github.com/azarus/f369ee2ab0283ba0793b0ccf0e9ec590
Browserify & Gulp samples included.
So anyone got any solution or a really nice workaround without babel to make this happen ?

@Kitanotori
Copy link
Author

@Kitanotori Kitanotori commented Jun 9, 2017

@azarus I went with the solution of adding this to my top level file:

import * as AppModulePath from 'app-module-path';
AppModulePath.addPath(__dirname);

The downside is that if the loading order differs from expected, the app crashes. Better solution would be to have a compiler plugin for converting absolute paths to relative paths. However, I'm not sure if TypeScript has any kind of plugin feature.

@Kitanotori
Copy link
Author

@Kitanotori Kitanotori commented Jun 9, 2017

I created a post-build script for converting absolute paths to relative (doesn't yet have way to set the module root paths, but one can easily implement such): https://gist.github.com/Kitanotori/86c906b2c4032d4426b750d232a4133b

I was thinking of having the module roots being set in package.json via moduleRoots array containing path strings relative to package.json. I wonder what kind of risks there are in this kind of approach?

@azarus
Copy link

@azarus azarus commented Jun 9, 2017

I've ended up with my own post build script too,
https://gist.github.com/azarus/f369ee2ab0283ba0793b0ccf0e9ec590
Browserify & Gulp samples included.

It acutally uses the tsconfig.json paths and baseUrl setup :)
I am gonna make a npm package from this during the weekend, i just haven't had time to properly test the script.

@kayjtea
Copy link

@kayjtea kayjtea commented Aug 3, 2017

I have this in my tsconfig:

{
  "compilerOptions": {
    ...
    "module": "commonjs",
    "moduleResolution": "node",
    "rootDir": "src",
    "baseUrl": "src",
    "paths": {
      "~/*": ["*"]
    }
  },

I can't remember if the other options above are necessary, but "paths" will allow this:

import {fundManagersApi} from "~/core/api/fund-managers.api";

The core directory is at src/core.

And then I use the npm package "module-alias" and do:

require('module-alias').addAlias("~", __dirname + "/src");

At the top of my top level file.

Webstorm understands the paths option and will auto-import correctly even.

@vakhtang
Copy link

@vakhtang vakhtang commented Sep 6, 2017

I use tsconfig-paths to handle this during runtime. I'm not aware of any performance implications or issues otherwise. The one objection I could see is monkey-patching Node's module resolution strategy.

@0x80
Copy link

@0x80 0x80 commented Jan 6, 2018

@kayjtea I don't think module and moduleResolution are related. Here's a slightly simpeler version that seems to work:

    "baseUrl": "./",
    "paths": {
      "~/*": [
        "src/*"
      ]
    },

Personally, I prefer writing @src, and for this you can use:

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

In VSCode I don't seem to have to do anything with my top-level files or npm module-alias.

--- edit ---
Ah damn it. I see now that the paths don't work at runtime work if you don't do more then make the compiler and VSCode happy.

@azarus
Copy link

@azarus azarus commented Jan 6, 2018

@0x80
Copy link

@0x80 0x80 commented Jan 6, 2018

@azarus I think I might have to go that route too.

I have two repo's that are also depending on roughly the same set of helpers and services. And I am getting tired of copying code between the two.

@azarus
Copy link

@azarus azarus commented Jan 6, 2018

@0x80 i ran into a similar problem when had to share model definitions between 5 different services. I solved this by creating a shared folder that everyone were able to access if needed.
Setting up an NPM link is also easy and less hassle than you would think.
But i am sad that proper solution is still not in place.

@duffman
Copy link

@duffman duffman commented Jan 14, 2018

Use tspath (https://www.npmjs.com/package/tspath) run it in the project folder after compile, no config needed, it uses the tsconfig to figure things out (a recent version of node is needed)

@0x80
Copy link

@0x80 0x80 commented Jan 14, 2018

@duffman Thanks for the tip! I'll check it out 👍

@stereobooster
Copy link

@stereobooster stereobooster commented Feb 20, 2018

In case you want to configure create-react-app project add:

{
  "compilerOptions": {
    //... other react-scripts-ts options
    "paths": {
      "src/*": ["*"]
    },
    "baseUrl": "src"
  },

Is equivalent to NODE_PATH=./ in .env in standard c-r-a project.

@nibmz7
Copy link

@nibmz7 nibmz7 commented Aug 28, 2020

Wasted hours on this but it's probably because I'm new to Typescript. Still glad I found this thread so I can stop trying. Reverted back to relative path.

Update: I now use this feature alongside webpack config.

zappen999 added a commit to zappen999/g-code-builder that referenced this issue Sep 14, 2020
TypeScript doesn't have support for rewriting import paths, even though
they have support for baseUrl and paths compiler option. We therefore
must alter the import paths after tsc has built the code. This is done
using 'tsc-alias'.

microsoft/TypeScript#15479

For the test case, we use 'tsconfig-paths' package to solve this in
runtime (because we don't recompile the entire project on each save when
watching tests).
@kasvith
Copy link

@kasvith kasvith commented Dec 17, 2020

I think, if tsc not gonna resolve these paths in a meaningful way(even with an option in tsconfig.json or a flag), it's not useful as the user intends to resolve this path by typescript itself.

If we use another tool to resolve modules as a post-build step, there is no useful outcome from tsc. You gotta do additional work. I wanted to use path aliases and now I will not use it at all.

@dustinlacewell
Copy link

@dustinlacewell dustinlacewell commented Jan 16, 2021

Braindead decision.

@Dylan0916
Copy link

@Dylan0916 Dylan0916 commented Jan 24, 2021

I fixed it using this library tsc-alias

"script": {
    "build": "tsc && npx tsc-alias"
}

Ex: from @src/controllers/user.ts to ../controllers/user.js

It saved me

@pwhissell
Copy link

@pwhissell pwhissell commented Apr 26, 2021

@RyanCavanaugh do you think eslint-plugin-import should be responsible for adding a rule that could ensure the developper uses ".js" extension for every relative import?

github-actions bot pushed a commit to nydelic/toolbox that referenced this issue May 30, 2021
# 1.0.0 (2021-05-30)

### Bug Fixes

* baseUrl not working with compiliation (microsoft/TypeScript#15479) ([0439fd9](0439fd9))
* init base configs and files ([30c427e](30c427e))
* init npmignore ([2801f0e](2801f0e))
* init readme ([b451a64](b451a64))
* init repository ([1e24dd0](1e24dd0))
* init ttsc + sematic release ([b873beb](b873beb))
* update release yml ([a87e812](a87e812))
@TeemuKoivisto
Copy link

@TeemuKoivisto TeemuKoivisto commented Jun 21, 2021

It was a bit annoying but I finally managed to work this out with my Rollup config. I wrote about it in StackOverflow but what I basically did was add @zerollup/ts-transform-paths

@isometriq
Copy link

@isometriq isometriq commented Jun 24, 2021

It was a bit annoying but I finally managed to work this out with my Rollup config. I wrote about it in StackOverflow but what I basically did was add @zerollup/ts-transform-paths

@TeemuKoivisto Thank you so much, it worked great with a rollup config. Now I have all boxes checked: clean absolute paths, vscode that understands them (absolute auto-imports) and correct definitions for external use.

@jpbberry
Copy link

@jpbberry jpbberry commented Jul 26, 2021

I just don't understand why this isn't a thing. TypeScript added support for the paths and then they're nearly completely useless for normal runtime, why? The fact that it's here, doesn't error at compile-time, and then errors at runtime, doesn't make any sense, it should be translating the paths to actually run like it says it will when defining these paths. Even if it's just an option, the lack of the ability to do this directly within tsc makes the entire paths option useless and even misleading.

@RacerDelux
Copy link

@RacerDelux RacerDelux commented Apr 8, 2022

Another issue is when you use typescript with the msbuild typescript package.
I don't have NPM, I don't use NPM build pipeline, I don't use webpack.
I just want the output javascript files to append the base url (as an option).
What is the argument against this setting in compilerOptions?:
"appendBaseUrl" : true or false

This would allow us to use relative paths just fine.
import { Something } from "global.js";
Would become:
import { Something } from "./global.js";
With these options in tsconfig:
"compilerOptions": { "baseUrl": "./", "appendBaseUrl": true }

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

No branches or pull requests