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

TypeScript paths config doesn't work with leading slashes. #13730

Closed
trusktr opened this issue Jan 27, 2017 · 53 comments
Closed

TypeScript paths config doesn't work with leading slashes. #13730

trusktr opened this issue Jan 27, 2017 · 53 comments
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@trusktr
Copy link

trusktr commented Jan 27, 2017

TypeScript Version: 2.1.1 / nightly (2.2.0-dev.201xxxxx)

2.1.5

Code

app.ts

import Foo from '/common/Foo'
console.log(new Foo)

tsconfig.json

{
    "compilerOptions": {
        "paths": {
            "/common/*":  ["../../common/*"]
        }
    }
}

Filesystem:

website/
    common/
        Foo.js
    somewhere/
        project/
            tsconfig.js
            app.js

Expected behavior:

tsc should be able to recognize that when it sees a dependency like /common/Foo it should look for website/common/Foo.js in the filesystem.

Actual behavior:

It can't.

** More details **

I can get the following to successfully resolve files, by simply removing the leading slashes:

app.ts

import Foo from 'common/Foo'
console.log(new Foo)

tsconfig.json

{
    "compilerOptions": {
        "paths": {
            "common/*":  ["../../common/*"]
        }
    }
}

Filesystem:

website/
    common/
        Foo.js
    somewhere/
        project/
            tsconfig.js
            app.js

In this case, however, the output module contains something like

output.js

define(['common/Foo'], function(Foo) { /*...*/ })

But this is problematic, because if baseUrl in RequireJS config is set,

require.config({baseUrl: '/some/place'})

then RequireJS will look for the module at example.com/some/place/common/Foo.js which doesn't exist. I need it to look for the dependency at example.com/common/Foo.js, f.e. TypeScript would output the following including the leading slash:

output.js

define(['/common/Foo'], function(Foo) { /*...*/ })

It would be great if TypeScript could support this case, then the output can still have an absolute path and work as expected.

Unfortunately, RequireJS doesn't support map configs like the following:

require.config({
    baseUrl: '/some/place',
    map: {
        common: '../../common'
    }
})

I think solving this in TypeScript is the best options because then it can be taken advantage of in other environments besides RequireJS.

@trusktr
Copy link
Author

trusktr commented Jan 30, 2017

Side note, I discovered that I was using RequireJS' map option wrong, so it is indeed possible to solve the problem in RequireJS:

require.config({
    baseUrl: '/some/place',
    map: {
        '*': {
            common: '../../common'
        }
    }
})

A TypeScript solution would still be nice.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label May 24, 2017
@hexsprite
Copy link

hexsprite commented Oct 17, 2017

I'm also having this problem when attempting to migrate my Meteor application to use TypeScript.

I have code like:

import { logger } from '/imports/logging'

Where Meteor maps /imports/logging to <srcdir>/imports/logging.js

Meteor doesn't use the RequireJS runtime so I don't have the ability to workaround like this.

My ideal would be to able to re-configure the root to my liking in tsconfig.json, ala: "absoluteRootPath": "./"

@mhegazy
Copy link
Contributor

mhegazy commented Oct 19, 2017

Leading slash refers to the root directory, so this is an absolute path. path mapping does not work on absolute or relative paths.

@mhegazy mhegazy added Working as Intended The behavior described is the intended behavior; this is not a bug and removed Needs Investigation This issue needs a team member to investigate its status. labels Oct 19, 2017
@hexsprite
Copy link

@mhegazy that's not compatible with Meteor JS which is a VERY popular Javascript application framework. (currently 38,469 stars on their GitHub Repo) https://github.com/meteor/meteor

all that would be required to solve this is a way to override the absolute path behavior for frameworks that desire it. The default could remain but just a flag as I described in my previous comment would work great.

@hexsprite
Copy link

@mhegazy here's some further info on how Meteor structures applications. https://guide.meteor.com/structure.html

@mhegazy
Copy link
Contributor

mhegazy commented Oct 19, 2017

it is not just a flag. it has implications on module names in output and generated declarations, which can then be consumed by third party apps.

The is really the same behavior as node today. / means the root.

@hexsprite
Copy link

This seems to go against the design goals of TypeScript because it introduces an opinion about import paths should be interpreted which is clearly not the same across all Javascript frameworks and platforms.

