Ambient modules declare types for JavaScript modules that don't have TypeScript declarations. They use declare module syntax. Example: declare module 'my-library' { export function doSomething(): void; }. They're commonly used in .d.ts files to provide type information for JavaScript libraries.
TypeScript supports several module resolution strategies: 1) Classic: Legacy resolution for AMD/System.js, 2) Node: Follows Node.js module resolution, 3) baseUrl: Resolves modules relative to a base directory, 4) paths: Allows custom module path mapping. These are configured in tsconfig.json using the moduleResolution option.
Re-exports allow you to export items from other modules. Syntax includes: export { Something } from './other', export * from './other', and export { Something as OtherThing } from './other'. This helps create clean public APIs and organize code hierarchy.
Synthetic default imports allow importing modules that don't have explicit default exports as if they did. Controlled by esModuleInterop compiler option. Example: import React from 'react' works even if React uses module.exports = React. This improves compatibility with CommonJS modules.
Module side effects are handled using import statements without bindings. Example: import './polyfills'; This executes the module code without importing any values. It's useful for polyfills, global styles, or other code that needs to run during module initialization.
Type-only imports/exports are used for importing/exporting types without value emissions. Syntax: import type { MyType } from './types'; export type { MyType }. This helps reduce bundle size by removing type information during compilation while maintaining type safety.
ES modules are the standard JavaScript module system that TypeScript supports. They use import/export syntax and provide better code organization and dependency management. Unlike namespaces (internal modules), ES modules are file-based, support better tree-shaking, and are the preferred way to organize code. Example: export class MyClass {} and import { MyClass } from './myModule';
TypeScript supports various export/import syntaxes: 1) Named exports: export { MyClass }; 2) Default exports: export default MyClass; 3) Import named: import { MyClass } from './module'; 4) Import default: import MyClass from './module'; 5) Import all: import * as module from './module'. Each type serves different purposes in module organization.
Namespaces (formerly 'internal modules') are TypeScript's own module system for encapsulating code. They use the namespace keyword and are useful for avoiding name collisions in global scope. Example: namespace Mathematics { export function add(x: number, y: number): number { return x + y; } }. However, ES modules are generally preferred for modern applications.
Namespace merging allows combining multiple namespace declarations with the same name. Example: namespace Animals { export class Dog {} } namespace Animals { export class Cat {} }. This creates a single namespace containing both Dog and Cat classes. It's similar to interface merging but for namespaces.
Dynamic imports allow loading modules on demand using import() syntax. Example: async function loadModule() { const module = await import('./myModule'); module.doSomething(); }. This enables code-splitting and lazy loading of modules for better performance.
Best practices include: 1) One class/feature per file, 2) Use barrel exports (index.ts) for related features, 3) Keep modules small and focused, 4) Use meaningful file names matching exported content, 5) Group related functionality in directories, 6) Use clear import paths, 7) Avoid circular dependencies. This improves code maintainability and readability.
Module path aliases are configured in tsconfig.json using the paths option. Example: { 'compilerOptions': { 'baseUrl': '.', 'paths': { '@app/*': ['src/app/*'] } } }. This allows using @app/feature instead of relative paths, making imports cleaner and more maintainable.
Module resolution fallbacks are configured through: 1) moduleResolution in tsconfig.json, 2) baseUrl and paths for custom mappings, 3) TypeScript path mapping patterns like * and **. Example: { 'paths': { '*': ['node_modules/*', 'fallback/*'] } }. This helps handle different module formats and locations.
Module augmentation allows you to add new declarations to existing modules. Example: declare module 'lodash' { interface LoDashStatic { myCustomFunction(arg: string): number; } }. This is useful for adding type definitions to existing JavaScript modules or extending third-party module declarations.
Circular dependencies occur when two modules import each other. They can be resolved by: 1) Restructuring code to avoid circularity, 2) Using interfaces instead of concrete implementations, 3) Moving shared code to a separate module, 4) Using import type for type-only imports. Example: import type { User } from './user' instead of import { User } from './user'.
Internal modules (namespaces) use the namespace keyword and are TypeScript-specific. External modules (ES modules) use import/export syntax and are the standard JavaScript module system. Example: namespace MyNamespace {} vs. export class MyClass {}. ES modules are preferred for modern applications.
Namespaces use export keyword for public members and can be accessed using dot notation. Example: namespace Math { export function add(x: number, y: number): number { return x + y; } } Usage: Math.add(1, 2). For cross-file namespaces, use /// <reference path='./math.ts' /> syntax.
Global modules are ambient modules that add types to the global scope. Example: declare global { interface Window { myCustomProperty: string; } }. They're useful for extending global objects or declaring global variables, but should be used sparingly to avoid polluting global namespace.
Lazy loading patterns include: 1) Dynamic imports: import('./module'), 2) Route-based splitting: loadChildren in Angular routes, 3) Component-based splitting in React.lazy(). Example: const MyComponent = React.lazy(() => import('./MyComponent')). This improves initial load time by loading modules on demand.
Module augmentation can be done through: 1) Declaration merging: declare module 'module' {}, 2) Global augmentation: declare global {}, 3) Namespace augmentation: namespace NS {}. Example: declare module 'express-session' { interface SessionData { userId: string; } }
Barrel exports consolidate multiple exports into a single entry point using an index.ts file. Example: export * from './math'; export * from './string'; export * from './array';. This simplifies imports by providing a single import point and helps manage large codebases by organizing related functionality.
Declaration files (.d.ts) contain type information for JavaScript modules. They define the shape of modules without implementation details. Example: declare module 'my-module' { export interface Config { debug: boolean; } export function initialize(config: Config): void; }. They enable TypeScript to understand external JavaScript libraries.
Monorepo module resolution involves: 1) Using project references in tsconfig.json, 2) Configuring path aliases for packages, 3) Setting up proper build order, 4) Managing shared dependencies. Example: { 'references': [{ 'path': '../common' }], 'paths': { '@org/*': ['packages/*/src'] } }.