Модули в ECMAScript 6: будущее уже сейчас
Эта статья сначала объясняет, как работают модули в ECMAScript 6, следующей версии JavaScript, затем дается описание инструментов, которые позволяют вам использовать модули уже сейчас.
Модульные системы для текущей версии JavaScript
Несмотря на то, что JavaScript не поддерживает модули на уровне языка, сообществом были созданы впечатляющие решения для их реализации. Два наиболее популярных (но, к сожалению, несовместимых) стандарта:
-
CommonJS (CJS): главное воплощение этого стандарта — модульная система Node.js (в Node.js есть несколько фич, выходящих за рамки CJS). Характеристики:
- компактный синтаксис;
- предназначен для синхронной загрузки;
- преимущественно используется на стороне сервера.
-
Asynchronous Module Definition (AMD): наиболее популярной реализацией этого стандарта стал RequireJS. Характеристики:
- синтаксис немного сложнее, что позволяет AMD работать без eval() или этапа компиляции;
- предназначен для асинхронной загрузки;
- преимущественно используется на стороне клиента.
Все это упрощенное объяснение текущего положения вещей. Если вы хотите углубится в эту тему, прочтите «Написание модульного JavaScript с помощью AMD, CommonJS и ES Harmony» Эдди Османи (Addy Osmani).
Модули в ECMAScript 6
Целью модулей ECMAScript 6 (ES6) было создание формата, удобного как для пользователей CJS, так и для пользователей AMD. В связи с этим они имеют такой же компактный синтаксис, как и модули CJS. С другой стороны, они не такие динамичные (например, вы не сможете условно загрузить модуль с помощью обычного синтаксиса). Это дает два основных преимущества:
- на этапе компиляции вы получите ошибки, если попытаетесь импортировать что-то, что не было предварительно экспортировано;
- вы можете легко осуществить асинхронную загрузку модулей ES6.
Стандарт 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 загрузки модулей имеет различные хаки для настройки. Несколько примеров их возможностей:
- настройка отображения идентификатора модуля;
- проверка валидности модуля при импорте (к примеру, посредством KSLint или JSHint);
- автоматическая трансляция модулей при импорте (они могут содержать код CoffeeScript или TypeScript);
- использовать существующие модули (AMD, Node.js).
Вы должны сами реализовать эти вещи, но хаки для них предусмотрены в стандарте.
Используем модули ECMAScript 6
Два наиболее свежих проекта, дающих возможность использовать модули ECMAScript 6:
-
ES6 Module Transpiler: позволяет писать свои модули, используя некую часть стандарта ECMAScript 6 (грубо говоря, ECMAScript 5 + экспорт + импорт), и компилирует их в модули AMD или CommonJS. Статья Райана Флоренца (Ryan Florence) детально объясняет этот подход.
-
ES6 Module Loader: позволяет использовать API загрузки модулей ECMAScript 6 в современных браузерах. Чтобы открыть для себя мир модулей, используйте API:
System.baseURL = ‘/lib’; System.import(‘js/test1’, function (test1) { test1.tester(); });
В реальных модулях вы используете ECMAScript 5 + экспорт + импорт. Например:
export function tester() {
console.log('Привет!');
}
Остальные проекты:
- require-hm: плагин для RequireJS, позволяющий загружать модули ECMAScript 6 (только ECMAScript 5 + экспорт + импорт). Статья Каолана Макмахона (Caolan McMahon) объясняет, как он работает. Предупреждение: плагин использует более старый синтаксис.
- Traceur (компилятор ECMAScript 6 в ECMAScript 5): частично поддерживает модули, возможно, в конечном счете будет поддерживать их полностью.
- TypeScript TypeScript (грубо говоря, ECMAScript 6 и поддержка статической типизации): компилирует модули из внешних файлов (которые могут использовать большую часть ECMAScript 6) в AMD или CommonJS.
Дополнительная литература
- Спецификация модулей ECMAScript 6: модули еще не включены в черновик спецификации ECMAScript 6. Пока их не включат, уточняйте детали в вики-справке Harmony.
- «ES6 Modules», автор – Иегуда Кац (Yehuda Katz): обсуждение распространенных вариантов использования и совместимости с существующими модульными системами.
Примечания
1 ECMAScript.next: классы
2 Странности JavaScript 6: области видимости переменных
3 Strict mode в Javascript: итог