Модульность является одной из ключевых особенностей современной разработки. Разделение кода на независимые файлы позволяет упрощать сопровождение проектов, повышать переиспользуемость компонентов и уменьшать связанность между частями приложения.
В экосистеме JavaScript исторически сформировались две основные модульные системы:
Parcel предоставляет полноценную поддержку обеих систем и способен автоматически анализировать зависимости между ними, обеспечивая совместимость даже в смешанных проектах.
ES-модули появились как часть стандарта ECMAScript и стали официальным способом организации кода в JavaScript.
Модуль может экспортировать переменные, функции, классы и другие сущности.
export const PI = 3.14159;
export function square(x) {
return x * x;
}
Другой файл может импортировать эти значения:
import { PI, square } from "./math.js";
console.log(PI);
console.log(square(4));
Модуль может содержать один экспорт по умолчанию.
export default function greet(name) {
return `Hello, ${name}`;
}
Импорт выполняется следующим образом:
import greet from "./greet.js";
console.log(greet("John"));
При необходимости импортируемые элементы могут получать другие имена.
import { square as calcSquare } from "./math.js";
console.log(calcSquare(5));
Для получения объекта со всеми экспортируемыми значениями используется специальный синтаксис.
import * as math from "./math.js";
console.log(math.PI);
console.log(math.square(10));
До появления ES-модулей экосистема Node.js использовала CommonJS.
function square(x) {
return x * x;
}
module.exports = {
square
};
Импорт:
const math = require("./math");
console.log(math.square(4));
Сокращённая форма:
exports.square = function (x) {
return x * x;
};
exports.cube = function (x) {
return x * x * x;
};
Использование:
const math = require("./math");
console.log(math.cube(3));
Одной из сильных сторон Parcel является встроенная поддержка современных стандартов JavaScript без необходимости дополнительной настройки.
Достаточно использовать стандартный синтаксис:
import { calculateTotal } from "./utils.js";
console.log(calculateTotal());
Parcel автоматически:
Parcel самостоятельно определяет используемую модульную систему.
Например, следующий файл будет распознан как ES-модуль:
import { api } from "./api.js";
export function loadData() {
return api.get();
}
Файл CommonJS:
const api = require("./api");
module.exports = {
loadData() {
return api.get();
}
};
Дополнительная конфигурация обычно не требуется.
Реальные проекты часто содержат обе модульные системы одновременно.
Parcel умеет корректно связывать такие зависимости.
Файл CommonJS:
module.exports = function sum(a, b) {
return a + b;
};
ES-модуль:
import sum from "./sum.js";
console.log(sum(2, 3));
Parcel автоматически создаёт совместимый интерфейс.
ES-модуль:
export function multiply(a, b) {
return a * b;
}
CommonJS:
const math = require("./math.js");
console.log(math.multiply(4, 5));
Во время сборки Parcel обрабатывает такую зависимость и обеспечивает корректную работу.
Одно из важных преимуществ ES-модулей заключается в возможности статического анализа.
Parcel способен определить используемые зависимости ещё до выполнения программы.
Пример:
import { formatDate } from "./date.js";
console.log(formatDate());
Сборщик заранее знает:
Для CommonJS подобный анализ менее эффективен, поскольку зависимости могут определяться динамически.
Tree Shaking — механизм удаления неиспользуемого кода из итоговой сборки.
Рассмотрим модуль:
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export function multiply(a, b) {
return a * b;
}
Использование:
import { add } from "./math.js";
console.log(add(5, 3));
Parcel обнаружит, что функции subtract и
multiply нигде не используются, и удалит их из
производственной сборки.
ES-модули имеют фиксированную структуру импортов и экспортов:
import { add } from "./math.js";
Зависимость известна заранее.
В CommonJS возможна динамическая загрузка:
const moduleName = "./math";
const math = require(moduleName);
В подобных случаях статический анализ существенно усложняется.
Parcel полностью поддерживает динамические импорты стандарта ECMAScript.
button.addEventListener("click", async () => {
const module = await import("./dialog.js");
module.showDialog();
});
При использовании динамического импорта Parcel создаёт отдельный бандл.
const editor = await import("./editor.js");
В результате:
Многие библиотеки публикуют одновременно версии CommonJS и ES Modules.
Пример структуры:
{
"main": "./dist/index.cjs",
"module": "./dist/index.mjs"
}
Здесь:
main указывает на CommonJS-версию;module содержит ESM-сборку.Parcel анализирует эти поля и выбирает наиболее подходящий вариант.
В Node.js тип модулей может определяться через поле
type.
{
"type": "module"
}
После этого файлы .js интерпретируются как
ES-модули.
Пример:
import { api } from "./api.js";
Если указано:
{
"type": "commonjs"
}
или поле отсутствует, используется поведение CommonJS.
Parcel учитывает эти настройки при обработке проекта.
Для явного указания типа могут использоваться специальные расширения.
app.mjs
export const version = "1.0.0";
app.cjs
module.exports = {
version: "1.0.0"
};
Parcel корректно обрабатывает оба варианта.
ES-модули позволяют создавать промежуточные точки доступа к API.
Файл:
export { add } from "./add.js";
export { subtract } from "./subtract.js";
export { multiply } from "./multiply.js";
Использование:
import { add, multiply } from "./math/index.js";
Parcel анализирует такие цепочки экспортов и включает только реально используемые зависимости.
Parcel поддерживает импорт JSON как модулей.
Файл:
{
"name": "Parcel Demo",
"version": "1.0.0"
}
Импорт:
import data from "./package-info.json";
console.log(data.name);
Содержимое JSON автоматически становится частью графа зависимостей.
В Parcel модульная система распространяется не только на JavaScript.
Можно импортировать различные ресурсы:
import "./styles.css";
import logo from "./logo.svg";
import image from "./photo.png";
import config from "./config.json";
Каждый такой импорт рассматривается как зависимость и проходит через соответствующий пайплайн обработки.
При создании библиотек рекомендуется публиковать ESM-версии.
Причины:
Пример:
export function parse() {}
export function validate() {}
export function serialize() {}
Если приложение использует только:
import { parse } from "my-library";
Parcel сможет исключить остальные функции из итогового бандла.
Parcel выполняет роль слоя совместимости между различными форматами модулей.
Поддерживаются:
| Источник | Получатель | Поддержка |
|---|---|---|
| ESM | ESM | Полная |
| CommonJS | CommonJS | Полная |
| CommonJS | ESM | Полная |
| ESM | CommonJS | Полная |
Благодаря этому возможно постепенно переводить крупные проекты с CommonJS на ES Modules без полного переписывания кодовой базы.
Для нового кода рекомендуется использовать:
import { createApp } from "./app.js";
вместо:
const app = require("./app");
Преимущества:
const chartModule = await import("./charts.js");
Подобный подход уменьшает размер начального бандла и ускоряет загрузку страницы.
Нежелательный вариант:
const moduleName = getModuleName();
const module = require(moduleName);
Такой код усложняет анализ зависимостей и снижает эффективность оптимизаций.
Предпочтительнее использовать явные импорты:
import analytics from "./analytics.js";
или динамический импорт ECMAScript:
const analytics = await import("./analytics.js");
Вместо крупного объекта:
export default {
add,
subtract,
multiply,
divide
};
часто выгоднее использовать именованные экспорты:
export function add() {}
export function subtract() {}
export function multiply() {}
export function divide() {}
Так Parcel получает больше возможностей для удаления неиспользуемого кода и оптимизации итоговой сборки.