Документирование фронтенда: как работают JSDoc
и Storybook

Две фиолетовые 3D-шестерёнки с логотипами Telegram и Flutter
8 августа 2025 · ? просмотров · ? мин
В современной веб-разработке качественная документация так же важна, как и качественный код. Когда ваше приложение разрастается до десятков или сотен компонентов, функций и модулей, становится практически невозможно удерживать в памяти все детали их работы. Хорошая документация не только облегчает поддержку проекта в долгосрочной перспективе, но и значительно ускоряет вхождение новых разработчиков в команду.

В этой статье мы рассмотрим два популярных подхода к документированию фронтенд-кода: JSDoc и Storybook. Они решают схожие задачи, но совершенно разными способами.

Читать на Habr

JSDoc: Документирование JavaScript-кода с помощью комментариев

JSDoc — это система документирования для JavaScript, которая использует специально форматированные комментарии для описания кода. Она похожа на JavaDoc и другие системы документирования, но адаптирована специально для JavaScript.

Если по какой-то причине ваше приложение не использует TypeScript и обходится только JavaScript'ом, тогда JSDoc является отличным вариантом для документирования. Этот инструмент достаточно популярный, что подтверждается значительным количеством скачиваний с npmjs.com.
статистика скачиваний на фиолетовом фоне
Для демонстрации полезности JSDoc, рассмотрим практический пример. Представим, что у нас есть функция calculateDistance, которая проводит какие-то вычисления. Тому, кто её написал, всё предельно понятно, но у того, кто увидит эту функцию впервые, могут возникнуть вопросы — что, как и почему.
const calculateDistance = (firstPoint, secondPoint) => {
   const deltaX = secondPoint.x - firstPoint.x;
   const deltaY = secondPoint.y - firstPoint.y;
   return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
};
(Понятно, что для примера взята простая функция, но так далеко не всегда).

При использовании такой функции IDE не даст никакой подсказки, что там происходит.
функция калькулятор на фиолетовом фоне
Тут нам на помощь приходит JSDoc, с помощью специальных тегов можно описать функцию, рассмотрим несколько часто используемых тегов:
1) @param - описывает входные параметры
/**
* @param {тип} название - описание
*/

2) @returns - описывает возвращаемое значение
/**
* @param {тип} - описание
*/

3) @example - описывает пример использования
/** @example
* Пример использования.
*/

4) @typedef - описывает пользовательский тип
/**
* @typedef {тип} Название
*/

5) @property - описывает поля объектов
/**
* @typedef {тип} Название
* @property {тип} Название - описание.
*/

Сочетание этих тегов позволяет создать подробную документацию к коду, которая будет доступна разработчикам непосредственно в IDE при работе с функциями и объектами.
Теперь, используя вышеупомянутые теги JSDoc, напишем несколько комментариев над функцией, тем самым создав её описание.

Получаем следующее:
/** @module Helpers */


/**
* @typedef {Object} Point
* @property {number} x - Координата X.
* @property {number} y - Координата Y.
*/


/**
* Рассчитывает расстояние между двумя точками.
*
* @param {Point} firstPoint - Первая точка.
* @param {Point} secondPoint - Вторая точка.
* @returns {number} Расстояние между точками.
*
* @example
* const firstPoint = { x: 0, y: 0 };
* const secondPoint = { x: 3, y: 4 };
* calculateDistance(firstPoint, secondPoint); // Возвращает 5
*/
const calculateDistance = (firstPoint, secondPoint) => {
   const deltaX = secondPoint.x - firstPoint.x;
   const deltaY = secondPoint.y - firstPoint.y;
   return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
};
В итоге есть хорошее описание входных параметров, результата выполнения и примера использования — всё это будет отображаться в IDE при наведении курсора на функцию.
отображение в IDE на фиолетовом фоне
Также для более эффективного использования JSDoc можно автоматически генерировать документацию на основе этих комментариев. Существует множество инструментов, которые позволяют это сделать, например:

  • JSDoc CLI — официальный инструмент для генерации документации;
  • ESDoc — генератор документации для JavaScript-проектов;
  • TypeDoc — поддерживает работу с JSDoc и TypeScript
Рассмотрим поподробнее JSDoc CLI.

Чтобы воспользоваться этим инструментом, его необходимо установить в проект.
npm install jsdoc --save-dev
После этого можно добавить в package.json и выполнить следующий скрипт.
"jsdoc index.js -d docs"
Где index.js — файл, на основе которого будет генерироваться документация.
-d docs — папка с результатом.

Также index.js можно заменить, например, на название папки, допустим, src, и тогда документация будет генерироваться на основе содержимого этой папки.

После запуска скрипта в папке с результатами откройте файл index.html— в браузере откроется сгенерированная документация. Вот пример того, что получилось на основе нашей функции.
файл index.html — в браузере
Для более детальной настройки JSDoc можно использовать файл конфигурации в формате JSON или модуля CommonJS. Например, мы создали файл .jsdoc.conf.json и настроили паттерн файлов, которые будет обрабатывать JSDoc.
"source": {
 "includePattern": ".+\\.js(doc|x)?$",
 "excludePattern": "(^|\\/|\\\\)_"
},
Такой способ документирования выглядит удобным, но на практике имеет следующие недостатки:
  • Увеличение размера кодовой базы: простые функции могут стать слишком громоздкими из-за комментариев.
  • Ограниченная поддержка сложных типов.
  • Избыточность на проектах с TypeScript.
  • Необходимость постоянно актуализировать комментарии при внесении изменений.

Storybook: интерактивная документация.

Storybook представляет куда более практичное, обширное и удобное средство для документирования кода на фронтенде. Эта библиотека пользуется большой популярностью и доступна для большинства (если не всех) современных фронтенд-фреймворков. Мы же рассмотрим этот инструмент на примере Next с TypeScript.

Количество скачиваний на npmjs.com уже в разы больше, чем у JSDoc.
статистика установок с цифрами и графиком
Для того чтобы начать работу со Storybook, практически ничего не нужно. Достаточно выполнить команду npm create storybook@latest, после чего Storybook инициализируется и создаст минимальный жизнеспособный конфиг, с которым уже можно будет писать свои «сторисы» — компоненты в изоляции.

Скорее всего, все, кто использует Storybook, расширяют его конфигурацию. Это нужно для базовых вещей вашего проекта — подключить SVG, SCSS, Redux, тему и т. п.
К счастью, у Storybook отличная документация, и такие улучшения делаются без особых проблем.

Например, у нас использовался SVGR, и чтобы Storybook умел с этим работать, нужно было написать свой webpack.config.ts и добавить в него правила.
config.module.rules.push({
   test: /\.svg$/,
   use: ['@svgr/webpack'],
});
Также в нашем случае необходимо было подключить SCSS, тему и Redux. С SCSS всё просто — для этого в preview.ts нужно было импортировать файл со стилями. А чтобы использовать тему и Redux для наших изолированных компонентов, можно добавлять декораторы для конкретного варианта компонента (это будет показано в примере ниже).

Если с импортом всё понятно, то про декоратор можно сказать, что это функция, позволяющая обернуть истории в дополнительные элементы или контекст. Например, мы создали моковый провайдер и декоратор темы, а также декоратор для остальных используемых в приложении провайдеров — AppLayoutDecorator, чтобы все сторисы, которые мы будем писать далее, можно было создавать в двух вариантах —
в соответствии с темами приложения — и наполнять их разными наборами данных.
Это удобно, когда нужно отобразить компоненты в разных состояниях.
const ThemeDecoratorProvider = ({ theme, children }: ThemeDecoratorComponentProps) => {
   useEffect(() => {
       if (theme) {
           document.documentElement.setAttribute('data-theme', theme);

           return () => {
               document.documentElement.removeAttribute('data-theme');
           };
       }
       return undefined;
   }, [theme]);

   return children;
};

export const ThemeDecorator = (theme?: Theme) => (StoryComponent: StoryFn) => (
   <ThemeDecoratorProvider theme={theme}>
       <StoryComponent />
   </ThemeDecoratorProvider>
);
export const AppLayoutDecorator =
   (state: DeepPartial<StateSchema>) => (StoryComponent: StoryFn) => {
       const queryClient = new QueryClient();
       return (
           <SessionProvider session={null}>
               <QueryClientProvider client={queryClient}>
                   <StoreProvider initialState={state}>
                       <WebSocketProvider>
                           <MainLayoutDecorator>
                               <StoryComponent />
                           </MainLayoutDecorator>
                       </WebSocketProvider>
                   </StoreProvider>
               </QueryClientProvider>
           </SessionProvider>
       );
   };
В идеале для компонентов Storybook стоит добавить все обёртки (провайдеры), используемые в вашем приложении, на этапе настройки. Это поможет избежать проблем в будущем, когда Storybook «не узнает» о каком-либо провайдере при написании сторисов.

После выполнения всех этих настроек попробуем написать истории для компонента страницы — BidsPage. Создаём соответствующий файл BidsPage.stories.tsx и заполняем его следующим образом:
export default {
    title: 'pages/BidsPage',
    component: BidsPage,
    parameters: {
        layout: 'centered',
    },
    argTypes: {},
} satisfies Meta<typeof BidsPage>;

const Template: StoryFn = () => <BidsPage />;

const state: DeepPartial<StateSchema> = {
    bids: {
        entities: {
            1: {...данные заявки},
        },
        page: 0,
        isNextPage: false,
        isInitialLoading: false,
        isRefetchCache: false,
        pageSize: 9,
        isLoadingMore: false,
        ids: [1],
    },
    folders: {
        currentFolder: 'actual',
        folders: [
            {...данные папки},
        ],
        isFoldersError: false,
        isFoldersInitialLoading: false,
    },
    sellerBids: {
        entities: {
            1: {...данные заявки},
        },
        page: 0,
        isNextPage: false,
        isInitialLoading: false,
        isRefetchCache: false,
        pageSize: 9,
        isLoadingMore: false,
        ids: [1],
    },
    subscriptions: {
        subscriptions: [
            {...данные подборки},
        ],
        currentSubscription: 'actual',
        isSubscriptionsInitialLoading: false,
        isSubscriptionsError: false,
    },
};

