As someone relatively new to TypeScript, I am currently working on a small prototyping framework for WebGl. During my project refactoring, I encountered challenges in organizing my code, debating between using modules or namespaces as both have their drawbacks.
This post does not focus on how to use these patterns, but rather on overcoming the issues each presents.
Current Approach: Utilizing Namespaces
Coming from a background in C#, utilizing namespaces felt like the natural route to take. Each class/module is allocated its appropriate namespace, and by providing the "outFile" parameter in the tsconfig.json file, everything gets combined into one large file. After compilation, the root namespace becomes a global object. However, project dependencies are not integrated, requiring manual inclusion of the necessary *.js files in the HTML (not ideal).
Example File:
namespace Cross.Eye {
export class SpriteFont {
//code omitted
}
}
Example Usage (Ensure that the Cross namespace is loaded into the global namespace by including the respective JS file in the HTML):
namespace Examples {
export class _01_BasicQuad {
context: Cross.Eye.Context;
shader: Cross.Eye.ShaderProgram;
//code omitted
}
}
Pros
- Straightforward for those transitioning from C#/Java
- Not dependent on filenames—changing file names won’t break the code
- Easily refactorable: IDEs can efficiently rename namespaces/classes consistently throughout the codebase
- Convenient: Adding a class to the project simply involves creating a file and declaring it in the desired namespace
Cons
For most projects, we recommend utilizing external modules and using namespaces only for quick demos or migrating old JavaScript code.
Source: https://basarat.gitbooks.io/typescript/content/docs/project/namespaces.html
- A root namespace is always (?) a global object (not ideal)
- Incompatible with tools like browserify or webpack, critical for bundling libraries with dependencies or when using custom code with the library
- Considered bad practice if planning to release an npm module
Modern Practice (?): Modules
TypeScript supports ES6 Modules, which are regarded as innovative and preferred by many developers. The concept entails each file serving as a module, allowing clear definition of dependencies through import statements for efficient code packing by bundling tools. In my case, having one class per file doesn't seem to align well with the module pattern.
Post-refactor, my file structure is as follows:
Additionally, each folder contains an index.ts file enabling the importing of all classes within the folder via
import * as FolderModule from "./folder"
export * from "./AggregateLoader";
export * from "./ImageLoader";
export * from "./TiledLoader";
export * from "./XhrLoaders";
export * from "./XmlSpriteFontLoader";
Example File - The challenge becomes apparent 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 - Noteworthy is the direct import of classes without navigating through namespaces.
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 code more modular since it's no longer tied to namespaces
- Compatible with bundling tools like browserify or webpack, which can bundle all dependencies together
- Enhanced flexibility in importing classes without traversing namespace chains
Cons
- Becomes laborious if each class resides in a separate file, necessitating repetitive import statements
- Changing file names disrupts the code flow
- Renaming classes does not automatically update imports (critical flaw depending on IDE; tested on VS Code)
In my opinion, both approaches exhibit flaws. Namespaces appear outdated and impractical for larger projects, while using modules poses inconvenience and detracts from some of TypeScript's core benefits. Ideally, I would construct my framework using the namespace pattern and export it as a module for comprehensive usage. Unfortunately, this integration seems improbable without resorting to unsightly workarounds.
Here's my question: How have you addressed these dilemmas? What strategies can be employed to mitigate the downsides imposed by each approach?
Update
Following further experience in TypeScript and JavaScript development, modules emerge as the predominant choice for about 90% of scenarios.
The remaining 10% predominantly involve legacy projects reliant on global namespaces, where TypeScript integration serves as a beneficial enhancement.
Many criticisms of modules have been alleviated—with enhanced IDE support being instrumental. Visual Studio Code now features automatic module resolution, streamlining the development process significantly.