Основы TypeScript: Модули (Modules)
Модули в TypeScript (и в современном JavaScript, как ECMAScript Modules) — это способ организации кода в отдельные файлы, которые могут импортировать и экспортировать функциональность друг из друга. Модули помогают создавать более структурированные, поддерживаемые и масштабируемые приложения.
TypeScript поддерживает два основных типа модулей:
- ECMAScript Modules (ES Modules): Используют синтаксис
importиexport. Это стандартный формат модулей в современном JavaScript (ES6 и выше) и являются предпочтительным способом организации кода в TypeScript. - CommonJS Modules: Используют синтаксис
require()иmodule.exports. Это традиционный формат модулей, используемый Node.js.
В большинстве современных TypeScript проектов рекомендуется использовать ES Modules.
ECMAScript Modules (ES Modules)
ES Modules основаны на файлах. Каждый файл является отдельным модулем. Объявления (переменные, функции, классы, интерфейсы и т.д.) внутри модуля являются локальными по умолчанию. Чтобы сделать их доступными из других модулей, необходимо использовать ключевое слово export. Для использования функциональности из других модулей используется ключевое слово import.
Экспорт (export)
Вы можете экспортировать объявления двумя способами:
Именованный экспорт (Named Exports): Позволяет экспортировать несколько объявлений из одного модуля, каждое из которых имеет свое имя.
typescript// math.ts export function add(a: number, b: number): number { return a + b; } export const PI = 3.14159; export interface Calculator { add(a: number, b: number): number; }Экспорт по умолчанию (Default Export): Позволяет экспортировать одно значение (переменную, функцию, класс и т.д.) как экспорт по умолчанию модуля. Каждый модуль может иметь только один экспорт по умолчанию.
typescript// message.ts const message = "Hello from module!"; export default message; // Или так: export default function greet(name: string): string { return `Hello, ${name}!`; }
Импорт (import)
Вы также можете импортировать объявления из других модулей несколькими способами:
Именованный импорт (Named Imports): Импортирует одно или несколько конкретных объявлений, экспортированных с именами. Имена при импорте должны совпадать с именами при экспорте.
typescript// main.ts import { add, PI, Calculator } from './math'; import type { Point } from './geometry'; // Импорт только типа console.log(add(5, 3)); // Выведет 8 console.log(PI); // Выведет 3.14159 const myCalc: Calculator = { add: (a, b) => a + b }; let origin: Point = { x: 0, y: 0 };Импорт всего модуля (
import * as ...): Импортирует все экспортированные члены модуля в один объект с указанным именем.typescript// main.ts import * as MathUtils from './math'; console.log(MathUtils.add(10, 2)); // Выведет 12 console.log(MathUtils.PI); // Выведет 3.14159Импорт по умолчанию (Default Import): Импортирует значение, экспортированное как экспорт по умолчанию. При импорте по умолчанию можно использовать любое имя для импортируемой переменной.
typescript// main.ts import message from './message'; import greet from './message'; // Если message.ts экспортирует функцию по умолчанию console.log(message); // Выведет "Hello from module!" console.log(greet("Alice")); // Выведет "Hello, Alice!" (если message.ts экспортирует функцию greet по умолчанию)Комбинированный импорт: Можно комбинировать именованные и дефолтные импорты в одном операторе.
typescript// utils.ts export const UTILITY_CONSTANT = 123; const helperFunction = () => { /* ... */ }; export default helperFunction; // main.ts import helper, { UTILITY_CONSTANT } from './utils'; console.log(helper()); console.log(UTILITY_CONSTANT);Импорт для побочных эффектов: Иногда модуль может не экспортировать никаких значений, но выполнять какой-либо код при загрузке (например, устанавливать глобальные обработчики). В этом случае можно использовать импорт без указания импортируемых членов.
typescript// analytics.ts console.log("Analytics module loaded."); // ... код для инициализации аналитики // main.ts import './analytics'; // Импортируем analytics.ts для выполнения его кода
Разрешение модулей (Module Resolution)
TypeScript компилятор должен знать, как найти импортируемые модули во время компиляции. Процесс, с помощью которого компилятор определяет местоположение модуля на основе строки в операторе import, называется разрешением модулей.
Существует две основные стратегии разрешения модулей:
- Classic: Историческая стратегия, используемая по умолчанию до Node.js.
- Node.js: Имитирует механизм разрешения модулей Node.js. Рекомендуется для большинства проектов, особенно если вы планируете запускать код в Node.js или использовать инструменты, основанные на экосистеме npm.
Стратегия разрешения модулей определяется настройкой moduleResolution в файле tsconfig.json. Для большинства современных проектов рекомендуется использовать "node" или "node16", "nodenext".
При использовании стратегии Node.js, компилятор будет искать модули в следующих местах (упрощенно):
- Относительные импорты (например,
./math,../utils): Поиск начинается в текущей директории и поднимается вверх по иерархии директорий, ища файлы с расширениями.ts,.tsx,.js,.jsxи директории с файломindex(и соответствующими расширениями). - Неотносительные импорты (например,
lodash,my-package): Поиск начинается в директорииnode_modules, расположенной на том же уровне, что и текущий файл, а затем поднимается вверх по иерархии директорий до корневой директории проекта.
TypeScript также учитывает поля types, typings, main в файлах package.json пакетов npm для определения местоположения файлов с объявлениями типов (.d.ts) и JavaScript-кода.
Типы модулей в tsconfig.json
Настройка module в файле tsconfig.json определяет формат генерируемого JavaScript-кода для модулей. Наиболее распространенные значения:
"ES2020","ES2022","ESNext": Генерирует код с использованием нативного синтаксиса ES Modules."CommonJS": Генерирует код с использованием синтаксиса CommonJS (require,module.exports)."UMD": Генерирует код, который может работать как в CommonJS, так и в AMD (Asynchronous Module Definition) средах."System": Генерирует код в формате SystemJS.
Выбор значения module зависит от среды выполнения вашего кода (браузер, Node.js) и используемых инструментов сборки. Для современных веб-проектов часто используется "ESNext" в сочетании с таким инструментом, как Webpack или Parcel, который выполняет сборку и оптимизацию модулей для браузера. Для Node.js проектов может использоваться "CommonJS" или более новые форматы ES Modules, поддерживаемые Node.js.
Декларативные файлы (.d.ts)
При использовании JavaScript-библиотек, которые не написаны на TypeScript или не предоставляют встроенные объявления типов, вам могут понадобиться декларативные файлы (.d.ts). Эти файлы описывают типы экспортируемых значений библиотеки, позволяя TypeScript понимать, как их использовать.
Многие популярные JavaScript-библиотеки имеют декларативные файлы, опубликованные в репозитории DefinitelyTyped (https://github.com/DefinitelyTyped/DefinitelyTyped). Вы можете установить их с помощью npm, используя префикс @types/. Например, для библиотеки lodash вы можете установить типы с помощью:
npm install --save-dev @types/lodashTypeScript автоматически обнаруживает файлы .d.ts в вашем проекте и в директории node_modules/@types.
Вы также можете создавать собственные декларативные файлы для JavaScript-кода в вашем проекте или для библиотек, для которых отсутствуют готовые типы.
CommonJS Modules
CommonJS — это формат модулей, исторически используемый Node.js. В TypeScript вы можете писать код, используя синтаксис CommonJS, если ваша цель — запустить код в среде Node.js или если вы работаете с проектом, который использует CommonJS.
Экспорт в CommonJS
В CommonJS модули экспортируют значения, присваивая их объекту module.exports или напрямую заменяя его.
// utils.ts (CommonJS)
function formatString(str: string): string {
return str.trim();
}
module.exports = formatString;
// Или экспортировать несколько значений как свойства объекта module.exports:
module.exports = {
format: formatString,
VERSION: '1.0.0'
};Импорт в CommonJS
Для импорта значений из CommonJS модулей используется функция require().
// main.ts (CommonJS)
const format = require('./utils');
// Или при импорте нескольких значений:
const utils = require('./utils');
const formatString = utils.format;
const version = utils.VERSION;
console.log(format(" hello ")); // Выведет "hello"
console.log(version); // Выведет "1.0.0"При использовании CommonJS в TypeScript, компилятор обычно настраивается на генерацию кода в формате CommonJS ("module": "CommonJS" в tsconfig.json).
Заключение
Модули являются фундаментальным способом организации кода в TypeScript. ES Modules являются современным и предпочтительным стандартом для большинства проектов, обеспечивая лучшую инкапсуляцию и поддержку статического анализа. Понимание того, как экспортировать и импортировать значения, а также как TypeScript разрешает модули, критически важно для разработки крупных и поддерживаемых приложений. При работе с JavaScript-библиотеками декларативные файлы (.d.ts) играют важную роль в обеспечении типовой безопасности. Выбор между ES Modules и CommonJS зависит от вашей среды выполнения и требований проекта.