How do I use namespaces with TypeScript external modules?
Asked Answered
W

10

293

I have some code:

baseTypes.ts

export namespace Living.Things {
  export class Animal {
    move() { /* ... */ }
  }
  export class Plant {
    photosynthesize() { /* ... */ }
  }
}

dog.ts

import b = require('./baseTypes');

export namespace Living.Things {
  // Error, can't find name 'Animal', ??
  export class Dog extends Animal {
    woof() { }
  }
}

tree.ts

// Error, can't use the same name twice, ??
import b = require('./baseTypes');
import b = require('./dogs');

namespace Living.Things {
  // Why do I have to write b.Living.Things.Plant instead of b.Plant??
  class Tree extends b.Living.Things.Plant {

  }
}

This is all very confusing. I want to have a bunch of external modules all contribute types to the same namespace, Living.Things. It seems that this doesn't work at all -- I can't see Animal in dogs.ts. I have to write the full namespace name b.Living.Things.Plant in tree.ts. It doesn't work to combine multiple objects in the same namespace across file. How do I do this?

Wycoff answered 20/5, 2015 at 18:27 Comment(0)
W
1162

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: enter image description here

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:

enter image description here

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:

enter image description here

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!)
Wycoff answered 20/5, 2015 at 18:27 Comment(22)
This is a non-answer. The premise that you shouldn't need or want namespaces for external modules is a faulty one. While the file system is a sort of organization scheme you can kinda use for these purposes, it isn't nearly as nice for the consumer to have n import statements for using n classes or functions from a given project; especially since it also muddies the naming convention when you are down in actual code.Henebry
No matter how much one might want it, it's still not possible.Wycoff
A single ns module could gather all the different pastas together into a Pasta box. And a Pantry module could gather all the canned goods and dried goods. Then we can have.our.namespaces to facilitate discoverability at the cost of loading more then was needed.Lactate
@RyanCavanaugh What if you want to write your ts such that there's one class per file? In this case, I want to import each class file into a module file for convenience.Td
I don't understand, we are not writing pascal anymore. Since when is organizing using the file system the way to go?Stereotyped
So, using TypeScript modules, there is no way to wrap all the code I've written into one namespace, like my name, or my company name, without having one giant module that contains it all. Is that correct?Lightness
You can by having a "wrapper" module that imports and re-exports everything of interest to consumers of your library. But again, using a "namespace" there is not going to provide any value other than forcing another level of indirection for anyone using your code.Wycoff
Great write-up, thank you. I feel like you should link to this from www.typescriptlang.org/docs/handbook/namespaces.html . I must have read that typescriptlang.org link 3 or 4 times and as a C# dev, I naturally want to put everything in a namespace. I've read some suggestions saying not to, but with no explanation why and nothing as definitive (and well described) as this. Plus nothing in the typescript docs mentions this AFAIKGallop
With your snacks, glasses ans paper sheets, Typescript docs would have been more concise, therefore easier to read and understand. Also more funny I guess.Secretin
In version 2, the global namespaces, how do the import statements look, both for the global.ts files and the files that are using them? I am having trouble getting it to work.Philipphilipa
Silly me - they all have to be global, ie, no import or export statements at the top level. Anything referenced from there also needs to be global.Philipphilipa
IMHO, even anologies are useful to describe things,sometimes analogies do not reflect the real practices. Since it is an analogy, you can find other similarities and defend the opposite and create an antipattern to this approach.Just as an example, I developed in java and C#before. Both has pros/cons and one of what I really liked in java was file organization(restrictions+). Lang. restrict you to write the code exactly where it should be. No partial classes, each class in its own file and its own package. Being able to and being forced to be organized is a selection.We must all be so o...Stanhope
sometimes it feels like we need to have external namespaces sharing if we want to split implementation in different packages. For example in c# "System.Collection" do not need to be in one single package some collections can be implemented in multiple packages like if class is not sealed. Having Company Level namespace reduced the namespace conflicts when comes to windows global name space. and also give more flexibility for having shareable dynamic components implemented in multiple packagesLoredo
In real life lib, you are using bundler for JS and bundler for your d.ts after that you lose information about lib structure. It's why you need namespaces and why it's a bad answer.Rebeckarebeka
I also agree that it is not always good to have everything at the root of the module. For instance, I have a "Path" utility module and for documentation sake I want to say Path.getName(...), etc. Path is exported as a default namespace, so anyone can rename as they like, but it also provides a way to import all functions under a common name to be shared by many people, and also provides code completion in one place (as opposed to importing one function at a time and cluttering the import statement with dozens of imports).Haroun
Note that Babel 7+ transpiles Typescript into JavaScript (without performing type checking) but does not support namepaces. This can influence you descision to use modules instead of namespaces.Moyra
Don't use export default. You just end up with different names for file/import/export statements. Instead, name the module with the same name as an exporting variable and import it with curely braces in another file. There're event lints for itLisk
@Henebry if a bunch of import statements is a problem your IDE is not good enough (probably at the time you wrote that your IDE wasn't good at writing import statements for JS/TS)Wil
@Stereotyped in languages like Java where fully-qualified class names have to be unique, if you need to use package A and package B but they each need different versions of package C, you're stuck in dependency hell because the two versions of C have namespace conflicts with each other. Not a problem with filesystem-based organization. If any non-filesystem-based module systems avoid dependency hell it's news to me. Node's module system is super practical and the popularity of TS namespaces will probably wane.Wil
I'd just like to say thank you for changing the subject matter from plants and animals to candy bars before stuffing them into small plastic cups on your desk :)Dungdungan
If the declarations were to be prefixed with declare, i.e. export declare namespace A would that change any of this behavior?Logicize
Modules have a terrible DX compared to namespaces IMO. Reasoning stated hereAshes
T
66

Nothing wrong with Ryan's answer, but for people who came here looking for how to maintain a one-class-per-file structure while still using ES6 namespaces correctly please refer to this helpful resource from Microsoft.

One thing that's unclear to me after reading the doc is: how to import the entire (merged) module with a single import.

Edit Circling back to update this answer. A few approaches to namespacing emerge in TS.

All module classes in one file.

export namespace Shapes {
    export class Triangle {}
    export class Square {}      
}

Import files into namespace, and reassign

import { Triangle as _Triangle } from './triangle';
import { Square as _Square } from './square';

export namespace Shapes {
  export const Triangle = _Triangle;
  export const Square = _Square;
}

Barrels

// ./shapes/index.ts
export { Triangle } from './triangle';
export { Square } from './square';

// in importing file:
import * as Shapes from './shapes/index.ts';
// by node module convention, you can ignore '/index.ts':
import * as Shapes from './shapes';
let myTriangle = new Shapes.Triangle();

A final consideration. You could namespace each file

// triangle.ts
export namespace Shapes {
    export class Triangle {}
}

// square.ts
export namespace Shapes {
    export class Square {}
}

But as one imports two classes from the same namespace, TS will complain there's a duplicate identifier. The only solution as this time is to then alias the namespace.

import { Shapes } from './square';
import { Shapes as _Shapes } from './triangle';

// ugh
let myTriangle = new _Shapes.Shapes.Triangle();

This aliasing is absolutely abhorrent, so don't do it. You're better off with an approach above. Personally, I prefer the 'barrel'.

Td answered 7/2, 2016 at 17:57 Comment(4)
What are "ES6 namespaces"?Furbish
@AluanHaddad when importing es2015+, the things imported are either default, destructured, or namespaced. const fs = require('fs'), fs is the namespace. import * as moment from 'moment', moment is the namespace. This is ontology, not the specification.Td
I'm aware of that but you would do well to explain it in your answer. ES6 namespaces are actually a thing, however, and the require example does not apply to them for a number of reasons, including that ES6 namespaces may not be called, while require returns a plain object which may well be callable.Furbish
I don't follow, because whether the imported thing is callable or not it still serves as a namespace logically speaking. I don't think the caveats are material to my answer above.Td
H
7

Try to organize by folder:

baseTypes.ts

export class Animal {
    move() { /* ... */ }
}

export class Plant {
    photosynthesize() { /* ... */ }
}

dog.ts

import b = require('./baseTypes');

export class Dog extends b.Animal {
    woof() { }
}   

tree.ts

import b = require('./baseTypes');

class Tree extends b.Plant {
}

LivingThings.ts

import dog = require('./dog')
import tree = require('./tree')

export = {
    dog: dog,
    tree: tree
}

main.ts

import LivingThings = require('./LivingThings');
console.log(LivingThings.Tree)
console.log(LivingThings.Dog)

The idea is that your module themselves shouldn't care / know they are participating in a namespace, but this exposes your API to the consumer in a compact, sensible way which is agnostic to which type of module system you are using for the project.

Henebry answered 12/8, 2015 at 23:30 Comment(4)
LivingThings.dog.Dog is what you have here.Lactate
I recommend keeping the letter case consistent, if you export "Tree", then import "Tree", not "tree".Hellfire
Also, how can you import anything from tree.ts when it has no exported member at all?Hellfire
Man TS sure has some silly old syntax, like import and require together in one statement.Wil
M
5

Try this namespaces module

namespaceModuleFile.ts

export namespace Bookname{
export class Snows{
    name:any;
    constructor(bookname){
        console.log(bookname);
    }
}
export class Adventure{
    name:any;
    constructor(bookname){
        console.log(bookname);
    }
}
}





export namespace TreeList{
export class MangoTree{
    name:any;
    constructor(treeName){
        console.log(treeName);
    }
}
export class GuvavaTree{
    name:any;
    constructor(treeName){
        console.log(treeName);
    }
}
}

bookTreeCombine.ts

---compilation part---

import {Bookname , TreeList} from './namespaceModule';
import b = require('./namespaceModule');
let BooknameLists = new Bookname.Adventure('Pirate treasure');
BooknameLists = new Bookname.Snows('ways to write a book'); 
const TreeLis = new TreeList.MangoTree('trees present in nature');
const TreeLists = new TreeList.GuvavaTree('trees are the celebraties');
Medeah answered 23/10, 2018 at 15:50 Comment(0)
S
4

Several of the questions/comments I've seen around this subject sound to me as if the person is using Namespace where they mean 'module alias'. As Ryan Cavanaugh mentioned in one of his comments you can have a 'Wrapper' module re-export several modules.

If you really want to import it all from the same module name/alias, combine a wrapper module with a paths mapping in your tsconfig.json.

Example:

./path/to/CompanyName.Products/Foo.ts

export class Foo {
    ...
}


./path/to/CompanyName.Products/Bar.ts

export class Bar {
    ...
}


./path/to/CompanyName.Products/index.ts

export { Foo } from './Foo';
export { Bar } from './Bar';



tsconfig.json

{
    "compilerOptions": {
        ...
        paths: {
            ...
            "CompanyName.Products": ["./path/to/CompanyName.Products/index"],
            ...
        }
        ...
    }
    ...
}



main.ts

import { Foo, Bar } from 'CompanyName.Products'

Note: The module resolution in the output .js files will need to be handled somehow, such as with this https://github.com/tleunen/babel-plugin-module-resolver

Example .babelrc to handle the alias resolution:

{
    "plugins": [
        [ "module-resolver", {
            "cwd": "babelrc",
            "alias": {
                "CompanyName.Products": "./path/to/typescript/build/output/CompanyName.Products/index.js"
            }
        }],
        ... other plugins ...
    ]
}
Selfconceit answered 1/11, 2017 at 2:7 Comment(0)
B
3

Small impovement of Albinofrenchy answer:

base.ts

export class Animal {
move() { /* ... */ }
}

export class Plant {
  photosynthesize() { /* ... */ }
}

dog.ts

import * as b from './base';

export class Dog extends b.Animal {
   woof() { }
} 

things.ts

import { Dog } from './dog'

namespace things {
  export const dog = Dog;
}

export = things;

main.ts

import * as things from './things';

console.log(things.dog);
Burier answered 27/7, 2016 at 8:28 Comment(1)
Thanks for this! Just wanted to say that changes to an existing answer should preferably not be posted as new answers: they should either be added as a comment to the existing answer, or (better) should be suggested by suggesting an edit to the answer that you wish to improve.Vein
I
3

OP I'm with you man. again too, there is nothing wrong with that answer with 300+ up votes, but my opinion is:

  1. what is wrong with putting classes into their cozy warm own files individually? I mean this will make things looks much better right? (or someone just like a 1000 line file for all the models)

  2. so then, if the first one will be achieved, we have to import import import... import just in each of the model files like man, srsly, a model file, a .d.ts file, why there are so many *s in there? it should just be simple, tidy, and that's it. Why I need imports there? why? C# got namespaces for a reason.

  3. And by then, you are literally using "filenames.ts" as identifiers. As identifiers... Come on its 2017 now and we still do that? Ima go back to Mars and sleep for another 1000 years.

So sadly, my answer is: nop, you cannot make the "namespace" thing functional if you do not using all those imports or using those filenames as identifiers (which I think is really silly). Another option is: put all of those dependencies into a box called filenameasidentifier.ts and use

export namespace(or module) boxInBox {} .

wrap them so they wont try to access other classes with same name when they are just simply trying to get a reference from the class sit right on top of them.

Infusive answered 25/5, 2017 at 6:21 Comment(0)
W
1

You can use * as wrapper_var syntax to make all imported methods accessible under wrapper_var:

import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();
Wandy answered 28/7, 2021 at 13:13 Comment(0)
R
0

dog.ts

import b = require('./baseTypes');

export module Living.Things {
    // Error, can't find name 'Animal', ??
    // Solved: can find, if properly referenced; exporting modules is useless, anyhow
    export class Dog extends b.Living.Things.Animal {
        public woof(): void {
            return;
        }
    }
}

tree.ts

// Error, can't use the same name twice, ??
// Solved: cannot declare let or const variable twice in same scope either: just use a different name
import b = require('./baseTypes');
import d = require('./dog');

module Living.Things {
    // Why do I have to write b.Living.Things.Plant instead of b.Plant??
    class Tree extends b.Living.Things.Plant {
    }
}
Rumal answered 16/3, 2017 at 5:20 Comment(0)
H
-7

The proper way to organize your code is to use separate directories in place of namespaces. Each class will be in it's own file, in it's respective namespace folder. index.ts will only re-export each file; no actual code should be in the index.ts file. Organizing your code like this makes it far easier to navigate, and is self-documenting based on directory structure.

// index.ts
import * as greeter from './greeter';
import * as somethingElse from './somethingElse';

export {greeter, somethingElse};

// greeter/index.ts
export * from './greetings.js';
...

// greeter/greetings.ts
export const helloWorld = "Hello World";

You would then use it as such:

import { greeter } from 'your-package'; //Import it like normal, be it from an NPM module or from a directory.
// You can also use the following syntax, if you prefer:
import * as package from 'your-package';

console.log(greeter.helloWorld);
Hundredth answered 8/1, 2019 at 13:42 Comment(2)
This is misleading and absolutely incorrect. That is not how namespaces work. Also it does not answer the ops question.Instantly
I agree with Andrew. To understand how TypeScript uses modules and namespaces, its best you refer to the documentation. Try checking your TypeScript version as this could influence the usage of namespaces and modules. I managed to get mine working by following the documentation along with this stack post, this and lastly this one. Hope it helps =). Happy CodingTorsk

© 2022 - 2024 — McMap. All rights reserved.