Открыть сервис

ECMAScript Modules

ECMAScript Modules (сокращённо ESM) — это стандартизированная система модулей в языке программирования JavaScript, определённая в спецификации ECMAScript 2015 (ES6). Она предоставляет формальный механизм для организации кода в виде отдельных файлов (модулей), каждый из которых имеет собственную область видимости, а также поддерживает явный импорт и экспорт данных между ними. ESM является частью стандарта ECMAScript и поддерживается всеми современными браузерами и средами выполнения JavaScript, в том числе Node.js.

История

До появления ECMAScript Modules в JavaScript отсутствовала встроенная модульная система. Разработчики использовали различные обходные пути: глобальные переменные, шаблон «модуль» (module pattern) с помощью немедленно вызываемых функций (IIFE), а также сторонние спецификации, такие как CommonJS (используемая в Node.js) и Asynchronous Module Definition (AMD, применяемая в RequireJS). Эти решения имели ограничения: CommonJS синхронна и плохо подходит для браузеров, AMD сложна в написании, а оба подхода не являются частью стандарта языка.

Работа над стандартизацией модулей началась в рамках комитета TC39 (Technical Committee 39), который отвечает за развитие ECMAScript. Первоначальный проект был представлен в 2010 году, но из-за сложности и разногласий окончательная спецификация была включена в стандарт ECMAScript 2015 (ES6). Реализация в браузерах и средах выполнения заняла несколько лет. Первая экспериментальная поддержка появилась в 2016 году (Firefox, Chrome), а к 2020 году ESM стали поддерживаться всеми основными браузерами и Node.js (начиная с версии 12.17.0 и 13.2.0).

Синтаксис

ECMAScript Modules используют два ключевых оператора: export и import.

Экспорт

Модуль может экспортировать переменные, функции, классы или объекты. Существует два типа экспорта:

  • Именованный экспорт (named export): каждое значение экспортируется под своим именем. Можно экспортировать несколько значений из одного модуля.

``javascript // module.js export const pi = 3.14159; export function square(x) { return x x; } export class Circle { / ... */ } ``

  • Экспорт по умолчанию (default export): модуль может иметь один экспорт по умолчанию, который не требует имени при импорте.

``javascript // module.js export default function greet(name) { return Hello, ${name}!; } ``

Экспорт можно объявить как в момент объявления значения, так и отдельно, в конце файла: ``javascript const pi = 3.14159; export { pi }; ``

Импорт

Импорт осуществляется с помощью оператора import. Синтаксис зависит от того, что импортируется.

  • Именованный импорт:

``javascript import { pi, square } from './module.js'; console.log(square(pi)); // 9.869604401089358 ``

  • Импорт по умолчанию:

``javascript import greet from './module.js'; console.log(greet('World')); // Hello, World! ``

  • Импорт всего содержимого модуля как объекта (namespace import):

``javascript import * as math from './module.js'; console.log(math.pi); // 3.14159 ``

Можно комбинировать именованный и импорт по умолчанию: ``javascript import greet, { pi } from './module.js'; ``

Импорт также может быть динамическим — функция import() возвращает Promise и позволяет загружать модули по требованию: ``javascript const module = await import('./module.js'); ``

Отличия от CommonJS

CommonJS — это модульная система, изначально созданная для серверной платформы Node.js. ESM и CommonJS имеют несколько принципиальных отличий:

ХарактеристикаCommonJSESM
Синтаксисrequire() / module.exportsimport / export
ЗагрузкаСинхроннаяАсинхронная (по умолчанию)
Статический анализНет (возможны динамические require)Да (импорты/экспорты определяются на этапе разбора)
Область видимостиФайл — единая областьКаждый модуль — своя область
КэшированиеКэшируется после первого requireКэшируется после первого импорта
Поддержка циклических зависимостейЧастичная (возможны проблемы)Улучшенная (благодаря статическому анализу)

ESM является статической системой: все импорты и экспорты определяются на этапе разбора кода, до его выполнения. Это позволяет оптимизировать загрузку (например, tree shaking — удаление неиспользуемого кода) и улучшить производительность.

Использование в браузерах

В браузерах ECMAScript Modules подключаются через тег <script> с атрибутом type="module": ``html <script type="module" src="app.js"></script> ``

Модули в браузере загружаются асинхронно, выполняются в строгом режиме (strict mode) и имеют собственную область видимости. Они не создают глобальных переменных. По умолчанию модули откладывают выполнение до полной загрузки DOM (аналогично атрибуту defer).

Для импорта модулей из других источников используется указание полного URL или относительного пути. Поддерживается импорт из внешних источников (CDN), но с ограничениями CORS (Cross-Origin Resource Sharing).

Использование в Node.js

Node.js поддерживает ESM начиная с версии 12.17.0 (LTS) и 13.2.0. Для использования ESM необходимо:

  • Либо указать в package.json поле "type": "module" (все .js файлы в проекте будут интерпретироваться как ESM);
  • Либо использовать расширение .mjs для файлов модулей.

Node.js поддерживает как ESM, так и CommonJS, и позволяет импортировать CommonJS-модули из ESM (но не наоборот, без специальных обходных путей). Импорт CommonJS-модуля из ESM осуществляется через импорт по умолчанию: ``javascript import fs from 'fs'; // fs — это CommonJS-модуль ``

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

Функция import() (динамический импорт) позволяет загружать модули асинхронно, по требованию. Она возвращает Promise, который разрешается в объект модуля. Динамический импорт поддерживается как в браузерах, так и в Node.js.

Пример использования для ленивой загрузки: ``javascript button.addEventListener('click', async () => { const module = await import('./heavy-module.js'); module.doHeavyWork(); }); ``

Динамический импорт позволяет реализовать разделение кода (code splitting) и уменьшить начальный размер загружаемого JavaScript.

Преимущества и недостатки

Преимущества

  • Стандартизированность: является частью языка, не требует сторонних библиотек.
  • Статический анализ: позволяет инструментам сборки оптимизировать код (tree shaking, dead code elimination).
  • Асинхронная загрузка: улучшает производительность в браузерах.
  • Чёткая область видимости: предотвращает загрязнение глобального пространства имён.
  • Поддержка циклических зависимостей: реализована лучше, чем в CommonJS.

Недостатки

  • Обратная совместимость: старые браузеры и среды (например, Node.js до версии 12) не поддерживают ESM.
  • Различия в поведении между средами: некоторые тонкости (например, обработка циклических зависимостей) могут различаться в браузерах и Node.js.
  • Сложность миграции: крупные проекты, использующие CommonJS, могут потребовать значительных изменений для перехода на ESM.
  • Ограничения в Node.js: импорт CommonJS-модулей из ESM работает не всегда идеально, особенно с динамическими require.

Инструменты и сборщики

Для поддержки ESM в старых средах и для оптимизации кода используются сборщики (bundlers), такие как Webpack, Rollup, Parcel, Vite. Они преобразуют ESM в формат, понятный старым браузерам (например, в скрипты с глобальными переменными или в CommonJS), а также выполняют tree shaking, минификацию и разделение кода.

Современные сборщики всё чаще ориентируются на ESM как на нативный формат. Например, Vite использует ESM в режиме разработки, что позволяет избежать полной сборки и ускоряет горячую перезагрузку модулей (HMR).

Пример

Простой пример использования ESM:

math.js ``javascript export const add = (a, b) => a + b; export const subtract = (a, b) => a - b; export default function multiply(a, b) { return a * b; } ``

app.js ```javascript import multiply, { add, subtract } from './math.js';

console.log(add(2, 3)); // 5 console.log(subtract(5, 2)); // 3 console.log(multiply(4, 5)); // 20 ```

index.html ``html <!DOCTYPE html> <html> <head> <title>ESM Example</title> </head> <body> <script type="module" src="app.js"></script> </body> </html> ``

Источники

  • ECMAScript 2015 Language Specification (ECMA-262, 6th Edition), раздел 15.2 «Modules»
  • MDN Web Docs: «JavaScript modules» (mozilla.org)
  • Node.js Documentation: «ECMAScript Modules» (nodejs.org)
  • Axel Rauschmayer, «Exploring ES6», глава 16 «Modules»
  • TC39 Proposal: «import() – dynamic module loading» (github.com/tc39/proposal-dynamic-import)

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →