-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Path mappings based module resolution #5039
Comments
The proposal seems sound 👍 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:
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
|
I guess I am just missing how the last two points can't be addressed by a simple |
@kitsonk I have the use case @vladima is talking about. For me, just a set of |
@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, This works if you pass as |
@mprobst Thanks for clarifying! I didn't understand the |
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 |
👍 |
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), |
@bryanforbes I'm not sure how well these map with SystemJS, but for what it's worth, |
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 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 |
@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,
|
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). |
Is there a way to recreate this directory structure using TS: https://gist.github.com/ryanflorence/daafb1e3cb8ad740b346 In short:
Which means that Webpack will recursively look up the directory tree for the This allows us to import shared components even from deeply nested directories without referencing the relative path:
This will work as long as Button lives in any Is there a way to tell |
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:
Then when I add outDir:
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 |
@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. |
that is how it is meant to work. the compiler needs the |
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? |
@sapjax, did you find a solution? |
I'm using https://github.com/ds300/react-native-typescript-transformer now, And it gives another advantage, we no need to use |
Damn! it worked! Thank you @sapjax! |
@fforres, follow the instructions it will work. |
Proposed module resolution strategy
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 propertiesthat will allow to customize resoluton process in existing resolution strategies but base strategy still is used as a fallback.
rootDirs
are decoupled from thebaseUrl
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 usebaseUrl
to refer to modules defined inside the projectand 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
androotDirs
(paths
can only be used ifbaseUrl
is set). If at least one of these properties is defined then compiler will try touse 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 asclassic
and then try to load module from directory(check
/a/b/c/index
with supported extension, then peek intopackage.json
etc. More details can be found in this issue)Properties
BaseUrl
All non-rooted paths are computed relative to baseUrl.
Value of baseUrl is determined as either:
Path mappings
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:
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.
Resolution process
Non-relative module names are resolved slightly differently comparing
to relative (start with "./" or "../") and rooted module names (start with "/", drive name or schema).
Resolution of non-relative module names (mostly matches SystemJS)
Resolution of relative module names
Default resolution logic (matches SystemJS)
Relative module names are computed treating location of source file that contains the import as base folder.
Path mappings are not applied.
Using
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:
Logically files in
userFiles/project
andshared/projects/project
belong to the same project andafter build they indeed will be bundled together.
In order to support this we'll add configuration property "rootDirs":
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.jsonConfiguration for the example above:
Example 1
the location of tsconfig.json ->
projectRoot
projectRoot/folder2/file2.ts
relative to the location of containing file: resolved module file name =
projectRoot/folder2/file3.ts
Example 2
the location of tsconfig.json ->
projectRoot
folder1/file2
projectRoot/folder1/file2.ts
.This file exists.
the location of tsconfig.json and will be folder that contains tsconfig.json
folder2/file3
projectRoot/folder2/file3.ts
.File does not exists, move to the second substitution
generated/folder2/file3
projectRoot/generated/folder2/file3.ts
.File exists
Example 3
All non-rooted entries in
rootDirs
are expanded using location of tsconfig.json as base location so after expansionrootDirs
willlook like this:
rootDir/folder1/file2
rootDirs
-rootDir/
and for this prefix compute as suffix -folder1/file2
rootDirs
was found try to resolve module usingrootDir
- first check ifrootDir/folder1/file2
can be resolved as module - such module does not exist
rootDirs
- check if modulerootDir/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 usingrootDir
- first check ifrootDir/generated/folder1/file1
can be resolved as module - such module does not exist
rootDirs
- check if modulerootDir/folder1/file1
exists - yes.The text was updated successfully, but these errors were encountered: