Перейти к основному содержимому

Экосистема JavaScript - инструменты и фреймворки

Play ITЗагрузка интерактивного демо…

Play ITЗагрузка интерактивного демо…

См. также: Карта экосистемы — Runtime и Frameworks · Тестирование — Vitest и Testing Library · Модули в Node.js · Первая программа на Node.js

Разработчику Архитектору

Play ITЗагрузка интерактивного демо…


Инструменты и фреймворки

Архитектура крупных JavaScript-приложений

Современные JavaScript‑приложения — особенно CRM, BPM, ERP и другие enterprise‑системы — достигают значительных масштабов — сотни тысяч строк кода, десятки тысяч активных пользователей, интеграции с внешними сервисами и внутренними подсистемами. Архитектурная устойчивость таких систем опирается на принцип модульности: разложение приложения на логически и технически обособленные части, каждая из которых отвечает за чётко определённую область функциональности.

Соответственно, в крупном приложении добавляются принципы:

  • Модульность — разделение кода на автономные, переиспользуемые и тестируемые части.
  • Ленивая загрузка (lazy loading) — загрузка кода по требованию, а не единовременно при старте приложения.
  • Безопасное взаимодействие между компонентами — минимизация связности, контроль над интерфейсами.
  • Управление зависимостями — явное декларирование и разрешение зависимостей между модулями.
  • Поддержка версионной совместимости — возможность одновременной работы различных версий библиотек.

На практике эти принципы решают очень конкретные проблемы команды:

ПринципЧто предотвращает в реальном проекте
МодульностьЭффект "одна правка сломала полприложения"
Ленивая загрузкаМедленный первый экран и тяжёлый бандл
Безопасные интерфейсыСкрытые зависимости между командами
Управление зависимостямиКонфликты версий и нестабильные сборки
Версионная совместимостьБолезненные массовые апгрейды "за один релиз"

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

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

Преимущества модульного подхода:

  • Уменьшение глобального загрязнения.
  • Повторное использование кода.
  • Легкость тестирования и замены реализаций.
  • Возможность независимой разработки и развёртывания.

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

Пример структуры модуля:

// modules/user/profile.js
const apiUrl = '/api/v2/users'; // приватная константа

function fetchUserProfile(id) {
return fetch(`${apiUrl}/${id}`).then(r => r.json());
}

function formatDisplayName(user) {
return `${user.lastName} ${user.firstName}`;
}

export { fetchUserProfile, formatDisplayName };

Разбор:

  • apiUrl объявлен внутри модуля и не экспортируется, поэтому остаётся внутренней деталью реализации.
  • fetchUserProfile(id) формирует URL через шаблонную строку и возвращает промис с JSON-данными пользователя.
  • В then(r => r.json()) колбэк преобразует HTTP-ответ в объект JavaScript для дальнейшей работы.
  • formatDisplayName(user) отделяет представление данных от их загрузки: один метод получает, второй форматирует.
  • export { ... } явно задаёт публичный API модуля — внешнему коду доступны только перечисленные функции.
  • Такая структура снижает связность и делает безопасным внутренний рефакторинг файла.

Здесь apiUrl и логика fetchUserProfile инкапсулированы. Внешний код может использовать только то, что указано в export. Инкапсуляция обеспечивает внутреннюю согласованность модуля и минимизирует побочные эффекты при изменении его реализации.

Модули могут быть атомарными (один файл — один модуль) или составными (модуль представлен директорией с index.js, агрегирующим внутренние подмодули). Такая гибкость позволяет выстраивать иерархию — от простых утилит до сложных подсистем (например, модуль "Управление документооборотом" может включать подмодули "Шаблоны", "Маршруты", "Подписи").

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

Библиотеки встраиваются в приложение через менеджеры зависимостей (npm, pnpm, yarn), где каждая версия фиксируется в манифесте (package.json). Это позволяет одновременно использовать несколько версий одной библиотеки (в редких случаях — через алиасы или отдельные сборки), но в типичной практике приложение фиксирует одну версию каждой внешней зависимости.

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


import { fetchUserProfile } from './modules/user/profile.js';
import { formatDateTime } from 'date-fns';

Разбор:

  • import в начале файла объявляет зависимости модуля заранее, поэтому граф связей читается сразу.
  • Относительный путь ./modules/user/profile.js подключает локальный модуль проекта.
  • Импорт из 'date-fns' подключает внешнюю библиотеку из менеджера пакетов.
  • Фигурные скобки в обоих импортах означают именованные экспорты: имя в импорте должно совпадать с именем в export.
  • Такой стиль импорта помогает сборщику выполнять статический анализ и убирать неиспользуемый код при tree shaking.

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

Если модуль не используется ни в одном месте приложения, он может быть полностью исключён из итоговой сборки — механизм, известный как tree shaking. Он работает при соблюдении трёх условий:

  1. Используется формат ES‑модулей (не CommonJS).
  2. Модуль не содержит побочных эффектов при импорте.
  3. Сборщик поддерживает статический анализ (Rollup, Webpack ≥5, Vite).

