Модули: import и export
Как разделить программу на файлы, подключить нужную функцию и разобраться в ошибках импорта.
На этой странице
Пока мы писали программу в одном main.js. Когда расчёты, данные и работа со страницей оказываются вместе, нужный фрагмент труднее найти. Можно разделить их на файлы и явно указать, что один файл использует из другого.
Модуль – файл JavaScript со своей областью видимости. export делает выбранное значение доступным для импорта, а import позволяет использовать его в другом модуле. Просто положить два файла рядом недостаточно: связь между ними нужно записать.
Разделим расчёт и вывод
Создайте отдельную папку с тремя файлами:
project/
index.html
main.js
pricing.jsВ pricing.js оставим расчёт стоимости билетов. Он не зависит от элементов страницы:
export function calculateTotal(price, quantity) {
return price * quantity;
}В main.js подключим функцию и покажем результат:
import { calculateTotal } from "./pricing.js";
console.log(calculateTotal(300, 2)); // 600exportперед объявлением разрешает импортировать функцию.{ calculateTotal }выбирает экспорт с этим именем. Оно должно совпасть с именем вpricing.js.from "./pricing.js"указывает источник../означает текущую папку, здесь – папку файлаmain.js.- Сам импорт не вызывает
calculateTotal. Расчёт происходит в строкеcalculateTotal(300, 2).
В index.html подключите только входной файл main.js:
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Стоимость билетов</title>
<script type="module" src="./main.js"></script>
</head>
<body>
<h1>Стоимость билетов</h1>
<p>Результат расчёта появится в консоли браузера.</p>
</body>
</html>type="module" сообщает браузеру, что файл использует правила модулей. Его зависимости браузер загружает по импортам: отдельно подключать pricing.js через script не нужно. Для этой записи без async выполнение откладывается до разбора HTML, поэтому defer не требуется.
Откройте страницу через Live Server, затем вкладку Console в DevTools: должно появиться 600. Адрес должен начинаться с http:// или https://. Открытие файла двойным щелчком через file:// для этого примера не подходит.
Именованный экспорт и экспорт по умолчанию
Первый пример использует именованный экспорт. В одном файле их может быть несколько:
export const serviceFee = 50;
export function calculateTotal(price, quantity) {
return price * quantity;
}import { calculateTotal, serviceFee } from "./pricing.js";
console.log(calculateTotal(300, 2) + serviceFee); // 650Можно импортировать только нужные имена. Если локальное имя занято, переименуйте импорт через as:
import { calculateTotal as getTotal } from "./pricing.js";
console.log(getTotal(300, 2)); // 600getTotal здесь обозначает ту же функцию. Её имя в исходном файле не меняется.
Экспорт по умолчанию записывается через export default. В модуле он может быть один. Ниже альтернативная версия двух файлов: замените предыдущий код, а не дописывайте её к нему.
export default function calculateTotal(price, quantity) {
return price * quantity;
}import getTotal from "./pricing.js";
console.log(getTotal(300, 2)); // 600Для экспорта по умолчанию не нужны фигурные скобки, а локальное имя выбирает импортирующий файл. Здесь функцию назвали getTotal, хотя в исходном файле она называется calculateTotal.
| Вид экспорта | Перед объявлением | Имя в импорте |
|---|---|---|
| Именованный | export | { calculateTotal }: имя должно совпасть |
| По умолчанию | export default | getTotal: локальное имя на Ваш выбор |
Один модуль может содержать и именованные экспорты, и один экспорт по умолчанию. Но это разные виды экспорта: скобки в импорте нельзя произвольно добавлять или убирать.
Переключите запись экспорта и импорта. Пример показывает, почему несовпадение мешает запуску программы:
Что предоставляет pricing.js
export function calculateTotal(price, quantity) { return price * quantity;}Что запрашивает main.js
import { calculateTotal } from "./pricing.js";
console.log(calculateTotal(300, 2));Ожидаемый результат
Импорт совпал с экспортом. В консоли появится 600.
Что остаётся внутри модуля
Переменные разных модулей не становятся общими автоматически. Чтобы использовать serviceFee из pricing.js в main.js, её нужно экспортировать и импортировать.
Экспорт не переносит файл в другой файл и не создаёт копию функции или объекта. Импортируемый модуль выполняет свой код верхнего уровня; затем использующие его модули могут работать с экспортами. При нескольких обычных импортах одного и того же модуля в рамках страницы он не запускается заново для каждого импорта.
Модули работают в строгом режиме автоматически: многие сомнительные действия становятся ошибками. Например, присваивание необъявленной переменной запрещено. Отдельную строку "use strict" для модуля добавлять не нужно.
Откуда берётся путь
В обычном браузерном примере путь считают от импортирующего файла, а не от HTML. Если перенести расчёт в папку lib, запись станет from "./lib/pricing.js". Для файла на одну папку выше используют ../pricing.js.
В этих примерах пишите расширение .js и точное имя файла. Pricing.js и pricing.js могут оказаться разными файлами на сервере.
Позже встретится запись import { useState } from "react". Здесь react – имя установленного пакета, а не файл рядом с main.js. В учебном проекте Next.js такие импорты обрабатывают инструменты сборки. Сам по себе обычный браузер не найдёт пакет по его имени без дополнительной настройки. Для текущих примеров достаточно относительных путей.
Если пример не запускается
| Что видно | Что проверить |
|---|---|
Cannot use import statement outside a module | У входного script должен быть type="module" |
does not provide an export named ... | Совпадают ли имя и вид экспорта с импортом |
404 при загрузке файла | Путь от импортирующего файла, расширение и регистр |
Ошибка загрузки при адресе file:// | Откройте страницу через Live Server |
| Вместо JavaScript получен HTML, ошибка MIME-типа | Проверьте в Network ответ запроса: сервер мог вернуть страницу ошибки вместо файла |
Если зависимость не загрузилась или в ней нет нужного экспорта, использующий её модуль не выполнится. Например, console.log из main.js не появится. Поэтому сначала читайте ошибку импорта, а не ищите проблему в формуле расчёта.
Попробуйте сами
- Соберите три файла из первого примера и получите
600в консоли. - Добавьте именованный экспорт
formatPrice(price), который возвращает строку вроде"600 ₽". Импортируйте его и покажите оформленный результат расчёта. - Временно уберите
exportу функции. Прочитайте сообщение браузера и восстановите рабочую версию. - Перенесите
pricing.jsв папкуlibи исправьте путь. Формула и результат должны остаться прежними.
Теперь можно выполнить задание JS-03. Для дополнительной практики в нём можно разделить готовую книжную полку на модули.
Справочники: модули JavaScript на MDN, import и export.