Bare node interprets it this way, sure, but many frameworks including the popular Meteor framework and also users of babel support mapping the leading slash to mean the project root not filesystem root.

Let's face it, importing a file from the filesystem root is about as useful as a caps lock key and we know many people remap that key as well.

And not that I think you should support everything that Flow does, but they also support mapping the leading slash to something else: https://www.npmjs.com/package/babel-plugin-root-import#dont-let-flow-be-confused

Here's some other examples of platforms that use the leading slash to mean something else:
https://www.npmjs.com/package/babel-root-slash-import

If you can't tell I REALLY want to use TypeScript on this project but this is a show-stopper. Thanks for considering this.

@mhegazy
Copy link
Contributor

mhegazy commented Oct 19, 2017

This seems to go against the design goals of TypeScript because it introduces an opinion about import paths should be interpreted which is clearly not the same across all Javascript frameworks and platforms.

This is about what a path means. we have mirrored the node module resolution in our module resolution strategy, and that is how @types\node\index.d.t.s works for instance. not sure why we should pick and chose what parts of the node module names is good and what is bad.

Here's some other examples of platforms that use the leading slash to mean something else:
https://www.npmjs.com/package/babel-root-slash-import

Here is you are expecting the compiler to rewrite your module name in the otuput. this is something the compiler does not do... it only needs to know where the file is, module names do not change.
Please see similar discussion in #18951 and #16640

@hexsprite
Copy link

Thanks for the extra details.

Actually, I just want the compiler to pass it through unchanged as Meteor in this case will properly interpret the paths in the resulting JS. It implements its own module loader.

I might have hijacked this thread a bit but ideally I just want TypeScript to find the files in the right location so that it can typecheck everything but I'm fine if it just passes the paths through unchanged and have the framework handle it at in its existing way at runtime.

So perhaps I should open a different issue for my case as it's somewhat different than the OP's issue (though related).

@mhegazy
Copy link
Contributor

mhegazy commented Oct 19, 2017

sure.

@mgrivera
Copy link

mgrivera commented Nov 3, 2017

Hi @hexsprite,
have you opened that issue already. I am interested in this theme because, like you, I use meteor and VS code and typescript, and have been experiencing this problem myself. Bye ...

@trusktr
Copy link
Author

trusktr commented Nov 3, 2017

@mhegazy

not sure why we should pick and chose what parts of the node module names is good and what is bad.

Hello Mohamed, you don't have to pick, you can let the end user pick by giving the user a configurable option. Then everyone will be happy doing it their own way.

@mhegazy mhegazy added Suggestion An idea for TypeScript and removed Working as Intended The behavior described is the intended behavior; this is not a bug labels Nov 9, 2017
@ribrdb
Copy link

ribrdb commented Jan 8, 2018

There should at least be a way to make this work for javascript.
I'm trying to get vscode to understand my .js files that are written for the browser. Browsers can import from absolute paths, but the path is obviously different in the url than it is on my disk.

@Bettelstab
Copy link

I have the same issue as @ribrdb. I am working at a project with other devs and the idea is to use typescript to analyze javascript with jsdoc annotations as the project should stay vanilla js, with using es6 modules in the browser. Removing the leading slash just doesn't work for that.

@krailler
Copy link

I have same issue :/

@turbobuilt
Copy link

Having the same issue with meteor and typescript... Just need to be able to map the "/" to the root of my project. Right now I have to use const Utilities = require("/imports/Utilities").default; and I don't get any intellisense. It seems to me like allowing a person to map "/" to any path they like is the easiest option. But maybe there is a better one out there. Regardless, there are a lot of people who want to use Meteor & typescript, and I think supporting Meteor would help the typescript project gain even more popularity.

@EECOLOR
Copy link
Contributor

EECOLOR commented Apr 18, 2018

The is really the same behavior as node today. / means the root.

While you are of course technically correct we could ask 'What does root mean?'. In web applications this means the web root. There are exactly 0 use cases where I want to include anything from the root of my system, my build server or my production server. One could even argue that it's unsafe to allow that.

In any case, to me it makes much more sense that / refers to the root of the application (for example ./src/). I however understand that for some other people (like the meteor crew) / should mean the directory that contains imports/.

I would recommend you to either allow for extensions to influence the resolving process (an example for webpack and an example for eslint) or allow the paths setting to contain absolute paths:

{ "paths": {  "/*": "./src/*" } }