В крупных системах отключение ненужных модулей особенно важно при поддержке нескольких вариантов сборки — например, одна версия для внутреннего админ‑интерфейса, другая — для внешнего портала клиентов. Тогда каждая сборка включает только те модули, которые действительно требуются в данном контексте.

Модули делятся на обязательные и необязательные по критерию жизненно важности для старта приложения.

Обязательные модули — ядро, без которого приложение не может функционировать. К ним относятся — механизм маршрутизации, базовый UI‑фреймворк (например, React с его reconciler’ом), глобальный store, сервисы аутентификации и авторизации. Эти модули загружаются синхронно на этапе инициализации.

Необязательные модули — функциональность, активируемая по требованию — отчёты, аналитика, настройки интеграций, редко используемые формы. Такие модули подключаются динамически, что снижает объём первоначальной загрузки и ускоряет время отклика.

Динамический импорт в JavaScript реализуется через import() — функцию, возвращающую промис:

async function loadReportingModule() {
const { generateReport } = await import('./modules/reporting/engine.js');
return generateReport;
}

Разбор:

  • import() — динамический импорт: модуль загружается только в момент вызова функции, а не при старте приложения.
  • await приостанавливает выполнение loadReportingModule, пока чанк не будет загружен и инициализирован.
  • Деструктуризация { generateReport } извлекает конкретный экспорт из загруженного модуля.
  • Возврат generateReport позволяет отложить тяжёлую функциональность до реальной потребности пользователя.
  • Этот паттерн лежит в основе lazy loading и уменьшает размер начального бандла.

Сборщики преобразуют такой вызов в отдельный чанк (файл), который загружается только при выполнении условия (например, при переходе в раздел "Отчёты"). Это — основа стратегии code splitting.

При проектировании системы важно чётко разделять обязательные и необязательные модули уже на этапе архитектурного анализа. Карта использования функциональности (usage map), построенная по логам или аналитике, помогает определить, какие модули следует динамизировать.


Алгоритмические шаблоны для архитектуры крупных JavaScript-приложений

1. Определение модуля

<модуль> = {
<приватные_сущности>,
<публичный_интерфейс>
}

2. Экспорт публичного интерфейса

export { <имя_функции>, <имя_константы>, <имя_класса> };

3. Именование и размещение модуля

<корень_проекта>/modules/<домен>/<подсистема>/<модуль>.js

4. Составной модуль (агрегация)

<корень_проекта>/modules/<домен>/<подсистема>/index.js
export { <часть1> } from './часть1.js';
export { <часть2> } from './часть2.js';

5. Импорт обязательного модуля


import { <имя> } from '<путь_к_модулю>';


6. Импорт внешней библиотеки


import { <имя> } from '<название_библиотеки>';


7. Динамический импорт необязательного модуля

const { <имя> } = await import('<путь_к_модулю>');

8. Условная загрузка модуля

if (<условие_активации>) {
const модуль = await import('<путь_к_модулю>');
модуль.<метод>(<аргументы>);
}

9. Построение графа зависимостей

<приложение> → <обязательные_модули> → <ядро>
<приложение> → <необязательные_модули> → <ленивая_загрузка>

10. Отключение модуля через исключение из графа

удалить import '<путь_к_модулю>' из всех файлов
→ сборщик исключает модуль из финального бандла

11. Версионное управление зависимостей

<библиотека>@<версия> в package.json

12. Алиасирование пути к модулю

// vite.config.js или webpack.config.js
{
resolve: {
alias: {
"@user": "./modules/user"
}
}
}

import { fetchUserProfile } from "@user/profile.js";


13. Использование модуля через его интерфейс

const результат = <модуль>.<публичный_метод>(<аргументы>);

14. Инкапсуляция внутренней реализации

внутри <модуль>.js:
- <приватная_переменная> не экспортируется
- <вспомогательная_функция> не экспортируется
- доступ возможен только через export

15. Tree shaking — автоматическое исключение неиспользуемых частей

если <экспортируемая_сущность> не упоминается в import,
то она исключается из итоговой сборки

16. Разделение сборок по функциональным наборам

сборка "админка" → импортирует [модульА, модульБ, модульЯдро]
сборка "портал" → импортирует [модульВ, модульГ, модульЯдро]

17. Жизненный цикл модуля

загрузка → инициализация → использование → (опционально) выгрузка

18. Обработка ошибок при динамическом импорте

try {
const { <имя> } = await import('<путь>');
} catch (ошибка) {
// обработка недоступности модуля
}

Организация модульной архитектуры

Зависимости и безопасное взаимодействие

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


1. Статический контроль типов (при использовании TypeScript)

TypeScript проверяет совместимость сигнатур при компиляции. Если модуль A ожидает функцию вида (id — number) => Promise<User>, а модуль B предоставляет (id: string) => Promise<User>, ошибка будет обнаружена на этапе сборки.

