Введение
Многие продукты поддерживают и встроенную справку (для оффлайн-доступа и контекстной справки внутри приложения), и онлайн-документацию на веб-сайте (для SEO, для пользователей, ищущих помощь до установки приложения, для удобного шаринга прямых ссылок). Поддержка двух параллельных, независимо обновляемых копий одного и того же контента — почти гарантированный путь к их рассинхронизации со временем. В статье разберём стратегию единого источника истины для контента, из которого генерируются оба представления.
Концепция
Подход единого источника (single source of truth) предполагает, что исходный контент (Markdown-файлы, как в статье 170, или специализированный формат типа AsciiDoc/reStructuredText) хранится в одном месте, и из него автоматически генерируются оба целевых формата: .qch для встроенной справки через уже разобранный пайплайн, и статический HTML-сайт (через тот же MkDocs/Sphinx/Hugo) для размещения на веб-сервере. Различия между двумя представлениями (например, онлайн-версия может включать раздел с информацией для потенциальных покупателей, не нужный в уже установленном приложении) управляются через систему условной компиляции/исключения контента, поддерживаемую большинством генераторов статических сайтов документации.
Пример кода: Совместное использование Qt
docs/
├── content/ # единый источник для обоих представлений
│ ├── getting_started.md
│ ├── advanced_features.md
│ └── pricing.md # только для онлайн-версии
├── mkdocs.yml # конфигурация для онлайн-сайта
└── build_scripts/
├── build_online_site.sh
└── build_qch.sh
# mkdocs.yml — конфигурация генерации онлайн-документации
site_name: MyApp Documentation
docs_dir: content
nav:
- Главная: index.md
- Начало работы: getting_started.md
- Расширенные возможности: advanced_features.md
- Тарифы и покупка: pricing.md # присутствует только онлайн
#!/usr/bin/env bash
# build_scripts/build_qch.sh — генерация .qch из ТОГО ЖЕ исходного контента,
# но с исключением страниц, не предназначенных для встроенной справки
set -e
EXCLUDED_PAGES="pricing.md" # эта страница не нужна в установленном приложении
STAGING_DIR="staging_for_qch"
rm -rf "$STAGING_DIR"
mkdir -p "$STAGING_DIR"
for mdfile in content/*.md; do
basename=$(basename "$mdfile")
if [[ " $EXCLUDED_PAGES " == *" $basename "* ]]; then
echo "Пропуск страницы (не для встроенной справки): $basename"
continue
fi
cp "$mdfile" "$STAGING_DIR/"
done
echo "Конвертация оставшихся страниц в HTML и сборка .qch..."
bash build_help_from_markdown.sh "$STAGING_DIR"
#!/usr/bin/env bash
# build_scripts/build_online_site.sh — генерация полного онлайн-сайта
mkdocs build --config-file mkdocs.yml --site-dir ../public_site
Пояснения к коду
Оба скрипта (build_online_site.sh и build_qch.sh) читают исходные данные из одной и той же директории content/ — изменение текста в getting_started.md автоматически отражается и в следующей пересборке онлайн-сайта, и в следующей пересборке встроенной справки, устраняя саму возможность рассинхронизации текста между двумя представлениями (хотя структура навигации и набор включённых страниц всё же могут различаться, как показано явным исключением pricing.md).
build_qch.sh демонстрирует простой механизм исключения: страница «Тарифы и покупка», уместная на публичном сайте (для привлечения новых пользователей), копируется в промежуточную staging-директорию избирательно — список EXCLUDED_PAGES явно документирует, какие страницы предназначены только для одного из двух представлений, что делает решение об исключении видимым и поддерживаемым, а не скрытым в логике где-то ещё.
Подводные камни
- Расхождение структуры навигации (TOC) между .qch и онлайн-сайтом, даже при общем исходном контенте — если структура
mkdocs.yml(nav:) и структура TOC в генерируемом .qhp поддерживаются раздельно (что и происходит в показанном примере), они могут со временем разойтись по порядку разделов или группировке, даже когда сам текст страниц идентичен — для полной синхронизации нужен единый источник и для структуры навигации, не только для текста контента. - Внутренние ссылки между страницами, по-разному резолвящиеся в двух средах. Ссылка вида
[см. раздел Тарифы](pricing.md)в Markdown-исходнике корректно резолвится онлайн-сайтом (на котором страница существует), но окажется битой в встроенной справке после исключения этой страницы при сборке .qch — нужна либо явная проверка на этапе сборки .qch (отдельная задача для CI-проверки целостности из статьи 171), либо условные блоки в исходном Markdown, явно убирающие такие ссылки для встроенной версии. - Различная поддержка возможностей Markdown между движком, генерирующим онлайн-сайт (часто богатый, с плагинами), и упрощённым пайплайном для .qch. Если онлайн-документация использует специфичные для MkDocs расширения (вкладки, всплывающие подсказки, интерактивные диаграммы), а упрощённый конвертер для .qch не поддерживает эти расширения, встроенная справка может отображать контент менее качественно или с артефактами разметки по сравнению с онлайн-версией того же текста.
- Двойная инфраструктура сборки (два отдельных скрипта/пайплайна) увеличивает поверхность для ошибок настройки. Изменение общей структуры проекта документации (например, переименование директории
content/) требует синхронного обновления обоих скриптов сборки — без общей, централизованной конфигурации путей легко обновить один пайплайн, забыв про другой, что обнаружится только при следующей попытке собрать второй вариант документации.