Типизация плагинов

Фреймворк Fresh, основанный на Deno и современном подходе к рендерингу на стороне сервера, предлагает гибкую систему плагинов, расширяющую возможности приложения. Одним из ключевых аспектов безопасной и масштабируемой разработки является корректная типизация этих плагинов. Типизация обеспечивает строгую проверку параметров, предсказуемое поведение и удобство автодополнения в редакторах.


Основы системы плагинов

Плагин в Fresh представляет собой функцию, которая может изменять поведение приложения на уровне обработки запросов, маршрутизации или рендеринга компонентов. Каждый плагин получает объект контекста (ctx) и может возвращать модифицированный контекст или новые свойства для приложения.

Типичная структура плагина:

import type { PluginContext } from "fresh/server.ts";

export function examplePlugin(ctx: PluginContext) {
  // Модификация контекста
  ctx.state.example = true;
  return ctx;
}

Ключевой момент: объект PluginContext содержит всю необходимую информацию о текущем запросе, состоянии приложения и его конфигурации. Типизация контекста позволяет строго определять, какие свойства допустимы и какие методы можно использовать.


Создание собственных типов для плагинов

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

interface ExampleState {
  user?: { id: string; name: string };
  isAuthenticated: boolean;
}

declare module "fresh/server.ts" {
  interface PluginContext {
    state: ExampleState;
  }
}

export function authPlugin(ctx: PluginContext) {
  const token = ctx.request.headers.get("Authorization");
  ctx.state.isAuthenticated = !!token;
  return ctx;
}

Расширение встроенного интерфейса PluginContext через declare module позволяет интегрировать новые свойства напрямую в контекст, сохраняя строгую типизацию на уровне TypeScript.


Типизация параметров плагина

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

interface LoggerOptions {
  level: "info" | "warn" | "error";
  timestamp?: boolean;
}

export function loggerPlugin(options: LoggerOptions) {
  return (ctx: PluginContext) => {
    if (options.timestamp) {
      console.log(`[${new Date().toISOString()}]`);
    }
    console.log(`[${options.level}] Request: ${ctx.request.url}`);
    return ctx;
  };
}

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


Композиция плагинов и сохранение типизации

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

function composePlugins(...plugins: Array<(ctx: PluginContext) => PluginContext>) {
  return (ctx: PluginContext) => plugins.reduce((acc, plugin) => plugin(acc), ctx);
}

const appPlugin = composePlugins(authPlugin, loggerPlugin({ level: "info", timestamp: true }));

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


Ограничения и рекомендации по типизации

  1. Избегать any: Использование any подрывает преимущества TypeScript. Все новые свойства лучше описывать через интерфейсы.
  2. Строгое разделение контекста: Плагины не должны модифицировать чужие свойства без необходимости. Каждый плагин расширяет контекст через собственные типы.
  3. Использование дженериков для параметров: Это позволяет создавать универсальные плагины, поддерживающие разные типы данных.
  4. Интеграция с IDE: Правильная типизация обеспечивает автодополнение и предупреждения о некорректном использовании, что критично для больших проектов.

Интеграция с компонентами Fresh

Плагины напрямую влияют на серверный контекст, который используется компонентами Fresh. Типизированный контекст обеспечивает корректную работу хуков и серверных функций:

export default function Page({ ctx }: { ctx: PluginContext }) {
  return (
    <div>
      {ctx.state.isAuthenticated ? `User is logged in` : `Guest`}
    </div>
  );
}

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


Практический паттерн: типизированная фабрика плагинов

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

function createFeaturePlugin<T extends object>(feature: T) {
  return (ctx: PluginContext & { state: T }) => {
    Object.assign(ctx.state, feature);
    return ctx;
  };
}

const analyticsPlugin = createFeaturePlugin({ analyticsEnabled: true });

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


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