Это особенно ценно при рефакторинге: изменение интерфейса одного модуля немедленно обнаруживается в местах его использования.


2. Контракты на уровне данных

Интерфейсы модулей часто реализуются через DTO (Data Transfer Objects) — простые объекты без методов, передаваемые между слоями. Для валидации таких объектов применяются схемы — zod, io-ts, yup. Проверка схемы происходит при входе в модуль:


import { UserProfileSchema } from './schemas/user';

function processUserProfile(raw: unknown) {
const validated = UserProfileSchema.parse(raw);
// дальше — работа только с validated, гарантированно соответствующим ожиданиям
}

Такой подход локализует ошибки: проблема возникает в точке входа, а не где‑то глубоко внутри логики. Общая шкала проверок — Проверка и валидация.


3. Изоляция побочных эффектов

Хорошо спроектированный модуль не вызывает побочные эффекты за пределами своего контекста без явного согласия. Например, модуль не подписывается на глобальные события, не изменяет DOM вне своей области ответственности, не записывает в localStorage без параметризации.

В случаях, где побочный эффект неизбежен (например, HTTP‑запрос), он инкапсулируется в специализированный адаптер — сервис с чётким интерфейсом (HttpClient, EventBus, StorageGateway). Модуль зависит не от реализации, а от абстракции, что упрощает тестирование и подмену реализаций.


4. Версионирование интерфейсов

Крупные системы живут годами, и за это время интерфейсы модулей могут эволюционировать. Чтобы избежать нарушения совместимости, применяются практики:

  • Семантическое версионирование (SemVer) внешних библиотек.
  • Интерфейсные версии (API versioning) для внутренних модулей: export { v1 as default } from './v1'; export { v2 } from './v2';.
  • Обратная совместимость на уровне конфигурации: новые параметры добавляются необязательно, старые сохраняются с устаревшим статусом (@deprecated в JSDoc).

Когда модуль A зависит от модуля B, в манифесте проекта (или в метаинформации сборки) фиксируется диапазон допустимых версий B. Инструменты вроде npm outdated или pnpm audit отслеживают расхождения.

npm outdated

Совместимость между версиями библиотек

Совместимость — практический результат управления зависимостями. В JavaScript‑экосистеме она обеспечивается через:

  • Плоскую структуру зависимостей (flat node_modules) — менеджеры вроде pnpm или Yarn Plug’n’Play позволяют избежать дублирования одних и тех же библиотек в разных версиях, тем самым устраняя конфликты.
  • Алиасы зависимостей — в package.json можно явно указать, какую версию библиотеки использовать, даже если другие пакеты требуют иную:
{
"dependencies": {
"react": "18.2.0"
},
"resolutions": {
"react": "18.2.0"
}
}
  • Полифилы и адаптеры — если новая версия библиотеки убрала метод, а старый код на него завязан, создаётся временный адаптер, восстанавливающий совместимость до полной модернизации.

Критически важно избегать ситуаций, когда две части приложения используют разные экземпляры одной библиотеки в несовместимых версиях — особенно это касается библиотек, хранящих глобальное состояние (например, React). В таких случаях возникают трудноуловимые ошибки — дублирование контекстов, рассинхронизация хуков, конфликты подписок.

Для предотвращения этого применяется стратегия единый экземпляр (single instance): сборщик настраивается так, чтобы все импорты react разрешались в один файл. В Webpack это достигается через resolve.alias или externals.


Системы модулей (AMD, CommonJS, ES Modules).

AMD (Asynchronous Module Definition) — это спецификация, определяющая способ организации модулей с поддержкой асинхронной загрузки. Она была разработана с учётом особенностей браузерного окружения, где сетевая задержка при загрузке файлов является ключевым фактором производительности.

AMD позволяет определять модули таким образом, что их зависимости загружаются асинхронно, без блокировки выполнения основного потока. Это критично для больших приложений, где загрузка всех модулей сразу привела бы к долгому времени запуска.

Две основные функции в AMD - define и require.

define(id?, dependencies, factory) — используется для определения модуля.

Здесь:

  • id — необязательный идентификатор модуля.
  • dependencies — массив строк, указывающих на другие модули, от которых зависит текущий.
  • factory — функция-конструктор, которая будет вызвана после загрузки всех зависимостей. Аргументы функции соответствуют загруженным модулям в порядке объявления.
define(['dependency1', 'dependency2'], function(dep1, dep2) {
// Логика модуля
return {
doSomething: function() {
dep1.action();
dep2.log('Module is ready');
}
};
});

require(dependencies, callback) — используется для загрузки и использования модулей в произвольном месте кода.

require(['moduleA', 'moduleB'], function(A, B) {
A.doSomething();
B.render();
});

Функция require гарантирует, что все перечисленные зависимости будут загружены до вызова колбэка.

