3
votes

I'm having some trouble getting WebPack to inject imported dependencies for a project written in TypeScript. My first issue is getting TypeScript to recognize the imported module.

I have a header.ts file that declares a module that is nested under vi.input and exports a VIInputDirective class. In the main.ts file, I try to import the exported VIInputDirective class from the header.ts file, but can't seem to get TypeScript to recognize it.

header.ts

module vi.input.header {
  import IDirective = angular.IDirective;

  export class VIInputDirective implements IDirective {
    ...
  }
}

main.ts

import {VIInputDirective} from "./header.ts"; // Nothing imported; Cannot Resolve issue
VIInputDirective.whatever(); // Does not work

material.dt.s

declare module vi.input {
  ...
}

If I swap import {VIInputDirective} from "./header.ts"; in the main.ts file with import VIMHeaderDirective = vi.input.header.VIInputDirective; it works fine, but then webpack on transpile/inject gives me the following error:

VM1469:1Uncaught ReferenceError: vi is not defined

I've tried exporting the vi.input.header module directly (i.e. export module vi.input.header) but that did not work. I also tried using the reference sytnax to include the file but that did not work either: ///<reference path="file_path"/>.

It's an issue with the nesting of modules because if I remove the module and directly export the VIInputDirective class, it works fine. However, I want to keep it in a nested module.

2
Have you tried import * as x from "./header" then new x.vi.input.header.VIInputDirectiveBanners
Also perhaps you should try to add an export statement to the module declaration (now changed to 'namespace' in typescript)Banners
Just tried this "import * as VIInputDirective from "./header.ts" and TypeScript isn't able to find the module. I get the Cannot Resolve File error. If I don't wrap header.ts in a module (i.e. module vi.input.header {}), it works fine, but I want to keep that in.sags
Sorry I am not at a computer to give you a working answer. This is a useful post stackoverflow.com/questions/30357634/…Banners
Changed "module vi.input.header" to "export namespace vi.input.header" with the same results.sags

2 Answers

6
votes

You are not using modules. Modules have one or more top level import or export statements. Instead, you are using the global namespace and creating subnamespaces of the global namespace to organize your program. This is called the revealing module pattern. It does not involve using modules.

Unfortunately, TypeScript used to refer to this pattern as using Internal Modules. This terminology has since been deprecated and use of the module x {} syntax (note the lack of "s around x) is strongly discouraged. The language has introducing a synonymous keyword namespace, to reduce confusion.

JavaScript loaders and bundlers like Webpack, RequireJS, and SystemJS work with modules, that is what TypeScript referred to as external modules.

To clarify the following constructs you mention are not module related

  1. top level, non-exported module/namespace syntactic declarations

    module vi.input.header { ... }
    

    this would now be written as

    namespace vi.input.header { ... }
    

    in order to minimize confusion but, irregardless, the emit has always resulted in.

    var vi;
    (function (vi) {
        var input;
        (function (input) {
            var header;
            (function (header) {
            })(header = input.header || (input.header = {}));
        })(input = vi.input || (vi.input = {}));
    })(vi || (vi = {}));
    

    Note this mutates the global scope in a pattern commonly used by various libraries. namespaces (formerly called internal modules) like the one above have the interesting property that multiple files can contribute to their contents, and this is in fact their primary purpose. This explains the degree of nesting and the conditional assignments to variables in the emit above. This has nothing to do with using Modules.

  2. import assignments that reference namespace members such as

    import IDirective = angular.IDirective;
    

    do not qualify as top level import declarations, and thus do not cause their containing file to be considered a module. This is true even if they are placed at the top level of a file. The reason is that module systems, be they AMD, CommonJS, System, or ES2015, all use strings as module specifiers, importing from such strings; which incidentally may represent file paths, urls, resolved simple names, or synthetic module ids.

Again, the import name = qualified.global.name statements in your code are a TypeScript specific feature that is unrelated to Modules. They can be quite useful for aliasing nested types and values, but modules they do not make.

Now here is where it gets interesting, and where it intersects with your specific question namespaces can be used, and at times quite elegantly, from within external modules, but their semantics are very different

Consider

services.ts

export namespace app {
  export class SomeService { }
}

which compiles into the following JavaScript

export var app;
(function (app) {
    class SomeService {
    }
    app.SomeService = SomeService;
})(app || (app = {}));

main.ts

export namespace app {
  export function bootstrap() { }
}

which compiles into the following JavaScript

export var app;
(function (app) {
    function bootstrap() { }
    app.bootstrap = bootstrap;
})(app || (app = {}));

Both of above are external modules, that is true modules, that use namespaces as internal code organization mechanisms but they key takeaway is that they do not contribute to a shared app namespace, each having their own, file scoped app variable. Neither has implicit access to the other's members, the declarations of namespace app do not merge across files, and their having a similar internal naming scheme is incidental to their modularity.

So how does all of this relate to your question and to the suggestions you tried to apply?

Let us see

headers.ts

module vi.input.header {
  import IDirective = angular.IDirective;

  export class VIInputDirective implements IDirective {
    static whatever() { }
  }
}

This file is not a module, as explain above, and uses the global namespace to expose its declarations. If we were writing this today, we would use the namespace keyword instead of the module keyword, by convention.

main.ts

import {VIInputDirective} from "./header.ts"; // Nothing imported; Cannot Resolve issue
VIInputDirective.whatever(); // Does not work

Indeed neither line works because you are importing headers.ts as if it were a module, but as we have just seen it is not a module. Furthermore, the first line, which is a top level import statement that imports from a module specifier string, ironically makes main.ts, itself, a module.

In short the two styles do not mix well, and sharing code between them is not simple and is not something you should likely try to do (I have left the UMD format out of this answer to try to keep it relatively straightforward).

Now we come full circle

strong text

declare module vi.input {
    ....
}

If I swap import {VIInputDirective} from "./header.ts"; in the main.ts file with import VIMHeaderDirective = vi.input.header.VIInputDirective; it works fine, but then webpack on transpile/inject gives me the following error:

Indeed as explained above. This import, which does not target a module specifier string, and the absence of any others top level imports or exports, changes main.ts such that it is no longer a module. This causes TypeScript to TypeCheck it correctly, global variables referencing each other using import = namespace.value is perfectly legitimate, but these are not modules and JavaScript tools, such as Webpack, operate on modules.

So how would you write this app in this brave new world? Since you are using a module bundling tool, Webpack, you would write it using proper modules all the way down.

main.ts

import {VIInputDirective} from "./header.ts";
VIInputDirective.whatever();

headers.ts

import {IDirective} from 'angular';

export class VIInputDirective implements IDirective {
  static whatever() { }
}

material.d.ts no longer looks as it did when you wrote this, as it has been updated to work with proper modules. If you need to reference something from it, use the module syntax

my-dialog-options.ts

import {material} from 'angular';

const dialogOptions: material.IDialogOptions = { ... };

export default dialogOptions;

I have tried not to oversimplify but some hand waving has been necessary to avoid writing a novella on the subject, but I believe I hope to have covered and conveyed the key points.

0
votes

You are looking for namespaces not module, so.

header.ts

export namespace vi.input.header {
  export class VIInputDirective {

  }
}

main.ts

import { vi } from "./header.ts"; 
var foo = new vi.input.header.VIInputDirective();