Модули и инструменты — Примеры

Пример 1 — Один файл, глобальные имена и работа, выполняемая при импорте

Всё, что нужно приложению, живёт в одном скрипте, а вызов аналитики выполняется как побочный эффект загрузки модуля. Скажите, чего не хватает границе файла, и разделите код так, чтобы каждый модуль заявлял, что он предлагает, и ничто не выполнялось случайно.

// utils.js — каждое имя глобально
function formatMoney(cents) { /* ... */ }
function buildInvoice(order) { /* ... */ }
function sendReceipt(invoice) { /* ... */ }
// entry.js
import "./analytics.js";   // голый импорт; эффект невидим
trackPageView();           // и это тоже выполняется на верхнем уровне модуля

Решение

// format.js
export const CURRENCY = "GBP";
export function formatMoney(cents) { /* ... */ }
// invoice.js
import { CURRENCY, formatMoney } from "./format.js";
export function buildInvoice(order) { /* ... */ }
// entry.js — читается как список импортов и один осознанный вызов
import { buildInvoice } from "./invoice.js";
import { trackPageView } from "./analytics.js";

trackPageView();

Именованные экспорты дают каждому файлу границу: invoice.js точно заявляет, что ему нужно из format.js, опечатка в имени падает сразу, а граф зависимостей становится чем-то, что сборщик умеет прочитать. Модуль выполняется один раз, в порядке графа, поэтому вызов верхнего уровня происходит в момент, которого никто не выбирал, и его нельзя ни повторить, ни отменить; однако если экспортировать функцию и вызвать её из точки входа, время выбирает автор.

Пример 2 — Тяжёлый редактор в начальном бандле

Редактор форматированного текста импортируется в начале модуля, поэтому каждый посетитель скачивает его до того, как страница станет интерактивной. Открывают его при этом немногие. Скажите, что сборщик вынужден сделать, и загружайте редактор тогда, когда он понадобится.

import { Editor } from "./editor.js";   // ~240 kB, всегда в бандле точки входа

export function openEditor() {
  return new Editor();
}

Решение

export async function openEditor() {
  const { Editor } = await import("./editor.js");   // отдельный чанк
  return new Editor();
}

// вызывающий код
button.addEventListener("click", async () => openEditor());

Статический import входит в граф, который сборщик обязан включить в бандл точки входа, поэтому 240 kB редактора оплачивает каждый, кто загружает страницу. import() возвращает промис и разрешается во время выполнения — благодаря этому сборщик выделяет отдельный чанк, а запрашивается он тем взаимодействием, которому нужен. Плата — короткое ожидание при первом использовании функции и async-функция, которую вызывающему придётся обработать.