Конфигурация TypeScript

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

TypeScript позволяет обеспечивать строгую проверку типов при взаимодействии с DOM и данными контроллера. Для этого требуется настроить проект таким образом, чтобы Stimulus контроллеры писались в .ts файлах и корректно компилировались в ES модули.


Структура проекта

Типичный проект с Stimulus и TypeScript имеет следующую структуру:

src/
 ├─ controllers/
 │   ├─ index.ts
 │   ├─ hello_controller.ts
 ├─ styles/
 │   └─ main.css
 ├─ index.ts
tsconfig.json
package.json
  • controllers/ — директория с контроллерами Stimulus.
  • index.ts — точка входа для регистрации всех контроллеров.
  • tsconfig.json — конфигурация TypeScript.
  • package.json — зависимости и скрипты сборки.

Конфигурация TypeScript (tsconfig.json)

Для корректной работы с Stimulus рекомендуется использовать следующие настройки:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Node",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "baseUrl": "./src"
  },
  "include": ["src/**/*"]
}

Ключевые моменты:

  • strict: true — включает строгую типизацию, предотвращает ошибки на этапе компиляции.
  • esModuleInterop: true — обеспечивает совместимость с модулями CommonJS и ES6.
  • moduleResolution: "Node" — позволяет TypeScript корректно находить модули, включая Stimulus.

Создание контроллера на TypeScript

Контроллер Stimulus представляет собой класс, наследуемый от Controller. На TypeScript рекомендуется явно указывать типы для свойств и методов.

Пример простого контроллера:

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static targets = ["output"] as const;

  declare readonly outputTarget: HTMLElement;

  connect(): void {
    this.outputTarget.textContent = "Stimulus + TypeScript работает!";
  }

  greet(event: Event): void {
    const button = event.currentTarget as HTMLButtonElement;
    alert(`Привет, ${button.dataset.name}`);
  }
}

Пояснения:

  • static targets = ["output"] as const; — использование as const позволяет TypeScript корректно выводить типы для target’ов.
  • declare readonly outputTarget: HTMLElement; — объявление property, соответствующего target, с указанием типа.
  • Явное указание типов в методах (void, Event) повышает безопасность кода.

Регистрация контроллеров

В TypeScript обычно создается центральный файл controllers/index.ts, который автоматически регистрирует все контроллеры:

import { Application } from "@hotwired/stimulus";
import HelloController from "./hello_controller";

const application = Application.start();
application.register("hello", HelloController);

Можно использовать динамическую регистрацию через import.meta.glob, если сборщик поддерживает ES-модули:

const context = import.meta.glob("./**/*_controller.ts", { eager: true });
Object.entries(context).forEach(([path, module]) => {
  const name = path.match(/([^/]+)_controller\.ts$/)?.[1];
  if (name && module.default) {
    application.register(name, module.default);
  }
});

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


Типизация данных и значений элементов

Stimulus предоставляет механизм data-* атрибутов. В TypeScript рекомендуется использовать строгую типизацию при доступе к данным:

const name = this.element.dataset.name as string;
const count = parseInt(this.element.dataset.count || "0", 10);

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

Для target’ов и values можно задать собственные интерфейсы:

import { Controller } from "@hotwired/stimulus";

interface CounterValues {
  count: number;
}

export default class extends Controller {
  static values = { count: Number };

  declare countValue: number;

  increment(): void {
    this.countValue += 1;
    this.element.textContent = this.countValue.toString();
  }
}
  • static values = { count: Number }; — объявление value для контроллера.
  • declare countValue: number; — корректная типизация свойства.

Сборка проекта с TypeScript

Используется стандартный сборщик, например Vite или Webpack, с поддержкой TypeScript:

Vite (vite.config.ts):

import { defineConfig } from "vite";

export default defineConfig({
  root: "src",
  build: {
    outDir: "../dist",
    rollupOptions: {
      input: "index.ts"
    }
  }
});

Компиляция выполняется командой vite build или tsc, в зависимости от выбранного инструмента.


Продвинутые возможности

  • Декораторы и миксины: TypeScript позволяет создавать обобщенные миксины для повторного использования логики в нескольких контроллерах.
  • Строгая типизация событий: можно определять собственные типы событий для методов Action, что минимизирует runtime ошибки.
  • Интеграция с другими фреймворками: Stimulus + TypeScript легко сочетаются с Rails, React и другими SPA-компонентами при условии корректной настройки типов.

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