Frontender Magazine

Модули в ECMAScript 6: будущее уже сейчас

Эта статья сначала объясняет, как работают модули в ECMAScript 6, следующей версии JavaScript, затем дается описание инструментов, которые позволяют вам использовать модули уже сейчас.

Модульные системы для текущей версии JavaScript

Несмотря на то, что JavaScript не поддерживает модули на уровне языка, сообществом были созданы впечатляющие решения для их реализации. Два наиболее популярных (но, к сожалению, несовместимых) стандарта:

Все это упрощенное объяснение текущего положения вещей. Если вы хотите углубится в эту тему, прочтите «Написание модульного JavaScript с помощью AMD, CommonJS и ES Harmony» Эдди Османи (Addy Osmani).

Модули в ECMAScript 6

Целью модулей ECMAScript 6 (ES6) было создание формата, удобного как для пользователей CJS, так и для пользователей AMD. В связи с этим они имеют такой же компактный синтаксис, как и модули CJS. С другой стороны, они не такие динамичные (например, вы не сможете условно загрузить модуль с помощью обычного синтаксиса). Это дает два основных преимущества:

Стандарт ES6 module состоит из двух частей:

Синтаксис модуля ECMAScript 6

Модули ECMAScript 6 очень похожи на модули Node.js. Модуль — это просто файл с JavaScript кодом внутри. Для примера рассмотрим проект, файлы которого находятся в папке calculator/.

calculator/
    lib/
        calc.js
    main.js

Экспорт

Ключевое слово export, стоящее перед объявлением переменной (посредством var, let, const), функции или класса экспортирует их значение в остальные части программы 1. В нашем примере calculator/lib/calc.js содержит следующий код:

// calculator/lib/calc.js
let notExported = 'abc';
export function square(x) {
    return x * x;
}
export const MY_CONSTANT = 123;

Этот модуль экспортирует функцию square и значение MY_CONSTANT.

Импорт

main.js, другой модуль, импортирует square с calc.js:

// calculator/main.js
import { square } from 'lib/calc';
console.log(square(3));

main.js ссылается на calc.js посредством идентификатора модуля — строки «lib/calc». По умолчанию интерпретацией идентификатора модуля является относительный путь к импортируемому модулю. Обратите внимание, что, при необходимости, вы можете импортировать несколько значений:

// calculator/main.js
import { square, MY_CONSTANT } from 'lib/calc';

Вы также можете импортировать модуль как объект, значениями свойств которого будут экспортированные значения:

// calculator/main.js
import 'lib/calc' as c;
console.log(c.square(3));

Если вам неудобно использовать имена, определенные в экспортируемом модуле, вы можете переименовать их при импорте:

// calculator/main.js
import { square as squ } from 'lib/calc';
console.log(squ(3));

Экспорт по умолчанию

Иногда модуль экспортирует только одно значение (большой класс, например). В таком случае удобно определить это значение как экспортируемое по умолчанию:

// myapp/models/Customer.js
export default class { // анонимный класс
    constructor(id, name) {
        this.id = id;
        this.name = name;
    }
};

Синтаксис импорта таких значений аналогичный обычному импорту без фигурных скобок (для простоты запоминания: вы не импортируете что-либо с модуля, а импортируете сам модуль):

// myapp/myapp.js
import Customer from 'models/Customer';
let c = new Customer(0, 'Jane');

Встроенные модули

Другим вариантом использования может быть предотвращение того, чтобы переменные становились глобальными. На данный момент, например, для этой цели лучше всего использовать самовызывающуюся функцию 2:

<script>
    (function () {  // начало самовызывающейся функции
        var tmp = …;  // не станет глобальной
    }());  // конец самовызывающейся функции
</script>

В ECMAScript 6 вы можете использовать анонимный внутренний модуль:

<script>
    module {  // анонимный внутренний модуль
        let tmp = …;  // не станет глобальной
    }
</script>

Кроме того, что такая конструкция проще с точки зрения синтаксиса, ее содержание автоматически отображается в strict mode 3.

Обратите внимание, что не обязательно осуществлять импорт внутри модуля. Инструкция import может использоваться в контексте обычного скрипта.

Альтернатива встроенному экспорту

Если не хотите вставлять export-ы в код, то можно все экспортировать позже, например, в конце:

let notExported = 'abc';
function square(x) {
    return x * x;
}
const MY_CONSTANT = 123;

export { square, MY_CONSTANT };

Также можно переименовывать значения во время экспорта:

export { square as squ, MY_CONSTANT as SOME_CONSTANT };

Осуществление реэкспорта

Можно реэкспортировать значения из другого модуля:

export { encrypt as en } from 'lib/crypto';

Также вы можете реэкспортировать все сразу:

export * from 'lib/crypto';

API загрузки модулей ECMAScript 6

В дополнение к декларативному синтаксису для работы с модулями, в стандарте также присутствует программный API. Он позволяет делать две вещи: программно работать с модулями и скриптами и настраивать загрузку модулей.

Импорт модулей и загрузка скриптов

Вы можете программно импортировать модули, используя синтаксис, напоминающий AMD:

System.import(
    ['module1', 'module2'],
    function (module1, module2) {  // успешное выполнение
        …
    },
    function (err) {  // ошибка
        …
    }
);

Среди прочего, это делает возможной условную загрузку модулей.

System.load() работает аналогично с System.import(), но загружает файлы скриптов вместо импорта модулей.

Настройка загрузки модулей

API загрузки модулей имеет различные хаки для настройки. Несколько примеров их возможностей:

Вы должны сами реализовать эти вещи, но хаки для них предусмотрены в стандарте.

Используем модули ECMAScript 6

Два наиболее свежих проекта, дающих возможность использовать модули ECMAScript 6:

В реальных модулях вы используете ECMAScript 5 + экспорт + импорт. Например:

export function tester() {
    console.log('Привет!');
}

Остальные проекты:

Дополнительная литература


Примечания

1 ECMAScript.next: классы
2 Странности JavaScript 6: области видимости переменных
3 Strict mode в Javascript: итог

Если вы заметили ошибку, вы всегда можете отредактировать статью, создать issue или просто написать об этом Антону Немцеву в skype ravencry.

Axel Rauschmayer
Автор:
Axel Rauschmayer
GitHub:
rauschma
Twitter:
@rauschma
Сaйт:
http://rauschma.de/
Email:
axel@rauschma.de
Glen Swift
Переводчик:
Glen Swift
GitHub:
glenswift
Twitter:
@SwiftGlen
Сайт:
http://glenswift.com/
Email:
glen.swift@gmail.com

Комментарии (1 комментарий, если быть точным)

Автар пользователя
Globik

Дык, а можно модули сбросить с кэша? Подвергнуть перезагрузке при изменении кода и не перезапуская сервер?