{"action":"created","issue":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/5039","labels_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/5039/labels{/name}","comments_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/5039/comments","events_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/5039/events","html_url":"https://github.com/Microsoft/TypeScript/issues/5039","id":109143324,"number":5039,"title":"Path mappings based module resolution","user":{"login":"vladima","id":8052307,"avatar_url":"https://avatars.githubusercontent.com/u/8052307?v=3","gravatar_id":"","url":"https://api.github.com/users/vladima","html_url":"https://github.com/vladima","followers_url":"https://api.github.com/users/vladima/followers","following_url":"https://api.github.com/users/vladima/following{/other_user}","gists_url":"https://api.github.com/users/vladima/gists{/gist_id}","starred_url":"https://api.github.com/users/vladima/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vladima/subscriptions","organizations_url":"https://api.github.com/users/vladima/orgs","repos_url":"https://api.github.com/users/vladima/repos","events_url":"https://api.github.com/users/vladima/events{/privacy}","received_events_url":"https://api.github.com/users/vladima/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/In%20Discussion","name":"In Discussion","color":"556b75"},{"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/Suggestion","name":"Suggestion","color":"006b75"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":32,"created_at":"2015-09-30T17:42:16Z","updated_at":"2015-11-17T14:27:50Z","closed_at":null,"body":"# Proposed module resolution strategy\r\n\r\nProposed set of rules is an extension to *baseUrl* based mechanics used by\r\n[RequireJS](http://requirejs.org/docs/api.html#config-baseUrl) or [SystemJS](https://github.com/systemjs/systemjs/blob/master/docs/overview.md#baseurl).\r\n\r\nCore elements of the system are [baseUrl](#baseUrl) and [path mappings](#pathMappings). [Resolution process](#resolutionProcess) describes how they are used together.\r\n\r\n## BaseUrl <a id=\"baseUrl\"></a>\r\nAll non-rooted paths are computed relative to *baseUrl*. \r\nValue of *baseUrl* is determined as either:\r\n- value of *baseUrl* command line argument (if given path is relative it is computed based on current directory)\r\n- value of *baseUrl* propery in 'tsconfig.json' (if given path is relative it is computed based on location of 'tsconfig.json')\r\n- location of 'tsconfig.json' file\r\n\r\n## Path mappings <a id=\"pathMappings\"></a>\r\n\r\nSometimes modules are not directly located under *baseUrl*. It is possible to control how locations are computed in such cases\r\nusing path mappings. Path mappings are specified using the following JSON structure:\r\n```javascript\r\n{\r\n    \"paths\": {\r\n        \"pattern-1\": \"substitution\" | [\"list of substitutions\"],\r\n        \"pattern-2\": \"substitution\" | [\"list of substitutions\"],\r\n        ...\r\n        \"pattern-N\": \"substitution\" | [\"list of substitutions\"]\r\n    }\r\n}\r\n```\r\n\r\nPatterns and substitutions are strings that can have zero or one asteriks ('*'). \r\nInterpretation of both patterns and substitutions will be described in [Resolution process](#resolutionProcess) section.\r\n\r\n## Resolution process <a id=\"resolutionProcess\"></a>\r\n\r\nNon-relative module names are resolved slightly differently comparing \r\nto relative (start with \"./\" or \"../\") and rooted module names (start with \"/\", drive name or schema). \r\n\r\n### Resolution of non-relative module names (mostly matches SystemJS)\r\n\r\n \r\n ```javascript\r\n \r\n// mimics path mappings in SystemJS\r\n// NOTE: fileExists checks if file with any supported extension exists on disk\r\nfunction resolveNonRelativeModuleName(moduleName: string): string {\r\n    // check if module name should be used as-is or it should be mapped to different value\r\n    let longestMatchedPrefixLength = 0;\r\n    let matchedPattern: string;\r\n    let matchedWildcard: string;\r\n\r\n    for (let pattern in config.paths) {\r\n        assert(pattern.countOf('*') <= 1);\r\n        let indexOfWildcard = pattern.indexOf('*'); \r\n        if (indexOfWildcard !== -1) {\r\n            // if pattern contains asterisk then asterisk acts as a capture group with a greedy matching\r\n            // i.e. for the string 'abbb' pattern 'a*b' will get 'bb' as '*'\r\n\r\n            // check if module name starts with prefix, ends with suffix and these two don't overlap\r\n            let prefix = pattern.substr(0, indexOfWildcard);\r\n            let suffix = pattern.substr(indexOfWildcard + 1);\r\n            if (moduleName.length >= prefix.length + suffix.length && \r\n                moduleName.startsWith(prefix) &&\r\n                moduleName.endsWith(suffix)) {\r\n\r\n                // use length of matched prefix as betterness criteria\r\n                if (longestMatchedPrefixLength < prefix.length) {\r\n                    // save length of the prefix\r\n                    longestMatchedPrefixLength = prefix.length;\r\n                    // save matched pattern\r\n                    matchedPattern = pattern;\r\n                    // save matched wildcard content \r\n                    matchedWildcard = moduleName.substr(prefix.length, moduleName.length - suffix.length);\r\n                }\r\n            }\r\n        }\r\n        else {\r\n            // pattern does not contain asterisk - module name should exactly match pattern to succeed\r\n            if (pattern === moduleName) {\r\n                // save pattern\r\n                matchedPattern = pattern;\r\n                // drop saved wildcard match \r\n                matchedWildcard = undefined;\r\n                // exact match is found - can exit early \r\n                break;\r\n            }\r\n        }\r\n    }\r\n\r\n    if (!matchedPattern) {\r\n        // no pattern was matched so module name can be used as-is\r\n        let path = combine(baseUrl, moduleName);\r\n        return fileExists(path) ? path : undefined;\r\n    }\r\n\r\n    // some pattern was matched - module name needs to be substituted\r\n    let substitutions = config.paths[matchedPattern].asArray();\r\n    for (let subst of substitutions) {\r\n        assert(substs.countOf('*') <= 1);\r\n        // replace * in substitution with matched wildcard\r\n        let path = matchedWildcard ? subst.replace(\"*\", matchedWildcard) : subst;\r\n        // if substituion is a relative path - combine it with baseUrl\r\n        path = isRelative(path) ? combine(baseUrl, path) : path;\r\n        if (fileExists(path)) {\r\n            return path;\r\n        }\r\n    }\r\n\r\n    return undefined;\t\r\n}\r\n\r\n```\r\n\r\n### Resolution of relative module names\r\n\r\n#### Default resolution logic (matches SystemJS)\r\n\r\nRelative module names are computed treating location of source file that contains the import as base folder.\r\nPath mappings are not applied.\r\n\r\n```javascript\r\nfunction resolveRelativeModuleName(moduleName: string, containingFile: string): string {\r\n    let path = combine(getDirectoryName(containingFile), moduleName);\r\n    return fileExists(path) ? path : undefined;\r\n}\r\n```\r\n\r\n#### Mapping relative module names \r\n\r\nIn some cases it also will be useful to apply path mappings to relative module names, for example \r\nwhen project uses source files that are spreaded across file system (not under the same root) but still prefers to \r\nuse relative module names because in runtime such names can be successfully resolved due to bundling.\r\nFor example consider this project structure:\r\n```\r\nuserFiles\r\n  |-project\r\n      |-src\r\n         |-views\r\n             |-view1.ts (imports './view2')\r\nshared\r\n  |-projects\r\n      |-project\r\n          |-src\r\n             |-views\r\n             |  |-view2.ts (imports './view1')\r\n             |-viewManager.ts (imports './views/view1')\r\n```\r\n\r\nLogically files in `userFiles/project` and `shared/projects/project` belong to the same project and\r\nafter build they indeed will be bundled together.\r\n\r\nIn order to support this we'll add configuration property \"rootDirs\":\r\n```json\r\n{\r\n    \"rootDirs\": [\r\n        \"rootDir-1/*\",\r\n        \"rootDir-2/*\",\r\n        ...\r\n        \"rootDir-n/*\"\r\n    ]\r\n}\r\n```\r\n\r\nThis property stores list of base folders, every folder name can be either absolute or relative (then it will be computed using *baseUrl*).\r\n```javascript\r\n///Algorithm for resolving relative module name\r\nfunction resolveRelativeModuleName(moduleName: string, containingFile: string): string {\r\n    // convert relative module name to absolute using location of containing file\r\n    // this step is exactly the same as when doing resolution without path mapping\r\n    let path = combine(getDirectoryName(containingFile), moduleName);\r\n    if (fileExists(path)) {\r\n        // target module is located next to containingFile\r\n        return path;\r\n    }\r\n    \r\n    let normalizedRootDirs = rootDirs.map(d => isRelative(d) ? combine(baseUrl, d) : d);\r\n    // convert absolute module name to non-relative\r\n    // try to find longest match with path and extract captured wildcard content\r\n    // logic is the same with pattern matching in 'resolveNonRelativeModuleName' \r\n    // and can be shared in actual implementaiton    \r\n    let nonRelativeName = tryMatchPatternAndGetCapturedWildcardContent(normalizedRootDirs, path);\r\n    if (!nonRelativeName) {\r\n        // cannot extract non relative name\r\n        return undefined;\r\n    }\r\n    // run normal resolution for non-relative module names\r\n    return resolveNonRelativeModuleName(nonRelativeName);\r\n}\r\n```\r\n\r\nConfiguration for the example above:\r\n\r\n```json\r\n{\r\n    \"rootDirs\": [\r\n        \"userFiles/project/*\",\r\n        \"/shared/projects/project/*\"\r\n    ],\r\n    \"paths\": {\r\n        \"*\": [\r\n            \"userFiles/project/*\",\r\n            \"/shared/projects/project/*\"\r\n        ]\r\n    }\r\n}\r\n```\r\n\r\n*CONSIDER:* As the way to avoid duplication of configuration information we can allow to use references to data instead of data itself in configuration:\r\n\r\n```json\r\n{\r\n    \"dirs\": [\r\n        \"userFiles/project/*\",\r\n        \"/shared/projects/project/*\"\r\n    ],\r\n    \"rootDirs\": { \"ref\": \"dirs\" },\r\n    \"paths\": {\r\n        \"*\": { \"ref\": \"dirs\" }\r\n    }\r\n}\r\n```\r\n\r\n### Example 1\r\n\r\n```\r\nprojectRoot\r\n |-folder1\r\n     |-file1.ts // imports 'folder2/file2'\r\n |-folder2\r\n     |-file2.ts // imports './file3'\r\n     |-file3.ts\r\n |-tsconfig.json // has no path mappings/baseUrl/rootDirs\r\n```\r\n\r\n* import 'folder2/file2'\r\n  1. *baseUrl* is not specified in configuration -> *baseUrl* = location of tsconfig.json\r\n  2. path mappings are not available -> path = moduleName\r\n  3. resolved module file name = combine(baseUrl, path) -> `projectRoot/folder2/file2.ts`\r\n  \r\n* import './file3'\r\n  1. moduleName is relative and *rootDirs* are not specified in configuration - compute module name\r\n  relative to the location of containing file: resolved module file name = `projectRoot/folder2/file3.ts`\r\n  \r\n\r\n### Example 2\r\n\r\n```\r\nprojectRoot\r\n  |-folder1\r\n      |-file1.ts // imports 'folder1/file2' and 'folder2/file3'\r\n      |-file2.ts\r\n  |-generated\r\n      |-folder1\r\n          |-...\r\n      |-folder2\r\n          |-file3.ts\r\n  |-tsconfig.json\r\n```\r\n\r\n```javascript\r\n// configuration in tsconfig.json\r\n{\r\n    \"paths\": {\r\n    \"*\": [\r\n            \"*\",\r\n            \"generated/*\" \r\n        ]\r\n    }\r\n}\r\n```\r\n\r\n* import 'folder1/file2'\r\n  1. *baseUrl* is not specified in configuration -> *baseUrl* = location of tsconfig.json\r\n  2. configuration contains path mappings.\r\n  3. pattern '*' is matched and wildcard captures the whole module name\r\n  4. try first substitution in the list: '*' -> `folder1/file2`\r\n  5. result of substitution is relative name - combine it with *baseUrl* -> `projectRoot/folder1/file2.ts`.\r\n  This file exists.\r\n  \r\n* import 'folder2/file2'\r\n  1. *baseUrl* is not specified in configuration -> *baseUrl* = location of tsconfig.json\r\n  2. configuration contains path mappings.\r\n  3. pattern '*' is matched and wildcard captures the whole module name\r\n  4. try first substitution in the list: '*' -> `folder2/file3`\r\n  5. result of substitution is relative name - combine it with *baseUrl* -> `projectRoot/folder2/file3.ts`.\r\n  File does not exists, move to the second substitution\r\n  6. second substitution 'generated/*' -> `generated/folder2/file3` \r\n  7. result of substitution is relative name - combine it with *baseUrl* -> `projectRoot/generated/folder2/file3.ts`.\r\n  File exists\r\n  \r\n## Example 3\r\n```\r\nprojectRoot\r\n  |-folder1\r\n      |-file1.ts // imports './file2'\r\n  |-generated\r\n      |-folder1\r\n          |-file2.ts\r\n      |-folder2\r\n          |-file3.ts // imports '../folder1/file1'\r\n  |-tsconfig.json\r\n```\r\n\r\n```javascript\r\n// configuration in tsconfig.json\r\n{\r\n    \"rootDirs\": [\r\n        \"*\",\r\n        \"generated/*\" \r\n    ],\r\n    \"paths\": {\r\n        \"*\": [\r\n            \"*\",\r\n            \"generated/*\" \r\n        ]\r\n    }\r\n}\r\n```\r\n\r\n* import './file2'\r\n  1. *baseUrl* is not specified in configuration -> *baseUrl* = location of tsconfig.json `projectRoot`\r\n  2. module name is relative and configuration contains set of rootDirs to convert relative name to non-relative.\r\n  3. convert relative module name to absolute: `projectRoot/folder1/file2`\r\n  4. normalize relative rootDirs :\r\n  ```javascript\r\n  normalizedRootDirs = [ \"projectRoot/*\", \"projectRoot/generated/*\"]\r\n  ```\r\n  5. use `normalizedRootDirs` to obtain non-relative module name: `folder1/file2` (matches `projectRoot/*`)\r\n  6. resolve non-relative module name: `projectRoot/generated/folder1/file2.ts` (by matching '*' and applying second substitution)\r\n  \r\n* import '../folder1/file1'\r\n  1. *baseUrl* is not specified in configuration -> *baseUrl* = location of tsconfig.json `projectRoot`\r\n  2. module name is relative and configuration contains set of rootDirs to convert relative name to non-relative.\r\n  3. convert relative module name to absolute: `projectRoot/generated/folder1/file1`\r\n  4. normalize relative rootDirs :\r\n  ```javascript\r\n  normalizedRootDirs = [ \"projectRoot/*\", \"projectRoot/generated/*\"]\r\n  ```\r\n  5. use `normalizedRootDirs` to obtain non-relative module name: `folder1/file1` (matches `projectRoot/generated/*`)\r\n  6. resolve non-relative module name: `projectRoot/folder1/file1.ts` (by matching '*' and applying first substitution)  "},"comment":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/comments/157385674","html_url":"https://github.com/Microsoft/TypeScript/issues/5039#issuecomment-157385674","issue_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/5039","id":157385674,"user":{"login":"janakerman","id":3799225,"avatar_url":"https://avatars.githubusercontent.com/u/3799225?v=3","gravatar_id":"","url":"https://api.github.com/users/janakerman","html_url":"https://github.com/janakerman","followers_url":"https://api.github.com/users/janakerman/followers","following_url":"https://api.github.com/users/janakerman/following{/other_user}","gists_url":"https://api.github.com/users/janakerman/gists{/gist_id}","starred_url":"https://api.github.com/users/janakerman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/janakerman/subscriptions","organizations_url":"https://api.github.com/users/janakerman/orgs","repos_url":"https://api.github.com/users/janakerman/repos","events_url":"https://api.github.com/users/janakerman/events{/privacy}","received_events_url":"https://api.github.com/users/janakerman/received_events","type":"User","site_admin":false},"created_at":"2015-11-17T14:27:50Z","updated_at":"2015-11-17T14:27:50Z","body":"Just to confirm - would I be correct in thinking that the resolution of .d.ts files are within the scope of this issue?\r\n\r\nI'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. \r\n\r\nIn 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)."}}