AMD имеет особенности:

  • Поддержка асинхронной загрузки — модули могут быть загружены по сети динамически.
  • Гибкость в динамическом подключении — можно загружать модули "на лету", например, при переходе по маршруту.
  • Поддержка не-JavaScript ресурсов — некоторые реализации AMD (например, RequireJS) позволяют загружать CSS, шаблоны, JSON и другие типы файлов через плагины.

AMD был особенно популярен в период до массового внедрения ES6 и ESM.

Он широко использовался в enterprise-фреймворках:

  • Dojo Toolkit
  • Ext JS
  • Ранние версии AngularJS (через адаптеры)
  • SAPUI5
  • Creatio (ранее BPM'online) и BPMSoft.

RequireJS — одна из наиболее известных реализаций AMD. Она предоставляет динамическую загрузку модулей, управление графом зависимостей, оптимизацию сборки с помощью утилиты r.js, которая объединяет модули в единые файлы для production и поддержку конфигурации путей, алиасов и условной загрузки.

Несмотря на то, что RequireJS утратил доминирующую роль в новых проектах, он остаётся в legacy-системах, особенно в корпоративных решениях, где миграция на современные стандарты сопряжена со значительными рисками.

CommonJS — это инициатива сообщества, направленная на стандартизацию JavaScript вне браузера, в первую очередь для серверной среды.

CommonJS использует синхронную модель загрузки модулей через функцию require. Каждый файл представляет собой отдельный модуль с собственной областью видимости. Экспорт осуществляется через объект module.exports, импорт — через require.

Пример:

// math.js
function add(a, b) {
return a + b;
}
module.exports = { add };

// app.js
const math = require('./math');
console.log(math.add(2, 3));

Особенности CommonJS:

  • Синхронная загрузка — require блокирует выполнение до завершения загрузки и выполнения модуля.
  • Подходит для Node.js, где файлы доступны локально на диске и задержки минимальны.
  • Не поддерживается нативно в браузерах — требует сборщика (Webpack, Browserify и др.) для преобразования в браузерный формат.
  • Прост в использовании, хорошо работает с файловой системой.

CommonJS имеет и ограничения, к примеру, он непригоден для браузера без инструментов сборки из-за синхронной природы, не поддерживает динамическую загрузку "из коробки" (хотя современные сборщики эмулируют её) и может приводить к большим bundle-файлам при отсутствии tree-shaking. CommonJS остаётся стандартом де-факто в Node.js, хотя начиная с Node.js 14+ активно развивается поддержка ESM.

ES Modules (ECMAScript Modules, ESM) — это официальный стандарт модульности, принятый в спецификацию ECMAScript (ES6, 2015). Он представляет собой декларативный подход к модульности с поддержкой как статического, так и динамического импорта. Синтаксис включает два основных варианта:

  • import — для импорта сущностей.
  • export — для экспорта.

Код ITЗагрузка примера кода…

Также поддерживается дефолтный экспорт:

// logger.js
export default function log(message) {
console.log('[LOG]', message);
}

// main.js

import logger from './logger.js';

logger('Hello world');

ESM поддерживает динамический импорт через import() — функцию, возвращающую Promise. Это позволяет загружать модули по требованию. Это ключевой механизм для ленивой загрузки в современных SPA:

button.addEventListener('click', async () => {
const module = await import('./heavyComponent.js');
module.render();
});

Какие же особенности есть у ESM?

  • Статическая структура — анализ зависимостей возможен на этапе сборки (enables tree-shaking).
  • Асинхронная загрузка — реализуется через import() и поддерживается браузерами.
  • Нативная поддержка — в современных браузерах и Node.js (с флагом .mjs или "type": "module" в package.json).
  • Однократная загрузка — модуль кэшируется; повторные импорты возвращают тот же экземпляр.
  • Жёсткие правила относительных/абсолютных путей — в браузере требуется полное имя файла с расширением (например, ./utils.js, а не ./utils).

Преимущества перед предыдущими системами:

  • Единый стандарт, поддерживаемый движками.
  • Интеграция с современными инструментами (Vite, Webpack, Rollup).
  • Поддержка tree-shaking, code splitting, HMR.
  • Совместимость с TypeScript и JSX.

Переход с CommonJS на ESM в Node.js потребовал решения проблемы двойной поддержки. Для этого используются расширения .cjs/.mjs, поле "type": "module" в package.json и условные exports ("exports" в package.json).

Архитектура крупных JavaScript-приложений невозможна без чёткой системы модулей. Выбор между AMD, CommonJS и ES Modules определяется контекстом — целевой средой, требованиями к производительности, размером команды и историческим багажом проекта.

Если вам придётся работать с JS, то скорее всего не на ванильном, а на каком-то из вышеперечисленных вариантов. К примеру, это может быть разработка систем на платформе Creatio, которая как раз использует AMD.


Песочница (Sandbox) и шина сообщений

В сложных приложениях, где множество независимых модулей (например, от разных команд), нельзя допускать прямого доступа одного модуля к другому. Решение — песочница.

Что такое песочница? Это изолированная среда выполнения для модулей.

Модули не видят глобальных переменных и не могут напрямую вызывать друг друга, а взаимодействие происходит через центральную шину сообщений (event bus).

Как это работает?

// Модуль отправляет сообщение
sandbox.publish('user:login', { userId: 123 });

// Другой модуль подписывается
sandbox.subscribe('user:login', function(data) {
console.log('Пользователь вошёл:', data.userId);
});

Преимущества:

  • Изоляция: один модуль не может сломать другой.
  • Гибкость: модули можно добавлять/удалять без переписывания кода.
  • Безопасность: предотвращается загрязнение глобальной области.
  • Поддержка микроприложений и плагинов.

Такой подход используется в:

  1. Creatio / BPMSoft. Например, в Creatio можно представить, что:

    • Ядро предоставляет песочницу и шину сообщений.
    • Каждый модуль — это AMD-модуль, загружаемый по требованию.
    • Внешние библиотеки (Angular, Ext JS) подключаются как зависимости.
  2. Ext JS + Sencha Cmd. Ext JS активно использовал RequireJS и AMD до перехода на современные сборщики. MVC/MVVM архитектура хорошо сочетается с шиной сообщений.

  3. Backbone.js с Marionette

Angular, Vue, React не используют такой подход - там ES Modules.

Сегодня вместо AMD и песочниц используются:

  • ES Modules + Dynamic import() — для динамической загрузки.
  • Micro-Frontends — архитектура, где каждая часть приложения — независимое приложение.
  • Module Federation (Webpack 5) — позволяет объединять модули из разных приложений.
  • Web Workers / iframes — для настоящей изоляции.

В архитектуре крупных JavaScript-приложений, особенно в условиях мультикомандной разработки, высокой динамики изменений и необходимости поддержки долгосрочной совместимости, критически важным становится управление взаимодействием между модулями. Прямые зависимости между компонентами приводят к повышению связанности, затрудняют тестирование, усложняют замену реализаций и увеличивают риски регрессии.

Для решения этих проблем применяется подход, основанный на изоляции модулей и опосредованном взаимодействии через централизованные механизмы. Два ключевых элемента такого подхода — песочница (sandbox) и шина сообщений (event bus).

Песочница (sandbox) — это абстрактная среда выполнения, предоставляемая каждому модулю при его инициализации. Она выступает в роли посредника между модулем и остальной системой, ограничивая прямой доступ к глобальному окружению, другим модулям или внутренним механизмам ядра.

Цель песочницы — обеспечить контролируемую и безопасную коммуникацию, исключив возможность несанкционированного вмешательства в работу других компонентов.

Основные характеристики песочницы:

  • Изоляция: модуль не имеет прямого доступа к объектам других модулей.
  • Ограниченный API: модуль может использовать только те методы, которые явно предоставлены песочницей.
  • Единая точка входа/выхода: весь обмен данными проходит через строго определённые интерфейсы.
  • Контекст исполнения: песочница может инжектировать зависимости, управлять жизненным циклом модуля и перехватывать ошибки.

Таким образом, песочница представляет собой программный слой, реализующий политику безопасности и взаимодействия.

При отсутствии прямых ссылок между модулями возникает необходимость в альтернативном способе обмена информацией. Этой функцией служит шина сообщений (message bus, event bus) — централизованный механизм маршрутизации событий.

В шине сообщений, модули не вызывают друг друга напрямую. Вместо этого они:

  • Публикуют события — отправляют сообщение в шину с указанием типа события и полезной нагрузки.
  • Подписываются на события — объявляют интерес к определённым типам сообщений и регистрируют обработчики.
// Модуль A: публикует событие
sandbox.publish('user:login', { userId: 123 });

// Модуль B: подписывается на событие
sandbox.subscribe('user:login', function(data) {
console.log('Пользователь вошёл:', data.userId);
});

Система шины сообщений гарантирует, что все подписчики получат уведомление, если событие произошло, независимо от времени их инициализации (при условии, что подписка была зарегистрирована до момента публикации).

Типы событий:

  • События домена — отражают бизнес-события (order:created, user:updated).
  • События UI — связаны с интерфейсом (modal:open, form:submit).
  • Системные события — управляют жизненным циклом (module:loaded, app:ready).

Причём эти события уже не относятся к DOM-событиям вроде кликов мыши, нет. Это полноценные действия в других модулях.

Такая архитектура естественно поддерживает модели, где функциональность расширяется за счёт внешних модулей, в том числе сторонних разработчиков. Можно динамически добавлять, удалять или заменять модули без изменения кода других компонентов. Например, плагин может просто подписаться на событие ядра и расширить его функциональность. Модули не зависят друг от друга ни на уровне кода, ни на уровне времени загрузки. Это позволяет разрабатывать, тестировать и развёртывать модули независимо. Песочница предотвращает загрязнение глобального пространства имён, блокирует прямой доступ к внутренним структурам и минимизирует влияние ошибок одного модуля на другие. Поведение модуля можно проверять, имитируя события и анализируя публикуемые ответы, без необходимости запуска всей системы.

Одним из характерных примеров применения песочницы и шины сообщений является платформа Creatio (ранее BPM'online), ориентированная на enterprise-сегмент и построенная на основе AMD и собственной архитектуры модульности.

Архитектурные особенности:

  • Ядро системы предоставляет экземпляр песочницы каждому загружаемому модулю.
  • Все модули являются AMD-модулями, загружаемыми по требованию.
  • Взаимодействие между модулями осуществляется исключительно через sandbox.publish() и sandbox.subscribe().
  • Внешние библиотеки (например, Ext JS) подключаются как зависимости через AMD, но их использование контролируется через API песочницы.

Такой подход позволяет настраивать поведение CRM/BPM-системы без изменения ядра, разрабатывать модули независимыми командами и обеспечивать обратную совместимость между версиями.

Ранние версии фреймворка Ext JS активно использовали RequireJS и AMD для организации модульности. Архитектура MVC/MVVM, заложенная в Ext JS, хорошо сочетается с шиной сообщений: контроллеры могут обмениваться данными через глобальный event bus, минимизируя прямые связи между представлениями и моделями. Sencha Cmd — инструмент сборки — позволял управлять зависимостями, оптимизировать загрузку и создавать изолированные модули, что соответствовало принципам песочницы.

Фреймворк Backbone.js, хотя и не предусматривает шины сообщений "из коробки", допускает её реализацию через Backbone.Events. Библиотека Marionette расширяет этот подход, предоставляя EventAggregator — централизованный менеджер событий, выполняющий функции шины.

В ранних моделях микрофронтендов, особенно в гибридных SPA, где части приложения разрабатываются разными командами и интегрируются в runtime, используется шина сообщений для координации. Например, один микрофронтенд может уведомлять другой о смене пользователя или состоянии авторизации.

Хотя модель песочницы и шины сообщений остаётся актуальной в legacy-системах, в современных архитектурах она постепенно вытесняется более масштабируемыми и стандартизированными решениями. С появлением нативной поддержки ES Modules и динамического импорта (import()), необходимость в AMD и внешних системах загрузки отпала. Теперь модули могут загружаться по требованию без дополнительных библиотек:

button.addEventListener('click', async () => {
const { renderChart } = await import('./charts.js');
renderChart(data);
});

Это решает задачу ленивой загрузки, но не обеспечивает изоляции на уровне выполнения.

Архитектура микрофронтендов предполагает, что каждая часть приложения — это самостоятельное приложение со своей технологической стеком, CI/CD и командой. Взаимодействие между ними может происходить через Custom Events, Shared libraries, State managers или ту же шину сообщений, но на уровне окна (window.postMessage).

Module Federation — механизм, позволяющий объединять модули из разных сборок Webpack в единую runtime-среду. Он даёт возможность импортировать модули "на лету" из удалённой сборки, совместно использовать библиотеки, строить распределённые приложения без централизованной сборки. Module Federation можно рассматривать как эволюцию идеи песочницы: модули остаются независимыми, но получают контролируемый доступ к функциональности друг друга.

Для достижения настоящей изоляции, когда модуль не может повлиять на глобальное состояние, используются:

  • Web Workers — выполнение кода в отдельном потоке без доступа к DOM.
  • iframes — изоляция на уровне документа, с собственным window, document, localStorage.

Такие подходы применяются, например, в платформах, принимающих пользовательские скрипты (виджеты, плагины), где безопасность является первостепенной.


Общее о фреймворках и транслируемых языках

Прочие транслируемые языки, подобные TypeScript - CoffeeScript, Flow, Dart, Brython.

ActionScript — язык среды Adobe Flash и AIR, синтаксически основан на ECMAScript (особенно версия AS3 с классами и строгими типами). Им делали анимацию, видеоплееры, браузерные игры и богатые интерфейсы до распространения HTML5 Canvas, WebGL и зрелого JavaScript. После отключения Flash в браузерах (около 2020 года) новые веб-проекты на ActionScript не начинают; термин полезен при разборе legacy и при сравнении с современным стеком SPA.

Фреймворки JavaScript.

Фронтенд:

ФреймворкОписание
ReactБиблиотека для UI. Создана Facebook (Meta), работает с компонентами — маленькими частями интерфейса. Не навязывает архитектуру, но требует больше самостоятельности. Часто используется с ReactDOM, React Router, Redux, Next.js react.dev metanit.com/web/react cheatsheets.zip/react
VueПрогрессивный фреймворк. Легко внедряется в существующий проект, весьма простой и понятный для новичков. Используется с Vue Router, Vuex, Nuxt.js vuejs.org metanit.com/web/vue cheatsheets.zip/vue
AngularJSФреймворк 1.x (2010): расширения HTML, двусторонняя привязка, digest-цикл. Legacy; с 2016 года Google развивает платформу Angular (2+) — другой код и инструменты. Angular в разделе
AngularФреймворк от Google. Написан на TypeScript, имеет чёткую структуру: компоненты, сервисы, директивы, модули. angular.dev metanit.com/web/angular2
Ember.jsПолноценный фреймворк (2011): маршруты, сервисы, Ember Data, шаблоны; ориентирован на предсказуемые enterprise-SPA. emberjs.com
SvelteКомпилируется в чистый JavaScript, не использует виртуальный DOM. Логика, разметка и стили объединены в одном файле компонента. svelte.dev
Ext JSExtended JavaScript (Sencha Ext JS), включает виджеты, графики, древовидные структуры и меню для создания сложных интерфейсов с поддержкой MVC-шаблона с разделением логики приложения, данных и представлений. www.sencha.com/products/extjs metanit.com/web/extjs · справочник раздела

Фронтенд-фреймворки помогают создавать динамические интерфейсы и управлять состоянием приложения.

Бэкенд (Node.js):

Официальный сайт Node.js - https://nodejs.org/

ФреймворкОписание
ExpressМинималистичный фреймворк expressjs.com
NestJSФреймворк с TypeScript nestjs.com
FastifyАльтернатива Express (высокая производительность) fastify.dev
KoaAsync middleware от авторов Express koajs.com
HapiКонфигурация через объекты, встроенная валидация hapi.dev
HonoЛёгкий, Node/Deno/Bun/edge hono.dev
AdonisJSMVC «как Laravel» для Node adonisjs.com
FeathersJSREST + WebSocket, hooks feathersjs.com
MeteorFull-stack JS, live-данные meteor.com

Meta-frameworks (UI + маршрутизация + SSR): Next.js, Nuxt, SvelteKit, Remix, Blitz.js, RedwoodJS — см. Meta Frameworks и справочник веб-фреймворков.

Фреймворк — это набор готовых решений и структура, которые помогают разрабатывать приложения быстрее, проще и с лучшими практиками.

Фреймворк — это заранее подготовленная структура кода , которая предоставляет готовые компоненты, архитектурные шаблоны, правила организации проекта и расширяемость. В отличие от библиотек, где мы решаем, когда и как использовать функции, во фреймворке всё наоборот: фреймворк управляет нашим кодом . То есть мы подстраиваемся под его правила.

Работа с фреймворком подразумевает:

  1. Импорт модулей (к примеру, import React from 'react';).
  2. Использование специфичных конструкций, к примеру

Код ITЗагрузка примера кода…

  1. Следование определённой структуре проекта. Например, в Angular или NestJS у нас будут папки components, services, modules и так далее.
  2. Использование CLI (интерфейс командной строки) - большинство фреймворков предоставляют CLI-инструменты:
- ng new my-app — Angular
- create-react-app my-app — React
- nest new my-project — NestJS
- npm create vue@latest — Vue

Эти команды создают уже подготовленную структуру проекта с нужными зависимостями и конфигами.

Среди сред можно выделить Electron — открытый фреймворк для десктопа на Chromium (отрисовка UI, V8) и Node.js (файлы, процессы, сеть). Окно приложения — это веб-страница; "главный" процесс Node управляет жизненным циклом, renderer-процессы изолированы. Сборки работают на Windows, macOS и Linux. Подробнее о роли Chromium и V8 — в применении JavaScript в вебе; практика десктопа — Electron + React.

На Electron работают Visual Studio Code, Slack, Discord, Postman, Atom, Trello Desktop, GitHub Desktop - то есть, среда довольно функциональная и позволяет формировать GUI. У Electron есть и альтернативы вроде Tauri (для Rust), NW.js, Flutter Desktop (Dart), React Native.

Минимальное приложение на таком движке имеет package.json, main.js, index.html.


Букмарклеты

Букмарклет (bookmarklet) — закладка в браузере, в поле URL которой записан код javascript:.... При нажатии скрипт выполняется в контексте текущей открытой страницы — может подсветить ссылки, собрать таблицу в буфер, изменить стили для чтения. Это наследие эпохи, когда не было единого API расширений.

Сегодня те же задачи чаще решают расширения браузера, userscripts (Tampermonkey) или закладка "открыть DevTools". Букмарклеты остаются учебным примером того, как мало нужно для "встраивания" JS в чужой сайт — и почему важны политики CSP и осторожность с чужим кодом в закладках.


jQuery

jQuery — JavaScript-библиотека для DOM, событий, анимации и AJAX (Джон Резиг, 2006). На пике популярности была на большинстве сайтов; сегодня — legacy, CMS и простые страницы, в новых SPA её заменяют React, Vue и нативные API.

Полный разбор: jQuery — обзор · справочник API · jquery.com · api.jquery.com


Сборка для браузера — Webpack и Vite

Браузер не выполняет "как есть" цепочку из сотен файлов import из node_modules, не понимает SCSS и не транспилирует новый синтаксис ES в старые движки. Сборщик (bundler) собирает граф зависимостей, прогоняет файлы через loaders, применяет plugins и выдаёт готовые *.js, *.css и ассеты в каталог dist/.

ЭтапЧто делает
Entryточка входа — с неё обход графа import
Loadersпреобразуют типы файлов (JS→JS, SCSS→CSS, PNG→URL)
Pluginsпобочные эффекты — очистка dist, копирование, минификация
Outputимена и путь итоговых бандлов

Связка с модулями — в структуре и подключении JS; npm-скрипты и devDependencies — в Первая программа на Node.js.

Минимальный Webpack 5

// webpack.config.js
const path = require('path');

module.exports = {
entry: { main: './src/index.js' },
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
mode: 'development', // или 'production'
};
{
"scripts": {
"dev": "webpack --mode development",
"build": "webpack --mode production"
},
"devDependencies": {
"webpack": "^5",
"webpack-cli": "^5"
}
}

npm run dev — сборка без агрессивной минификации; npm run build — production-бандл для деплоя.

Ключевые настройки Webpack

НастройкаНазначение
entryодна строка, массив или объект { main: './src/index.js', admin: './src/admin.js' }
output.filenameшаблон [name].[contenthash].js — cache busting в production
module.rulesцепочки loaders (babel-loader, css-loader, sass-loader)
optimization.splitChunksвынести node_modules в vendor/chunk
resolve.aliasкороткие пути (@/./src/)
devtool: 'source-map'source maps для отладки

Несколько точек входа — отдельные бандлы для страниц или legacy-скриптов:

entry: {
main: './src/index.js',
library: './src/library.js',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},

Транспиляция под старые браузерыbabel-loader + @babel/preset-env + файл .browserslistrc (список целевых браузеров для Autoprefixer и Babel):

>= 1%
last 2 versions
not dead

Стили в бандле — import в JS и цепочка loaders (справа налево):


import './styles/main.scss';

module: {
rules: [{
test: /\.scss$/i,
use: ['style-loader', 'css-loader', 'sass-loader'],
}],
},

В production CSS часто выносят в отдельный файл через mini-css-extract-plugin. JSON и npm-пакеты подключаются обычным import — Webpack разрешает их из node_modules.

Vendor-chunk — код библиотек отдельно от прикладного:

optimization: {
splitChunks: { chunks: 'all', cacheGroups: { vendor: { test: /node_modules/ } } },
},

Vite — dev-сервер и быстрая сборка

Vite (используют шаблоны Vue/React/Svelte и многие greenfield-проекты):

РежимПоведение
vite (dev)нативные ES-модули в браузере, трансформация "по запросу", HMR
vite buildproduction-сборка через Rollup, tree shaking, code splitting
// vite.config.js

import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
resolve: {
alias: { '@': path.resolve(__dirname, 'src') },
},
build: {
outDir: 'dist',
sourcemap: true,
},
});
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}