export const BidsPageCustomer = Template.bind({});
BidsPageCustomer.decorators = [ThemeDecorator(Theme.CUSTOMER), AppLayoutDecorator(state)];

export const BidsPageSeller = Template.bind({});
BidsPageSeller.decorators = [ThemeDecorator(Theme.SELLER), AppLayoutDecorator(state)];
Дефолтный экспорт

Это основной объект метаданных для истории в Storybook. Он описывает, как Storybook должен работать с компонентом: задаёт название, компонент, параметры, аргументы
и т. д.

const Template

Шаблонная функция для рендера компонента. Используется для создания различных вариаций (сторисов) на основе одного и того же компонента с разными данными или контекстом.

const state

Моковая часть Redux-стейта — набор данных, необходимый для корректной работы страницы. Используется при создании сторисов, чтобы воспроизвести поведение компонента в нужном состоянии.

BidsPageCustomer

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

По-хорошему, таких историй, как BidsPageCustomer, следует создавать столько, сколько существует вариантов отображения компонента в зависимости от его пропсов и других условий.

Если указать в tags объекта meta значение 'autodocs', тогда Storybook создаст дополнительную страницу с описанием компонента, для которого мы писали истории. Там будут описаны все пропсы компонента, основываясь на типе, который мы для них создали.

После написания историй можно запустить Storybook с помощью команды: storybook dev -p 6006 -c ./config/storybook.
проект с темой покупателя - окно в браузере
После запуска storybook, откроется страница с нашими сторисами, вот как они будут выглядеть в нашем случае. (рис. 3)
тема продавец в окне браузера
Здесь мы получаем два варианта страницы — с разной темой и различным набором данных из Redux-стейта. Таким образом можно создать большое количество сторисов этой страницы в разных её состояниях.

Например, просто изменив набор данных в state для конкретного сториса, можно посмотреть, как будет выглядеть страница при отсутствии элементов в списке заявок.
заявки в теме покупатель
Рассмотрим ещё один пример. Для этого напишем новый сторис для новой страницы — BidDetail. Всё делается по уже знакомому сценарию: создаём файл BidDetail.stories.tsx
и заполняем его.

Однако в данном примере будет небольшое различие: через пропсы BidDetail получает набор данных, необходимых для отображения этой страницы, поэтому создаём тестовый объект с данными, который передаём в наш компонент. Получаем следующий код:
export default {   
title: 'pages/BidDetail',
   component: BidDetail,
   parameters: {
       layout: 'centered',
   },
   argTypes: {},
} satisfies Meta<typeof BidDetail>;


const Template: StoryFn<BidDetailProps> = (args: BidDetailProps) => <BidDetail {...args} />;


const args: BidDetailProps = {
   id: 1,
   count: 1,
   created_date: new Date().toDateString(),
   bid_photos: [],
   customer: {...данные покупателя},
   delivery_place: 'Москва',
   delivery_type: { id: 4, name: 'ПЭК' },
   description: 'Вот такое описание',
   find_only_in_my_city: true,
   name: 'Название',
   number: 4,
   offer_id: 5,
   offers_count: 6,
   originality: true,
   photos: [],
   published: true,
   request_on_order: true,
   spare_part_type: { id: 7, name: 'Запчасть' },
   taxation: 8,
   relevance_in_days: 9,
   technique_card: {...данные о технической карточке},
   is_favorite: true,
};


export const BidDetailSeller = Template.bind({});
BidDetailSeller.args = args;
BidDetailSeller.decorators = [ThemeDecorator(Theme.SELLER), AppLayoutDecorator({})];


export const BidDetailCustomer = Template.bind({});
BidDetailCustomer.args = args;
BidDetailCustomer.decorators = [ThemeDecorator(Theme.CUSTOMER), AppLayoutDecorator({})];
В этом случае отличается Template компонента, поскольку BidDetail имеет пропсы, необходимо сообщить об этом Storybook и соответственно типизировать аргументы
в шаблоне. Создаётся объект args такого же типа, как и пропсы BidDetail, и передаётся
в сторисы.

После запуска Storybook и открытия соответствующего сториса можно будет взаимодействовать с боковой панелью, которая содержит несколько вкладок.
Мы же рассмотрим вкладку Controls. Здесь Storybook автоматически анализирует пропсы компонента и создаёт элементы управления под каждый из параметров, что позволяет нам менять параметры и сразу видеть изменения в окне просмотра компонента.
работа сторибука в браузере
Таким образом, разработчики, которые в дальнейшем будут взаимодействовать с этими компонентами, смогут легко разобраться, что и как должно отображаться на данной странице.

Если вам нужна детальная техническая документация внутри кодовой базы
— с описанием типов данных, параметров функций и возможных возвращаемых значений
— стоит выбрать JSDoc.

А если требуется интерактивная документация пользовательских интерфейсных компонентов, демонстрирующая их поведение в различных состояниях, — то отличным выбором будет Storybook.
Оценить материал