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

browserifymodulenamespacestypescriptwebpack

I'm pretty new to typescript and I'm writing a small prototyping framework for WebGl. I'm currently refactoring my project and have run into some problems as how to organize my project, as both (modules and namespaces) approaches seem to have serious drawbacks.

This post is not about HOW to use these patterns, but how to overcome the problems each of these brings.

Status Quo: Using namespaces

Coming from C# this seemed like the most natural way to go. Every class/module gets it's appropriate namespace and I supply the "outFile" parameter in the tsconfig.json so everything gets concatenated into one large file.
After compilation I have my root namespace as a global object. Dependencies are not built into the project, so you manually have to supply the needed *.js files in you html (not good)

Example file

namespace Cross.Eye {
    export class SpriteFont {   
        //code omitted
    }    
}

Example usage (You have to make sure the Cross namespace is loaded into the global namespace before by suppling the js file in the html)

namespace Examples {
    export class _01_BasicQuad {
        context: Cross.Eye.Context;
        shader: Cross.Eye.ShaderProgram;

        //code omitted
    }
}

Pros

  • Straightforward to use if you are comming from C#/Java
  • Independant of filenames – renaming files won't break your code.
  • Easy to refactor: IDEs can easily rename namespaces/classes and the changes will be applied consistently througout your code.
  • Convenience: Adding a class to the project is as simple as adding a file and declaring it in the desired namespace.

Cons

For most projects we recommend using external modules and using namespace for quick demos and porting old JavaScript code.

from https://basarat.gitbooks.io/typescript/content/docs/project/namespaces.html

  • A root namespace is always(?) a global object (bad)
  • Cannot(?) be used with tools like browserify or webpack which is essential to bundle the lib with its dependencies or bundle your custom code with the lib when actually using it.
  • Bad practice if you plan to release a npm module

State of the art (?): Modules

Typescript supports ES6 Modules, they are new and shiny and everybody seems to agree they are the way to go. The idea seems to be that each file is a module and by suppling the files in import statements you can define your dependencies very explicitly which makes it easy for bundling tools to efficently pack your code. I mostly have one class per file which doesn't seem to work to well with dhte module pattern.

This is my file structure after the refactor:

enter image description here

Also I'm having an index.ts file in each folder so I can import all of its classes by import * as FolderModule from "./folder"

export * from "./AggregateLoader";
export * from "./ImageLoader";
export * from "./TiledLoader";
export * from "./XhrLoaders";
export * from "./XmlSpriteFontLoader";

Example file – I think the problem becomes clearly visible here..

import {SpriteFont} from "./SpriteFont";
import {ISpriteTextGlyph, ISpriteChar} from "./Interfaces";
import {Event,EventArgs} from "../../Core";
import {Attribute, AttributeConfiguration} from "../Attributes";
import {DataType} from "../GlEnums";
import {VertexStore} from "../VertexStore";
import {IRectangle} from "../Geometry";
import {vec3} from "gl-matrix";

export class SpriteText {
    // code omitted
}

Example usage. As you can see I no longer have to walk through the namespaces, because I can import the classes directly.

import {
    Context,
    Shader,
    ShaderProgram,
    Attribute,
    AttributeConfiguration,
    VertexStore,
    ShaderType,
    VertexBuffer,
    PrimitiveType
} from "../cross/src/Eye";

import {
    Assets,
    TextLoader
} from "../cross/src/Load";

export class _01_BasicQuad {
    context: Context;
    shader: ShaderProgram;

    // code omitted.
}

Pros

  • Makes your code more modular as its no longer bound to namespaces.
  • You can use bundling tools like browserfy or webpack, which can also bundle all your dependencies
  • You are more flexible when importing classes and no longer have to walk namespace chains.

Cons

  • 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)

IMO both approaches seem to flawed. Namespaces seem to be terribly outdated, impractical for large projects and incompatible with common tools while using modules is quite inconvenient and breaks some of the features I was adapting typescript for in the first place.

In a perfect world I would write my framework using the namespace pattern and export it as a module which then can be imported and bundled with its dependencies. However this doesn't seem to be possible without some ugly hacks.

So here's my question: How have you dealt with these problems? How can I minimize the drawbacks each approach implies?

Update

After gaining a bit more experience with typescript and javascript developement in general, I have to point out that modules are probably the way to go for 90% of all use cases.

The last 10% are hopefully legacy projects which use global namespaces, that you want to spice up with a little typescript (which works great by the way).

Much of my critic for modules can be (and has been) resolved by better IDE support. Visual Studio Code has since added automatic module resolution which works great.

Best Answer

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.