- Home »

Как использовать модули в TypeScript
Если ты работаешь с Node.js на серверах, то рано или поздно столкнёшься с необходимостью понимать, как правильно организовать код. TypeScript модули — это не просто синтаксический сахар, а полноценная система для создания масштабируемых приложений. Особенно важно это для серверных разработчиков, которые пишут микросервисы, API и различные автоматизационные скрипты. В этой статье разберём, как использовать модули в TypeScript так, чтобы твой код был чистым, переиспользуемым и легко поддерживаемым.
Как работают модули в TypeScript
TypeScript использует стандарт ES6 модулей с дополнительными возможностями типизации. Модуль — это файл, который экспортирует или импортирует функциональность. В отличие от обычного JavaScript, TypeScript позволяет экспортировать не только значения, но и типы, интерфейсы и декораторы.
Основные способы работы с модулями:
- Named exports — именованные экспорты для конкретных функций или классов
- Default exports — экспорт по умолчанию для основной функциональности модуля
- Namespace imports — импорт всего модуля как объекта
- Type-only imports — импорт только типов без runtime кода
Быстрая настройка проекта с модулями
Сначала создаём базовую структуру проекта. Если у тебя нет VPS для разработки, можешь взять здесь или использовать выделенный сервер для более серьёзных проектов.
mkdir typescript-modules-demo
cd typescript-modules-demo
npm init -y
npm install -D typescript @types/node ts-node
npx tsc --init
Настраиваем tsconfig.json для работы с модулями:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Практические примеры использования
Создадим простой HTTP сервер с модульной архитектурой. Структура проекта:
src/
├── config/
│ └── database.ts
├── models/
│ └── User.ts
├── services/
│ └── UserService.ts
├── utils/
│ └── logger.ts
└── app.ts
Файл конфигурации базы данных (src/config/database.ts):
// Named exports для конфигурации
export const DB_CONFIG = {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432'),
database: process.env.DB_NAME || 'myapp'
};
// Type-only export для интерфейса
export interface DatabaseConnection {
connect(): Promise;
disconnect(): Promise;
}
// Default export для основной функции
export default function createConnection(): DatabaseConnection {
return {
async connect() {
console.log(`Connecting to ${DB_CONFIG.host}:${DB_CONFIG.port}`);
},
async disconnect() {
console.log('Disconnecting from database');
}
};
}
Модель пользователя (src/models/User.ts):
export interface User {
id: number;
email: string;
name: string;
createdAt: Date;
}
export class UserModel {
constructor(private data: Omit) {}
static create(userData: Omit): UserModel {
return new UserModel(userData);
}
public validate(): boolean {
return this.data.email.includes('@') && this.data.name.length > 0;
}
}
Сервис для работы с пользователями (src/services/UserService.ts):
import { User, UserModel } from '../models/User';
import { logger } from '../utils/logger';
import type { DatabaseConnection } from '../config/database';
export class UserService {
private users: User[] = [];
private nextId = 1;
constructor(private db: DatabaseConnection) {}
async createUser(userData: Omit): Promise {
logger.info('Creating new user', { email: userData.email });
const userModel = UserModel.create(userData);
if (!userModel.validate()) {
throw new Error('Invalid user data');
}
const user: User = {
id: this.nextId++,
...userData,
createdAt: new Date()
};
this.users.push(user);
return user;
}
async findUserById(id: number): Promise {
return this.users.find(user => user.id === id) || null;
}
async getAllUsers(): Promise {
return [...this.users];
}
}
Утилита для логирования (src/utils/logger.ts):
interface LogLevel {
INFO: 'info';
ERROR: 'error';
DEBUG: 'debug';
}
class Logger {
private readonly levels: LogLevel = {
INFO: 'info',
ERROR: 'error',
DEBUG: 'debug'
};
info(message: string, data?: any): void {
this.log(this.levels.INFO, message, data);
}
error(message: string, error?: Error): void {
this.log(this.levels.ERROR, message, error);
}
debug(message: string, data?: any): void {
if (process.env.NODE_ENV === 'development') {
this.log(this.levels.DEBUG, message, data);
}
}
private log(level: string, message: string, data?: any): void {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level: level.toUpperCase(),
message,
...(data && { data })
};
console.log(JSON.stringify(logEntry));
}
}
// Экспортируем экземпляр (singleton pattern)
export const logger = new Logger();
Главный файл приложения (src/app.ts):
import createConnection, { DB_CONFIG } from './config/database';
import { UserService } from './services/UserService';
import { logger } from './utils/logger';
async function bootstrap() {
logger.info('Starting application');
const db = createConnection();
await db.connect();
const userService = new UserService(db);
try {
const user = await userService.createUser({
email: 'john@example.com',
name: 'John Doe'
});
logger.info('User created successfully', { userId: user.id });
const allUsers = await userService.getAllUsers();
logger.info('All users retrieved', { count: allUsers.length });
} catch (error) {
logger.error('Application error', error as Error);
} finally {
await db.disconnect();
}
}
bootstrap();
Сравнение подходов к импорту
Подход | Синтаксис | Преимущества | Недостатки |
---|---|---|---|
Named imports | import { func } from './module' |
Явность, tree-shaking | Много кода при большом количестве импортов |
Default imports | import func from './module' |
Краткость, гибкость именования | Менее явно, проблемы с tree-shaking |
Namespace imports | import * as utils from './module' |
Организация в пространство имён | Больше кода, нет tree-shaking |
Type-only imports | import type { Interface } from './module' |
Разделение типов и runtime кода | Дополнительная сложность |
Продвинутые возможности модулей
Re-exports для создания публичного API:
// src/index.ts - главный экспорт модуль
export { UserService } from './services/UserService';
export { UserModel } from './models/User';
export type { User } from './models/User';
export { logger } from './utils/logger';
// Re-export с переименованием
export { default as createDbConnection } from './config/database';
Динамические импорты для ленивой загрузки:
async function loadModule(moduleName: string) {
try {
const module = await import(`./plugins/${moduleName}`);
return module.default;
} catch (error) {
logger.error(`Failed to load module: ${moduleName}`, error);
return null;
}
}
// Использование
const plugin = await loadModule('emailService');
if (plugin) {
await plugin.sendEmail('test@example.com', 'Hello');
}
Автоматизация и скрипты
Создадим полезные npm скрипты для автоматизации:
// package.json
{
"scripts": {
"dev": "ts-node src/app.ts",
"build": "tsc",
"start": "node dist/app.js",
"watch": "tsc -w",
"clean": "rm -rf dist",
"type-check": "tsc --noEmit",
"bundle-analyze": "npx webpack-bundle-analyzer dist/stats.json"
}
}
Автоматический генератор модулей (create-module.js):
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
function createModule(name, type = 'service') {
const templates = {
service: `export class ${name}Service {
constructor() {}
async execute(): Promise {
// Implementation here
}
}`,
model: `export interface ${name} {
id: number;
createdAt: Date;
}
export class ${name}Model {
constructor(private data: Omit<${name}, 'id' | 'createdAt'>) {}
}`,
util: `export class ${name}Util {
static process(data: any): any {
return data;
}
}`
};
const fileName = `${name}${type.charAt(0).toUpperCase() + type.slice(1)}.ts`;
const filePath = path.join(`src/${type}s`, fileName);
fs.writeFileSync(filePath, templates[type]);
console.log(`Created ${filePath}`);
}
// Использование: node create-module.js User model
const [,, name, type] = process.argv;
createModule(name, type || 'service');
Интеграция с другими инструментами
Webpack конфигурация для модулей:
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/app.ts',
target: 'node',
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.ts', '.js'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@models': path.resolve(__dirname, 'src/models'),
'@services': path.resolve(__dirname, 'src/services')
}
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
ESLint правила для модулей:
// .eslintrc.js
module.exports = {
rules: {
'import/prefer-default-export': 'off',
'import/no-default-export': 'error',
'import/order': [
'error',
{
'groups': [
'builtin',
'external',
'internal',
'parent',
'sibling',
'index'
],
'newlines-between': 'always'
}
]
}
};
Мониторинг и отладка модулей
Полезная утилита для анализа зависимостей:
// src/utils/moduleAnalyzer.ts
import * as fs from 'fs';
import * as path from 'path';
export class ModuleAnalyzer {
private dependencies: Map = new Map();
analyzeFile(filePath: string): void {
const content = fs.readFileSync(filePath, 'utf-8');
const imports = this.extractImports(content);
this.dependencies.set(filePath, imports);
}
private extractImports(content: string): string[] {
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
const imports: string[] = [];
let match;
while ((match = importRegex.exec(content)) !== null) {
imports.push(match[1]);
}
return imports;
}
generateReport(): void {
console.log('Module Dependencies Report:');
for (const [file, deps] of this.dependencies) {
console.log(`\n${file}:`);
deps.forEach(dep => console.log(` → ${dep}`));
}
}
}
Интересные факты и нестандартные применения
TypeScript модули можно использовать для создания plugin-системы:
// src/plugins/PluginManager.ts
interface Plugin {
name: string;
version: string;
execute(data: any): Promise;
}
export class PluginManager {
private plugins: Map = new Map();
async loadPlugin(pluginPath: string): Promise {
try {
const pluginModule = await import(pluginPath);
const plugin: Plugin = new pluginModule.default();
this.plugins.set(plugin.name, plugin);
logger.info(`Plugin ${plugin.name} loaded successfully`);
} catch (error) {
logger.error(`Failed to load plugin from ${pluginPath}`, error);
}
}
async executePlugin(name: string, data: any): Promise {
const plugin = this.plugins.get(name);
if (!plugin) {
throw new Error(`Plugin ${name} not found`);
}
return plugin.execute(data);
}
}
Модульная система для микросервисов:
// src/microservices/ServiceRegistry.ts
export class ServiceRegistry {
private services: Map = new Map();
registerService(name: string, service: any): void {
this.services.set(name, service);
}
async callService(serviceName: string, method: string, ...args: any[]): Promise {
const service = this.services.get(serviceName);
if (!service) {
throw new Error(`Service ${serviceName} not found`);
}
if (typeof service[method] !== 'function') {
throw new Error(`Method ${method} not found in service ${serviceName}`);
}
return service[method](...args);
}
}
Полезные ссылки и ресурсы
Заключение и рекомендации
Модули в TypeScript — это мощный инструмент для создания масштабируемых серверных приложений. Используй named exports для публичного API, type-only imports для типов, и не забывай про re-exports для создания удобных точек входа.
Основные рекомендации:
- Предпочитай named exports вместо default exports для лучшей читаемости
- Используй type-only imports для разделения типов и runtime кода
- Создавай index.ts файлы для публичного API модулей
- Настраивай alias в webpack/tsconfig для удобства импортов
- Используй динамические импорты для ленивой загрузки
Модульная архитектура особенно важна при работе с микросервисами и большими проектами на серверах. Правильно организованные модули упрощают тестирование, рефакторинг и масштабирование приложений.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.