Modules and Namespaces
Modules and Namespaces
Modern TypeScript is built on ES modules. Namespaces are mostly legacy, but you’ll still encounter them in older codebases.
Item 101: Prefer ES Modules
// math.ts
export function add(a: number, b: number) {
return a + b;
}
// main.ts
import { add } from "./math";Why it matters: ES modules are the standard across modern tooling. They enable tree‑shaking, faster bundling, and predictable import behavior.
This is the standard pattern for modern tooling.
Item 102: Avoid namespace in New Code
Namespaces were used before ES modules. They don’t work well with bundlers:
// Avoid this
namespace Utils {
export function sum(a: number, b: number) {
return a + b;
}
}Why it matters: namespaces create global pollution and don’t align with bundlers or code splitting. ES modules solve the same problem in a standard way.
Item 103: Use import type for Type‑Only Imports
import type { User } from "./types";Why it matters: type‑only imports are erased at runtime, reducing bundle size and preventing side effects from unnecessary imports.
This prevents unwanted runtime imports and helps tree-shaking.
Item 104: Use export type for Clean Re‑exports
export type { User } from "./types";
export { fetchUser } from "./api";Why it matters: this keeps your public API tidy without accidentally re‑exporting runtime values you don’t intend to expose.
Item 105: Be Careful With Barrel Files
Barrels (index.ts) simplify imports but can create circular dependencies. Keep them small and stable.
Why it matters: circular dependencies cause confusing runtime bugs and broken type inference. Direct imports are safer in large codebases.
Item 106: Avoid export default in Shared Libraries
Named exports are easier to refactor and search, and reduce confusion in IDEs.
Why it matters: default exports can be renamed arbitrarily at import sites, which makes refactors harder and reduces discoverability.
Item 107: Understand exports in package.json
When publishing libraries, configure your package entry points:
{
"name": "my-lib",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
}Why it matters: correct exports ensures consumers get the right JS + types and avoids “cannot find module” issues in modern bundlers.
This ensures TypeScript and bundlers resolve the right files.
Scenario: Avoiding Circular Imports
// BAD: barrel importing everything
import { User } from "./index";
// GOOD: import directly
import type { User } from "./types";Key Takeaways
- ES modules are the modern standard.
- Avoid namespaces in new code.
- Use
import typeandexport typefor type-only flows. - Be cautious with barrels to avoid cycles.
- Prefer named exports in libraries.
Next: declaration files deep dive.