Explain Codes LogoExplain Codes Logo

How do I use namespaces with TypeScript external modules?

javascript
module-exports
typescript-modules
namespace-management
Nikita BarsukovbyNikita Barsukov·Aug 22, 2024
TLDR

Group your exports in TypeScript using export to handle namespaces within external modules. For example, put utility functions into a module and then import them with a namespace in your consuming code.

mathUtils.ts:

export const PI = 3.14; // let's not redo this calculation every time we need it export function add(x: number, y: number): number { return x + y; // 'cause we strictly follow the KISS principle, Keep It Simple, Silly }

Consumer File:

import * as MathUtils from './mathUtils'; // Good ol' math is back in the game console.log(MathUtils.add(15, 30)); // will print: 45 console.log(MathUtils.PI); // will print: 3.14

Through the import * as NamespaceName technique, you can import and use functions and constants under a single named umbrella, MathUtils. This maintains code organization and readability.

An efficient guide to structuring TypeScript modules

Organizing your modules efficiently can make your exports work like a well-oiled machine. Here are a few key principles:

Single exports? Make 'export default' your best friend

If your module is exporting a sole entity like a class or function, you can use export default to simplify the import process on consuming end.

singleExport.ts:

export default class MyClass { /* I'm alone here, anybody out there? */ }

Consumer File:

import MyClass from './singleExport'; // Now MyClass isn't alone, we got it.

Bring harmony with logical file system structures

Using your file system wisely can create a namespace-like structure and make codebases more navigable. Group associated modules in a similar directory and streamline imports using barrel files (index.ts).

utils/index.ts:

export * from './mathUtils'; export * from './stringUtils'; // All for one and one for all

Consumer File:

import { add, toUpperCase } from './utils'; // Time to bring in the big guns!

Use the alias Kulcha for handling multiple modules

When managing multiple modules, aliasing can save you from naming conflicts and keep your code clear.

arrayUtils.ts:

export function concat(a: any[], b: any[]): any[] { /*...*/ }

Consumer File:

import { concat as arrayConcat } from './arrayUtils'; // The "concat" just got an alias!

Taming the beast for large-scale projects

For large-scale applications, there are a few additional techniques to stop your code from getting out of hand:

Wrapping your exports in a namespace

If your utility set is like a Swiss army knife, consider creating a module that wraps and re-exports members for easy across-the-board use.

numericUtils.ts:

export const EPSILON = 0.0001; // Because floating point math is hard!

utilsWrapper.ts:

export * as Numeric from './numericUtils'; export * as String from './stringUtils'; // Numerics and Strings, all wrapped up!

Giving your modules a nickname using paths

To keep the relative paths at bay, TypeScript’s paths configuration option in tsconfig.json comes to rescue. It helps create module aliases, making imports across your project a walk in the park.

tsconfig.json snippet:

{ "compilerOptions": { // ... "paths": { "@utils/*": ["src/utils/*"] // Because who doesn't like nicknames? } } }

Consumer File:

import { Numeric, String } from '@utils/utilsWrapper'; // Prefix too long. Let's make it short!

Watch out for the red flags

Prevent your modules from getting messy by avoiding redundant exports. Ensure each module only exports unique entities.

Fill up your TypeScript toolkit

Here are a few more techniques to keep up your sleeve when working with TypeScript modules:

Power-up with project references

For projects spanning multiple packages, using project references can accelerate the build process and enhance code organization.

tsconfig.json:

{ "compilerOptions": { // ... }, "references": [ { "path": "./core" }, { "path": "./utils" } // referencing projects like a boss ] }

Embrace isolation

Remember, every external module is a world of its own. The imports create a one-way road – you can bring in the exported members of another module, but a module doesn't necessarily know about its consumers.

Use the right tool for the right job

When TypeScript is transpiled to JavaScript, tools like Babel may come into play. In such scenarios, babel-plugin-module-resolver can manage path aliases in a similar fashion to the TypeScript compiler.

.babelrc.js:

module.exports = { plugins: [ ['module-resolver', { alias: { "@utils": "./src/utils" // Babel is now in on the whole alias game } }] ] };