TypeScript Module System
TypeScript Module Basics
TypeScript Module Types:
- ES Modules (ESM):
- Uses
import/exportsyntax. - Standard module system for modern frontend development.
- Supports static analysis and Tree Shaking.
- Uses
- CommonJS (CJS):
- Uses
require/module.exportssyntax. - Traditional module system for Node.js.
- Supports dynamic loading.
- Uses
- AMD (Asynchronous Module Definition):
- Uses
define/requiresyntax. - Primarily for asynchronous loading in browsers.
- Implemented via RequireJS.
- Uses
Module Declaration Examples:
// ES Module example
export interface User {
id: number;
name: string;
}
export function getUser(id: number): Promise<User> {
// Implementation
}
// CommonJS example
interface Product {
id: number;
name: string;
price: number;
}
function getProduct(id: number): Product {
// Implementation
}
module.exports = {
getProduct
};Module File Extensions:
.ts: TypeScript source files..tsx: TypeScript JSX files..d.ts: TypeScript declaration files.
Module Import and Export Syntax
ES Module Import/Export:
// Named exports
export const PI = 3.14159;
export function circleArea(radius: number): number {
return PI * radius * radius;
}
// Default export
export default class Circle {
constructor(public radius: number) {}
area(): number {
return PI * this.radius * this.radius;
}
}
// Re-export
export { PI } from './constants';
export { default as Circle } from './circle';CommonJS Import/Export:
// Export
const utils = {
add: (a: number, b: number) => a + b,
subtract: (a: number, b: number) => a - b
};
module.exports = utils;
// Or
exports.add = (a: number, b: number) => a + b;
exports.subtract = (a: number, b: number) => a - b;
// Import
import utils = require('./utils');
// Or (preferred in TypeScript with ESM syntax)
import * as utils from './utils';Dynamic Import:
// ES Module dynamic import
const module = await import('./dynamic-module');
// Dynamic import with type assertion
interface DynamicModule {
default: { init: () => void };
helper: () => string;
}
const { default: main, helper } = await import('./dynamic-module') as DynamicModule;
main.init();
console.log(helper());Module Resolution Strategies
TypeScript Module Resolution Rules:
- Classic Resolution:
- Similar to Node.js
requireresolution. - Supports
node_moduleslookup.
- Similar to Node.js
- Node Resolution:
- More precise simulation of Node.js module resolution algorithm.
- Supports
package.jsonmainfield.
Configuring Module Resolution Strategy:
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node", // or "classic"
"baseUrl": "./", // Base path for resolution
"paths": { // Path mappings
"@/*": ["src/*"]
},
"rootDirs": ["src", "types"] // Multiple root directories
}
}Path Mapping Example:
// Import using path mapping
import { Button } from '@/components/Button';
// Resolves to src/components/Button
// tsconfig.json configuration
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"components/*": ["src/components/*"]
}
}
}TypeScript and Modular Toolchains
TypeScript Integration with Webpack
Webpack TypeScript Configuration:
// webpack.config.js
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = {
entry: './src/index.ts',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
resolve: {
extensions: ['.ts', '.tsx', '.js']
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
plugins: [
new ForkTsCheckerWebpackPlugin() // Type checking in a separate process
]
};Advanced Webpack + TypeScript Configuration:
// webpack.config.js
module.exports = {
// ...other configurations
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true, // Transpile only, skip type checking
happyPackMode: true, // Compatible with HappyPack
experimentalWatchApi: true // Experimental watch API
}
}
],
exclude: /node_modules/
}
]
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
},
extensions: ['.ts', '.tsx', '.js', '.json']
}
};TypeScript Integration with Rollup
Rollup TypeScript Configuration:
// rollup.config.js
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
export default {
input: 'src/index.ts',
output: {
file: 'dist/bundle.js',
format: 'esm'
},
plugins: [
resolve(), // Resolve modules from node_modules
commonjs(), // Convert CommonJS modules to ES6
typescript({
tsconfig: './tsconfig.json',
declaration: true, // Generate declaration files
declarationDir: 'dist/types' // Declaration file output directory
})
]
};Advanced Rollup + TypeScript Configuration:
// rollup.config.js
import typescript from '@rollup/plugin-typescript';
import { terser } from 'rollup-plugin-terser';
import dts from 'rollup-plugin-dts';
// Main configuration (for generating JS)
const config = {
input: 'src/index.ts',
output: [
{
file: 'dist/bundle.cjs.js',
format: 'cjs'
},
{
file: 'dist/bundle.esm.js',
format: 'esm'
}
],
plugins: [
typescript({
tsconfig: './tsconfig.json',
exclude: ['**/*.test.ts']
})
]
};
// Declaration file configuration (separate generation)
const dtsConfig = {
input: 'src/index.ts',
output: [{ file: 'dist/types/index.d.ts', format: 'esm' }],
plugins: [dts()]
};
export default [config, dtsConfig];TypeScript Integration with Vite
Vite TypeScript Configuration:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': '/src'
}
},
server: {
port: 3000
},
// TypeScript configuration is typically managed via tsconfig.json
// Vite has built-in TypeScript support, no additional plugins required
});Advanced Vite + TypeScript Features:
- Environment Variable Type Declarations:
// src/env.d.ts
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_APP_TITLE: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}- Path Alias Type Support:
// src/path-aliases.d.ts
/// <reference types="vite/client" />
declare module '@/components/*';
declare module '@/utils/*';TypeScript Modular Best Practices
Module Design and Organization
Module Design Principles:
- Single Responsibility: Each module should do one thing.
- Clear Interfaces: Clearly define inputs and outputs.
- Minimal Exposure: Expose only what is necessary.
- Explicit Dependencies: Explicitly declare all dependencies.
Module Organization Structure Example:
src/
├── components/ # UI components
│ ├── Button/
│ │ ├── index.ts # Main export
│ │ ├── Button.tsx
│ │ ├── Button.css
│ │ └── types.ts # Component-specific types
│ └── ...
├── hooks/ # Custom Hooks
│ ├── useFetch.ts
│ └── ...
├── services/ # API services
│ ├── api.ts
│ └── user-service.ts
├── utils/ # Utility functions
│ ├── helpers.ts
│ └── ...
├── types/ # Global type definitions
│ ├── user.ts
│ └── ...
└── index.ts # Application entryModule Export Patterns:
// Recommended: Clear single export file
// components/Button/index.ts
export { default } from './Button';
export type { ButtonProps } from './types';
// Not recommended: Direct export of multiple contents (may cause naming conflicts)
// components/Button/index.ts
export { default as Button } from './Button';
export { ButtonProps } from './types';Type-Safe Modules
Module Type Definitions:
// Define module interface
interface LoggerModule {
log: (message: string) => void;
error: (message: string) => void;
setLevel: (level: 'debug' | 'info' | 'warn' | 'error') => void;
}
// Implement module
const logger: LoggerModule = {
log: (message) => console.log(message),
error: (message) => console.error(message),
setLevel: (level) => { /* ... */ }
};
// Export module
export default logger;
// Use module (type-safe)
import logger from './logger';
logger.log('Hello'); // Correct
logger.log(123); // Type errorGeneric Module Example:
// Define generic storage module
interface StorageModule<T> {
get(key: string): T | undefined;
set(key: string, value: T): void;
remove(key: string): void;
}
// Implement specific storage module
class LocalStorageModule<T> implements StorageModule<T> {
get(key: string): T | undefined {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : undefined;
}
set(key: string, value: T): void {
localStorage.setItem(key, JSON.stringify(value));
}
remove(key: string): void {
localStorage.removeItem(key);
}
}
// Use generic module
const userStorage = new LocalStorageModule<User>();
userStorage.set('current-user', { id: 1, name: 'Alice' });
const user = userStorage.get('current-user'); // Type is User | undefinedModule Version Control and Compatibility
Semantic Versioning (SemVer) with TypeScript:
- Major Version:
- Breaking API changes.
- May require significant updates to TypeScript type definitions.
- Minor Version:
- Backward-compatible feature additions.
- May add new types or optional properties.
- Patch Version:
- Backward-compatible bug fixes.
- May fix errors in type definitions.
Version Compatibility Strategies:
// package.json
{
"name": "my-library",
"version": "1.2.3",
"types": "dist/types/index.d.ts", // Type declaration file location
"type": "module", // or "commonjs"
"exports": {
".": {
"import": "./dist/esm/index.js", // ES module entry
"require": "./dist/cjs/index.js" // CommonJS entry
},
"./types": {
"import": "./dist/types/index.d.ts",
"require": "./dist/types/index.d.ts"
}
},
"peerDependencies": {
"react": ">=16.8.0 <18.0.0" // React version compatibility requirements
}
}Type Compatibility Checks:
// Check module type compatibility
import { SomeType } from 'some-library';
// Use type assertion to check compatibility
function isCompatible(value: unknown): value is SomeType {
return (
typeof value === 'object' &&
value !== null &&
'requiredProperty' in value &&
typeof (value as SomeType).requiredProperty === 'string'
);
}
// Runtime type checking libraries (e.g., io-ts, zod) can be used with TypeScript
import * as t from 'io-ts';
import { either } from 'fp-ts/Either';
const User = t.type({
id: t.number,
name: t.string
});
type User = t.TypeOf<typeof User>; // Generate corresponding TypeScript type
function validateUser(input: unknown): either<t.Errors, User> {
return User.decode(input);
}Advanced TypeScript Modular Topics
Dynamic Module Loading with Type Safety
Type-Safe Dynamic Imports:
// Define dynamic module types
interface DynamicModule {
default: {
init: () => void;
version: string;
};
helper: (input: string) => number;
}
// Dynamic import with type assertion
async function loadDynamicModule(): Promise<DynamicModule> {
const module = await import('./dynamic-module');
// Optional runtime check
if (!module.default || typeof module.default.init !== 'function') {
throw new Error('Invalid module structure');
}
// Type assertion
return module as DynamicModule;
}
// Use dynamic module
const { default: main, helper } = await loadDynamicModule();
main.init();
console.log(helper('test'));Dynamic Imports with React Components:
// Define type for dynamically loaded components
type LazyComponent = React.LazyExoticComponent<React.ComponentType<any>>;
// Dynamic component loading function
function lazyLoadComponent<T extends React.ComponentType<any>>(
importFn: () => Promise<{ default: T }>
): LazyComponent {
return React.lazy(importFn);
}
// Usage example
const LazyButton = lazyLoadComponent(() =>
import('./components/Button') as Promise<{ default: React.ComponentType<{ text: string }> }>
);
// Use in React component
function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<LazyButton text="Click me" />
</React.Suspense>
);
}Module Federation with TypeScript
TypeScript with Webpack Module Federation:
// Define remote module types (in consumer)
// src/remote-types.d.ts
declare module 'remote-app/Button' {
import { ComponentType } from 'react';
const Button: ComponentType<{ text: string }>;
export default Button;
}
// Use remote module
import RemoteButton from 'remote-app/Button';
function App() {
return <RemoteButton text="Remote Button" />;
}Shared Type Configuration:
// Sharing types in Module Federation
// Option 1: Use a separate type package
// Create a types package containing all shared types
// Other applications depend on this types package
// Option 2: Use declaration merging
// Declare remote module types in consumer (as above)
// Option 3: Use API extraction tools
// Extract type definitions from remote modules using toolsType Sharing in Micro-Frontends:
// Main application defines shared types
// shared-types.d.ts
export interface User {
id: number;
name: string;
email: string;
}
export interface AuthToken {
token: string;
expiresAt: Date;
}
// Reference shared types in micro-apps
// Ensure type definitions are synchronized
// Can be shared via npm package or declaration filesModular Testing Strategies
Module Unit Testing:
// Test utility module
// math-utils.test.ts
import { add, multiply } from './math-utils';
describe('math-utils', () => {
it('should add two numbers correctly', () => {
expect(add(2, 3)).toBe(5);
});
it('should multiply two numbers correctly', () => {
expect(multiply(2, 3)).toBe(6);
});
});
// Test React component
// Button.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import Button from './Button';
describe('Button', () => {
it('should render with given text', () => {
render(<Button text="Click me" />);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('should call onClick when clicked', () => {
const onClick = jest.fn();
render(<Button text="Click me" onClick={onClick} />);
screen.getByText('Click me').click();
expect(onClick).toHaveBeenCalledTimes(1);
});
});Module Integration Testing:
// Test module integration
// user-service.test.ts
import UserService from './user-service';
import api from './api';
jest.mock('./api');
describe('UserService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should fetch user data correctly', async () => {
const mockUser = { id: 1, name: 'Alice' };
(api.get as jest.Mock).mockResolvedValue({ data: mockUser });
const userService = new UserService();
const user = await userService.getUser(1);
expect(api.get).toHaveBeenCalledWith('/users/1');
expect(user).toEqual(mockUser);
});
});
// Test React component and Hook integration
// useFetch.test.ts
import { renderHook, act } from '@testing-library/react-hooks';
import useFetch from './useFetch';
describe('useFetch', () => {
it('should fetch data on mount', async () => {
const mockData = { message: 'Hello' };
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve(mockData),
})
) as jest.Mock;
const { result, waitForNextUpdate } = renderHook(() => useFetch('/test'));
expect(result.current.loading).toBe(true);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
expect(result.current.data).toEqual(mockData);
});
});Summary
TypeScript brings robust type safety to modular development, enabling developers to leverage the organizational benefits of modularity while benefiting from compile-time type checking. By properly configuring TypeScript integration with various modular toolchains (Webpack, Rollup, Vite, etc.), developers can build modern frontend applications that are both efficient and reliable.



