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

Зачем?

Работая на разных проектах в разных конторах, или создавая open-source проекты я очень часто сталкивался с тем что необходимо было создать документацию к моей библиотеке, что бы другим разработчикам было просто разобраться, посмотреть различные примеры и варианты использования для тех или иных функций. И если говорить об Angular библиотеке то выбор тут не велик, зачастую приходится выбирать между StoryBook и Compodoc, ну или брать инструмент который не как не привязывается к вашей кодовой базе и позволяет писать гайды по типу GitBook.

StoryBook и Compodoc меня не устраивали т.к. первый заставляет писать много шаблонного кода и уж много всего конфигурировать, второй генерирует только API документацию что зачастую не подходит особенно для OpenSource'а.

Есть еще вариант от Тинькофф с @taiga-ui/addon-doc но по сути это просто набор ангуляр компонентов, и написание документации выливается в собирание ангуляр приложения, что опять же как по мне слишком долго и требует много сил.

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

Отсюда и вылилось желаение создать инструмент который автоматизировал бы всю рутину на столько на сколько это возможно при этом позволяя легко а главное быстро писать документацию к проектам.

Что умеет NgDoc?

В настоящее время доступен следующий список функций:

  • Создание гайдов при помощи Markdown + Nunjucks. Nunjucks позволяет сделать ваши темплейты более гибкими, к примеру включать один темплейт в другой или наследовать их, а так же множество других функций что делают ваш темплейт динамичным.

  • Два варианта отображения демо, демо в NgDoc это простой Angular компонент который может быть отображен на странице.

  • Playground, позволяет создавать интерактивные демо для компонентов и директив, для которых NgDoc прочитает список @Input полей и отобразит контролы для управления ими, таким образом пользователь может наглядно увидеть как изменяется компонент или директива с разными значениями.

  • Создание ссылок при помощи ключевых слов (Keywords), если вы упоминаете какую либо сущность в блоках кода или в строчном коде при помощи Markdown и эта сущность есть в вашем API, то NgDoc автоматически создает ссылку на страницу API, таким же образом можно создавать ссылки и на другие страницы, как итог они всегда будут валидны, а если ссылка на страницу более не действительна, NgDoc уведомит вас об этом при сборке проекта.

  • Генерация API документации на основании кода и комментариев к нему.

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

  • Кастомизируемый интерфейс при помощи CSS переменных.

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

Как это работает?

Для тех кому интересно, немного расскажу как это работает внутри, NgDoc не является приложением как таковым, это библиотека для приложения Angular, которая устанавливается и интегрируется в существующее приложение, что бы генерировать и отображать в нем документацию.

NgDoc имеет два билдера, один для сборки проекта (альтернатива @angular-devkit/build-angular:browser), второй для его разработки (альтернатива @angular-debkit/build-angular:dev-server).

Когда вы запускаете приложение, NgDoc интегрируется в сборку, для того что бы находить ваши гайды и API, и генерировать на их основе Angular компоненты, модули и роутинг, которые в последствии будут импортированы в вашем корневом модуле автоматически, далее запускается стандартный Angular builder который соберет и запустит ваше приложение.

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

С чего начать?

NgDoc - это достаточно новая библиотека, и была создана для Angular 15+ проектов, по этому она может не работать со старыми версиями Angular.

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

ng add @ng-doc/add

Для Nx окружения, можно выполнить команду ниже:

npm i @ng-doc/add && npx nx g @ng-doc/add:ng-add

Если вы запустите ваше приложение (ng serve), вы должны увидеть пустую страницу с хедером и полем поиска.

Создание гайдов

Обычный гайд состоит из index.md и ng-doc.page.ts файлов, ng-doc.page.ts содержит в себе конфигурацию страницы, ее заголовок, категорию, роутинг и прочее что не может в себе содержать markdown файл.

Для того что бы создать гайд, вы можете воспользоваться специальной схематик-командой, для этого откройте дирректорию в которой вы хотели бы создать гайд (например src/app) и выполните команду ниже:

ng g @ng-doc/builder:page "Getting Started"

Данная команда создаст дирректорию getting-started с двумя файлами внутри index.md и ng-doc.page.ts который выглядит следующим образом:

import {NgDocPage} from '@ng-doc/core';

const GettingStartedPage: NgDocPage = {
	title: `Getting Started`,
	mdFile: './index.md',
};

export default GettingStartedPage;

Если вы вернетесь в приложение то должны увидеть новую страницу в меню

Теперь вы можете редактировать index.md файл вашего гайда а NgDoc пересоберет необходимые файлы и обновит приложение.

Nunjucks

Как упоминалось выше, NgDoc использует Nunjucks под капотом, что позволяет создавать более динамичный контент, к примеру в свежесозданном гайде вы можете заметить что для заголовка используется следующая конструкция:

# {{ NgDocPage.title }}

NgDocPage содержит объект конфигурации текущей страницы, который вы заполняете в файле ng-doc.page.ts, таким образом вы можете выводить любую информацию динамически.

