Модульная архитектура

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


Разделение маршрутов и контроллеров

В модульной архитектуре каждый маршрут должен быть привязан к отдельному контроллеру. Контроллер — это модуль, который отвечает за обработку данных и взаимодействие с представлением. Пример структуры проекта:

/app
  /controllers
    homeController.js
    aboutController.js
    userController.js
  /views
    homeView.js
    aboutView.js
    userView.js
  /routes
    index.js
  app.js

homeController.js:

import { renderHome } from '../views/homeView.js';

export function homeController(ctx) {
  const data = { title: 'Главная страница', content: 'Добро пожаловать!' };
  renderHome(data);
}

aboutController.js:

import { renderAbout } from '../views/aboutView.js';

export function aboutController(ctx) {
  const data = { title: 'О нас', content: 'Информация о компании' };
  renderAbout(data);
}

Определение маршрутов через Page.js

Маршруты лучше держать в отдельном модуле, чтобы не смешивать конфигурацию навигации с логикой контроллеров:

routes/index.js:

import page from 'page';
import { homeController } from '../controllers/homeController.js';
import { aboutController } from '../controllers/aboutController.js';
import { userController } from '../controllers/userController.js';

export function setupRoutes() {
  page('/', homeController);
  page('/about', aboutController);
  page('/user/:id', userController);
  page();
}
  • page(path, handler) — назначает обработчик для конкретного маршрута.
  • Динамические сегменты (:id) позволяют получать параметры маршрута через объект ctx.params.

Контекст маршрута (ctx) и его возможности

Каждый обработчик получает объект ctx, который содержит:

  • ctx.path — текущий путь в адресной строке.
  • ctx.params — параметры маршрута (например, ctx.params.id).
  • ctx.querystring — строка запроса после ?.
  • ctx.state — объект для хранения данных между middleware и контроллерами.

Использование ctx.state упрощает передачу информации между различными модулями без глобальных переменных.


Middleware и цепочка обработки

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

Пример middleware для авторизации:

function authMiddleware(ctx, next) {
  if (!ctx.state.user) {
    page.redirect('/login');
  } else {
    next();
  }
}

Использование в маршрутах:

page('/dashboard', authMiddleware, dashboardController);
  • Middleware вызывается последовательно, пока не будет вызван next().
  • Это позволяет создавать повторно используемые модули логики, независимые от конкретных маршрутов.

Рендеринг представлений

Представления также лучше держать в отдельных модулях. Это обеспечивает независимость от контроллеров и упрощает тестирование. Представление обычно получает данные и формирует HTML:

homeView.js:

export function renderHome(data) {
  const app = document.getElementById('app');
  app.innerHTML = `
    

${data.title}

${data.content}

`; }

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

  • Контроллеры и представления могут развиваться независимо.
  • Легко добавлять новые страницы без изменения существующего кода.
  • Улучшается читаемость и поддержка проекта.

Динамические маршруты и параметры

Page.js позволяет создавать маршруты с динамическими сегментами, что особенно полезно для SPA с профилями пользователей или товарами.

userController.js:

import { renderUser } from '../views/userView.js';

export function userController(ctx) {
  const userId = ctx.params.id;
  const userData = getUserDataById(userId);
  renderUser(userData);
}
  • ctx.params.id получает значение из URL /user/123.
  • Функция getUserDataById может быть отдельным модулем сервиса, что соответствует принципам модульной архитектуры.

Асинхронные операции и загрузка данных

Контроллеры могут выполнять асинхронные операции, такие как запросы к API. Page.js поддерживает async функции:

export async function userController(ctx) {
  try {
    const user = await fetch(`/api/users/${ctx.params.id}`).then(res => res.json());
    renderUser(user);
  } catch (error) {
    console.error('Ошибка загрузки пользователя', error);
  }
}
  • Асинхронные контроллеры остаются совместимыми с middleware.
  • Ошибки можно централизованно обрабатывать через отдельный модуль errorHandler.

Управление состоянием приложения

Модульная архитектура предполагает централизованное хранение состояния приложения. Можно создать отдельный модуль store.js:

export const state = {
  user: null,
  settings: {}
};

export function setUser(user) {
  state.user = user;
}

export function setSettings(settings) {
  state.settings = settings;
}

Контроллеры и middleware используют этот модуль для чтения и изменения состояния, что исключает глобальные переменные и делает код предсказуемым.


Интеграция Page.js с другими модулями

Page.js легко интегрируется с компонентными библиотеками и фреймворками:

  • С шаблонизаторами: Handlebars, EJS, Lit-html.
  • С компонентными системами: создание отдельных компонентов для каждой страницы.
  • С сервисами API: отдельные модули для запросов к серверу.

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