The default behavior can remain the same and from a naive perspective this change does not seem to involve much as testing for an absolute path is rather easy: path.startsWith('/').

@rconnamacher
Copy link

FYI, this issue prevents VSCode from working with native web browser module loaders. Browsers don't support named imports, only relative paths starting with . or absolute paths relative to the web server's root starting with /. In a web browser not using a file: URL, import "/foo.js" doesn't attempt to access the root of the filesystem.

@Guema
Copy link

Guema commented Jun 11, 2018

While you are of course technically correct we could ask 'What does root mean?'. In web applications this means the web root. There are exactly 0 use cases where I want to include anything from the root of my system, my build server or my production server. One could even argue that it's unsafe to allow that.

Best point so far IMO. even for nodejs code, server code, there is no real sence to use root directory.
perhaps, it can be more understandable if you code an Electron.js (or Electron.js like tech) desktop app, maybe, but it is quite a special case...

The problem is, that the actual "NO WAY TO HACK" is pretty annoying, to me, at least, as it prevent plenty of framework using this syntax to be intellisence-compatible, and im not able to hack it... Im currently using meteor, and it is quite laborious, especially when coding large apps with great amount of data model. Intellisence is really useful to get instantly in the code, get desciptions on the go, and correct eventual spelling problem without having to read all files again.

I suggest please to at least add a a js/tsconfig flag, allowing to compute initial "/" as app path instead of system root for intellisence, as it is not possible at all to hack it afterward, as glob does not interpret "/" as any other character.

@EECOLOR
Copy link
Contributor

EECOLOR commented Jun 16, 2018

I have not heard any convincing arguments to not put this in.

I am willing to dive in and create a pull request but I would need some confirmation from the maintainers before I start working on it. TypeScript is a new codebase for me and I never typed anything in TypeScript. So it will take some effort to get to know the conventions, setup the test environment, etc. The investment in time would be too large to just add it and later discover that the change was rejected.

I had a peek at the code (at least one place that the configuration was used) and it looks very readable. There is quite some indirection and here some console.log or breakpoints would be needed to find out how it is used.

Anyway, do the maintainers want to add this feature? And if so, would accepting the following form be ok?

{ "paths": {  "/*": "./src/*" } }

@EECOLOR
Copy link
Contributor

EECOLOR commented Oct 18, 2018

It turns out the matching is not holding us back. It's that leading slash imports are not passed into the path mapping facility. I created a pull request that checks all boxes except for the 'has a label on the issue' one.

The essential code that I needed to add was:

if (baseUrl && paths && startsWithSlash(moduleName) && getOwnKeys(paths).find(startsWithSlash)) {
  const resolved = tryLoadModuleUsingPaths(extensions, moduleName, baseUrl, paths, loader, /*onlyRecordFailures*/ false, state);
  if (resolved) return resolved.value;
}

@EECOLOR
Copy link
Contributor

EECOLOR commented Oct 18, 2018

I forgot to mention in this conversation that we are using leading slash imports instead of adding { "*": "./src/*" } to prevent problems (collisions) with node modules. We have had a case where a module in our source directory had the same name as a node module. Because the module in our source directory was added (quite some time) later and the original reference was in an untested module, we only found the problem in a later (more problematic) stage.

@coagmano
Copy link

coagmano commented Nov 8, 2018

Thanks @EECOLOR and the team for all the work you put into this!

@nexsodev
Copy link

nexsodev commented Sep 5, 2019

Has this not been resolved yet?

@EECOLOR
Copy link
Contributor

EECOLOR commented Sep 5, 2019

@nexsodev It has been resolved: #27980

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

@red-meadow
Copy link

@EECOLOR, I've just noticed that with javascript.updateImportsOnFileMove.enabled, file renaming/move replaces paths with leading slashes with relative ones. I've found no way to configure this behavior. Should it be reported as a separate issue?

@EECOLOR
Copy link
Contributor

EECOLOR commented Jan 10, 2020

@red-meadow I should indeed be reported as separate issue. I don't think I'm qualified to help with this issue. A quick search on Github let me to: https://github.com/microsoft/TypeScript/blob/master/src/services/getEditsForFileRename.ts

That code is used throughout the code base. From a glance it seems that paths (aliases) are not taken into account when determining the target rename.

If this feature would be added it would probably be something like preferPathsAliases or something. This however is probably not preferred when dealing with 'child' directories (from ./a/a.js to ./a/b.js). On top of that it will probably have performance implications.

