Перейти к содержимому

3. ES Modules (ESM)

Иллюстрация к уроку

ES Modules — современный стандарт модулей JavaScript. Используется в браузерах и Node.js. Синтаксис: import / export.

// CommonJS (старый стиль)
const express = require('express');
module.exports = { myFunc };
// ESM (современный стиль)
import express from 'express';
export { myFunc };
export default myFunc;

Два способа:

// package.json — включить ESM для всего проекта
{
"type": "module"
}
// Или использовать расширение .mjs для ESM файлов
// Тогда .js остаётся CommonJS, .mjs — ESM
math.js
// Named export — именованный экспорт
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
export class Calculator {
constructor() {
this.result = 0;
}
add(n) {
this.result += n;
return this;
}
}
// Default export — экспорт по умолчанию (один на файл)
export default function multiply(a, b) {
return a * b;
}
main.js
// Named imports
import { add, PI } from './math.js';
// Внимание: расширение .js обязательно в ESM!
// Default import
import multiply from './math.js';
// Совмещённый импорт
import multiply, { add, PI } from './math.js';
// Импорт всего как объект
import * as math from './math.js';
console.log(math.add(2, 3));
console.log(math.PI);
console.log(math.default(4, 5)); // default export
// Переименование при импорте
import { add as sum, PI as pi } from './math.js';
// Динамический импорт (работает в CommonJS тоже!)
const module = await import('./math.js');
// В ESM можно использовать await на верхнем уровне!
// В CommonJS это недоступно.
import { readFile } from 'fs/promises';
// Прямо на верхнем уровне — без async функции
const config = JSON.parse(await readFile('./config.json', 'utf8'));
console.log(config);
// Динамический выбор конфига
const env = process.env.NODE_ENV ?? 'development';
const { default: settings } = await import(`./config.${env}.js`);
// В ESM нет __dirname и __filename
// Используй import.meta.url
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Теперь можно использовать __dirname как раньше
const dataPath = join(__dirname, 'data', 'users.json');
// Импорт JSON (Node.js 20+)
import data from './data.json' assert { type: 'json' };
console.log(data.users);
// Импорт Node.js встроенных модулей
import { readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
// Префикс node: — явно указывает что это встроенный модуль
// utils/index.js — "barrel" файл, собирает всё из папки
// Re-export named
export { add, multiply } from './math.js';
export { formatDate } from './date.js';
export { capitalize } from './string.js';
// Re-export с переименованием
export { default as Calculator } from './calculator.js';
// Re-export всего
export * from './helpers.js';
// Теперь можно импортировать всё из одного места
import { add, formatDate, capitalize } from './utils/index.js';
// В ESM модуле можно импортировать CommonJS пакеты
import express from 'express'; // express — CommonJS
import lodash from 'lodash'; // lodash — CommonJS
// Работает через автоматическую конвертацию
// В CommonJS нельзя напрямую require() ESM файлы
// Нужен динамический import():
async function loadESMModule() {
const { default: something } = await import('./esm-module.js');
return something;
}
CommonJS ESM
────────────────────────── ──────────────────────────
Старые проекты Новые проекты
require() уже есть Хочешь современный стиль
Пакеты только CJS Top-level await нужен
__dirname доступен Tree-shaking (bundlers)
Браузер + Node.js код
  1. Создай проект с "type": "module" в package.json
  2. Создай модуль services/user.js с ESM экспортами
  3. Используй top-level await для загрузки конфига из JSON
  4. Создай barrel файл utils/index.js с re-export
  5. Попробуй динамический import() для условной загрузки модуля