Модули: import и export

ES-модули (ESM) — стандартная система разбиения JavaScript-кода на изолированные файлы с явным указанием экспортируемых и импортируемых значений через ключевые слова export и import.

Зачем нужно

До ESM разработчики использовали глобальные переменные или системы вроде CommonJS (require/module.exports). ES-модули позволяют писать чистый, тестируемый код с чёткими зависимостями, избегать конфликтов имён и эффективно применять tree-shaking в бандлерах.

Где используется

  • Любой современный фронтенд-проект (React, Vue, Svelte, Vanilla)
  • Node.js (.mjs или "type": "module" в package.json)
  • Браузерный <script type="module">
  • Бандлеры: Webpack, Vite, Rollup, esbuild

Named export (именованный экспорт)

// math.js
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

export class Vector {
  constructor(x, y) { this.x = x; this.y = y; }
  length { return Math.sqrt(this.x ** 2 + this.y ** 2); }
}
// main.js
import { PI, add, Vector } from './math.js';

console.log(PI);        // 3.14159
console.log(add(2, 3)); // 5

const v = new Vector(3, 4);
console.log(v.length); // 5

Default export (экспорт по умолчанию)

// logger.js
export default class Logger {
  log(msg) { console.log(`[LOG] ${msg}`); }
}
// main.js
import Logger from './logger.js'; // имя выбирается произвольно
import MyLogger from './logger.js'; // тоже сработает

const log = new Logger();
log.log('Старт');

Переименование при импорте/экспорте

// Экспорт с псевдонимом
export { add as sum, PI as pi };

// Импорт с псевдонимом
import { sum as addNumbers, pi } from './math.js';
console.log(addNumbers(1, 2)); // 3

Импорт всего модуля

import * as Math from './math.js';

console.log(Math.PI);       // 3.14159
console.log(Math.add(1,2)); // 3

Реэкспорт (barrel-файл)

// index.js — «бочка»: объединяет несколько модулей
export { add, PI } from './math.js';
export { default as Logger } from './logger.js';
export * from './utils.js';
// Теперь всё можно импортировать из одного места
import { add, Logger } from './index.js';

Динамический импорт

// Загрузка модуля по условию (lazy loading)
async function loadChart() {
  const { Chart } = await import('./chart.js');
  return new Chart();
}

// Используется в роутерах, code splitting
button.addEventListener('click', async () => {
  const module = await import('./heavyModule.js');
  module.run;
});

Подключение в HTML

<script type="module" src="./main.js"></script>
<!-- или inline -->
<script type="module">
  import { greet } from './greet.js';
  greet('World');
</script>

Особенности ESM

  • Строгий режим ('use strict') включён автоматически
  • this на верхнем уровне равен undefined (не window)
  • Модули выполняются один раз и кешируются
  • Поддерживают цикличные зависимости (но с осторожностью)

Частые ошибки

1. Смешение default и named без скобок

// logger.js: export default Logger
import Logger from './logger.js';    // OK — default
import { Logger } from './logger.js'; // undefined — нет named export с таким именем

2. Расширение .js обязательно в браузере и Node ESM

import { add } from './math';    // ошибка в браузере и Node (ESM)
import { add } from './math.js'; // правильно

3. Изменение live binding

// counter.js
export let count = 0;
export function increment() { count++; }
import { count, increment } from './counter.js';
console.log(count); // 0
increment;
console.log(count); // 1 — импорт это «живая» ссылка, не копия

Связанные темы

Ресурсы