Вы так же можете создавать другие шаблоны которые могут быть в последствии встроены в вашу страницу, к примеру вот так:

## Installation

{% include '../shared/node-warning.md' %}

To install MyLib, you can run the following command:

```bash
npm i my-lib
```

Демо

Для создания демо, вам потребуется создать файл ng-doc.dependencies.ts файл и Angular Module рядом с файлом вашего гайда ng-doc.page.ts. Для этого вы можете воспользоваться специальной схематик-командой, или указать дополнительный параметр -d при генерации страницы.

Для этой статьи, я создам новую страницу "Button" которая будет отображать демо для компонента кнопки, при помощи следуюшей команды:

ng g @ng-doc/builder:page Button -d

Помимо базовых файлов гайда, данная команда сгенерирует Angular Module и файл ng-doc.dependencies.ts который выглядит следующим образом:

import {NgDocDependencies} from '@ng-doc/core';
import {ButtonPageModule} from './ng-doc.module';

const ButtonPageDependencies: NgDocDependencies = {
	module: ButtonPageModule,
	// Add the demos that you are going to use on the page here
	demo: {},
};

export default ButtonPageDependencies;

Файл ng-doc.dependencies.ts используется для регистрации зависимостей и конфигурации Playgrounds которые вы собираетесь использовать на странице гайда.

Теперь мы можем создать демо компонент который будет демонстрировать вариант использования кнопки, для этого создайте папку demo внутри папки button, и создайте обычный Angular Component выполнив следующую команду:

ng g component ButtonDemo --skip-tests

Angular создаст новый компонент и автоматически добавит его в секцию declaration в модуле страницы, после чего вам необходимо зарегестрировать данный компонент в файле ng-doc.dependencies.ts добавив его в секцию demo следующим образом:

import {NgDocDependencies} from '@ng-doc/core';
import {ButtonPageModule} from './ng-doc.module';
// Импорт компонента
import {ButtonDemoComponent} from "./demo/button-demo/button-demo.component";

const ButtonPageDependencies: NgDocDependencies = {
	module: ButtonPageModule,
	// Регистрация компонента
	demo: {ButtonDemoComponent},
};

export default ButtonPageDependencies;

Теперь компонент станет доступным для отображения в вашем гайде, и вы сможете отобразить его при помощи специального метода из класса NgDocActions, к примеру так:

# {{ NgDocPage.title }}

This section describes how to use the Button component.

## Demo

{{ NgDocActions.demo("ButtonDemoComponent") }}

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

Playgrounds

Playgrounds - довольно мощный вид демо который может быть использован для компонентов и директив. Как упоминалось выше, NgDoc автоматически считывает @Input поля вашего компонента или директивы и создает контролы управления для тех полей, типы которых он поддерживает.

Да, из коробки NgDoc поддерживает только инпуты простых типов таких как string, number, boolean а так же Type Alias типы которые являются перечислениями простых типов, но для более сложных типов такие как объекты, вы можете создать кастомный контрол, и научить Playground понимать его.

Для демонстрации, я создал компонент кнопки, его код вы можете посмотреть ниже под спойлером.

button.component.ts
import {Component, HostBinding, Input} from '@angular/core';
import {CommonModule} from '@angular/common';

@Component({
  selector: 'button[app-button]',
  standalone: true,
  imports: [CommonModule],
  template: `
    <ng-content></ng-content>
  `,
  styles: [`
    :host {
      padding: 0 16px;
      border: none;
      border-radius: 4px;

      &[data-color="primary"] {
        background-color: #3f51b5;
        color: #fff;
      }

      &[data-color="accent"] {
        background-color: #ff4081;
        color: #fff;
      }

      &[data-color="warn"] {
        background-color: #f44336;
        color: #fff;
      }

      &[data-size="small"] {
        height: 32px;
      }

      &[data-size="medium"] {
        height: 40px;
      }

      &[data-size="large"] {
        height: 48px;
      }

      &[data-rounded="true"] {
        border-radius: 24px;
      }

      &[data-disabled="true"] {
        opacity: 0.5;
        pointer-events: none;
      }
    }
  `]
})
export class ButtonComponent {
  @Input()
  @HostBinding('attr.data-color')
  color: 'primary' | 'accent' | 'warn' = 'primary';

  @Input()
  @HostBinding('attr.data-size')
  size: 'small' | 'medium' | 'large' = 'medium';

  @Input()
  @HostBinding('attr.data-rounded')
  rounded: boolean = false;

  @Input()
  @HostBinding('attr.data-disabled')
  disabled: boolean = false;
}

Для того что бы создать Playground для компонента кнопки, впервую очередь необходимо добавить модуль компонента в секцию exports модуля гайда, т.к. в данном случае мы имеем standalone компонент, то нам необходимо импортировать и экспортировать сам компонент.

import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ButtonDemoComponent} from './demo/button-demo/button-demo.component';
import {ButtonComponent} from "../components/button/button.component";

