How to Use Namespaces with Typescript External Modules

How do I use namespaces with TypeScript external modules?

Candy Cup Analogy

Version 1: A cup for every candy

Let's say you wrote some code like this:

Mod1.ts

export namespace A {
export class Twix { ... }
}

Mod2.ts

export namespace A {
export class PeanutButterCup { ... }
}

Mod3.ts

export namespace A {
export class KitKat { ... }
}

You've created this setup:
Sample Image

Each module (sheet of paper) gets its own cup named A. This is useless - you're not actually organizing your candy here, you're just adding an additional step (taking it out of the cup) between you and the treats.



Version 2: One cup in the global scope

If you weren't using modules, you might write code like this (note the lack of export declarations):

global1.ts

namespace A {
export class Twix { ... }
}

global2.ts

namespace A {
export class PeanutButterCup { ... }
}

global3.ts

namespace A {
export class KitKat { ... }
}

This code creates a merged namespace A in the global scope:

Sample Image

This setup is useful, but doesn't apply in the case of modules (because modules don't pollute the global scope).



Version 3: Going cupless

Going back to the original example, the cups A, A, and A aren't doing you any favors. Instead, you could write the code as:

Mod1.ts

export class Twix { ... }

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

export class KitKat { ... }

to create a picture that looks like this:

Sample Image

Much better!

Now, if you're still thinking about how much you really want to use namespace with your modules, read on...



These Aren't the Concepts You're Looking For

We need to go back to the origins of why namespaces exist in the first place and examine whether those reasons make sense for external modules.

Organization: Namespaces are handy for grouping together logically-related objects and types. For example, in C#, you're going to find all the collection types in System.Collections. By organizing our types into hierarchical namespaces, we provide a good "discovery" experience for users of those types.

Name Conflicts: Namespaces are important to avoid naming collisions. For example, you might have My.Application.Customer.AddForm and My.Application.Order.AddForm -- two types with the same name, but a different namespace. In a language where all identifiers exist in the same root scope and all assemblies load all types, it's critical to have everything be in a namespace.

Do those reasons make sense in external modules?

Organization: External modules are already present in a file system, necessarily. We have to resolve them by path and filename, so there's a logical organization scheme for us to use. We can have a /collections/generic/ folder with a list module in it.

Name Conflicts: This doesn't apply at all in external modules. Within a module, there's no plausible reason to have two objects with the same name. From the consumption side, the consumer of any given module gets to pick the name that they will use to refer to the module, so accidental naming conflicts are impossible.


Even if you don't believe that those reasons are adequately addressed by how modules work, the "solution" of trying to use namespaces in external modules doesn't even work.

Boxes in Boxes in Boxes

A story:

Your friend Bob calls you up. "I have a great new organization scheme in my house", he says, "come check it out!". Neat, let's go see what Bob has come up with.

You start in the kitchen and open up the pantry. There are 60 different boxes, each labelled "Pantry". You pick a box at random and open it. Inside is a single box labelled "Grains". You open up the "Grains" box and find a single box labelled "Pasta". You open the "Pasta" box and find a single box labelled "Penne". You open this box and find, as you expect, a bag of penne pasta.

Slightly confused, you pick up an adjacent box, also labelled "Pantry". Inside is a single box, again labelled "Grains". You open up the "Grains" box and, again, find a single box labelled "Pasta". You open the "Pasta" box and find a single box, this one is labelled "Rigatoni". You open this box and find... a bag of rigatoni pasta.

"It's great!" says Bob. "Everything is in a namespace!".

"But Bob..." you reply. "Your organization scheme is useless. You have to open up a bunch of boxes to get to anything, and it's not actually any more convenient to find anything than if you had just put everything in one box instead of three. In fact, since your pantry is already sorted shelf-by-shelf, you don't need the boxes at all. Why not just set the pasta on the shelf and pick it up when you need it?"

"You don't understand -- I need to make sure that no one else puts something that doesn't belong in the 'Pantry' namespace. And I've safely organized all my pasta into the Pantry.Grains.Pasta namespace so I can easily find it"

Bob is a very confused man.

Modules are Their Own Box

