<p dir="auto">Update 20150908: applied feedback from comments. Thanks <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/jbrantly/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/jbrantly">@jbrantly</a>! Also renamed "Legacy mode" to "Mixed mode" and clarified what it's about.<br>
Update 20150909: more feedback from <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/jbrantly/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/jbrantly">@jbrantly</a>.</p>
<p dir="auto">Now that <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="60991541" data-permission-text="Title is private" data-url="https://github.com/microsoft/TypeScript/issues/2338" data-hovercard-type="issue" data-hovercard-url="/microsoft/TypeScript/issues/2338/hovercard" href="https://github.com/microsoft/TypeScript/issues/2338">#2338</a> is implemented, we have nice resolution of typings for 'native TS modules'.<br>
However, exporting typings for non-TS modules can be problematic in case of deeper dependency trees.</p>
<p dir="auto">Issue <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="69690167" data-permission-text="Title is private" data-url="https://github.com/microsoft/TypeScript/issues/2839" data-hovercard-type="issue" data-hovercard-url="/microsoft/TypeScript/issues/2839/hovercard" href="https://github.com/microsoft/TypeScript/issues/2839">#2839</a> (by me) contains a proposal to solve this, but does not talk about deeper-than-one dependencies in case of conflicting versions. Issue <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="105047101" data-permission-text="Title is private" data-url="https://github.com/microsoft/TypeScript/issues/4665" data-hovercard-type="issue" data-hovercard-url="/microsoft/TypeScript/issues/4665/hovercard" href="https://github.com/microsoft/TypeScript/issues/4665">#4665</a> (by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/mhegazy/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/mhegazy">@mhegazy</a>) also addresses non-TS resolution, but does not explicitly mention the lookup logic.</p>
<h2 dir="auto">Example dependency tree</h2>
<ul dir="auto">
<li>myprogram (native TS module)
<ul dir="auto">
<li><strong>mylib</strong> (native TS module, with 'proper external' typing)
<ul dir="auto">
<li>foolib@1.0 (plain JS module)
<ul dir="auto">
<li><strong>utils@3.0</strong> (plain JS module)</li>
</ul>
</li>
<li>barlib@1.0 (plain JS module)
<ul dir="auto">
<li><strong>utils@4.0</strong> (plain JS module)</li>
</ul>
</li>
</ul>
</li>
<li>myotherlib (native TS module, with 'proper external' typing)
<ul dir="auto">
<li>foolib@2.0 (plain JS module)
<ul dir="auto">
<li>utils@4.0 (plain JS module)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p dir="auto">Assumptions about this tree:</p>
<ul dir="auto">
<li>Mainly talking about NodeJS (CommonJS) environment here, not trying to solve the bower problem</li>
<li>None of the JS modules pollute the global namespace, i.e. they are 'normal' CommonJS modules</li>
</ul>
<h2 dir="auto">Problem description</h2>
<p dir="auto">In this case, because <code class="notranslate">mylib</code> is the 'first' package that knows about TS, it somehow needs to provide the typings for all non-TS stuff it exposes: <code class="notranslate">foolib@1.0</code>, <code class="notranslate">barlib@1.0</code> but also both <code class="notranslate">utils@3.0</code> <strong>and</strong> <code class="notranslate">utils@4.0</code>.</p>
<p dir="auto">In an ideal world, using 'proper external modules' (see <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="105047101" data-permission-text="Title is private" data-url="https://github.com/microsoft/TypeScript/issues/4665" data-hovercard-type="issue" data-hovercard-url="/microsoft/TypeScript/issues/4665/hovercard" href="https://github.com/microsoft/TypeScript/issues/4665">#4665</a> and <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="60991541" data-permission-text="Title is private" data-url="https://github.com/microsoft/TypeScript/issues/2338" data-hovercard-type="issue" data-hovercard-url="/microsoft/TypeScript/issues/2338/hovercard" href="https://github.com/microsoft/TypeScript/issues/2338">#2338</a>) would solve this if they were all TS modules (i.e. they provide their own typings in their npm package).</p>
<p dir="auto">In this case though, the <code class="notranslate">import</code> statements need to resolve to e.g. DefinitelyTyped typings, typically located in e.g. <code class="notranslate">mylib/typings/</code>.</p>
<p dir="auto">So, <code class="notranslate">mylib/typings/</code> needs to have a <code class="notranslate">utils.d.ts</code> for version 3.0 and 4.0, and the compiler needs to know how to find them, and which version to use.</p>
<p dir="auto">Especially note the difference between the concept of the current JS module versus current TS module in the following proposal.</p>
<h2 dir="auto">Proposed algorithm</h2>
<p dir="auto">Naming (taken from <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="105047101" data-permission-text="Title is private" data-url="https://github.com/microsoft/TypeScript/issues/4665" data-hovercard-type="issue" data-hovercard-url="/microsoft/TypeScript/issues/4665/hovercard" href="https://github.com/microsoft/TypeScript/issues/4665">#4665</a>):</p>
<ul dir="auto">
<li>'ambient external' module typing means it contains <code class="notranslate">declare module "Y" { ... }</code> declarations</li>
<li>'proper external' module typings don't have <code class="notranslate">declare module "Y" { ... }</code>, but directly export their classes, variables, etc.</li>
</ul>
<p dir="auto">When compiling a package <code class="notranslate">X</code>, and looking for typings of an external module <code class="notranslate">Y/Z</code>, let:</p>
<ul dir="auto">
<li><code class="notranslate">CurrentTSModule</code> = X</li>
<li><code class="notranslate">CurrentJSModule</code> = X</li>
<li><code class="notranslate">Z</code> = <code class="notranslate">"index"</code> if module is imported without a path (i.e. <code class="notranslate">import "Y"</code> instead of <code class="notranslate">import "Y/Z"</code>)</li>
</ul>
<p dir="auto">Now:</p>
<ul dir="auto">
<li>Use the logic of <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="60991541" data-permission-text="Title is private" data-url="https://github.com/microsoft/TypeScript/issues/2338" data-hovercard-type="issue" data-hovercard-url="/microsoft/TypeScript/issues/2338/hovercard" href="https://github.com/microsoft/TypeScript/issues/2338">#2338</a> to locate <code class="notranslate">Y</code>'s <code class="notranslate">package.json (starting at</code>CurrentJSModule`)
<ul dir="auto">
<li>i.e. also traversing <code class="notranslate">node_modules</code> of parent packages</li>
</ul>
</li>
<li>If <code class="notranslate">Y</code> provides its own typing (either <code class="notranslate">index.d.ts</code> or by following <code class="notranslate">typings</code> property in <code class="notranslate">package.json</code> in the package directory)
<ul dir="auto">
<li>Parse that typing as a 'proper external module' typing</li>
<li>Set <code class="notranslate">CurrentTSModule</code> and <code class="notranslate">CurrentJSModule</code> variables to <code class="notranslate">Y</code>, i.e. any external modules used by <code class="notranslate">Y</code> should be resolved by looking in <code class="notranslate">&lt;Y&gt;/node_modules</code> and <code class="notranslate">&lt;Y&gt;/typings/</code>, no longer in <code class="notranslate">&lt;X&gt;/typings/</code></li>
<li>Done</li>
</ul>
</li>
<li>Otherwise, search for typings using either:
<ul dir="auto">
<li>ALGORITHM A: searches for <code class="notranslate">Y</code> in <code class="notranslate">X</code>'s typings folder (let's call it <code class="notranslate">typings/</code>):
<ul dir="auto">
<li><code class="notranslate">typings/&lt;Y&gt;@&lt;majorY.minorY.patchY&gt;/&lt;Z&gt;.d.ts</code> (proper mode)</li>
<li><code class="notranslate">typings/&lt;Y&gt;@&lt;majorY.minorY&gt;/&lt;Z&gt;.d.ts</code> (proper mode)</li>
<li><code class="notranslate">typings/&lt;Y&gt;@&lt;majorY&gt;/&lt;Z&gt;.d.ts</code> (proper mode)</li>
<li><code class="notranslate">typings/&lt;Y&gt;/&lt;Z&gt;.d.ts</code> (proper mode)</li>
<li><code class="notranslate">typings/&lt;Y&gt;/&lt;Y&gt;.d.ts</code> (mixed mode)</li>
<li><code class="notranslate">typings/&lt;Y&gt;.d.ts</code> (mixed mode)</li>
</ul>
</li>
<li>ALGORITHM B: match against .d.ts files with <code class="notranslate">&lt;library ... /&gt;</code> tags
<ul dir="auto">
<li>For all .d.ts files passed on commandline/tsconfig.json that have a <code class="notranslate">&lt;library ... /&gt;</code> tag, match <code class="notranslate">Y</code>'s <code class="notranslate">package.json</code> name and version against the name and semver specification in the <code class="notranslate">&lt;library&gt;</code> tag</li>
</ul>
</li>
</ul>
</li>
<li>When a match is found:
<ul dir="auto">
<li>Keep <code class="notranslate">CurrentTSModule</code> as <code class="notranslate">X</code>, but set <code class="notranslate">CurrentJSModule</code> to <code class="notranslate">Y</code>
<ul dir="auto">
<li>This ensures that the correct version of any sub-package is found</li>
</ul>
</li>
<li>If 'proper mode': parse as 'proper external module', done</li>
<li>If 'mixed mode': determine whether it's a 'proper external' or 'ambient external' typing:
<ul dir="auto">
<li>If 'proper external': use it as-is, done</li>
<li>If 'ambient external':
<ul dir="auto">
<li>Don't expose any ambient declarations (modules, namespaces, variables, types), nor recursive<br>
<code class="notranslate">&lt;reference&gt;</code>'d declarations, to the global module namespace, and</li>
<li>Look for the <code class="notranslate">declare module "Y/Z" { ... }</code> and use its contents as the result (i.e. 'convert to proper external')</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p dir="auto">Notes:</p>
<ul dir="auto">
<li><code class="notranslate">&lt;majorY&gt;</code> etc. are based on the version as found in <code class="notranslate">Y</code>'s <code class="notranslate">package.json</code></li>
<li>In contrast to the lookup in <code class="notranslate">node_modules</code>, lookups in <code class="notranslate">typings</code> do <em>not</em> traverse <code class="notranslate">typings/</code> dirs of parent packages. <em>Only</em> those of (TS-)package <code class="notranslate">X</code> are searched.</li>
<li>This algorithm correctly determines that <code class="notranslate">foolib</code> uses <code class="notranslate">utils@3.0</code>, but <code class="notranslate">barlib</code> uses <code class="notranslate">utils@4.0</code> (i.e. <code class="notranslate">CurrentTSModule</code> stays <code class="notranslate">mylib</code>, but <code class="notranslate">CurrentJSModule</code> switches from <code class="notranslate">foolib</code> to <code class="notranslate">barlib</code>)</li>
</ul>
<h2 dir="auto">Mixed mode</h2>
<p dir="auto">Mixed mode (previously called "legacy mode"), is intended to allow existing DefinitelyTyped typings<br>
(which use 'ambient external' scheme) to basically be used as external modules (CommonJS) without making a lot of 'accidental' globals available (e.g. the <code class="notranslate">Promise</code> type in <code class="notranslate">bluebird</code> typings), while still also allowing them to be used in AMD and plain script modes ('isomorphic typings').</p>
<p dir="auto">These isomorphic typings typically declare lots of things as globals (perfect for plain script mode),<br>
but these are usually not made globally available when loaded as CommonJS.</p>
<p dir="auto">So, the idea is to wrap the whole typing into its own private space, then only make the actually requested external module part of it available.</p>
<p dir="auto">Note that if an isomorphic typing includes a 'proper external' typing, and the proper external typing<br>
<code class="notranslate">&lt;reference&gt;</code>'s another typing, that typing is still allowed to declare globals (they will not be 'isolated').</p>
<p dir="auto">Having two packages declare the same global (e.g. when a proper external typing references a .d.ts, which explicitly marks a variable, class, etc as being globally available) currently leads to a compiler error ("Duplicate identifier").<br>
One idea I had was to 'merge' the types of such globals instead (e.g. if two packages both declare a <code class="notranslate">Promise</code>, but they are in fact of different types, the resulting global will be typed as e.g. <code class="notranslate">declare var Promise: PromiseType1|PromiseType2;</code><br>
This way, the compiler can error when the global is actually <em>used</em> (as opposed to when declared) in an incompatible way (see <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="105101612" data-permission-text="Title is private" data-url="https://github.com/microsoft/TypeScript/issues/4673" data-hovercard-type="issue" data-hovercard-url="/microsoft/TypeScript/issues/4673/hovercard?comment_id=138859595&amp;comment_type=issue_comment" href="https://github.com/microsoft/TypeScript/issues/4673#issuecomment-138859595">#4673 (comment)</a>)..</p>
<h2 dir="auto">Discussion items</h2>
<ul dir="auto">
<li>I'm not sure whether I prefer Algorithm A or B, yet.<br>
Algorithm A has the advantage of being 'filesystem based', just like node's environment.<br>
But B supports more complex semver matches and prevents bikeshedding over e.g. the name and structure of <code class="notranslate">typings/</code>.</li>
<li>When using Algorithm A (files), <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/jbrantly/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/jbrantly">@jbrantly</a> suggested to have an extra check for ES6 typings, i.e. preferring e.g. <code class="notranslate">typings/&lt;Y&gt;.es6.d.ts</code> over <code class="notranslate">typings/&lt;Y&gt;.d.ts</code> when available. Or possibly <code class="notranslate">typings/&lt;Y&gt;.&lt;target&gt;.d.ts</code> where <code class="notranslate">&lt;target&gt;</code> is the <code class="notranslate">--target</code> passed to tsc. See comments below for discussions on pros/cons.</li>
<li>How to 'override' the type a global actually will have, in case of conflicting types (see <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="105101612" data-permission-text="Title is private" data-url="https://github.com/microsoft/TypeScript/issues/4673" data-hovercard-type="issue" data-hovercard-url="/microsoft/TypeScript/issues/4673/hovercard?comment_id=138859595&amp;comment_type=issue_comment" href="https://github.com/microsoft/TypeScript/issues/4673#issuecomment-138859595">#4673 (comment)</a>)</li>
</ul>
<p dir="auto">I would like to extend <code class="notranslate">Function</code> and this compiles fine</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="interface Function {
    hello(): any
}
Function.prototype.hello = function() {
    console.log('hello world')
}

function noop() {}

noop.hello() //==&gt; 'hello world' in the console"><pre class="notranslate"><code class="notranslate">interface Function {
    hello(): any
}
Function.prototype.hello = function() {
    console.log('hello world')
}

function noop() {}

noop.hello() //==&gt; 'hello world' in the console
</code></pre></div>
<p dir="auto">However, as soon as I try to add an import statement, say</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="import { sayHello } from './Anything'"><pre class="notranslate"><code class="notranslate">import { sayHello } from './Anything'
</code></pre></div>
<p dir="auto">where <code class="notranslate">Anything</code> only contains</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="export function sayHello() { console.log('hello')}"><pre class="notranslate"><code class="notranslate">export function sayHello() { console.log('hello')}
</code></pre></div>
<p dir="auto">The compilation fails on <code class="notranslate">Function.protoype.hello</code> with the message <code class="notranslate">error TS2339: Property 'hello' does not exist on type 'Function'</code></p>
<p dir="auto">Why ?</p>
<p dir="auto"><a href="http://www.typescriptlang.org/Playground#src=%0A%20%20%20%20interface%20Function%20%7B%0A%20%20%20%20%09hello%28%29%3A%20any%0A%20%20%20%20%7D%0A%20%20%20%20Function.prototype.hello%20%3D%20function%28%29%20%7B%0A%20%20%20%20%09console.log%28'hello%20world'%29%0A%20%20%20%20%7D%0A%09%0A%09function%20noop%28%29%20%7B%7D%0A%09%0A%09noop.hello%28%29%0A%09%0A%09%2F%2Fimport%20%7B%20hello%20%7D%20from%20'whatever'%20%20%0A%09%2F*%0A%09%20*%20Adding%20the%20import%20statement%20%28also%29%20triggers%20the%20Error%3A%20%0A%09%20*%20Property%20'hello'%20does%20not%20exists%20on%20type%20%60Function%60%0A%09%20*%20Why%20%3F%0A%09%20*%2F" rel="nofollow">View in Playground</a></p>
<p dir="auto">Note1: the compilation fails with tsc options set to either <code class="notranslate">--target es5 --module commonjs</code> or <code class="notranslate">--target es6</code><br>
Note2: my final goal is to export utilities in a library to be able to write code like <code class="notranslate">myfunc.liftM(myMonad1, MyMonad2)</code> or <code class="notranslate">myfunc1.compose(myfunc2)</code>, etc...</p>