Модули
Концепция модулей как способа организации JavaScript-кода существовала давно.
Когда приложение сложное и кода много -- мы пытаемся разбить его на файлы. В каждом файле описываем какую-то часть, а в дальнейшем -- собираем эти части воедино.
Модули в стандарте ECMAScript предоставляют удобные средства для этого.
Такие средства предлагались сообществом и ранее, например:
- AMD -- одна из самых древних систем организации модулей, требует лишь наличия клиентской библиотеки, к примеру, require.js, но поддерживается и серверными средствами.
- CommonJS -- система модулей, встроенная в сервер Node.JS. Требует поддержки на клиентской и серверной стороне.
- UMD -- система модулей, которая предложена в качестве универсальной. UMD-модули будут работать и в системе AMD и в CommonJS.
Все перечисленные выше системы требуют различных библиотек или систем сборки для использования.
Новый стандарт отличается от них прежде всего тем, что это -- стандарт. А значит, со временем, будет поддерживаться браузерами без дополнительных утилит.
Однако, сейчас браузерной поддержки почти нет. Поэтому ES-модули используются в сочетании с системами сборки, такими как webpack, brunch и другими, при подключённом Babel.JS. Мы рассмотрим это далее.
Что такое модуль?
Модулем считается файл с кодом.
В этом файле ключевым словом export
помечаются переменные и функции, которые могут быть использованы снаружи.
Другие модули могут подключать их через вызов import
.
export
Ключевое слово export
можно ставить:
- перед объявлением переменных, функций и классов.
- отдельно, при этом в фигурных скобках указывается, что именно экспортируется.
Например, так экспортируется переменная one
:
// экспорт прямо перед объявлением
export let one = 1;
Можно написать export
и отдельно от объявления:
let two = 2;
export {two};
При этом в фигурных скобках указываются одна или несколько экспортируемых переменных.
Для двух переменных будет так:
export {one, two};
При помощи ключевого слова as
можно указать, что переменная one
будет доступна снаружи (экспортирована) под именем once
, а two
-- под именем twice
:
export {one as once, two as twice};
Экспорт функций и классов выглядит так же:
export class User {
constructor(name) {
this.name = name;
}
};
export function sayHi() {
alert("Hello!");
};
// отдельно от объявлений было бы так:
// export {User, sayHi}
"Для экспорта обязательно нужно имя" Заметим, что и у функции и у класса при таком экспорте должно быть имя.
Так будет ошибка:
// функция без имени
export function() { alert("Error"); };
В экспорте указываются именно имена, а не произвольные выражения.
import
Другие модули могут подключать экспортированные значения при помощи ключевого слова import
.
Синтаксис:
import {one, two} from "./nums";
Здесь:
"./nums"
-- модуль, как правило это путь к файлу модуля.one, two
-- импортируемые переменные, которые должны быть обозначены вnums
словомexport
.
В результате импорта появятся локальные переменные one
, two
, которые будут содержать значения соответствующих экспортов.
Например, при таком файле nums.js
:
export let one = 1;
export let two = 2;
Модуль ниже выведет "1 and 2":
import {one, two} from "./nums";
alert( `${one} and ${two}` ); // 1 and 2
Импортировать можно и под другим именем, указав его в "as":
// импорт one под именем item1, а two – под именем item2
import {one as item1, two as item2} from "./nums";
alert( `${item1} and ${item2}` ); // 1 and 2
"Импорт всех значений в виде объекта"
Можно импортировать все значения сразу в виде объекта вызовом import * as obj
, например:
import * as numbers from "./nums";
// теперь экспортированные переменные - свойства numbers
alert( `${numbers.one} and ${numbers.two}` ); // 1 and 2
export default
Выше мы видели, что модуль может экспортировать выбранные переменные при помощи export
.
Однако, как правило, код стараются организовать так, чтобы каждый модуль делал одну вещь. Иначе говоря, "один файл -- одна сущность, которую он описывает". Например, файл user.js
содержит class User
, файл login.js
-- функцию login()
для авторизации, и т.п.
При этом модули, разумеется, будут использовать друг друга. Например, login.js
, скорее всего, будет импортировать класс User
из модуля user.js
.
Для такой ситуации, когда один модуль экспортирует одно значение, предусмотрено особое ключевое сочетание export default
.
Если поставить после export
слово default
, то значение станет "экспортом по умолчанию".
Такое значение можно импортировать без фигурных скобок.
Например, файл user.js
:
*!*export default*/!* class User {
constructor(name) {
this.name = name;
}
};
...А в файле login.js
:
import User from './user';
new User("Вася");
"Экспорт по умолчанию" -- своего рода "синтаксический сахар". Можно было бы и без него, импортировать значение обычным образом через фигурные скобки {…}
. Если бы в user.js
не было default
, то в login.js
необходимо было бы указать фигурные скобки:
// если бы user.js содержал
// export class User { ... }
// …то при импорте User понадобились бы фигурные скобки:
import {User} from './user';
new User("Вася");
На практике этот "сахар" весьма приятен, так как позволяет легко видеть, какое именно значение экспортирует модуль, а также обойтись без лишних символов при импорте.
CommonJS
Если вы раньше работали с Node.JS или использовали систему сборки в синтаксисе CommonJS, то вот соответствия.
Для экспорта по умолчанию вместо:
module.exports = VARIABLE;
Пишем:
export default VARIABLE;
А при импорте из такого модуля вместо:
let VARIABLE = require('./file');
Пишем:
import VARIABLE from './file';
Для экспорта нескольких значений из модуля, вместо:
exports.NAME = VARIABLE;
Пишем в фигурных скобках, что надо экспортировать и под каким именем (без as
, если имя совпадает):
export {VARIABLE as NAME};
При импорте -- также фигурные скобки:
import {NAME} from './file';
Использование
Современный стандарт ECMAScript описывает, как импортировать и экспортировать значения из модулей, но он ничего не говорит о том, как эти модули искать, загружать и т.п.
Такие механизмы предлагались в процессе создания стандарта, но были убраны по причине недостаточной проработанности. Возможно, они появятся в будущем.
Сейчас используются системы сборки, как правило, в сочетании с Babel.JS.
Система сборки обрабатывает скрипты, находит в них import/export
и заменяет их на свои внутренние JavaScript-вызовы. При этом, как правило, много файлов-модулей объединяются в один или несколько скриптов, смотря как указано в конфигурации сборки.
Ниже вы можете увидеть полный пример использования модулей с системой сборки webpack.
В нём есть:
nums.js
-- модуль, экспортирующийone
иtwo
, как описано выше.main.js
-- модуль, который импортируетone
,two
изnums
и выводит их сумму.webpack.config.js
-- конфигурация для системы сборки.bundle.js
-- файл, который создала система сборки изmain.js
иnums.js
.index.html
-- простой HTML-файл для демонстрации.
[codetabs src="nums"]
Итого
Современный стандарт описывает, как организовать код в модули, экспортировать и импортировать значения.
Экспорт:
export
можно поставить прямо перед объявлением функции, класса, переменной.- Если
export
стоит отдельно от объявления, то значения в нём указываются в фигурных скобках:export {…}
. - Также можно экспортировать "значение по умолчанию" при помощи
export default
.
Импорт:
- В фигурных скобках указываются значения, а затем -- модуль, откуда их брать:
import {a, b, c as d} from "module"
. - Можно импортировать все значения в виде объекта при помощи
import * as obj from "module"
. - Без фигурных скобок будет импортировано "значение по умолчанию":
import User from "user"
.
На текущий момент модули требуют системы сборки на сервере. Автор этого текста преимущественно использует webpack, но есть и другие варианты.