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

Module path maps are not resolved in emitted code #10866

Closed
mika-fischer opened this issue Sep 12, 2016 · 76 comments
Closed

Module path maps are not resolved in emitted code #10866

mika-fischer opened this issue Sep 12, 2016 · 76 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@mika-fischer
Copy link

TypeScript Version: 2.0.2

Code

tsconfig.json

{
    "compilerOptions": {
        "target": "ES6",
        "module": "commonjs",
        "baseUrl": ".",
        "paths": {
            "foo/*": ["*"]
        }
    }
}

app.ts

import {Foo} from 'foo/utils';
console.log(Foo);

utils.ts

export const Foo = 'Foo';

Expected behavior:

% ./node_modules/.bin/tsc && node app.js
Foo

Actual behavior:

% ./node_modules/.bin/tsc && node app.js
module.js:457
    throw err;
    ^

Error: Cannot find module 'foo/utils'
    at Function.Module._resolveFilename (module.js:455:15)
    at Function.Module._load (module.js:403:25)
    at Module.require (module.js:483:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/home/mfischer/src/videmo/tsc-test/app.js:2:17)
    at Module._compile (module.js:556:32)
    at Object.Module._extensions..js (module.js:565:10)
    at Module.load (module.js:473:32)
    at tryModuleLoad (module.js:432:12)
    at Function.Module._load (module.js:424:3)

app.js

"use strict";
const utils_1 = require('foo/utils');
console.log(utils_1.Foo);

Typescript is finding the right module, but in the emitted code, the module path is left as-is instead of applying the path aliases from tsconfig.json. Obviously node has no idea where to find the module. I would have expected typescript to resolve the module path and replace it with something that node can resolve.

If this behavior is intended, then how can the path maps be used to solve the relative-import-hell in conjunction with node?

@mhegazy
Copy link
Contributor

mhegazy commented Sep 13, 2016

Do you use some other bundling tool like browserify or webpack on the generated output? or do you expect this to run directly on node?

@kitsonk
Copy link
Contributor

kitsonk commented Sep 13, 2016

If this behavior is intended, then how can the path maps be used to solve the relative-import-hell in conjunction with node?

Well and to add context, "paths" is designed for use with loaders that allow remapping, unlike the Node.js require(). The intended behaviour is to allow TypeScript to resolve type information for various module IDs used by various loaders, not to rewrite module IDs. Basically it doesn't do what you thought it did. Nor should it in my opinion, it should only have the capability to mirror the resolution strategies of loaders.

@mika-fischer
Copy link
Author

@mhegazy I expected it to work directly with node. It's for a backend application. Is @kitsonk correct in stating that this is working as intended and typescript will not rewrite module paths?

@vladima
Copy link
Contributor

vladima commented Sep 13, 2016

yes, this was the intent - to mitigate the mismatch between runtime and design time experience when module (as it is written by user) can be resolved in runtime but failed to be found by the compiler. At this point compiler always assumes that module id provided by the user is correct and never tries to change it.

@mhegazy
Copy link
Contributor

mhegazy commented Sep 13, 2016

similar response #9910 (comment)

@mhegazy mhegazy added the Working as Intended The behavior described is the intended behavior; this is not a bug label Sep 13, 2016
@mhegazy mhegazy closed this as completed Sep 13, 2016
@mika-fischer
Copy link
Author

All right, thanks. It might be useful to document this better in order to prevent more people from being confused. I now use https://www.npmjs.com/package/module-alias to make it work with node.

@papercuptech
Copy link

papercuptech commented Dec 20, 2016

Appreciating TS's position, here's a simple solution to the 90% use case for those of us using node, but wanting the convenience of using baseUrl relative require() calls without any fuss.

This solution hooks node's require() call, and resolves requests using the dirname of "main" to mimic baseUrl. It therefore assumes the baseUrl compiler option was also set to the same directory where the source "main.ts" was located.

To use, paste this tiny chunk of code at the top of your "main.ts".

import * as path from 'path'
import * as fs from 'fs'
(function() {
  const CH_PERIOD = 46
  const baseUrl = path.dirname(process['mainModule'].filename)
  const existsCache = {d:0}; delete existsCache.d
  const moduleProto = Object.getPrototypeOf(module)
  const origRequire = moduleProto.require
  moduleProto.require = function(request) {
    let existsPath = existsCache[request]
    if(existsPath === undefined) {
      existsPath = ''
      if(!path.isAbsolute(request) && request.charCodeAt(0) !== CH_PERIOD) {
        const ext = path.extname(request)
        const basedRequest = path.join(baseUrl, ext ? request : request + '.js')
        if(fs.existsSync(basedRequest)) existsPath = basedRequest
        else {
          const basedIndexRequest = path.join(baseUrl, request, 'index.js')
          existsPath = fs.existsSync(basedIndexRequest) ? basedIndexRequest : ''
        }
      }
      existsCache[request] = existsPath
    }
    return origRequire.call(this, existsPath || request)
  }
})()

@Parziphal
Copy link

Parziphal commented Sep 6, 2017

If you're going to use the module-alias package that mika-fischer mentioned, note that the paths you register to the package shouldn't end in /, and the paths are relative to the path where package.json is (it may be obvious but it's good to get it clear),

So if you have this in your tsconfig file:

"outDir": "./dist",
"baseUrl": ".",
"paths": {
  "foo/*": ["./src"]
}

You have to register this in your package.json:

"_moduleAliases": {
  "foo": "dist"
}

@jbgraug
Copy link

jbgraug commented Dec 12, 2017

Well and to add context, "paths" is designed for use with loaders that allow remapping, unlike the Node.js require(). The intended behaviour is to allow TypeScript to resolve type information for various module IDs used by various loaders, not to rewrite module IDs. Basically it doesn't do what you thought it did. Nor should it in my opinion, it should only have the capability to mirror the resolution strategies of loaders.

Got here after wasting some time trying to set it up in a big project.
If this behaviour is not going to change the least you could do is to update the documentation before closing this issue.
The official documentation https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping%20docs does not say a thing about having to use "loaders that allow remapping" whatsoever.

@duffman
Copy link

duffman commented Jan 14, 2018

install and run "tspath" in your project folder... https://www.npmjs.com/package/tspath

@mattyclarkson
Copy link

@ef-carbon/tspm

@DanyelMorales
Copy link

DanyelMorales commented Feb 22, 2018

Also you can try "momothepug/tsmodule-alias" to alias path resolution:

https://www.npmjs.com/package/@momothepug/tsmodule-alias

@BorntraegerMarc
Copy link

Only https://www.npmjs.com/package/module-alias worked for me...

@edufschmidt
Copy link

I too managed to get this working with module-alias, with the downside that I have to keep track of my aliases both within tsconfig.json and package.json. Has anyone found a simpler solution?

The solution pointed out by @mattyclarkson also works, but I couldn't find a way of using it side-by-side with ts-node. Any ideas?

@DanyelMorales
Copy link

DanyelMorales commented May 31, 2018 via email

@edufschmidt
Copy link

Thanks @DanyelMorales, this is really handy.

@DanyelMorales
Copy link

DanyelMorales commented May 31, 2018 via email

@robertmain
Copy link

Can someone tell me what the point of this feature is if the pathnames emitted are actually incorrect? That is to say, if the TypeScript compiler is happy with it, but the end result isn't runnable - what's the use case for this feature?

@tommedema
Copy link

How should one do relative path mapping for things that are NOT modules, i.e. not imports?

In a node script that reads a certain directory relative to the source file I used to do:

const starterDir = path.resolve(__dirname, './templates/starter')

since typescript compiles the script and writes it to another directory (based on config), __dirname will no longer lead to the above path resolving. What would be a solution to this?

@kitsonk
Copy link
Contributor

kitsonk commented Jul 12, 2018

How should one do relative path mapping for things that are NOT modules, i.e. not imports?

That is really "how do I use Node.js and TypeScript question" and much better asked in Gitter.im or StackOverflow.

ascott18 added a commit to IntelliTect/Coalesce that referenced this issue Jul 13, 2018
…alias to /src for all files in /src. Unfortunately, this breaks the compiled js because the paths don't get resolved in the js. Working as intended: microsoft/TypeScript#10866

Just going to get rid of that alias entirely - It isn't going to make things easier in the long term like I hoped it would. I originally only intended it to be used in the tests, but if VS code is going to be aggressive about it, then its better to just forgo it entirely.
@timkendall
Copy link

I love TypeScript but this is insanity.

@jpike88
Copy link

jpike88 commented Sep 20, 2018

I don't get it. Even with me knowing little about the TS codebase, this shouldn't be hard to implement. I've just started using a shared project with client and server... why does TS present the paths functionality in the first place, then makes me jump through hoops to actually use it? Why does TS assume that I want to use a bundler/loader with every project I make? I'm trying to use TS to simplify my projects, not tack on more tooling libraries to compensate for a 90% implemented TS feature.

@DanyelMorales
Copy link

DanyelMorales commented Sep 20, 2018 via email

@squidfunk
Copy link

+1!

@duffman
Copy link

duffman commented Jan 15, 2019 via email

@fabiospampinato
Copy link

fabiospampinato commented Jan 15, 2019

This is a feature that can be utilized if you manage the relative paths, take responsibility for them like Angular does with WebPack or as I do with all my TypeScript projects with TSPath!

This is a feature which makes the compiler output broken code, code that could be working if they only wrote 1 line of code for properly resolving those paths.

The fact that TS needs an external bundler just so that the outputted code can be executed is plain ridiculous.

@robertmain
Copy link

And it is generating executable code if you don´t use features which is
unsupported by JavaScript Engines,

I've always understood that TypeScript was supposed to compile to JavaScript. If you're telling me that certain features are not supported by JavaScript engines then why exactly are they there?

would you blame the C++ compiler if your application dynamic link libraries and that the program won´t run on a machine doesn't have these installed?

No, but I would blame it if it let me link to other C++ code that didn't actually exist with no compiler error or warning.

@duffman
Copy link

duffman commented Jan 15, 2019 via email

@duffman
Copy link

duffman commented Jan 15, 2019 via email

@robertmain
Copy link

Look, I see your point. I do. But part of the compilers job is to sanity check things one last time. At best code that compiles but doesn't run is inconsistent behavior and when I first read into this issue the documentation seemed to suggest behavior that clearly isn't there

@duffman
Copy link

duffman commented Jan 15, 2019 via email

@sukovec
Copy link

sukovec commented Jan 15, 2019

This feature has been added in order to SUPPORT LOADERS not
the other way around, read up
on Path Mapping in the official documentation, so again, you are using it
wrong!

https://austinknight.com/wp-content/uploads/2015/04/DesignVSUX.jpeg

@MikeMitterer
Copy link

@duffman Can't you see that people here just want this feature??? You are telling everyone that he/she is to stupid to understand how this "feature" should be used. OK - you can look at this that way but who knows - maybe it's the other way around...

@sukovec
Copy link

sukovec commented Jan 15, 2019

By the way, my opinion is following:

As aliases are builtin in compiler and compilation of project with them is OK. It may make users think, that it works way it's suggesting (and this issue is pretty much good proof I'm right). It even seems illogical - why something works in "official" editor (vscode - especially when using "auto import" feature, vscode uses aliased path), why copiler also works OK, when resulting code is not working? Saying "js engine don't support it" makes me wanting to ask even more - wasn't TS meant as a thing to mitigate some of JS "problems"?

I would expect one of two solutions of this:

  1. Correctly overriding imports with aliases
  2. Showing a warning

Saying "it's correct behavior" is, I think, wrong. It's not. TS is not assembly language, not even a C/C++.

@DanyelMorales
Copy link

DanyelMorales commented Jan 15, 2019 via email

@duffman
Copy link

duffman commented Jan 15, 2019

. TS is not assembly language, not even a C/C++.

I really don´t understand what you are trying to point out by establishing that TS is not C++, most of us are well aware of that I think!

We have also established that alias/path mapping is used in production all over the world, so naturally, VS Code should support that, but it´s still not an argument for MS to craft the compiler to suit your setup!

What I´m having a hard time understanding is why you keep at it, the compiler works as it´s supposed to work, again, read the docs which clearly states what the feature is for!

I mean you can set up a working TS development environment with path aliases in like 2 minutes, if you don´t want to use WebPack, you can use TSPath to resolve all paths in all js files in 1 second, add it to the package.json as a run script and you don´t even have to think about it, the problem does not exist, the compiler stays the way it was meant to function and you can carry on happy hurray!?

Or if it is so important to you that the actual compiler does this for you, then I suggest you fork the compiler and implement it yourself, maybe it would be a big hit or maybe people are happy the way they are since they have set up their environments to support aliases.

@MrXyfir
Copy link

MrXyfir commented Jan 15, 2019

Summoning the top five TypeScript contributors: @ahejlsberg @andy-ms @DanielRosenwasser @sandersn @sheetalkamat

Could the TypeScript team reconsider this issue? I think this thread offers some useful discussion on both viewpoints and given its recent popularity and the amount of time that's passed it should be looked at again.

@InvictusMB
Copy link

InvictusMB commented Jan 22, 2019

The status of this issue left me no other choice but to demote TS to type checker duty only.
Babel now has a decent support for TS syntax and together with babel-plugin-module-resolver does the job of emitting working code for this use case just fine.
The only downside is duplicating bits of configuration from tsconfig.json as Babel does not care about TS configs. But it is an acceptable price for working absolute paths in node projects and as a bonus I get the whole ecosystem with brilliant things like babel macros.

This is the minimum setup I got working as a drop in replacement for tsc compiler:

  • npm install --save-dev @babel/cli @babel/core @babel/preset-env @babel/preset-typescript babel-plugin-module-resolver @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread
  • in package.json:
    tsc -> tsc && babel ./src --out-dir ./dist --extensions ".ts,.js"
  • in tsconfig.json:
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@mynamespace/*": ["src/*"]
    },
    "outDir": "./dist",
    "noEmit": true, <--
    "allowJs": true,
    "target": "ES2017",
    "module": "CommonJS",
    "lib": [
      "ES2017"
    ]
  },
  "include": [
    "./src/**/*"
  ]
}
  • in .babelrc:
{
  "presets": [
    "@babel/preset-typescript",
    ["@babel/preset-env", {
      "targets": {
        "node": true
      }
    }]
  ],
  "plugins": [
    ["module-resolver", {
      "root": ["./src"],
      "alias": {
        "@mynamespace": "./src"
      },
      "extensions": [".js", ".ts"]
    }],
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-proposal-object-rest-spread"   
  ]
}

@ywwhack
Copy link

ywwhack commented Jan 28, 2019

I just want to use typescript with absolute path, but seems i have to config webpack or babel or something, it's too hard to achieve this simple feature, it should be easier 😞

@duffman
Copy link

duffman commented Jan 28, 2019 via email

@eps1lon
Copy link
Contributor

eps1lon commented Feb 7, 2019

Leaving this here because an actual documented use case for the current behavior of paths was not mentioned in this thread: @types/ packages do not backport features with regards to semver. They do however include updated types for older APIs that I can use. E.g. I'm using history@2 when history@3 is the latest.

"paths": {
    "history": [ "history/v2" ]
}

The compiler would need an extra option to differentiate between type aliases and "code" aliases. If we change the behavior to actually emit the path aliases then we need to add the ability to the compiler to find the correct types version.

This is not an argument against the proposed behavior. I'd rather have emitted aliases and just™ working solution for versioning of types. Just wanted to provide some context why changing this might not be as trivial as people think it is.

LouieK22 added a commit to FRC-3313-Team/scouting-back that referenced this issue Feb 12, 2019
PetarMetodiev added a commit to hristinaStanoeva/CatProject that referenced this issue Feb 12, 2019
Does not work currently due to microsoft/TypeScript#10866
@cruhl
Copy link

cruhl commented Feb 14, 2019

For the second time during a company-wide TS workshop, I've had to explain this inane and embarrassing behavior...

Seriously!

What kind of language compiler, especially one whose primary sales pitch is more correctness for JavaScript, produces broken code as a "feature"?!?!

@cruhl
Copy link

cruhl commented Feb 14, 2019

I'm sorry to have sounded so frustrated in my comment, but the community's views here are seemingly being ignored and downplayed repeatedly.

@cruhl
Copy link

cruhl commented Feb 14, 2019

Just look at how many times this has been referenced... What a waste of time and attention for so many people.

@kitsonk
Copy link
Contributor

kitsonk commented Feb 14, 2019

I understand your frustration, but lots of people feeling a behaviour is correct, doesn't mean it is the right thing to do.

TypeScript rewriting module identifiers is a slippery slippery slope. What has been expressed multiple times in this thread is that TypeScript is configurable to model the behaviour of other module resolvers and other build tools, not replace or implement them.

Just because TypeScript can be configured to resolve modules in flexible ways doesn't not mean that TypeScript emits "broken code". Certain loaders and bundlers that mirror this configuration would work just fine.

If we were to be critical of anything, we could blame the team for naming the option something that might look like it fixes a problem it was never intended to fix, though the documentation for the option makes it clear that it does not change the emit.

Because a particular tool doesn't solve a problem you have doesn't always mean it is that tools fault. It maybe you are just don't have all the tools you need to solve your problem.

@dwjft
Copy link

dwjft commented Feb 14, 2019

@kitsonk everything you just said is way off the mark.

The issue is that TS will operate one way during development / testing, and another after compilation has completed.

If TS wants to be a module resolver, it needs to choose a pattern and stick to it. If I run something with ts-node it should operate exactly as if I compiled some TS and ran it with node.

It does not, and that is the problem.

@DanielRosenwasser
Copy link
Member

Maybe module maps will alleviate the frustration in the future. But our position here along with the technical problems we want to solve are pretty explicit - path mapping reflects the behavior of an external resolution scheme (e.g. path mapping in AMD and System.js, aliases in Webpack and other bundlers). It doesn't imply we will change your paths.

I don't think any recent discussion has been constructive recently, nor do I foresee any future changes here, so I'm going to lock this issue.

@microsoft microsoft locked and limited conversation to collaborators Feb 14, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests