Модули: ES Modules в браузере

ES Modules (ESM) — нативный стандарт модульной системы JavaScript, поддерживаемый современными браузерами напрямую без сборщика через import/export.

Зачем нужно

До ESM в браузере не было официальной модульной системы: разработчики использовали глобальные переменные или конкатенацию файлов. Node.js сделал CommonJS (require/module.exports) стандартом, затем ES2015 принёс нативный ESM. Понимание ESM объясняет как работают сборщики (Webpack, Vite), почему Vite так быстро стартует в dev-режиме и как браузер самостоятельно загружает зависимости.

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

  • Vite dev-server — сервирует ESM-модули напрямую без бандлинга
  • Браузер с <script type="module"> — нативная загрузка
  • Node.js с расширением .mjs или "type": "module" в package.json
  • CDN-импорты для экспериментов: import { createApp } from 'https://esm.sh/vue'

Синтаксис ESM

// math.js — экспорт
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }

// default export — один на модуль
export default function greet(name) {
  return `Привет, ${name}!`;
}

// Переменные и константы
export const PI = 3.14159;
export class Calculator { /* ... */ }
// app.js — импорт
import greet, { add, multiply, PI } from './math.js';

// Переименование при импорте
import { add as sum } from './math.js';

// Импорт всего namespace
import * as MathUtils from './math.js';
MathUtils.add(1, 2);

// Динамический импорт — возвращает промис
const { add } = await import('./math.js');

// Side-effect only импорт (полифиллы, стили)
import './polyfills.js';

ESM в браузере без сборщика

<!DOCTYPE html>
<html>
<head>
  <!-- type="module" — включает ESM -->
  <!-- defer по умолчанию, всегда выполняется после парсинга HTML -->
  <script type="module" src="./app.js"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>
// app.js
// Браузер загружает и выполняет граф зависимостей автоматически
import { render } from './renderer.js';
import { createRouter } from './router.js';

// Относительные пути обязательны (./ или ../)
// Голые импорты ('react') не работают без Import Map
import { add } from './math.js'; // OK
import { add } from 'math'; // Ошибка без Import Map

Import Maps — голые импорты в браузере

<!-- Современные браузеры поддерживают Import Maps (2023+) -->
<script type="importmap">
{
  "imports": {
    "react": "https://esm.sh/react@18",
    "react-dom/client": "https://esm.sh/react-dom@18/client"
  }
}
</script>

<script type="module">
  import React from 'react'; // теперь работает
  import { createRoot } from 'react-dom/client';
</script>

ESM vs CommonJS

// CommonJS (Node.js, require)
const { add } = require('./math'); // синхронный
module.exports = { add };

// ESM — статический анализ → tree-shaking возможен
import { add } from './math.js'; // статический
export { add };

// CJS нельзя статически проанализировать:
const moduleName = condition ? 'a' : 'b';
const mod = require(moduleName); // динамический путь

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

  • Забывают расширение .js — в браузерном ESM путь должен быть точным: ./math.js, не ./math.
  • CORS ошибка при открытии через file:// — ESM требует HTTP-сервера; используйте npx serve или Live Server.
  • Смешивают CJS и ESMrequire не работает в ESM-контексте браузера; используйте только import/export.

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

Ресурсы