В любом зрелом фронтенде проблема редко в том, чтобы написать еще один Button.
Проблема начинается позже. Когда у компонента уже есть десятки использований, Storybook, типы, обвязки, legacy-слои и пара человек, которые “чуть-чуть расширили API, потому что так было удобнее”.
В этот момент UI-kit начинает незаметно плыть.
Где-то в проекте компоненту передают prop, которого в контракте уже нет. Где-то Storybook продолжает жить по старой версии API. Где-то обязательное поле не передали, но это замечают уже на ревью или после регресса. А где-то ты просто хочешь поменять один prop и не понимаешь, это локальная правка или мини-миграция на полпроекта.
Мне захотелось закрыть именно эту боль. Не “написать React за разработчика”, не встроить в IDE еще один чат, а сделать инструмент, который следит за контрактом компонента и показывает сигналы прямо там, где фронтендер работает каждый день: в редакторе.
Так появился Component Contract Guard — анализатор контрактов React-компонентов и плагин для WebStorm, который сопоставляет props, usages по проекту и Storybook, а затем подсвечивает расхождения прямо в коде.
Какая проблема меня вообще интересовала
Если упростить, фронтенд-компонент живет сразу в нескольких измерениях:
в типах;
в JSX-использованиях по проекту;
в Storybook;
в голове команды как “правильный способ” его использовать.
И вот между этими слоями очень легко появляется рассинхрон.
Например, компонент выглядит так:
type ButtonProps = { label: string; variant?: "primary" | "secondary"; icon?: React.ReactNode; }; export function Button({ label, variant = "primary", icon }: ButtonProps) { return ( <button data-variant={variant}> {icon} {label} </button> ); }
А использовать его начинают так:
<Button variant="primary" tone="loud" />
Или так:
<Card title="Health" variant="ghost" />
А Storybook при этом живет своей жизнью:
export const BrokenStory = { args: { mystery: true } };
Часть таких историй поймает TypeScript. Часть не поймает. Но даже когда он что-то ловит, он не отвечает на более интересные вопросы:
это единичный случай или контракт уже начал расползаться?
Storybook вообще соответствует компоненту?
если я поменяю Button.variant, сколько мест мне придется трогать?
у нас это осознанный API или уже накопилась архитектурная пыль?
Именно на этом уровне мне хотелось сделать инструмент.
Что делает плагин
Component Contract Guard строит модель компонента из трех источников:
контракт props из TypeScript;
реальные usages компонента в JSX;
args и argTypes в Storybook.
После этого он выдает несколько классов сигналов:
передан неизвестный prop;
не передан обязательный prop;
в Storybook есть поля, которых нет в контракте;
в Storybook не отражены props, которые есть у компонента;
prop формально существует, но не используется ни в коде, ни в stories;
если менять конкретный prop, сколько code usages и story usages это затронет.
То есть задача плагина не в том, чтобы “улучшать код вообще”, а в том, чтобы держать под контролем API компонентного слоя.
Почему это именно плагин, а не просто CLI
Сначала я сделала CLI-анализатор. Он сканировал проект, строил warnings и умел считать impact по props.
Это полезно, но быстро стало понятно, что отдельный запуск из консоли не решает главную проблему. Если инструмент живет где-то сбоку, он воспринимается как еще одна проверка “на потом”. А “потом” в разработке обычно наступает уже после того, как кто-то что-то сломал.
Поэтому я вынес логику в IDE-сценарий.
Теперь flow выглядит так:
проект открывается или файл сохраняется;
плагин запускает пересканирование;
в редакторе появляются inline-подсказки;
проблемные места подсвечиваются;
по ховеру виден tooltip с объяснением;
сверху по файлу можно показать короткую сводку;
отдельно можно запросить impact по prop.
И вот в этом виде инструмент начинает ощущаться как часть рабочего процесса, а не как внешняя утилита.
Архитектура: почему я разделила проект на два слоя
Здесь я сознательно не стала писать всю бизнес-логику на стороне IDE.
Вместо этого проект состоит из двух частей:
CLI-анализатор на TypeScript
WebStorm-плагин на Kotlin
Это решение оказалось очень практичным.
CLI-слой отвечает за анализ проекта:
загружает tsconfig.json, если он есть;
поднимает TypeScript Program;
находит экспортируемые React-компоненты;
извлекает контракт props;
собирает usages по JSX;
разбирает Storybook-файлы;
строит warnings и impact-отчеты.
Плагин для WebStorm делает другое:
запускает CLI;
получает JSON;
превращает его в подсветку, inline hints, тултипы и сводки в редакторе.
То есть плагин у меня не знает, как анализировать React-код. Он знает, как красиво и вовремя показать результат анализа внутри IDE.
У такого разделения есть сразу три плюса.
Во-первых, не нужно дублировать правила дважды.
Во-вторых, ядро можно использовать отдельно от IDE, например в CI.
В-третьих, если потом захочется сделать версию для другой среды, основная логика уже не привязана к JetBrains API.
Как устроен анализатор
В основе CLI у меня лежит TypeScript Compiler API.
Если в проекте есть tsconfig.json, анализатор поднимает программу через него. Если нет, он пытается собрать исходники сам. Это позволяет работать не только с “идеальными” проектами, но и с более простыми демо или внутренними пакетами.
Дальше логика делится на три прохода.
1. Сбор контрактов компонентов
Я прохожусь по экспортам модулей, ищу TSX-компоненты и вытаскиваю их первый параметр как props-контракт.
Из него собираются:
имя prop;
тип;
обязательность;
наличие default-значения;
документация, если она есть;
позиция в файле.
Отдельно учитывается распространенный кейс, когда props деструктурируются с дефолтами:
export function Button({ variant = "primary", label }: ButtonProps) { ... }
В таком случае variant остается частью контракта, но уже не считается “жестко обязательным”.
2. Сбор usages
На втором проходе анализатор идет по JSX и ищет использования найденных компонентов.
Для каждого usage он сохраняет:
файл;
строку и колонку;
какие props были переданы явно;
были ли spread-выражения.
Это важно, потому что с spread-пропсами нельзя честно утверждать, что обязательное поле точно отсутствует: часть значений может приехать из объекта. Поэтому предупреждения в таких местах нужно делать аккуратно.
3. Разбор Storybook
Отдельно я разбираю story-файлы в формате CSF:
default export с component;
meta.args;
meta.argTypes;
экспортируемые story objects.
На этом слое уже можно сопоставить контракт компонента и описание его API в Storybook. Именно здесь начинают проявляться очень неприятные расхождения: компонент давно поменялся, а stories все еще документируют прошлую реальность.
Что именно считается warning
После проходов по компонентам, usages и stories я собираю набор предупреждений.
Например:
Unknown prop "tone" is passed to Button
Button is missing required props: label
Story BrokenStory references unknown prop "mystery" for Button
Button.icon is not documented in Storybook args/argTypes
Отдельно для каждого prop считается impact:
сколько раз он используется в коде;
сколько раз фигурирует в stories;
насколько рискованно его менять.
Формально это пока простой scoring, но даже такая версия уже помогает отличать “локальную правку” от “осторожно, это часть публичного API”.
Как это выглядит в WebStorm
Сам плагин написан на Kotlin и работает как тонкий клиент к CLI.
Сервис уровня проекта хранит конфиг запуска и фоновые результаты анализа. Когда проект открывается или файл сохраняется, он ставит скан в очередь. Дальше раннер вызывает node dist/cli.js scan ... --format json, а результаты раскладываются по редакторам.
На уровне IDE я использую несколько каналов подачи:
inline-плашки над строками;
wave underline для конкретного участка;
tooltip с деталями и подсказкой;
file-level notification;
overview tool window для сводки по проекту.
Мне было важно не ограничиваться одним способом отображения. Для быстрых ошибок лучше работает подсветка на месте. Для обзора по файлу полезна сводка. Для анализа последствий удобен отдельный вызов impact.
В результате плагин не спорит с редактором, а аккуратно встраивается в него.
Где такой инструмент реально полезен
Я не думаю, что это плагин “для любого React-проекта на свете”.
Его настоящая зона пользы там, где уже есть компонентный слой как система:
UI-kit;
design system;
внутренние библиотеки компонентов;
большие фронтенд-монорепы;
проекты, где Storybook — не витрина, а часть рабочего контракта.
Именно там проблема дрейфа контракта стоит особенно остро. Когда у тебя 3 компонента, все и так видно глазами. Когда их 80, и каждый живет в десятках мест, ручной контроль начинает ломаться.
Что оказалось самым важным в этой работе
Самое интересное наблюдение для меня было таким: полезный developer tool начинается не с “магии”, а с правильного объекта контроля.
В моем случае этим объектом оказался не файл, не AST сам по себе и даже не отдельный warning. Им оказался контракт компонента как связь между несколькими слоями проекта:
типами;
usages;
документацией;
последствиями изменений.
Как только начинаешь смотреть на компонент не как на функцию, а как на публичный интерфейс, сразу становится понятно, почему обычной проверки типов недостаточно.
Типы отвечают на вопрос “это корректно прямо сейчас”.
А вот контрактный анализ отвечает на вопрос “во что превращается API этого компонента в живом проекте”.
И вот этот вопрос для UI-kit, на мой взгляд, намного интереснее.
Что хочу развивать дальше
Сейчас это рабочий MVP, но у него понятное продолжение.
Дальше здесь напрашиваются:
анализ breaking changes между версиями компонентов;
подсказки по миграции props;
CI-режим для pull request;
более умный учет wrapper-компонентов;
richer Storybook analysis;
объяснения impact не только числами, но и группировкой по слоям проекта.
То есть мне интересен не просто “плагин с предупреждениями”, а полноценный слой инструментов вокруг качества компонентного API.
Итог
Я хотела сделать инструмент не про генерацию интерфейсов, а про инженерный контроль над ними.
Так у меня получился Component Contract Guard: анализатор контрактов React-компонентов и плагин для WebStorm, который помогает ловить расхождения между типами, usages и Storybook до того, как они доедут до ревью, QA или продакшна.
Если вам близка идея developer tools для фронтенда, буду рада фидбеку.
Код проекта: https://github.com/varvaratikh/component-contract-guard