In any case, I would create a separate issue for it and see what direction the Typescript core team would want to go.

@red-meadow
Copy link

It turns out that path mapping entries are respected, but only the first one and only if it doesn't start with / (eg. $/file or |file is updated correctly). I will create a separate issue.

@andremarcondesteixeira
Copy link

This is happening with pure javascript as well. I currently have a Spring boot project and I am trying to use Intelissense with js, but it is currently impossible.

Here is my jsconfig.json:

{ "compilerOptions": { "baseUrl": "src/main/resources/static", "rootDir": "src/main/resources/static", "paths": { "/*": ["/*"] } } }

When I try something like import { FormValidation } from '/js/app/lib/forms/forms.js'; it can't find the file. But if I remove the trailing slash, Intelissense works, but my code stops working because the import point to an inexistent file. I am not using Babel, not Webpack, nor anything. Just pure javascript, that is published as is, and VSCode just can't handle it.

@EECOLOR
Copy link
Contributor

EECOLOR commented Mar 14, 2020

@andremarcondesteixeira Where do you expect /js/app/lib/forms/forms.js to be mapped to? If it is src/main/resources/static/js/app/lib/forms/forms.js, your config should probably look like this:

{
  ...
  "compilerOptions": {
    ...
   "baseUrl": "./src/main/resources/static",
    ...
    "paths": {
      "/*": ["*"]
    }
  }
}

I am not sure if * as a path mapping works, so you could do something like this:

{
  ...
  "compilerOptions": {
    ...
   "baseUrl": ".",
    ...
    "paths": {
      "/*": ["src/main/resources/static/*"]
    }
  }
}

@jeoy
Copy link

jeoy commented Mar 15, 2020

@nexsodev It has been resolved: #27980

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

Hi EECOLOR, I just try this feature in a brand new project created with nest-cli. However it seems not working as expected: check the detail

I was wondering if you could help me with this,

Thank you so much

@EECOLOR
Copy link
Contributor

EECOLOR commented Mar 15, 2020

@jeoy I will check it out as soon as I have time. You could try to add "include": ["./src/**/*", "./node_modules/**.*"]

@jeoy
Copy link

jeoy commented Mar 15, 2020

Thanks @EECOLOR , added "include": ["./src/**/*", "./node_modules/**.*"] still doesn't work.

@andremarcondesteixeira
Copy link

@EECOLOR
I have a file called vlan.js in src/main/resources/static/js/app/modules/equipments/equipment-options/network/vlan.js.

Inside this file I have an ES6 import like this:

import { FormValidation } from '/js/app/lib/forms/forms.js';

when using this path with the trailing slash, I expect it to look for the file in src/main/resources/static/js/app/lib/forms/forms.js.

I tried using both your suggestions, but both didn't work as expected.

If I remove the trailing slash, vscode can find the file and show me the JSDocs for my class, however, my app stops working because without the trailing slash, ES6 will treat the import as a relative import and will try to find the file in src/main/static/js/app/modules/equipments/equipment-options/network/js/app/lib/forms/forms.js which doesn't exist.

@trusktr
Copy link
Author

trusktr commented Aug 5, 2020

@jeoy I checked your link, and I don't see the include option in tsconfig. Can you add that, and push the update, so we can see if it works?

@AjManoj
Copy link

AjManoj commented Aug 28, 2021

@andremarcondesteixeira Were you able to resolve it ?

i have below setup
image

and with below folder structure
image

I am using gitpod and seems its not working.

@andremarcondesteixeira
Copy link

still an issue

@EECOLOR
Copy link
Contributor

EECOLOR commented Aug 29, 2021

@AjManoj @andremarcondesteixeira Does one of you have a pubic test project that I could check out?

I noticed in one of the cases a jsconfig.json was used. I have had problems in the past with that and we switched all javascript projects to tsconfig.json with allowjs and checkjs set to true.

@EECOLOR
Copy link
Contributor

EECOLOR commented Aug 30, 2021

@jeoy Previously I only checked vscode with your example and it worked. I now ran your code and discovered @nest made it fail. Upon digging I found it uses https://www.npmjs.com/package/tsconfig-paths which explicitly states: _ (that doesn't start with "/" or ".")_.

That library might not be aware of this change in paths, so you could file an issue with them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests