UPDATE: proposal below is updated based on the results of the design meeting.
Initial version can be found here.
Primary differences:
baseUrl
as a separate module resolution strategy we are introducing a set of propertiesrootDirs
are decoupled from the baseUrl
and can be used without it.Currently TypeScript supports two ways of resolving module names: classic
(module name always resolves to a file, module are searched using a folder walk)
and node
(uses rules similar to node module loader, was introduced in TypeScript 1.6).
These approaches worked reasonably well but they were not able to model _baseUrl_ based mechanics used by
RequireJS or SystemJS.
We could introduce third type of module resolution that will fill this gap but this will mean that once user has started to use this new type then support to
discover typings embedded in node modules (and distributed via npm
) is lost. Effectively user that wanted both to use baseUrl
to refer to modules defined inside the project
and rely on npm
to obtain modules with typings will have to choose what part of the system will be broken.
Instead of doing this we'll allow to declare a set of properties that will augment existing module resolution strategies. These properties are:
baseUrl
, paths
and rootDirs
(paths
can only be used if baseUrl
is set). If at least one of these properties is defined then compiler will try to
use it to resolve module name and if it fail - will fallback to a default behavior for a current resolution strategy.
Also choice of resolution strategy determines what does it mean to load a module from a given path. To be more concrete given some module name /a/b/c
:
classic
resolver will check for the presense of files /a/b/c.ts
, /a/b/c.tsx
and /a/b/c.d.ts
.node
resolver will first try to load module as file by probing the same files as classic
and then try to load module from directory/a/b/c/index
with supported extension, then peek into package.json
etc. More details can be found in this issue) All non-rooted paths are computed relative to _baseUrl_.
Value of _baseUrl_ is determined as either:
Sometimes modules are not directly located under _baseUrl_. It is possible to control how locations are computed in such cases
using path mappings. Path mappings are specified using the following JSON structure:
{
"paths": {
"pattern-1": ["list of substitutions"],
"pattern-2": ["list of substitutions"],
...
"pattern-N": ["list of substitutions"]
}
}
Patterns and substitutions are strings that can have zero or one asteriks ('*').
Interpretation of both patterns and substitutions will be described in Resolution process section.
Non-relative module names are resolved slightly differently comparing
to relative (start with "./" or "../") and rooted module names (start with "/", drive name or schema).
// mimics path mappings in SystemJS
// NOTE: moduleExists checks if file with any supported extension exists on disk
function resolveNonRelativeModuleName(moduleName: string): string {
// check if module name should be used as-is or it should be mapped to different value
let longestMatchedPrefixLength = 0;
let matchedPattern: string;
let matchedWildcard: string;
for (let pattern in config.paths) {
assert(pattern.countOf('*') <= 1);
let indexOfWildcard = pattern.indexOf('*');
if (indexOfWildcard !== -1) {
// if pattern contains asterisk then asterisk acts as a capture group with a greedy matching
// i.e. for the string 'abbb' pattern 'a*b' will get 'bb' as '*'
// check if module name starts with prefix, ends with suffix and these two don't overlap
let prefix = pattern.substr(0, indexOfWildcard);
let suffix = pattern.substr(indexOfWildcard + 1);
if (moduleName.length >= prefix.length + suffix.length &&
moduleName.startsWith(prefix) &&
moduleName.endsWith(suffix)) {
// use length of matched prefix as betterness criteria
if (longestMatchedPrefixLength < prefix.length) {
// save length of the prefix
longestMatchedPrefixLength = prefix.length;
// save matched pattern
matchedPattern = pattern;
// save matched wildcard content
matchedWildcard = moduleName.substr(prefix.length, moduleName.length - suffix.length);
}
}
}
else {
// pattern does not contain asterisk - module name should exactly match pattern to succeed
if (pattern === moduleName) {
// save pattern
matchedPattern = pattern;
// drop saved wildcard match
matchedWildcard = undefined;
// exact match is found - can exit early
break;
}
}
}
if (!matchedPattern) {
// no pattern was matched so module name can be used as-is
let path = combine(baseUrl, moduleName);
return moduleExists(path) ? path : undefined;
}
// some pattern was matched - module name needs to be substituted
let substitutions = config.paths[matchedPattern].asArray();
for (let subst of substitutions) {
assert(substs.countOf('*') <= 1);
// replace * in substitution with matched wildcard
let path = matchedWildcard ? subst.replace("*", matchedWildcard) : subst;
// if substituion is a relative path - combine it with baseUrl
path = isRelative(path) ? combine(baseUrl, path) : path;
if (moduleExists(path)) {
return path;
}
}
return undefined;
}
Relative module names are computed treating location of source file that contains the import as base folder.
Path mappings are not applied.
function resolveRelativeModuleName(moduleName: string, containingFile: string): string {
let path = combine(getDirectoryName(containingFile), moduleName);
return moduleExists(path) ? path : undefined;
}
rootDirs
'rootDirs' allows the project to be spreaded across multiple locations and resolve modules with relative names as if multiple project roots were merged together in one folder. For example project contains source files that are located in different directories on then file system (not under the same root) but user still still prefers to use relative module names because in runtime such names can be successfully resolved due to bundling.
For example consider this project structure:
โโโ projects
โโโ project
โโโ src
โโโ viewManager.ts (imports './views/view1')
โโโ views
โโโ view2.ts (imports './view1')
userFiles
โโโ project
โโโ src
โโโ views
โโโ view1.ts (imports './view2')
Logically files in userFiles/project
and shared/projects/project
belong to the same project and
after build they indeed will be bundled together.
In order to support this we'll add configuration property "rootDirs":
{
"rootDirs": [
"rootDir-1/",
"rootDir-2/",
...
"rootDir-n/"
]
}
This property stores list of base folders, every folder name can be either absolute or relative.
Elements in rootDirs
that represent non-absolute paths will be converted to absolute using location of tsconfig.json as a base folder - this is the common approach for all paths defined in tsconfig.json
///Algorithm for resolving relative module name
function resolveRelativeModuleName(moduleName: string, containingFile: string): string {
// convert relative module name to absolute using location of containing file
// this step is exactly the same as when doing resolution without path mapping
let path = combine(getDirectoryName(containingFile), moduleName);
// convert absolute module name to non-relative
// try to find element in 'rootDirs' that is the longest prefix for "path' and return path.substr(prefix.length) as non-relative name
let { matchingRootDir, nonRelativeName } = tryFindLongestPrefixAndReturnSuffix(rootDirs, path);
if (!matchingRootDir) {
// cannot extract non relative name
return undefined;
}
// first try to load module from initial location
if (moduleExists(path)) {
return path;
}
// then try other entries in rootDirs
for (const rootDir of rootDirs) {
if (rootDir === matchingRootDir) {
continue;
}
const candidate = combine(rootDir, nonRelativeName);
if (moduleExists(candidate)) {
return candidate;
}
}
// failure case
return undefined;
}
Configuration for the example above:
{
"rootDirs": [
"userFiles/project/",
"/shared/projects/project/"
]
}
โโโ folder1
โย ย โโโ file1.ts (imports 'folder2/file2')
โโโ folder2
โย ย โโโ file2.ts (imports './file3')
โย ย โโโ file3.ts
โโโ tsconfig.json
// configuration in tsconfig.json
{
"baseUrl": "."
}
projectRoot
projectRoot/folder2/file2.ts
projectRoot/folder2/file3.ts
โโโ folder1
โย ย โโโ file1.ts (imports 'folder1/file2' and 'folder2/file3')
โย ย โโโ file2.ts
โโโ generated
โย ย โโโ folder1
โย ย โโโ folder2
โย ย โโโ file3.ts
โโโ tsconfig.json
// configuration in tsconfig.json
{
"baseUrl": ".",
"paths": {
"*": [
"*",
"generated/*"
]
}
}
projectRoot
folder1/file2
projectRoot/folder1/file2.ts
.folder2/file3
projectRoot/folder2/file3.ts
.generated/folder2/file3
projectRoot/generated/folder2/file3.ts
.โโโ folder1
โย ย โโโ file1.ts (imports './file2')
โโโ generated
โย ย โโโ folder1
โย ย โย ย โโโ file2.ts
โย ย โย ย โโโ file3.ts (imports '../folder1/file1')
โย ย โโโ folder2
โโโ tsconfig.json
// configuration in tsconfig.json
{
"rootDirs": [
"./",
"./generated/"
],
}
All non-rooted entries in rootDirs
are expanded using location of tsconfig.json as base location so after expansion rootDirs
will
look like this:
"rootDirs": [
"rootDir/",
"rootDir/generated/"
],
rootDir/folder1/file2
rootDirs
- rootDir/
and for this prefix compute as suffix - folder1/file2
rootDirs
was found try to resolve module using rootDir
- first check if rootDir/folder1/file2
rootDirs
- check if module rootDir/generated/folder1/file2
exists - yes.rootDir/generated/folder1/file1
rootDirs
- rootDir/generated
and for this prefix compute as suffix - folder1/file1
rootDirs
was found try to resolve module using rootDir
- first check if rootDir/generated/folder1/file1
rootDirs
- check if module rootDir/folder1/file1
exists - yes.The proposal seems sound :+1:
I think the references to data should not be considered for now: their added value is marginal but the added configuration complexity would not be (eg: either refs are mandatory or there would have to be a good way to distinguish them from directory names). They seem like a nice improvement to consider after this is implemented and usage data about the mappings becomes available.
The biggest concern with this is that it sounds like a lot of complexity. I can understand the desire to align to SystemJS in hopes that it aligns to whatwg and eventually the loaders that are implemented in the runtime environments. Maybe it is the me being "myopic" but having lived with AMD for nearly a decade now, there are few use cases where my loader configuration is overly complex. For the most part "it just works". Up to this point with TypeScript, largely as well "it just worked", now I fear that I will have to spend a day trying to get TypeScript to understand where my modules are.
To be more specific, relative module resolution... Is there not a 95% happy path for relative module resolution? The AMD spec specifies:
- Relative identifiers are resolved relative to the identifier of the module in which "require" is written and called.
That is about as straight forward of a resolution logic as you can get.
And when that use case does not work, there is baseUrl, path, packages and the sledge hammer map. Each of those are simple and straight forward, without a significant amount of options. All the tools are there and the thing is 95% of the time, there is minimal configuration to get going, often with a single built layer for production, no configuration.
I am likely tilting at windmills, but sometimes it does seem like we are creating 32 varieties of Colgate here...
we have a request for this specific scenario, but I do agree that in majority of cases it is not necessary. That is why it should be opt-in thing: if rootDirs
are not specified then all relative module names are resolved using location of containing module which is the behavior most of people would expect. I've tried to emphasize the intent by increasing complexity in examples :
baseUrl
is enough (and sometimes it can be inferred)baseUrl
should be enoughbaseUrl
and rootDirs
.I guess I am just missing how the last two points can't be addressed by a simple paths
that only takes a path, either or absolute to the baseUrl
, and then some recursion that keeps relative MIDs relative to the importer. Even with the other scenarios, I suspect you would also still need some sort of sledgehammer map
. The specific use case I run into now that makes this challenging (and therefore map would work) is when I am using MIDs that contain AMD plugins. Right now I have to create an ambient module that imports and then exports what I am trying to do.
@kitsonk I have the use case @vladima is talking about. For me, just a set of --includeDir
s would be sufficient (but I do need the canonicalization-of-relative-paths!). Once you add that, you might as well try and be compatible with what SystemJS does, I don't think it substantially increases complexity over that.
@mprobst Could you provide a concrete example of this need and how you would solve it with SystemJS? I'm having a hard time coming up with a real-world example of merging two code trees together that couldn't be solved by creating a package for one tree and importing it in the other.
@bryanforbes I think @vladima's example is good. Imagine you have something that generates TypeScript source. E.g. Angular 2 will have a build tool that transforms templates to TypeScript code that you can directly call in your app, for various reasons (mostly app startup time).
So you have your Angular controllers together with your templates, src/some/widget/foo_controller.ts
and src/some/widget/foo_template.html
. But you don't want to generate files in your regular source folder, as that creates a mess with version control, so you follow best practice and have a src
folder and a build
folder. The Angular template compiler generates build/some/widget/foo_template.ts
. In your foo_controller.ts
, you import FooTemplate from './foo_template';
.
This works if you pass as rootDirs
src
and build
, as ./foo_template
would get canonicalized to some/widget/foo_template
, then looked up in src
and build
in order, and found in build/some/widget/foo_template.ts
. In SystemJS, I believe you would have a mapping of src/*
and build/*
.
@mprobst Thanks for clarifying! I didn't understand the generated
directory containing TypeScript files, but your explanation helps. What still confuses me is why merging two trees together necessitates another configuration flag with potentially duplicated settings (rootDirs
) instead of just using path mapping to solve everything.
My concern about using path mapping for both canonicalization of relative module names and remapping non-relative module names is that this configuration settings become overloaded:
To deal with duplication I'd rather prefer to have something for data sharing (i.e. references) instead of re-purposing field whose meaning is a already well-defined
:+1:
Can this go in 1.7? Pretty please with a cherry on top? And is there some way I can play with this now?
@vladima Sorry for the delay in replying. I think my concern now is that you're giving new names to already defined concepts. As I see it (and aside from the obvious difference that these property names take arrays), paths
is basically SystemJS's map
, and rootDirs
is basically like SystemJS's paths
. Why not just use those names instead? The ideas are similar to what we already know from AMD and SystemJS, so why invent new names?
@bryanforbes I'm not sure how well these map with SystemJS, but for what it's worth, rootDirs
is a much more specific and understandable name than just paths
. I'd take rootDirs
over paths
any time.
It looks like a lot of projects in the wild have already been using babel for ES6 in combination with webpack as a pretty standard configuration. It might be worth looking at how the module path resolution works in webpack.
There is no need to introduce other concepts/naming, it could be taken from https://webpack.github.io/docs/resolving.html and its configuration https://webpack.github.io/docs/configuration.html#resolve.
@opichals I might be missing something, but by my reading webpack's resolve does not meet the requirements above for resolving files relative to a set of directories.
@mprobst The resolve.root configuration variable makes it possible for a module to be searched for in folders similarly to the rootDirs
from the proposal.
The webpack's resolve.root can be an array of absolute paths (could not directly confirm this just by reading the docs, so I checked the sources.
@opichals yes, but relative imports are not canonicalized against the list of roots, are they? I read the docs as saying if I load ./foo
from a file physically located at /bar/baz
, I'll always end up at /bar/foo
instead of searching my rootDirs
.
@mprobst True. I would find that extremely confusing if any loader attempted to resolve relative path against anything else than just the folder it is physically located at (something require.js tries to do and became a nuisance to configure because of). As stated above such resolution logic seems to add complexity which reflects in complicated use and debugging.
I think we collected a couple of compelling use cases above.
Standa Opichal [email protected] schrieb am Mi., 21. Okt. 2015,
16:15:
@mprobst https://github.com/mprobst True. I would find that extremely
confusing if any loader attempted to resolve relative path against anything
else than just the folder it is physically located at (something require.js
tries to do and became a nuisance to configure because of). As stated above
such resolution logic seems to add complexity which reflects in complicated
use and debugging.โ
Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/5039#issuecomment-149928200
.
I would find that extremely confusing if any loader attempted to resolve relative path against anything else than just the folder it is physically located at (something require.js tries to do and became a nuisance to configure because of).
When does RequireJS do this?
You can achieve this with RequireJS because it applies the path mappings to any path segment of any require no matter whether the require module path is absolute or not. e.g. for { path: { 'pkg': '../folder1' } }:
Also it when two require() calls resolve to a single file using different arguments the module gets loaded twice.
For the proposal I would rather like to see an API-based extensibility approach to support the likes of 'Example 3' (let the user replace the resolution function somehow through a configuration or a plugin).
Hey guys
What if when resolving the imports if the path is not relative and is not found in the node_modules folders you check if is one of the parent directories
for example for a structure like :
my-project/
|- src/
|- app/
|- App.ts
|- CommonImports.ts
|- my-feature/
|- MyFeature.ts
|- sub-feature/
|- SubFeature.ts
CommonImports.ts could be imported in any of the other ts files like:
import * from "app/CommonImports" or
import * from "src/app/CommonImports"
The idea is you will search a parent folder that matches the begining of the import path till you get to the parent of the node_modules folder or the parent of the folder where tsc runs or tsconfig.json exist
this will also allow imports like
import bootstrap from "my-project/bower/src/bootstrap/bootstrap"
All this with 0 extra config in the tsconfig.json
An even is you later decide to implement path/map like systemjs could still work together giving preference to path declared in path/map
The only problem i see is that not sure if anyone else those this way, but should be easy to understand the i think.
A benefit of been able to resolve the import without requiring a path configuration, is that it will allow you to import the .ts files of a third party library completely written in ts, instead of the usual master.js + .d.ts that has to be imported usually, very useful for widgets libraries like angular material, where you are only interested in a few files not the entire lib, not sure if you ever though on supporting that kind of scenario
@gabrielguerrero the current behavior will not change. If you do specify path mappings it will be used, if you do not the current resolution logic will be used. i think this should work in your case.
Hey @mhegazy yeah the path/map will cover my case so cool if it gets implemented, I was just thinking if you wanted a no config required way, the resolve of parent dir in the import could provably work for most of the cases people need, so just giving ideas
Man, I'm feeling great about moving this feature to 1.7...
Someone tell Anders I said it was a go. :)
+1 million! The ability to do this will greatly improve support for different builds, assets organization, etc. For example with JSPM now I'm forced to duplicated my dependencies using npm just to get tsc happy.
:+1: I'm currently manually copying d.ts files to a faux node_modules folder in order to get tsc to compile. This looks like it would clean up my use case!
I haven't heard a yes or no to my question of whether or not this can be moved from 1.8 to 1.7. If there was a beta of 1.7 with this in it, I'd test it today!
1.7 is in stabilization mode right now, so I'm pretty sure the answer is no.
this is currently work in progress. I'll post on this thread once I have something to try
Just to confirm - would I be correct in thinking that the resolution of .d.ts files are within the scope of this issue?
I've got a scenario where Project A depends on the build output of Project B. I want to bundle the auto-generated d.ts files from Project B alongside the artefact for use in Project A.
In Project A I have: import {X} from 'projectb'
, where 'projectb' is the name of my exported module (paths set up in RequireJS). However, I've completely struggled to get Project A's compile step to understand any type information relating to X (the types of any exports from Project B).
Another use case for path mappings involves working with (git) submodules. We do this on large projects to separate repositories (work domains). But is has an undesirable side effect when the source of a module exists multiple times in a project due to the use of submodules (the TypeScript compiler treats those duplicate sources as separate modules which is technically correct). Assume the following simplified example:
Project git
|-- A
| |-- a.ts
| |-- C (git submodule)
| |-- c.ts
|-- B
|-- b.ts
|-- C (git submodule)
|-- c.ts
Submodule C in a separate git
|-- c.ts
Let's assume the submodule C contains a class which is used in module A and module B. Module C is a stand alone module (has its own repository) which is embedded in module A and B using a git submodule (so we can maintain C in a single place and easily update modules who depend on it using a git submodule update
). This results in duplicate sources for module C in the project repository.
Now let's say class A
extends C
:
// a.ts
import { C } from "./C/c.ts"; // Import C which is a submodule rep under A
class A extends C {
}
And b.ts
contains a function which accepts any class which is derived from class C
:
// b.ts
import { C } from "./C/c.ts"; // Import C which is a submodule rep under B
import { A } from "../A/a.ts";
function DoSomethingNice(c: C): void {
}
DoSomethingNice(new A()); // Oh noes, error!!!
The above example generates a compiler error. Class A is not derived from the same class C as used in module B. TypeScript returns something like Types have separate declarations bla bla
. This makes sense, since the compiler doesn't know that the submodule C in module A (implemented in ./A/C/c.ts
) is exactly the same as C in module B (implemented in ./B/C/c.ts
). It's a duplicate source.
Now the world would be perfect when the TypeScript compiler recognizes duplicate/identical implementations (by generating some sort of hash for each module), knowing which files are identical and implement the same module (_please develop TypeScript team_ :wink:). That would really pave the road for working with projects spanned over multiple repositories and using git submodules to chain it all together (sometimes resulting in duplicate sources for the same module used multiple times in a project)! It would even strengthen the use of git modules, since the compiler would fire errors when different versions (branches) of a submodule are used within the same project.
So with path mappings it would be possible to achieve sort of the same setup, by mapping duplicate module sources to a single location in the project.
Looking forward to TypeScript 1.8!
What module targets does this proposal affect? Specifically, will this bring baseUrl support to the commonjs and es6 module targets?
currently module resolution strategy is decoupled from module targets so you can use whatever combination you like by specifying moduleResolution
option. If this option is omitted then compiler will try to use strategy it thinks will be the most appropriate: i.e. use node
if target
is commonjs
Another work of art for Babel - https://github.com/tleunen/babel-plugin-module-alias
This is pretty essential to developing any real application with Angular 2.
Looks like using: "moduleResolution": "node", in tsconfig.json is required for Angular 2.
So the only way to include custom components is to use relative paths... yuck.
This is a nightmare when you start to scale and move around.
At least allow "moduleResolution": "node" to also resolve based on an absolute path from the root dir.
+1 can't wait :)
+1 want to use jspm with tsc
+1
Same here. JSPM rocks, but TypeScript integration can be a lot better with this feature.
+1 ะกustom resolver will not allow IDE analysis code. Ability to specify paths mappings is required. Paths mappings can be added to tsconfig.json then we can generate paths in tsconfig.json using build tools. Can solve this problem otherwise, by copying the definition files in a folder node_modules using build tools and specify moduleResolution: "node".
Somehow I got it in my head that this was slated for 1.8, which clearly didn't happen.
I realize that the in the grand scheme, there's a "real" path mapping strategy that needs to be developed, and that it somehow has to work with JSPM, etc.
But (a) is there any scenario in which '/someModule'
would not be interpreted as referring to someModule.ts at the project root? and (b) if '/someModule'
is to refer to the project root, could that portion of the problem be solved on the fast track while the bigger mapping strategy and technology are still being developed?
This would at least enable ES6-friendly path references and would allow developers to avoid creating full blown node modules just to be able to reference commonly used modules from a known location without having to go relative (in other words, '../../../someModule'
is a real pain in the butt).
+1 for @laurelnaiad's suggestion. This would be a quick win for that part of the problems discussed above.
Now that this is merged (๐) how would one set the path mappings so that the compiler picks up the modules that were installed via jspm?
Now that this is merged
@frederikschubert Is it? I don't think this is merged :rose:
@basarat looks like #5728 is indeed merged
Sweet :candy: Can this issue be closed :rose:
https://github.com/Microsoft/TypeScript/pull/5728 is merged. should be in typescript@next
later tonight. please try it out and let us know if there are any issues.
How do you install vnext in Visual Studio?
@alexdresko see- http://www.screencast.com/t/ogvAf2sQ
@amcdnl That's for VSCode, which is helpful certainly, but I'm mainly interested in installing vNext into Visual Studio 2015. I apologize for not being more specific.
@mhegazy By adding this to the 2.0 milestone, does that mean it won't be going into 1.8?
@alexdresko no, this will be in 1.8 ... see the roadmap page
I have the same question as @frederikschubert, how can we use this awesome new functionality to make tsc aware of jspm modules?
I'm testing this out and got Example 1 above working (only using baseUrl). But having trouble with Example 2. Probably I am doing something wrong but cannot see what. Here is what I have:
$ tree
.
โโโ folder1
โย ย โโโ file1.ts
โย ย โโโ file2.ts
โโโ generated
โย ย โโโ folder2
โย ย โโโ file3.ts
โโโ tsconfig.json
3 directories, 4 files
$ cat tsconfig.json
{
"compilerOptions": {
"module": "system",
"sourceMap": true,
"outDir": "js_out",
"noEmitOnError": true
},
"files": [
"folder1/file1.ts"
],
"baseUrl": ".",
"paths": {
"*": ["*", "generated/*"]
}
}
$ cat folder1/file1.ts
import {hello} from 'folder1/file2';
import {world} from 'folder2/file3';
console.log(hello + world);
$ cat folder1/file2.ts
export const hello = "Hello";
$ cat generated/folder2/file3.ts
export const world = " World!";
$ tsc --version
Version 1.9.0-dev.20160128
$ tsc
folder1/file1.ts(2,21): error TS2307: Cannot find module 'folder2/file3'.
folder1/file1.ts(2,21): error TS2307: Cannot find module 'folder2/file3'.
@mhegazy By adding this to the 2.0 milestone, does that mean it won't be going into 1.8?
that is correct. We branched for 1.8 already, and we just need to stabilize it. path mapping, and readonly property support are now available in typescript@next
, and in TS 2.0 but not in 1.8.
@alexdresko no, this will be in 1.8 ... see the roadmap page
sorry about that. road map should be updated now.
@janakerman, baseURL
and Paths
are part of the compilerOptions
, your tsconfig.json file should look like:
{
"compilerOptions": {
"module": "system",
"sourceMap": true,
"outDir": "js_out",
"noEmitOnError": true,
"baseUrl": ".",
"paths": {
"*": ["*", "generated/*"]
}
},
"files": [
"folder1/file1.ts"
]
}
@mhegazy Thanks, I know I was missing something obvious :-). Now it works!
@mhegazy if it is done, why did it get pushed to 2.0? :(
Also, is there any versioned docs? like that show these nightly updates?
@mhegazy if it is done, why did it get pushed to 2.0? :(
Sorry about that, but we need to stabilize the release to ship it, and adding more code, adds more bugs, and delays the whole process.
It should be in the nightly today, npm install typescript@next
and now on NuGet (https://www.myget.org/gallery/typescript-preview)
Also, is there any versioned docs? like that show these nightly updates?
i am not sure i understand the question
@alexdresko I had commented on the PR about support for proj files in vs and seems like it isn't supported.
https://github.com/Microsoft/TypeScript/pull/5728#issuecomment-173732744
Regarding specifying path mappings in .csproj - this won't work, paths and rootDirs will be options that can be specified only via tsconfig.json
@prabirshrestha I want tsconfig file support. I was referring to tooling and compiler support for this new stuff.
@mhegazy So vnext is 2.0? Does it also contain everything in 1.8 already?
So vnext is 2.0? Does it also contain everything in 1.8 already?
The plan is the next release is labeled 2.0. we try to keep them around 8 weeks apart. typescript@next
has all 1.8 features/bug fixes + any post 1.8 work.
From what I understand the nuget packages provide you with support for Visual Studio _builds_ (and msbuild) but not for intellisense as that would require changes in the compiler host of the VS TypeScript plugin.
Also, from what I understand the updates to the compiler host will only be available by the time 2.0 is released, so until then Visual Studio builds can be used with this new feature but you'll have to resort to some other strategy to get intellisense working.
Also, from what I understand the updates to the compiler host will only be available by the time 2.0 is released, so until then Visual Studio builds can be used with this new feature but you'll have to resort to some other strategy to get intellisense working.
Most changes do not require you to install a new version of the VS TypeScript plugin in. The nightly build currently does not include the full plugin setup, we are working on getting an installer published nightly along with the npm package
to use the nighlies in VS, please chek out the steps in https://github.com/Microsoft/TypeScript/wiki/Nightly-drops#visual-studio-2013-and-2015
So is support for JSPM available now in 1.8 nightly builds? or do we need to wait for 2.0?
JSPM is awesome and would love to have support for it with TS... tx for doing this...
regards
Sean
@born2net this change does not support JSPM directly, you can add a path mapping for every package you use.
see https://github.com/Microsoft/TypeScript/issues/6012 for JSPM support.
@mhegazy If I want to use the new 'paths' and 'baseUrl' mapping functionality with Visual Studio 2015 now, can I install the nightly build and expect that to work with Visual Studio, or are there Visual Studio components that would also need to be updated (that aren't available yet)? I currently have 1.8 installed and working with VS 2015 (after successfully working through some install issues documented in #6958). Is the new path resolution logic solely the responsibility of tsc, or would intellisense need updates for that as well?
Using the 1.9.x nightly build, I'm unable to get the new path mappings based module resolution to work with the command-line compiler (tsc
). Should it be working? Obviously, this is a bleeding edge feature, but it would be a huge simplifier for our development process, so we're trying to start using it now (since it already works for us with SystemJS for runtime compilation).
@mko works for me using [email protected]
@mpseidel Can you post an example usage or take a glance at my usage below? It might be in my configuration.
client/
-- app/
---- components/
------ app.component.ts
---- jspm_packages/
------ npm/
-------- [email protected]/
---------- core.d.ts
---------- core.js
-- tsconfig.json
tsconfig.json
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false,
"moduleResolution": "baseUrl",
"baseUrl": "",
"paths": {
"angular2/*": ["../jspm_packages/npm/[email protected]/*"]
}
}
}
app.component.ts
import { Component } from 'angular2/core';
Upon running tsc
on any files in our project, we get the following error:
error TS6063: Argument for '--moduleResolution' option must be 'node' or 'classic'.
If we run the TypeScript compiler via grunt-typescript
or atom-typescript
, we get the following error:
error TS2307: Cannot find module 'angular2/core'.
@mko
As far as I understand baseUrl
is not a valid option value for moduleResolution
.
This is my tsconfig.json. I use it for both client side (webpack) and for server side node code.
{
"compilerOptions": {
"sourceMap": true,
"target": "es6",
"module": "commonjs",
"jsx": "react",
"experimentalDecorators": true,
"baseUrl": ".",
"paths": {
"*": [
"*",
"app/*",
"app/client/*"
]
}
}
}
@mko try to omit the moduleResolution
option or specify "classic"
or "node"
@mko also take a look at tryLoadModuleUsingOptionalResolutionSettings
in typescript.js
. It is being called both from classicNameResolver
and nodeModuleNameResolver
Interesting. The way I read it in the original PR #5728 it looked like baseUrl
was meant to be a moduleResolution
value. I guess I got my signals crossed there.
With any of the three options (moduleResolution: "node"
, moduleResolution: "classic"
, and without any moduleResolution
value, I continue to get error TS2307: Cannot find module 'angular2/core'.
.
Your paths
value uses an interesting combination of wildcards that I saw in another one of the threads that said they had it working. We really need this to work for importing our jspm_packages
since their folder names include versions (which is really odd and makes referencing those packages in imports a pain if not using SystemJS).
Okay. So, I was able to get it to work with moduleResolution: "classic"
by changing baseUrl
to .
and restructuring to move our jspm_packages
under that base URL instead of using the ../
previous directory prefix.
I'll be really glad when this feature is fully documented. ;)
Have you tried baseUrl: "." And ... : "./jspm_packages..."?
Yep. That's what I changed it to to get it to work.
Ah - missed your last comment :) glad it works for you now!
I'm trying to get this feature to work in Visual Studio 2015.
I've followed the instructions in https://github.com/Microsoft/TypeScript/wiki/Nightly-drops#visual-studio-2013-and-2015 and have Version 1.9.0-dev.20160211 configured for Visual Studio. All of the syntax errors went away so it seems to be working when I save, but when I do a build visual studio gives the error Build: Unknown compiler option 'baseUrl' and the same for 'paths'.
Regardless of save or build, none of my .js files ever get created when working in visual studio but they work just fine when I just run tsc in the directory at the command line.
Any ideas?
Update
If I delete all of the version folders in the Microsoft SDK's folder, I get
The specified task executable location "C:\Program Files (x86)\Microsoft SDKs\TypeScript\1.9\tsc.exe" is invalid.
So it looks like it is still trying to use the old version instead of the npm installed/ VSDevMode.ps1 version.
Unfortunately this is not 'script-side-only' change and it needs updates for the managed side (that were not yet officially released) to work correctly.
Thanks for the clarification. Is there a timeline on when it will be released and work correctly?
Is there _any_ way to make this work now in VS (hacks, code, pre-release, etc)?
@vladima did this make it to 1.8.0
? I cannot use next
as tslint
has a peer dep of 1.7.x
and npm doesn't install either versions.
Nevermind, it seems like it is not part of it.
if you use typescript@next, you should use tslint@next as well.
@mhegazy yep, figured that out after one of the maintainers pointed it out :) thanks
+1
What's the reason for not using option rootDirs
for resolving non-relative module names? Is it a bug?
Ah, as far as I understand today, rootDirs
is for resolving relative module names and baseUrl
and paths
are for resolving non-relative module names. And that's it.
apologies, did not notice your question. Yes, your understanding is correct, baseUrl
/ paths
are used to resolve non-relative module names and rootDirs
are applied only for relative names
Well, that works fine, but looks a bit done twice:
{
"compilerOptions": {
"baseUrl": "src/main",
"paths": {
"app/*": [
"../branding/lh/app/*",
"../dev/app/*"
]
},
"rootDirs": [
"src/main/",
"src/branding/lh/",
"src/dev/"
],
"outDir": "build/ts2js",
...
}
this is great, it's what we need for jspm_modules (instead of node_modules)...
Can this implementation rewrite rooted paths to relative paths for a CommonJS target? E.g.
โโโ folder1
โ โโโ file1.ts
โโโ folder2
โ โโโ file2.ts
โโโ tsconfig.json
with moduleResolution node
and baseUrl "."
and module commonjs
, and this TypeScript:
file1.ts
import two from "folder2/file2";
Can it emit this JS such that Node can actually execute the require?
file1.js
var two = require("../folder2/file2");
@jwbay this feature, along with the rest of the module resolution capabilities, are only to help the compiler find the module source given a module name. no changes to the output js code. if you require "folder2/file1"
it will always be emitted this way. you might get errors if the compiler could not find a folder2/file1.ts
, but no change to the output.
You can find more documentation at https://github.com/Microsoft/TypeScript-Handbook/blob/release-2.0/pages/Module%20Resolution.md#additional-module-resolution-flags
This might be silly, but have you considered simply providing a hook into module resolution logic where users of the Typescript compiler are able to provide an async resolution function that returns the actual path on the filesystem? Instead of trying to find a "one size fits all" solution, this would allow the community to figure out ways to make it work with different packaging systems, file system layouts, etc.
Typescript is statically checked. Async is pretty far from static, so this sounds like it would be a stretch.
@guncha It has been considered but the answer was it was too much work. See here.
anyone knows if path mapping via tsconfig already landed in ts@next as does not seem to work,
tx
Sean
Path mapping support was checked in a while back. Can you please elaborate what are you trying to do and why do you think it does not work?
tx for the quick reply...
sure I am trying to move my project which is based on jspm 100% so I can get rid of my node_moduls dir and use only jspm_packages for module resolution (maintaining both package.json for ts and config.js for runtime is a SUCH a pain, be so sweet to just maintain a single module config file).
So, this is the angular2 project:
https://github.com/born2net/ng2Boilerplate
and so I configured WebStorm to use latest TS 1.9@next
and setup my tsconfig as:
{
"compilerOptions": {
"target": "ES5",
"inlineSources": true,
"module": "system",
"moduleResolution": "node",
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"noEmitHelpers": false,
"sourceMap": true,
"noResolve": false,
"baseUrl": "./jspm_packages",
"paths": {
"@angular/XXXXXX*": [
"npm/@angular/*"
],
"rxjs/*": [
"npm/[email protected]/*"
]
}
},
"filesGlob": [
"./**/*.ts",
"!./node_modules",
"/src/typings/app.d.ts"
],
"exclude": [
"src/public/world_data.ts",
"temp",
"node_modules",
"node_modules/*",
"jspm_packages",
"typings",
"dist",
"typings/*",
"typings/main.d.ts",
"typings/main"
],
"compileOnSave": false,
"buildOnSave": true
}
but noticed I put a fake XXXX to see if I get a TS error in WebStorm IDE (which again is configured to use TS 1.9) and yet no TS path errors popup, in other words it seems to still be using my node_packages directory for module resolution instead of jspm_packages.
please advice
tx again,
Sean.
Well, you probably aren't referencing any modules named @angular/XXXXXX*
in your code.
I think you are mis-understanding the purpose of paths
. It basically says to the compiler, when you have a module that you need to resolve, and it matches _this_ pattern, look _here_ to see if it exists. Because you still have module resolution set to node
, you have informed the compiler to also look in node_modules
. It is doing what you told it to do.
If you want to know exactly how the compiler is trying to resolve the modules, you can use --traceResolution
as a compiler option.
I understand, what value should I put for module resolution instead of node,
thanks
or just remove that line
TypeScript defaults to moduleResolution
of node
. The other mode is classic
.
ok thx again, will give it a shot
so the good news is that moving to 'classic' fixed it so it's no longer looking at the node_modules directory. the bad news is that it is still not looking in the jspm_packages folder.
current config is:
{
"compilerOptions": {
"target": "ES5",
"inlineSources": true,
"module": "system",
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"noEmitHelpers": false,
"sourceMap": true,
"noResolve": false,
"baseUrl": "./jspm_packages",
"paths": {
"@angular/core": [
"npm/@angular/[email protected]"
],
"rxjs/*": [
"npm/[email protected]/*"
]
}
},
"filesGlob": [
"./**/*.ts",
"!./node_modules",
"/src/typings/app.d.ts"
],
"exclude": [
"src/public/world_data.ts",
"temp",
"node_modules",
"node_modules/*",
"jspm_packages",
"typings",
"dist",
"typings/*",
"typings/main.d.ts",
"typings/main"
],
"compileOnSave": false,
"buildOnSave": true
}
here is a dump of: tsc --traceResolution
trying just to fix "@angular/core": at this point ( I can do the rest once that works)
======== Resolving module '@angular/core' from 'C:/msweb/studioDashboard/src/App.ts'. ========
'baseUrl' option is set to 'C:/msweb/studioDashboard/jspm_packages', using this value to resolve non-relative module name '@angular/core'
'paths' option is specified, looking for a pattern to match module name '@angular/core'.
Module name '@angular/core', matched pattern '@angular/core'.
Trying substitution 'npm/@angular/[email protected]', candidate module location: 'npm/@angular/[email protected]'.
File 'C:/msweb/studioDashboard/jspm_packages/npm/@angular/[email protected]' does not exist.
File 'C:/msweb/studioDashboard/jspm_packages/npm/@angular/[email protected]' does not exist.
File 'C:/msweb/studioDashboard/src/@angular/core.ts' does not exist.
File 'C:/msweb/studioDashboard/src/@angular/core.d.ts' does not exist.
File 'C:/msweb/studioDashboard/@angular/core.ts' does not exist.
File 'C:/msweb/studioDashboard/@angular/core.d.ts' does not exist.
File 'C:/msweb/@angular/core.ts' does not exist.
File 'C:/msweb/@angular/core.d.ts' does not exist.
File 'C:/@angular/core.ts' does not exist.
File 'C:/@angular/core.d.ts' does not exist.
======== Module name '@angular/core' was not resolved. ========
wondering why it's looking for .ts files? and not .js
this is configuration that works for me:
{
"compilerOptions": {
"target": "ES5",
"inlineSources": true,
"module": "system",
"moduleResolution": "node",
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"noEmitHelpers": false,
"sourceMap": true,
"noResolve": false,
"baseUrl": "./jspm_packages",
"paths": {
"@angular/*": [
"npm/@angular/*@2.0.0-rc.1"
],
"@angular/platform-browser/src/browser/browser_adapter": [
"npm/@angular/[email protected]/src/browser/browser_adapter"
],
"rxjs/*": [
"npm/[email protected]/*"
]
}
},
"filesGlob": [
"./**/*.ts",
"!./node_modules",
"/src/typings/app.d.ts"
],
"exclude": [
"src/public/world_data.ts",
"temp",
"node_modules",
"node_modules/*",
"jspm_packages",
"typings",
"dist",
"typings/*",
"typings/main.d.ts",
"typings/main"
],
"compileOnSave": false,
"buildOnSave": true
}
a few notes:
classic
module resolution only looks for files that match requested module name. It won't work in case of Angular so you'll need to set moduleResolution
to node
browser_adapter
needs a special entry in configuration since BrowserDomAdapter
does not appear in the root public surface of platform-browser
module and thus import peeks inside the module (I don't know if this is a well accepted practice to work with this component - just trying to keep original code as-is)thank you for that, it seems to be working, I am gonna play with it some more to make sure!
So did this get implemented? I see the paths
option show up on the tsconfig
schema in WebStorm, but the compiler says it doesn't know what that is.
yes and it's AWESOME ...
I am going to add it to the Angular 2 Kitchen sink: http://ng2.javascriptninja.io
and source@ https://github.com/born2net/ng2Boilerplate
Regards,
Sean
tx to @vladima for his support!
the only thing I am still not sure about is how to map using wildcards so we don't have to maintain two separate configs as in tsconfig and package.json
since right now it seems you have to hard code semver
"paths": {
"angular2-redux-util": [
"npm/[email protected]"
],
...
which is a bit of a pain
Is there any way to get typescript to compile with it, then? I get an error
that paths isn't recognized. I'm on 1.8.10.
On Jun 1, 2016 2:02 PM, "JavaScriptNinja" [email protected] wrote:
tx to @vladima https://github.com/vladima for his support!
โ
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/5039#issuecomment-223092367,
or mute the thread
https://github.com/notifications/unsubscribe/AAavtOTHbFrSDfyEvrSW4dfkkixjE7Vbks5qHdddgaJpZM4GGr-Q
.
u need to install 1.9 via npm install typescript@next
Any reason why the substitution pattern in paths
config has to have only one asterisk? Allowing two would nicely support a directory structure where modules live in separate directories named the same as the module, e.g., a lib/foobar/foobar.ts
could be imported as lib/foobar
.
We did not see that as a common scenario supported by module loaders. but it could be added in the future.
This feature appears to work brilliantly, thanks. I wonder if there is any way to output the paths as relative โ so for example import whatever from 'app/whatever'
would be emitted by tsc
as import whatever from '../../app/whatever'
(or whatever the correct path is)? The reason this is of interest is that it appears require
ing modules with "absolute" paths is slow when running with node, which means it takes a decent amount of time for my Mocha test suite to start up while it processes the require
s. If not, I might be able to tackle this with Babel...
I wonder if there is any way to output the paths as relative โ
The compiler does not rewrite module names. module names are considered resource identifiers, and are mapped to the output as they appear in the source
Is there a way to recreate this directory structure using TS: https://gist.github.com/ryanflorence/daafb1e3cb8ad740b346
In short:
Webpack allows you to do this:
resolve: {
extensions: ['', '.js', '.json', '.ts', '.tsx'],
modulesDirectories: ['shared', 'node_modules']
},
Which means that Webpack will recursively look up the directory tree for the shared
dir (the same way it does with the node_modules dir).
This allows us to import shared components even from deeply nested directories without referencing the relative path:
import Button from 'Button';
This will work as long as Button lives in any shared
folder that is higher up in the directory tree.
Is there a way to tell tsc
to look for modules in this fashion?
Is it intentional that the "paths" values are computed relative to "baseUrl" when baseUrl is set?
Is it intentional that the "paths" values are computed relative to "baseUrl" when baseUrl is set?
yes. this is the same behavior other module loaders like require follow as well.
Should outDir prevent some usage of paths?
This was working:
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"rootDir": ".",
"baseUrl": ".",
"paths": {
"util/*": [
"../../../../Util/src/main/ts/*"
]
}
}
}
Then when I add outDir:
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"rootDir": ".",
"baseUrl": ".",
"outDir": "../../target/ts",
"paths": {
"util/*": [
"../../../../Util/src/main/ts/*"
]
}
}
}
The compiler says: "error TS6059: File 'C:/tmp/ts/paths/Util/src/main/ts/Util.ts' is not under 'rootDir' 'C:/tmp/ts/paths/SisModules/src/main/ts'. 'rootDir' is expected to contain all source files."
if you are using outDir
, the compiler needs to mirror the input folder structure to the output directory. the root of the input is defined by rootDir
. if there are files in the input, regardless if you are using paths or not, are not under rootDir
, the compiler does not know how to emit them, and thus the error.
@mhegazy but "the compiler needs to mirror the input folder structure to the output directory" unfortunately creates the original paths in module names instead of the ones mapped to the virtual paths. It's weird at least.
unfortunately creates the original paths in module names instead of the ones mapped to the virtual paths. It's weird at least.
that is how it is meant to work. the compiler needs the paths
to find the declaration of your module. module names are resource identifiers and should be emitted as is and not altered. please see https://github.com/Microsoft/TypeScript/issues/5039#issuecomment-232470330.
I'm not experienced in TypeScript nor modules, but I think the vpaths names would also be good for resource identifiers. In the browser they make much more sense than the real paths.
This is not meant to be a substitute of your require.config files, or system.config. this is meant to be a mirror of them to tell the compiler what your loader already knows.
@mhegazy Why not add a compile option, so people can decide whether to rewrite module names or not?
I'm using react-native with Typescript, it's hard to resolve this problem.
@rochapablo
I'm using https://github.com/ds300/react-native-typescript-transformer now,
Use absolute paths
is works for me, it works with tsconfig paths
very well.
And it gives another advantage, we no need to use tsc -watch
to compile our code before react-native
package.
Damn! it worked!
Thank you @sapjax!
@fforres, follow the instructions it will work.
Most helpful comment
This feature appears to work brilliantly, thanks. I wonder if there is any way to output the paths as relative โ so for example
import whatever from 'app/whatever'
would be emitted bytsc
asimport whatever from '../../app/whatever'
(or whatever the correct path is)? The reason this is of interest is that it appearsrequire
ing modules with "absolute" paths is slow when running with node, which means it takes a decent amount of time for my Mocha test suite to start up while it processes therequire
s. If not, I might be able to tackle this with Babel...