Всем привет! Мы продолжаем разбирать наши решения. Сегодня расскажем о том, как, используя генератор Material for MkDocs, можно создать несложный, но удобный статический сайт с документацией (и не только!).

А ещё как встроить его в CI/CD для автосборки и автопубликации (мы используем Gitlab CI, о чём подробно рассказывалось в предыдущем туториале), а также как использовать плагины к генератору чтобы, к примеру, создавался не только сайт, но и его pdf-представление.

Добро пожаловать под кат!

Оглавление

  1. Что такое генератор статического сайта? И почему был выбран именно MkDocs?

  2. Создание и генерация тестового сайта.

  3. Как и на каком языке писать материалы для генератора MkDocs?

  4. Разбираем структуру файла mkdocs.yml.

  5. Настраиваем Gitlab CI для генерации статического сайта.

  6. Делаем свой docker-образ и добавляем в него плагин mkdocs-with-pdf для генерации PDF-файла («печать всего сайта»).

  7. Послесловие.

Перед началом

В этот раз мы публикуем не просто куски кода — мы выложили в открытый доступ весь «боевой» репозиторий с текущей документацией, а также скриптами сборки этого сайта.

Самые нетерпеливые могут сразу посмотреть репозиторий на сайте gitlab.com.

Что такое генератор статического сайта? И почему был выбран именно MkDocs?

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

Создание или редактирование статического сайта выполняется предельно просто:

  1. Владелец создаёт новый файл или редактирует существующий в человекочитаемом формате (например, в markdown).

  2. Далее запускается генератор статического сайта — и результатом работы он выдаёт сайт в виде набора html-страниц с уже применённой темой, форматированием, построенным оглавлением и пр.

  3. Полученный сайт владелец уже может использовать локально. Ну или выложить на каком-либо хостинге — чтобы к нему могли получить доступ другие люди.

И важно отметить: для статических сайтов не требуется поддержка каких-либо серверных языков и/или баз данных — всё уже свёрстано и готово к использованию.

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

Немного личных впечатлений

19 мая 2015 года Rich Lander сделал первый коммит в репозиторий с комментарием «Experimenting with docs». Похоже, что именно с этого коммита и началась история docs.microsoft.com — проекта, содержащего на текущий момент документацию по огромному количеству языков, технологий и продуктов Microsoft.

Впервые попав на этот сайт, я был поражён:

  • на сайте имелась «гибкая» вёрстка (было удобно смотреть сайт и с компьютеров, и с мобильных устройств),

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

Как выглядит docs.microsoft.com

В дальнейшем на сайте появился автоперевод статей на русский язык (а также возможность переключения и чтения статьи на языке оригинала).

Но самое главное, что меня зацепило — кто угодно (а не только сотрудник Microsoft) мог предложить правки, дополнения или перевод материалов. Ведь все материалы лежали в публичном репозитории github!

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

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

  1. Jekyll. Написан на Ruby, его использует Github для Github Pages (вероятно потому, что один из создателей Jekyll — сооснователь github?).

  2. Hugo. Написан на Go, позиционирует себя как самый быстрый генератор статических сайтов.

  3. Sphinx. Написан на Python. Именно с его помощью сгенерирована документация к языку Python.

  4. MkDocs. Также написан на Python.

MkDocs был выбран как самый популярный генератор на Github (на момент написания статьи у репозитория MkDocs было более 12 тыс. «звёздочек»). А больше всего приглянулся его форк с красивой (по моему мнению) встроенной темой, называемый Material for MkDocs. А всё потому, что сгенерированный с его помощью сайт из коробки предоставлял и гибкую вёрстку, и человекочитаемые url-ы, и встроенный быстрый поиск по всем статьям (который не требует какой-либо серверной реализации).

Создание и генерация тестового сайта

Для того, кто не очень знаком с языком Python, но немного представляет что такое контейнеризация, проще всего воспользоваться MkDocs с использованием их docker-образа.

При подготовке этого материала использовалась ОС Linux (дистрибутив Debian 10).

Перед созданием первого сайта на компьютере выполним в терминале (bash) команду docker pull squidfunk/mkdocs-material для загрузки docker-образа mkdocs.

На заметку: команды docker pull и docker run могут требовать прав суперпользователя. Соответственно, если для выполнения команды не хватает прав, перейдите в режим суперпользователя командой su или воспользуйтесь командой sudo.

Как создать новую папку с материалами для сайта

  1. Выберите папку, в которой будут храниться материалы для генерации сайта (например, /home/user/test/).

  2. Откройте в указанной папке терминал (или перейдите в существующем терминале в нужную папку (например, командой cd /home/user/test/).

  3. Выполните в открытом терминале команду

    docker run --rm -it -v ${PWD}:/docs squidfunk/mkdocs-material new .

В результате в нашей папке появятся два новых файла:

.
├─ docs/
│  └─ index.md
└─ mkdocs.yml

Файл mkdocs.yml — это файл конфигурации, используемый MkDocs. Файлы, находящиеся в папке docs (в частности, index.md) — это исходные материалы, из которых в дальнейшем будет сгенерирован наш сайт.

Как сгенерировать сайт по существующим материалам

Выполните в терминале, открытом в папке с материалами (папке с файлом mkdocs.yml) команду

docker run --rm -it -v ${PWD}:/docs squidfunk/mkdocs-material build

В результате в папке с материалами появилась подпапка site, в которую генератор записал наш только что созданный сайт. Выглядит он следующим образом:

Первый сгенерированный сайт

Как пересоздавать сайт «на лету» при внесении изменений в файлы

MkDocs умеет также создавать превью сайта «на лету», отслеживая изменения в исходных материалах в реальном времени и перестраивая только их. Для включения такого режима выполните в терминале, открытом в папке с материалами (папке с файлом mkdocs.yml) команду

docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material

Пока команда выполняется (прервать её можно, нажав комбинацию клавиш Ctrl+C), на вашем компьютере на порту 8000 будет запущен веб-сервер, который вы можете использовать для просмотра результатов ваших правок на сайте. Просто откройте браузер, введите в строке адреса http://localhost:8000/ — и смотрите как выглядит сгенерированный сайт (правда, для обновления текущий страницы придётся нажимать F5).

Как и на каком языке писать материалы для генератора MkDocs?

Генератор сайтов MkDocs в качестве исходных материалов поддерживает файлы как формата markdown (.md) так и формата html.

Стоп-стоп, скажете вы. Html? Зачем нам генерировать сайт из "материалов" на языке html, если результат — всё равно html?!

Дело в том, MkDocs не только преобразовывает markdown в html, но и корректно применяет к этим страницам темы, а также даёт простор для работы своим многочисленным плагинам, которые: осуществляют подсветку кода, индексируют содержимое для быстрого поиска, могут сгенерировать PDF-содержимое одной страницы или всего сайта и т.д.

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

Что же писать в исходных материалах?

Да в принципе всё, что захотите. MkDocs полностью поддерживает синтаксис markdown. Для примера картинка с markdown-текстом слева и сгенерированным из него html справа:

Текст в формате markdown и как он выглядит после преобразования в html

Более подробно о формате markdown и о том, как оформлять такие документы, написано в шпаргалке по markdown.

И, — что для меня оказалось очень важно — MkDocs совершенно нормально относится к html-тэгам как внутри html, так и внутри markdown-файлов.

Нам это пригодилось для вставки в начало некоторых страниц справки html-плеера с видеоуроком по той же теме, что описана в материале.

Пример встраивания youtube-плеера в наш help

Разбираем структуру файла mkdocs.yml

Давайте подробнее изучим содержимое созданного на предыдущем шаге файла mkdocs.yml

site_name: My Docs

Негусто.

К счастью, все сайты с документацией по MkDocs собраны с помощью генератора MkDocs и можно посмотреть содержимое mkdocs.yml, к примеру, сайта https://www.mkdocs.org/.

Приведём содержимое этого файла с дополнительными комментариями, на что влияют указанные в файле параметры:

# [Обязательное] Название сайта
site_name: MkDocs

# Базовый адрес сайта
site_url: https://www.mkdocs.org/

# Описание сайта. Добавляется в meta в html-заголовке каждой страницы.
site_description: Project documentation with Markdown.

# Автор (или авторы) сайта. Добавляется в html-заголовок каждой страницы.
site_author: MkDocs Team

# Ссылка на репозиторий, в котором хранятся исходные материалы.
repo_url: https://github.com/mkdocs/mkdocs/

# Относительный путь, который в сочетании со ссылкой на репозиторий (repo_url)
# и относительным адресом страницы даст адрес, по которому можно вызвать
# редактор страницы. Для repo_url-ов известных площадок (например,
# GitHub или GitLab можно оставить пустым - он подставится автоматически)
edit_uri: ""

# Информация о теме, которая будет применена для сгенерированного сайта.
# И её параметрах
theme:
    name: mkdocs    
    locale: en
    analytics: {gtag: 'G-274394082'}
    highlightjs: true
    hljs_languages:
        - yaml
        - django

# Структура навигации генерируемого сайта.
nav:
    - Home: index.md
    - Getting Started: getting-started.md
    - User Guide:
        - Overview: user-guide/index.md
        - Installation: user-guide/installation.md
        - Writing Your Docs: user-guide/writing-your-docs.md
        - Choosing Your Theme: user-guide/choosing-your-theme.md
        - Customizing Your Theme: user-guide/customizing-your-theme.md
        - Localizing Your Theme: user-guide/localizing-your-theme.md
        - Configuration: user-guide/configuration.md
        - Deploying Your Docs: user-guide/deploying-your-docs.md
    - Developer Guide:
        - Overview: dev-guide/index.md
        - Themes: dev-guide/themes.md
        - Translations: dev-guide/translations.md
        - Plugins: dev-guide/plugins.md
    - About:
        - Release Notes: about/release-notes.md
        - Contributing: about/contributing.md
        - License: about/license.md

extra_css:
    - css/extra.css

# Список подключённых расширений для препроцессора языка Markdown
markdown_extensions:
    - toc:
        permalink: 
    - admonition
    - attr_list
    - def_list
    - mdx_gh_links:
        user: mkdocs
        repo: mkdocs

copyright: Copyright &copy; 2014 <a href="https://twitter.com/_tomchristie">Tom Christie</a>.

# Список использованных плагинов MkDocs
plugins:
    - search
    - redirects:
        redirect_maps:
            user-guide/plugins.md: dev-guide/plugins.md
            user-guide/custom-themes.md: dev-guide/themes.md
            user-guide/styling-your-docs.md: user-guide/choosing-your-theme.md

На что хочется обратить внимание в структуре навигации из примера выше:

  • Путь страницы в url будет сгенерирован из site_url и относительного пути страницы в репозитории. Например, страница User Guide -> Overview будет доступна по url https://www.mkdocs.org/user-guide/index.html (потому, что файл, из которого сгенерировали страницу, имел относительный путь user-guide/index.md).

  • Один файл можно указать в навигации и более одного раза.

  • Если файл отсутствует в разделе nav — соответствующая страница всё равно будет сгенерирована, но не будет показана в навигации сайта. При этом перейти на неё, зная её url (см. первый пункт списка), вполне себе можно.

  • Плагин redirects (подробнее об использовании плагинов расскажу чуть ниже) позволяет настроить переадресацию страниц со старого расположения на новое.

Более подробно о структуре файла mkdocs.yml а также на то, как он влияет на используемую тему, расскажет подробная справка на сайтах mkdocs.org, Material for mkdocs а также примеры файлов mkdocs.yml в других репозиториях.

Настраиваем Gitlab CI и Gitlab Runner для генерации статического сайта

Следующий этап — это добавление сайта в систему контроля версии (СКВ) и написание скрипта, который сам собирал бы нам сайт как только мы сохраним новую его версию.

Воспользуемся git-ом как СКВ и Gitlab CI для обработки действий.

Внимание. У вас может возникнуть вопрос: а зачем мы, собственно, настраиваем Gitlab CI, если выше была показана команда serve, которая на лету создаёт сайт и поддерживает его в актуальном состоянии при изменениях в файлах?

Ответ включает как минимум следующие пункты:

  1. Команда serve не обеспечивает сохранение истории изменений, а СКВ для этого и предназначена.

  2. Команда serve не предназначена для работы в production-окружении. В частности, serve может «сломаться» если при очередной сборке произошла ошибка (например, структура mkdocs.yml стала некорректной).

Поскольку многие читатели знакомы с git, я уберу под спойлер инструкцию по созданию репозитория и его сохранению (пушу) в Gitlab.

Инструкция по созданию репозитория и пушу его в Gitlab

Нам потребуется установленный консольный или графический клиент git-а. По устоявшейся уже в сообществе традиции процесс в этой статье будет описан консольными командами.

Итак, для создания нового репозитория откройте терминал в папке с материалами, после чего:

  1. Создадим файл .gitignore и внесём в него 1 строку (команду игнорировать папку site); для этого в терминале введём команду echo '[Ss]ite/' > .gitignore

  2. Создадим репозиторий командой git init

  3. Создадим первый коммит, состоящий из всех созданных нами файлов git add -A git commit -m 'Сайт в состоянии hello, world'

Если при попытке выполнения команды git gommit команда выдаст ошибку из-за на отсутствия имени и/или электронной почты, то введите следующие две команды

git config user.name "<имя, которое будет отображаться в коммитах в этом репозитории>"
git config user.email "<электронная почта, которая будет отображаться в коммитах в этом репозитории>"

После чего повторите команду git commit.

Теперь осталось создать в gitlab новый проект, перейти на страницу созданного проекта, найти и скопировать на странице ссылку на репозиторий (она выделена в отдельную рамочку и снабжена кнопкой «скопировать»).

Далее воспользуемся этой ссылкой для сохранения созданного репозитория в созданный проект gitlab:

git remote add origin <ссылка на проект gitlab>
git push -u origin master

Теперь, когда репозиторий создан и связан с Gitlab, создадим в папке с исходными материалами файл .gitlab-ci.yml. О структуре этого файла мы кратко рассказывали в предыдущей публикации. Посему не будем подробно останавливаться на структуре, лишь приведём код файла:

stages:
  - build

build_docs_job:
  stage: build
  # tags: docker # раскомментируйте эту строку если вам 
                 # потребуются метки для определения конкретного Runner-а
                 # (см. ниже)
  only:
    - /^master$/
    - merge_requests
  image: 
    name: squidfunk/mkdocs-material
    entrypoint: [""]
  script:
    - 'mkdocs build --site-dir site'
  artifacts:
    name: "site_$($CI_PIPELINE_IID)"
    paths:
      - site

Этим файлом мы указали Gitlab CI собирать сайт каждый раз, когда появляются новые коммиты в ветке master или в процессе создания или обновления merge request-ов.

Конечно, для того, чтобы это заработало, потребуется установить Gitlab Runner на машину, которая и будет собирать сайт по написанной в файле .gitlab-ci.yml «инструкции».

На заметку: подробную и актуальную инструкцию по установке Gitlab Gunner на ту сборку ОС Linux, что установлена у вас, можно посмотреть на сайте документации Gitlab.

Ниже для примера мы приведём пошаговую инструкцию как это сделать для ОС Debian Linux:

  1. Посмотрите токен для регистрации Runner-а в вашем Gitlab. В зависимости от того, где будет доступен ваш Runner:

    • только в одном проекте — смотрите токен в меню проекта Settings > CI/CD в разделе Runners,

    • в группе проектов — смотрите токен в меню группы Settings > CI/CD в разделе Runners,

    • для всех проектов Gitlab-а — смотрите токен в секции администрирования, меню Overview > Runners.

  2. Найдите актуальную для вашей архитектуры версию Gitlab Runner на сайте https://gitlab-runner-downloads.s3.amazonaws.com/latest/index.html.

  3. Скачайте нужный deb-пакет с помощью браузера или команды в терминале curl -LJO "https://gitlab-runner-downloads.s3.amazonaws.com/latest/deb/gitlab-runner_${arch}.deb", где ${arch} следует заменить на выбранную вами архитектуру.

  4. Выполните в терминале команду для установки скачанного пакета Gitlab Runner sudo dpkg -i gitlab-runner_<arch>.deb где <arch> опять же, следует заменить на выбранную вами архитектуру.

  5. Зарегистрируйте раннер командой sudo gitlab-runner register Далее потребуется ввести ответы на вопросы мастера регистрации Runner-а:

    • coordinator URL — http или https адрес вашего сервера gitlab;

    • gitlab-ci token — введите токен, полученный на предыдущем шаге;

    • gitlab-ci description — описание Runner-а, которое будет показываться в интерфейсе Gitlab-а;

    • gitlab-ci tags — через запятую введите тэги для Runner-а. Если вы не знакомы с этим механизмом, оставьте поле пустым. Отредактировать его можно позднее через интерфейс самого gitlab-а. Тэги можно использовать для того, чтобы определённые задачи выполнялись на определённых Runner-ах (например, чтобы настроить сборку ПО на Runner-е, развёрнутом на компьютере ОС Windows, а подготовку документации на Runner-е с ОС Linux);

    • enter the executor — ответьте docker. На этом шаге указывается оболочка, в которой будут выполняться команды.

На заметку: если у вас несколько runner-ов с разными исполнителями (например, shell для сборки ПО, а docker для сбора документации), настройте тэги для этого раннера. Для этого добавьте в тэги раннера, например, слово docker — и уберите первый символ решётки (#) из файла .gitlab-ci.yml. Ну или аналогичным образом настройте тэг с другим именем.

Проверим работу того, что настроили — сделаем коммит с созданным кодом. Можно прямо в ветку master, если, конечно, вы работаете в тестовом репозитории. В ином случае рекомендуем создать отдельную ветку с этими изменениями, затем создать на эту ветку merge request.

Так выглядит настроенная автосборка

Делаем свой docker-образ и добавляем в него плагин для генерации PDF-файла («печать всего сайта»)

Что ж, в целом проект уже работает. Однако для того, чтобы им воспользоваться, требуется либо соединение с интернетом, либо передать клиенту много html-страничек. Но что если требуется, к примеру, распечатать все материалы? Давайте настроим экспорт всего сгенерированного сайта в формат PDF и добавим его в артефакты Gitlab CI.

Стандартный функционал MkDocs сделать печать в PDF не позволят — но не беда, для этого и многих других функций сам генератор поддерживает плагины. И нас будет интересовать плагин mkdocs-with-pdf.

Чтобы добавить его в docker-образ генератора, потребуется модифицировать этот образ. И сделать это можно буквально в пару строк:

  1. Создайте в папке с файлом mkdocs.yml новый файл с именем dockerfile

  2. Добавьте в него следующие строки:

    FROM squidfunk/mkdocs-material
    RUN pip install mkdocs-with-pdf

  3. Откройте страницу Container Registry в вашем проекте в Gitlab (<адрес вашего проекта в gitlab>/container_registry, на картинке стрелочки 1 и 2), подсмотрите на ней имя образа, которое следует использовать при сборке (стрелочка 3).

    Страница Container Registry в Gitlab
  4. Откройте терминал в папке с файлом Dockerfile и выполните в нём команду сборки модифицированного образа:

    sudo docker build -t <имя образа, которое следует использовать> .

    С вероятностью 100% сборка образа закончится ошибкой так как образ squidfunk/mkdocs-material основан на Alpine Linux, где многие компоненты (типа компилятора gcc) по умолчанию отсутствуют. Чтобы образ собрался корректно. нам понадобится перед установкой пакета загрузить все необходимые не связанные с python-ом зависимости для него.

    После дописывания зависимостей (которые были у плагина на момент написания статьи) файл Dockerfile будет выглядеть следующим образом:

    FROM squidfunk/mkdocs-material
    RUN apk add build-base ttf-ubuntu-font-family libffi-dev zlib-dev
    libwebp-dev jpeg-dev harfbuzz-dev fribidi-dev freetype-dev
    cairo-dev musl-dev pango-dev gdk-pixbuf-dev
    && pip install mkdocs-with-pdf

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

    docker login <имя образа для сервера gitlab, указанное на странице Container Registry>
    docker push <имя образа для сервера gitlab, указанное на странице Container Registry>

    И осталось только отредактировать mkdocs.yml чтобы включить в нём сборку сайта в PDF-файл. Для этого добавим раздел plugins, в котором перечислим все включаемые плагины (в данном случае только один — with-pdf), а также их параметры.

site_name: My Docs

# На заметку: без явного указания темы пересобранный с плагином образ material-mkdocs сайт собрать не может
theme:
  name: material

plugins:
    - with-pdf:
        cover: true
        cover_title: My Docs
        cover_subtitle: версия для PDF
        copyright: (C) 2021 Rostelecom PJSC
        toc_title: Оглавление
        toc_level: 3
        output_path: ./help.pdf

Пара слов об использованных параметрах:

  • cover_title — заголовок, который будет указан на сгенерированной странице PDF. Может отличаться от названия сайта.

  • cover_subtitle — подзаголовок, который будет указан на сгенерированной странице PDF.

  • copyright — копирайт.

  • toc_title — название оглавление (по умолчанию contents).

  • toc_level — оглавление по умолчанию строится сначала по структуре навигации, а затем по заголовкам в документе. Параметр toc_level указывает максимальный уровень заголовка, который включается в оглавление. Например, значение 3 говорит о том, что все заголовки от первого до третьего уровня (h1 — h3).

  • output_path — путь имя сгенерированного pdf-файла.

Полный их список доступен на странице плагина.

Запускаем в терминале сборку сайта (уже нашим образом)

docker run --rm -it -v ${PWD}:/docs <имя образа для сервера gitlab, указанное на странице Container Registry> build

И убеждаемся, что в корневой папке появился файл help.pdf со всем содержимым сайта, а также кликабельным оглавлением

Сгенерированный вместе с сайтом pdf-файл

Послесловие

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

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

  • Добавлен плагин monorepo для сборки сайта из нескольких отдельно живущих папок с материалами. Мы скомбинировали плагин monorepo с плагином with-pdf чтобы можно было собирать и отдельные части сайта в PDF (будут только некоторые тонкости при указании папки с переопределениями темы при генерации части сайта).

  • Включены плагины search (поиск по сайту) и git-revision-date (чтобы отображать дату последнего изменения страницы).

  • Тема сайта дорабатывается под корпоративные стандарты.

  • Также дорабатывается возможность автогенерации PDF-файла для справки прямо в процессе сборки нашего ПО (так как, в частности, материалы из раздела «Настольное приложение» поставляются вместе с ПО «Настольное приложение Видеонаблюдение»).

Если у вас будут вопросы — пишите в комментариях, мы с радостью ответим. Есть предложения по улучшению? — разделы issues и merge requests мы тоже просматриваем достаточно оперативно.

И спасибо за прочтение!