You've probably had something similar happen in real life: You order a few things on Amazon, and each item shows up in its own box, with a smaller box inside, with your item wrapped in its own packaging. Even if the interior boxes are similar, the shipments are not usefully "combined".

Going with the box analogy, the key observation is that external modules are their own box. It might be a very complex item with lots of functionality, but any given external module is its own box.



Guidance for External Modules

Now that we've figured out that we don't need to use 'namespaces', how should we organize our modules? Some guiding principles and examples follow.

Export as close to top-level as possible

  • If you're only exporting a single class or function, use export default:

MyClass.ts

export default class SomeType {
constructor() { ... }
}

MyFunc.ts

function getThing() { return 'thing'; }
export default getThing;

Consumption

import t from './MyClass';
import f from './MyFunc';
var x = new t();
console.log(f());

This is optimal for consumers. They can name your type whatever they want (t in this case) and don't have to do any extraneous dotting to find your objects.

  • If you're exporting multiple objects, put them all at top-level:

MyThings.ts

export class SomeType { ... }
export function someFunc() { ... }

Consumption

import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
  • If you're exporting a large number of things, only then should you use the module/namespace keyword:

MyLargeModule.ts

export namespace Animals {
export class Dog { ... }
export class Cat { ... }
}
export namespace Plants {
export class Tree { ... }
}

Consumption

import { Animals, Plants} from './MyLargeModule';
var x = new Animals.Dog();


Red Flags

All of the following are red flags for module structuring. Double-check that you're not trying to namespace your external modules if any of these apply to your files:

  • A file whose only top-level declaration is export module Foo { ... } (remove Foo and move everything 'up' a level)
  • A file that has a single export class or export function that isn't export default
  • Multiple files that have the same export module Foo { at top-level (don't think that these are going to combine into one Foo!)

How to use namespaces with import in TypeScript

A solution with namespaces (not recommended)

To resolve your issue, you can export your namespace:

// UtilBase.ts
import * as path from "path";
export namespace My.utils {
export class UtilBase {
protected fixPath(value: string): string {
return value.replace('/', path.sep);
}
}
}

Then, you should be able to import it:

// UtilOne.ts
import {My} from './UtilBase';
namespace My.utils {
export class UtilOne extends My.utils.UtilBase {
}
}

However, if the purpose is to organize the code, it is a bad practice to use namespaces and (ES6) modules at the same time. With Node.js, your files are modules, then you should avoid namespaces.

Use ES6 modules without namespaces

TypeScript supports the syntax of ES6 modules very well:

// UtilBase.ts
import * as path from "path";
export default class UtilBase {
protected fixPath(value: string): string {
return value.replace('/', path.sep);
}
}

// UtilOne.ts
import UtilBase from './UtilBase';
export default class UtilOne extends UtilBase {
}

It is the recommended way. ES6 modules prevent naming conflicts with the ability to rename each imported resource.

It will work on Node.js.

For a good introduction to the ES6 modules syntax, read this article.

Use a file tsconfig.json instead of /// <reference

Notice: The syntax /// <reference is replaced by the file tsconfig.json. An example for Node.js:

// tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es6"
},
"exclude": [
"node_modules"
]
}

Module vs Namespace - Import vs Require Typescript

I didn't get how we categorize them?

Namespaces are used to organize/encapsulate your code. External modules are used to organize/encapsulate your code AND to locate your code at runtime. In practice, you have two choices at runtime: 1) combine all transpiled code into one file, or 2) use external modules and have multiple files and require some other mechanism to get at those files.

When to export a class or namespace or package?

To make a type or value visible outside of the file that it's in, you have to export it if it's inside of a namespace. Whether you export it at the top level or within a namespace will decide if it's now in an external module.

If we export package/namespace, all classes within that are exported or need to be explicitly exported

Classes in a namespace will always need to be explicitly exported for the class to be visible at compile time outside of the file in which it is defined.

How each one of them can be imported/required?