Конфиг короче, cold start dev-сервера обычно быстрее Webpack. Сложные legacy-сборки (кастомные loaders, Module Federation) чаще остаются на Webpack.

Webpack и Vite — когда что

КритерийWebpackVite
Новый SPA (2024+)возможен, но реже старттипичный выбор
Module Federation, нестандартные loadersзрелая экосystemограниченнее
Скорость dev-серверамедленнее на больших проектахбыстрый старт
Конфигподробный, гибкийминимальный для типовых кейсов
Productionсвой pipelineRollup под капотом
Сборка CSS отдельно от JS

CSS иногда собирают отдельно от JavaScript цепочкой node-sass → autoprefixer → cleancss и npm-run-all, а JS — Webpack. В современных проектах SCSS/CSS почти всегда идут через loaders Vite/Webpack

вместо устаревшего node-sass используют пакет sass (Dart Sass).

Фреймворки часто прячут конфиг: React + Vite, Next.js, Nuxt — у них свои команды dev/build, но под капотом те же идеи entry → bundle → deploy. Практические маршруты по runtime и UI — в разделе 3-ecosystem.


Как выбирать инструменты без хаоса

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


Быстрая матрица выбора

СитуацияБазовая связкаПочему это работает
Небольшой внутренний сервисNode.js + Express/Fastify + ESMБыстрый запуск и понятные контракты
Публичный веб-продуктReact/Vue + Vite/Next + APIУправляемый UI и удобный деплой
Большой legacy-проектСтабилизация модулей и сборки, затем рефакторингСнижение риска регрессий
Много независимых командМодульные границы + контракты + CIПредсказуемая эволюция системы

Практический план роста проекта

  1. Зафиксировать структуру модулей и публичные интерфейсы.
  2. Ввести единые правила импорта, алиасов и именования.
  3. Добавить проверки в CI — линтер, тесты, type-check.
  4. Включить lazy loading для редко используемых разделов.
  5. Контролировать размер бандла и скорость первой загрузки.
Рекомендую читать дальше

Для backend-практики — Node.js и Express. Для UI — React, первая программа, Vue. Карта всех веток: 3-ecosystem. Сборка — Webpack / Vite выше.


Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.