@NgModule({
  imports: [CommonModule, ButtonComponent],
  declarations: [ButtonDemoComponent],
  exports: [ButtonComponent]
})
export class ButtonPageModule {}

Теперь, необходимо добавить конфигурацию для Playground что бы подсказать NgDoc как именно следует отрендерить представление для компонента, для этого откройте файл ng-doc.dependencies.ts и добавьте в него следующую конфигурацию:

import {NgDocDependencies} from '@ng-doc/core';
import {ButtonPageModule} from './ng-doc.module';
import {ButtonDemoComponent} from "./demo/button-demo/button-demo.component";
import {ButtonComponent} from "../components/button/button.component";

const ButtonPageDependencies: NgDocDependencies = {
  module: ButtonPageModule,
  demo: {ButtonDemoComponent},
  playgrounds: {
    // Конфигурация для Playground кнопки
    ButtonPlayground: {
      target: ButtonComponent,
      template: `<ng-doc-selector>My Button</ng-doc-selector>`
    }
  }
};

export default ButtonPageDependencies;

Для минимального Playground достаточно указать target класс вашего компонента или директивы и добавить template. Как можно заметить в темплейте используется ng-doc-selector вместо button[app-button] селектора, это рекомендуется делать что бы не завязываться на существующий селектор, а так же что бы добавить возможность генерировать сразу несколько демо внутри одного Playground если ваш компонент или директива поддерживает несколько возможных селекторов.

Теперь вы можете использовать название вашего Playground (в данном случае ButtonPlayground) для того что бы отобразить его на странице следующим образом:

## Playground

{{ NgDocActions.playground("ButtonPlayground") }}

Генерация документации для API

С генерацией документации все просто, что бы она генерировалась автоматически вам достаточно создать и сконфигурировать файл ng-doc.api.ts. Что бы создать его, перейдите в директорию src/app и выполните следующую команду:

ng g @ng-doc/builder:api

Данная команда создаст файл конфигурации который выглядит следующим образом:

import {NgDocApi} from '@ng-doc/core';

const Api: NgDocApi = {
	title: 'API',
	scopes: [
		// Add the paths to the source code of your project, based on which you want to generate the API here
	],
};

export default Api;

Теперь вам нужно указать скоупы для вашего API, скоупами может быть что угодно, библиотека или определенная ее папка, по этому то что считать за скоуп полностью лежит на вас, я же в качестве примера укажу путь до компонента кнопки в моем тестовом проекте:

import {NgDocApi} from '@ng-doc/core';

const Api: NgDocApi = {
  title: 'API',
  scopes: [{
    name: 'Components',
    route: 'components',
    include: 'src/app/components/**/*.ts',
  }],
};

export default Api;

Теперь если вы вернетесь в приложение вы должны увидеть новую страницу "API" а если откроете ее, то список экспортируемых деклараций которые нашел NgDoc на основании маски в поле include:

Для документации API используется TsDoc, он довольно сильно похож на JsDoc но более новее и поддерживает некоторые новые теги. Для документации API вы так же можете использовать Markdown внутри ваших комментариев что бы стилизовать ее или добавить специфичные для Markdown элементы. Документация к кнопке что я написал в коде компонента рендерится так:

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

Ключевые слова (Keywords)

Последнее про что хотелось бы рассказать это Keywords. Keywords - это определенный набор символов в markdown внутри блоков кода или строчного кода, которые NgDoc автоматически преобразует в ссылки.

Дело в том что когда мне приходилось писать документацию и указывать ссылку на другую страницу что бы перенаправить пользователя, программист внутри меня говорил - "Это не надежно, ссылка может поменятся, и что тогда? Будешь ходить и прокликивать ссылки вручную? А если не работает одна, искать все места где ты ее использовал?". Так пришла идея сделать генерацию ссылок автоматическими.

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

Here is a keywords for button - `ButtonComponent`

```ts
import {ButtonComponent} from 'my-lib';

const button = new ButtonComponent();
```

Для страниц, ключевое слово нужно преднастроить, для этого откройте ng-doc.page.ts необходимой страницы и добавьте поле keyword:

import {NgDocPage} from '@ng-doc/core';

const ButtonPage: NgDocPage = {
  title: `Button`,
  mdFile: './index.md',
  // Ключевое слово для страницы
  keyword: 'ButtonPage',
};

export default ButtonPage;

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

## Usage

To know how to use the button, see the `*ButtonPage` page.

Заключение

В заключении, надеюсь что моя библиотека будет полезна вам и поможет создавать более качественную документацию. Она довольно молодая, и возможно все еще присутствуют некоторые баги, но уже на данном этапе она умеет довольно много.

В будущем я так же планирую добавление новых фич, некоторые оптимизации движка и большее покрытие тестами. Ниже оставлю ссылки на документацию и репозиторий, в любом случае спасибо за проявленный интерес! :)

Официальная документация - https://ng-doc.com/
Репозиторий - https://github.com/ng-doc/ng-doc

Комментарии (0)