Strange error with nested internal modules and external module reference

Topics: General, Language Specification
Jan 19, 2014 at 9:22 AM
I have whittled this problem down as far as I can. This issue only happens on multiple files, nested internal modules, and a reference to an external module altogether. I cannot replicate it otherwise. Take the following three files:

Foo/Bar/Baz/One.ts:
module Foo.Bar.Baz.One {
    export interface IFace {
    }
}
Foo/Bar/Two.ts:
/// <reference path="../../other.d.ts" />
/// <reference path="Baz/One.ts" />

import other = require("other");

module Foo.Bar {
    import IFace = Foo.Bar.Baz.One.IFace;
}
other.d.ts:
declare module "other" { }
With tsc 0.9.5.0 compiling with this command (though the --module option doesn't matter, happens no matter what):
tsc --module commonjs "Foo/Bar/Baz/One.ts" "Foo/Bar/Two.ts" "other.d.ts"
I get
Foo/Bar/Two.ts(7,28): error TS2180: Could not find module 'Baz' in module 'Foo.Bar'.
I am posting here just in case it's not a bug and I am doing something wrong. I have a large project and this is happening, but I tried to break down the problem. Am I doing something wrong or should I file a bug?
Jan 19, 2014 at 9:36 AM
Hrmm, this may be related to https://typescript.codeplex.com/workitem/1846 (see discussion and SO post referenced). Is there a general rule that modules will not merge across files? Why does the presence of the unrelated external import trigger the bug whereas if I replace it with some innocuous global code (e.g. var a = "b") it does not fail?
Developer
Jan 20, 2014 at 3:21 PM
This is actually by design. An external module (a file containing top level imports and/or exports) has its own namespace and internal modules (or anything else) declared within it belong to that namespace. A key point of external modules is to provide this level of isolation.

It may help to visualize the namespace hierarchy declared by your program (pardon the ASCII graphics):
Global
|
+---Foo
|   +---Bar
|       +---Baz
|           +---IFace
|
+---"Foo/Bar/Two"
|   +---Foo
|       +---Bar
|           +---IFace
|
+---"other"
Note that the top level Foo is distinct and different from the Foo located inside the "Foo/Bar/Two" external module. The only way to declare (or merge with) anything in the global module is to put it in a file that isn't an external module. In your example, if you remove the 'import' statement in 'Foo/Bar/Two.ts', that file will no longer be considered an external module and it's declaration of Foo.Bar would merge with the one in 'Foo/Bar/Baz/One.ts'.
Jan 21, 2014 at 4:11 AM
Edited Jan 21, 2014 at 4:13 AM
Thank you very much for the response...from the man himself!

My biggest struggle is that I want to import an external module in a file at the top level where I can use it in my module. I cannot import the external inside the module. Assuming in one my my classes inside of the Foo.Bar module I wanted to use the "other" external module, how might I do that? My current workaround is to do my "require" inside the functions that need them. Otherwise, I cannot seem to require an external node.js module for use in my internal modules' functions. Should I just be doing a "var other = require('other')" instead? How come I can't replicate this if I put all the contents of the files together into a single file?
Developer
Jan 21, 2014 at 5:25 PM
The feature we're missing here (and considering for post-1.0) is multi-file external modules. Currently, a TypeScript source file is either (a) a "script-like" file that contributes to the single global module, or (b) an external module with its own namespace. There is no way to have multiple distinct source files contribute to the same external module.

Technically, when compiling for node.js, you should put everything in an external module. By design, node.js doesn't provide a way to place user defined code in the global module. This is unlike AMD where you can place code in the global module (using script tags) as well as in dynamically loaded external modules. TypeScript sort of stays neutral on this point and doesn't enforce "external modules only" when compiling for CommonJS modules since it really is an implementation detail of node.js. Also, for single file programs there really isn't much of a difference and compiler enforcement would just be an irritant.
Jan 21, 2014 at 11:46 PM
Edited Jan 21, 2014 at 11:51 PM
For a more real life situation, imagine if Two.ts from the original post looked like this:
/// <reference path="../../other.d.ts" />
/// <reference path="Baz/One.ts" />

import http = require("http");

module Foo.Bar {
    import IFace = Foo.Bar.Baz.One.IFace;

    export function doSomething(url: string, cb: (resp: IFace) => void) {
        http.get("http://www.google.com/index.html", res => cb(IFace.factoryFromStatusCode(res.statusCode)));
    }
}
So that's impossible just because how I use internal modules? I was hoping a third type of type script file was possible: c) a file of source that may be broken off for legibility and organization purposes but otherwise doesn't detract from the project as a whole. I do understand now that I am either building an external module per file (like I would in native JS) or a "main" script of sorts. Which means that my code above, which is not an external module, references an internal module and references an external module, can never be written. At the least it may be worth having a better error because the error given is hard to understand it's the result of module type mixing.

Thanks again for the info.