This depends of if you're using external modules. An external module will always need to be imported to "use" it. Importing a namespace that's not in an external module is really just providing an alias for the namespace -- you still have to prefix the type/whatever with the alias (and this is why you generally don't want to use namespaces with external modules; doing so means you always have to use a prefix when referencing anything provided by the external module.) Namespaces that aren't in an external module can span files, so if you're in the same namespace you can refer to anything exported by the namespace without needing any sort of import.

To really understand the above you need some background knowledge. The key thing to understand with references/namespaces/external modules is what these constructs do at compile time and what they do at runtime.

Reference directives are used at compile time to locate type information. Your source has a particular symbol in it. How does the TypeScript compiler locate the definition for that symbol? The reference directive has largely been subsumed by the tsconfig.json mechanism -- using tsconfig.json, you tell the compiler where all your sources are.

Namespaces can contain type definitions and/or implementation. If a namespace contain only type information then it has no runtime manifestation at all -- you can check this by looking at the JS output and finding an empty JS file. If a namespace has implementation code, then the code is wrapped inside a closure that is assigned to a global variable with the same name as the namespace. With nested namespaces, there will be a global variable for the root name space. Again, check the JS output. Namespaces are historically how JS client-side libraries have attempted to avoid the issue with naming collisions. The idea is to wrap your entire library into one closure and then expose as small a global footprint as possible -- just one global variable referencing the closure. Well, the problem is still that you've claimed a name in the global space. What if you wanted, say, two versions of a library? A TypeScript namespace still has the issue of how to locate the source for the namespace. That is, source code that references A.B still has the problem of telling the compiler how to locate A.B -- either by using reference directives or by using tsconfig.json. Or by putting the namespace into an external module and then importing the external module.

External modules originated with server-side JS. There is a one-to-one correspondence between an external module and a file on the file system. You can use the file system directory structure to organize external modules into a nested structure. Importing an external module will generally aways introduce a runtime dependency on that external module (the exception is when you import an external module but then don't use any of its exports in the value position -- that is, you only import the external module to get at its type information). An external module is implicitly in a closure, and this is key: the user of the module can assign the closure to whatever local variable they want. TypeScript/ES6 adds additional syntax around mapping the exports of the external modules to local names, but this is just a nicety. On the server side, locating an external module is relatively straight forward: just locate the file representing the external module on the local file system. If you want to use external modules on the client side, in a browser, it gets more complex as there's no equivalent to the file system that has the module available for loading. So now on the client side you need a way to bundle all those files into a form that can be used remotely in the browser -- this is where module bundlers like Webpack (Webpack does a heck of a lot more than bundle modules though) and Browserify come into play. Module bundlers allow runtime resolution of your external modules in the browser.

Real world scenario: AngularJS. Pretend external modules don't exist, use a single namespace to limit pollution of global space (in the example below a single variable MyApp is all that is in the global space), export only interfaces, and use AngularJS dependency-injection to make implementations available for use. Put all classes in a directory root, add a tsconfig.json to the root, install angularjs typings under the same directory root so that tsconfig.json picks it up too, combine all output int one JS file. This will work fine for most projects if code-reuse isn't much of a concern.

MyService.ts:

namespace MyApp {

// without an export the interface is not visible outside of MyService.ts
export interface MyService {
....
}

// class is not exported; AngularJS DI will wire up the implementation
class MyServiceImpl implements MyService {
}

angular.module("MyApp").service("myService", MyServiceImpl);
}

MyController.ts:

namespace MyApp {

class MyController {
// No import of MyService is needed as we are spanning
// one namespace with multiple files.
// MyService is only used at compile time for type checking.
// AngularJS DI is done on the name of the variable.
constructor(private myService: MyService) {
}
}
angular.module("MyApp").controller("myController", MyController);
}

Using IIFE to avoid polluting global runtime scope. In this example, no global variables are created at all. (A tsconfig.json is assumed.)

Foo.ts:

namespace Foo {
// without an export IFoo is not visible. No JS is generated here
// as we are only defining a type.
export interface IFoo {
x: string;
}
}

interface ITopLevel {
z: string;
}

(function(){
// export required above to make IFoo visible as we are not in the Foo namespace
class Foo1 implements Foo.IFoo {
x: string = "abc";
}
// do something with Foo1 like register it with a DI system
})();

Bar.ts:

// alias import; no external module created
import IFoo = Foo.IFoo;

(function() {

// Namespace Foo is always visible as it was defined at
// top level (outside of any other namespace).
class Bar1 implements Foo.IFoo {
x: string;
}

// equivalent to above
class Bar2 implements IFoo {
x: string;
}

// IToplevel is visible here for the same reason namespace Foo is visible
class MyToplevel implements ITopLevel {
z: string;
}

})();

Using IIFE you can make eliminate introducing MyApp as a global variable in the first example.

MyService.ts:

interface MyService { 
....
}

(function() {

class MyServiceImpl implements MyService {
}

angular.module("MyApp").service("myService", MyServiceImpl);
})();

MyController.ts:

(function() { 

class MyController {
constructor(private myService: MyService) {
}
}

angular.module("MyApp").controller("myController", MyController);
})();

General TypeScript usage for modules / namespaces

If you want to make things work without a module loader, you will have to make a few changes. First of all it's worth mentioning that TypeScript supports 2 primary ways of modularizing code, namespaces and modules.

NOTE: A slightly confusing nuance here is that the keywords namespace and module can be used interchangebly and don't by themselves determine whether a thing is a module or a namespace.

1) modules (formerly called external modules) - these use typical import, export, and require semantics and require a module loader and preferably a bundler of some sort. For this pattern, each file is considered a module. Any file that contains any import or export statements will be considered an external module by the TypeScript compiler.

2) namespaces (formerly called internal modules) - this pattern supports namespaces that can span multiple files. This is the module type that you would need to use if you don't want to use a module loader. Usage of this pattern is becoming less common, but it is still an option if you want to use it for whatever reason. To use this type of modularization, you can't have any import or export statements in the file.

