Концепция модулей как способа организации 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;
А при импорте из такого модуля вместо:
const 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-файл для демонстрации.
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
var _nums = __webpack_require__(1);
document.write('Сумма импортов: ' + (_nums.one + _nums.two));
/***/ },
/* 1 */
/***/ function(module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var one = 1;
exports.one = one;
var two = 2;
exports.two = two;
/***/ }
/******/ ]);
import {one, two} from './nums';
document.write(`Сумма импортов: ${one + two}`);
export let one = 1;
let two = 2;
export {two};
// Для использования нужен Node.JS
// Поставьте webpack:
// npm i -g webpack
// Поставьте babel-loader:
// npm i babel-loader
// Запустите его в директории с файлами:
// webpack
module.exports = {
entry: './main',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.js$/, loader: "babel" }
]
}
};
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
Итого
Современный стандарт описывает, как организовать код в модули, экспортировать и импортировать значения.
Экспорт:
export
можно поставить прямо перед объявлением функции, класса, переменной.- Если
export
стоит отдельно от объявления, то значения в нём указываются в фигурных скобках:export {…}
. - Также можно экспортировать «значение по умолчанию» при помощи
export default
.
Импорт:
- В фигурных скобках указываются значения, а затем – модуль, откуда их брать:
import {a, b, c as d} from "module"
. - Можно импортировать все значения в виде объекта при помощи
import * as obj from "module"
. - Без фигурных скобок будет импортировано «значение по умолчанию»:
import User from "user"
.
На текущий момент модули требуют системы сборки на сервере. Автор этого текста преимущественно использует webpack, но есть и другие варианты.
Комментарии
<code>
, для нескольких строк кода — тег<pre>
, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)