Модули: import / export
Любой большой проект — это не один огромный файл на 10 000 строк, а сотни маленьких, хорошо организованных файлов. Модули — это механизм, который позволяет разделить код на части и связать их между собой. Разбираемся, как это работает в JavaScript.
📁 Зачем вообще нужны модули?
Представьте: вы пишете приложение. Сначала — пара функций, несколько переменных, всё умещается в один файл. Жизнь прекрасна. Но через пару месяцев в этом файле уже 2000 строк, а через год — 8000. Вы уже не помните, где что находится. Функция с именем helper определена трижды в разных местах. Переменная data используется везде и непонятно, где она была изменена последний раз.
Это классическая проблема «спагетти-кода» — когда всё перемешано и невозможно разобраться. Именно для её решения были придуманы модули.
📦 Модуль — это просто файл с кодом на JavaScript. Но файл особый: он явно объявляет, что он отдаёт другим (export) и что он берёт от других (import). Это создаёт чёткие границы между частями кода.
Модули дают несколько важных преимуществ. Во-первых, изоляция: переменные внутри модуля не видны снаружи, если вы специально не экспортируете их. Никаких случайных конфликтов имён. Во-вторых, переиспользование: написал функцию один раз — импортируй её в любой файл проекта. В-третьих, читаемость: вместо одного монстра — много маленьких, понятных файлов, каждый из которых делает одно дело.
Один файл app.js на 5000 строк. Все переменные глобальные. Имена конфликтуют. Никто не понимает, что происходит.
utils.js, api.js, auth.js, main.js — каждый файл делает одно. Чёткие зависимости. Легко найти нужный код.
🏷️ Named exports — именованный экспорт
Самый распространённый тип экспорта — именованный. Это когда вы явно «публикуете» какие-то переменные, функции или классы из файла, давая им конкретные имена.
Представьте, что вы создаёте файл utils.js с набором вспомогательных функций. Вы хотите, чтобы другие файлы могли их использовать. Добавьте ключевое слово export перед каждой функцией или переменной, которую хотите «открыть»:
// utils.js
// Эти функции доступны для других файлов
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export function formatName(firstName, lastName) {
return `${firstName} ${lastName}`.trim();
}
// Эта переменная — ПРИВАТНАЯ, снаружи не видна
const secretKey = "abc123";
function helperInternal() {
// внутренняя утилита — не экспортируем
return "только для внутреннего использования";
}
Теперь в другом файле — main.js — мы можем импортировать именно то, что нам нужно, указав имена в фигурных скобках:
// main.js
// Импортируем только то, что нужно
import { add, multiply, PI } from './utils.js';
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
console.log(PI); // 3.14159
// Попытка использовать secretKey — ошибка!
// console.log(secretKey); // ReferenceError
💡 Путь к файлу всегда начинается с ./ (текущая папка) или ../ (родительская). Без точки — это встроенный или установленный пакет, например import { useState } from 'react'.
Есть ещё один вариант: экспортировать всё сразу в конце файла. Это удобно, когда хочется видеть в одном месте, что именно «публичное» в этом файле:
// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
const multiply = (a, b) => a * b;
// Экспортируем всё в конце — удобно для просмотра "публичного API" файла
export { add, subtract, multiply };
При импорте можно переименовать что-то через ключевое слово as, если имя занято или нужно сделать его короче:
// Переименование при импорте
import { formatName as format, add as sum } from './utils.js';
console.log(format("Иван", "Петров")); // Иван Петров
console.log(sum(10, 20)); // 30
⭐ Default export — экспорт по умолчанию
Каждый модуль может иметь один — и только один — экспорт по умолчанию. Это удобно, когда файл делает одно главное дело: например, определяет один класс, одну функцию или один компонент React.
// user.js
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
greet() {
return `Привет, меня зовут ${this.name}!`;
}
}
// Экспорт по умолчанию — только один на файл
export default User;
При импорте default-экспорта фигурные скобки не нужны, и вы можете дать импортируемому значению любое имя:
// main.js
// Имя выбираем произвольно — не обязательно User
import User from './user.js';
import MyUser from './user.js'; // Тоже сработает
const alice = new User("Алиса", "alice@example.com");
console.log(alice.greet()); // Привет, меня зовут Алиса!
Несколько экспортов из одного файла. При импорте — фигурные скобки { name }. Имя должно совпадать (или использовать as).
Один главный экспорт из файла. При импорте — без скобок. Имя выбираете сами. Удобно для компонентов и классов.
Можно смешивать оба типа в одном файле — это вполне нормальная практика:
// api.js
// Default export — главная функция файла
export default function fetchUser(id) {
return fetch(`/api/users/${id}`).then(r => r.json());
}
// Named exports — вспомогательные константы
export const BASE_URL = "https://api.example.com";
export const TIMEOUT = 5000;
// main.js
import fetchUser, { BASE_URL, TIMEOUT } from './api.js';
// Сначала default, потом в скобках named
🔀 Re-export — переэкспорт
В больших проектах часто создают файл-«точку входа» — обычно это index.js — который собирает экспорты из разных файлов и предоставляет их как единый фасад. Это называется переэкспортом (re-export).
Представьте, что у вас есть папка utils/ с несколькими файлами:
// utils/math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
// utils/string.js
export const capitalize = str => str[0].toUpperCase() + str.slice(1);
export const trim = str => str.trim();
// utils/index.js — точка входа, переэкспортирует всё
export { add, multiply } from './math.js';
export { capitalize, trim } from './string.js';
Теперь пользователи могут импортировать всё из одного места:
// main.js — удобно! Один импорт вместо двух
import { add, capitalize } from './utils/index.js';
// или ещё короче, если папка называется utils:
import { add, capitalize } from './utils';
✅ Практика: файл index.js в папке — стандартный паттерн JavaScript. Именно поэтому можно писать import X from './components/Button' вместо import X from './components/Button/Button.js'.
🔧 CommonJS vs ESM: две системы модулей
Исторически в JavaScript сложилось так, что существуют две разные системы модулей. Это важно знать, потому что вы столкнётесь с обеими в реальных проектах.
📦 CommonJS (старая система, Node.js)
CommonJS — исторически первая система модулей в Node.js. Использует require() для импорта и module.exports для экспорта. По умолчанию используется в Node.js, если не указано иначе.
// math.js — CommonJS экспорт
function add(a, b) { return a + b; }
module.exports = { add };
// main.js — CommonJS импорт
const { add } = require('./math.js');
console.log(add(2, 3)); // 5
✨ ESM — ECMAScript Modules (современная система)
ESM — стандартная система модулей JavaScript (ES6, 2015). Использует import/export. Это то, что мы изучали выше. Сегодня это предпочтительный вариант — он работает и в браузере, и в Node.js.
// math.js — ESM экспорт
export function add(a, b) { return a + b; }
// main.js — ESM импорт
import { add } from './math.js';
console.log(add(2, 3)); // 5
🔑 Чтобы использовать ESM в Node.js, нужно либо назвать файл .mjs, либо добавить "type": "module" в package.json. Большинство современных проектов делают именно так.
🌐 Модули в браузере
Браузеры поддерживают ESM нативно начиная с 2017 года. Чтобы подключить модульный JavaScript к HTML-странице, нужно добавить атрибут type="module" к тегу <script>:
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Мой проект</title>
</head>
<body>
<!-- type="module" — ключевой атрибут! -->
<script type="module" src="./main.js"></script>
</body>
</html>
Когда браузер видит type="module", он включает режим модулей: переменные не попадают в глобальную область видимости, выполнение строгое (use strict по умолчанию), и можно использовать import/export.
⚠️ Важно: модули в браузере работают только при загрузке через HTTP/HTTPS сервер. Если просто открыть HTML-файл двойным кликом (протокол file://), браузер заблокирует импорты из соображений безопасности. Используйте расширение Live Server в VS Code.
🛠️ Практика: разбиваем программу на модули
Давайте посмотрим на реальный пример. Представьте небольшое приложение — менеджер задач. Без модулей всё было бы в одном файле. С модулями мы разобьём это на три логических части:
// storage.js — работа с localStorage
export function saveTasks(tasks) {
localStorage.setItem('tasks', JSON.stringify(tasks));
}
export function loadTasks() {
const raw = localStorage.getItem('tasks');
return raw ? JSON.parse(raw) : [];
}
export function clearTasks() {
localStorage.removeItem('tasks');
}
// task.js — логика задач
export function createTask(text) {
return {
id: Date.now(),
text,
done: false,
createdAt: new Date().toISOString()
};
}
export function completeTask(task) {
return { ...task, done: true };
}
export function filterActive(tasks) {
return tasks.filter(t => !t.done);
}
// main.js — точка входа, связывает всё вместе
import { saveTasks, loadTasks } from './storage.js';
import { createTask, completeTask, filterActive } from './task.js';
let tasks = loadTasks();
// Добавить задачу
const newTask = createTask("Выучить модули JS");
tasks.push(newTask);
saveTasks(tasks);
// Найти активные задачи
const active = filterActive(tasks);
console.log(`Активных задач: ${active.length}`);
Заметьте, насколько это чище: каждый файл отвечает за одну вещь. Хотите изменить способ хранения данных? Правите только storage.js. Хотите изменить структуру задачи? Только task.js. Это называется принципом единственной ответственности.
✅ Итоги урока
- Модули позволяют разбить код на файлы с чёткими границами — что видно снаружи, а что нет
- Named export:
export const name = .../export function ...— можно несколько на файл - Named import:
import { name, func } from './file.js'— фигурные скобки обязательны - Default export:
export default ...— один на файл, без фигурных скобок при импорте - Переименование при импорте:
import { name as alias } from './file.js' - Re-export:
export { X } from './other.js'— удобно дляindex.js-фасадов - CommonJS (
require/module.exports) — старая Node.js система; ESM (import/export) — современный стандарт - В браузере:
<script type="module">включает поддержку ESM
В следующем уроке разберём npm — менеджер пакетов, который позволяет использовать тысячи готовых библиотек в своих проектах. 🚀
Тест: Модули JavaScript
7 вопросов