Here's the docs on namespaces https://www.typescriptlang.org/docs/handbook/namespaces.html

Assuming you want to go with your original plan, you will need to tweak a couple of things in your code.

1) As mentioned by @sbat, your index.d.ts is re-declaring your namespace and class types. This is likely causing duplicate definition errors. You can replace the entire contents with a simple override of the JQuery interface.

/** Extend the JQuery interface with custom method. */
declare interface JQuery {
TestNS: () => TestNS.TestClass
}

2) Make sure you don't have any top level exports or imports. Specifically you will want to remove the export from your namespace in test.ts. This will make your module an internal one instead of an external one.

namespace TestNS {
export class TestClass {
...
}
}

Understanding TypeScript import, module and namespace

The thing you are missing is that TypeScript supports two types of modules - external and internal. Internal modules were renamed to namespaces in newer versions (that's the reason there is older module and newer namespace keywords - you can use them as you wish in your project / they are equal).

When you are using import syntax in bar.ts you are trying to consume external module.

But foo.ts is not external module until you add export into root scope.

I'd recommend to stay with import and use external modules like this:

Foo.ts

export class Bar {
}

Bar.ts

import { Bar } from "./Foo"; // path needs to be relative here
// if referencing files are in same folder use ./ to force the path to be relative

There is no need to use module/namespace until you want to wrap classes into namespaces.

Modules vs. Namespaces: What is the correct way to organize a large typescript project?

tl;dr: Do not choose the past. Choose the future: Modules.

In early drafts of the ES6 modules specification, there was an inline modules notion, which then has been eliminated in September 2013. However, this notion was already implemented by the TypeScript team, in 2012, with the first beta versions of the language: it was internal modules. Then, the final specification for ES6 modules has been released in July 2014 without inline modules. A year later, in July 2015, with the version 1.5 of TypeScript, internal modules has been renamed to namespaces in order to avoid confusion with the standard.

Namespaces are a legacy feature. It won't be a part of the language ECMAScript. And the TypeScript team will continue to follow the standard. There has been no improvement regarding TS namespaces since the release of the ECMAScript modules standard in July 2014.

Cons [of ES6 modules]


  • Very tedious if every class is a different file, you will have to type the same import statements over and over.
  • Renaming files will break your code (bad).
  • Refactoring class names won't propagate through to your imports (very bad - might depend on your IDE though, I'm using vs-code)

We can hope some improvements on these issues with future IDEs. The first one is already resolved by WebStorm.



Related Topics



Leave a reply



Submit