Проверки
Зелёная сборка ещё не значит, что со страницами всё в порядке. Этот набор инструментов ставит по одной проверке на каждый слой — каждая ловит свой класс тихих поломок вида «сборка зелёная, страница сломана», — и две из них смотрят на страницы так, как смотрит читатель: в настоящем браузере.
§1Пять рубежей — пять слоёв
| Проверка | Едет с | Смотрит на | Когда запускать |
|---|---|---|---|
| check-content | движок, scripts/check-content.mjs | каждый исходный md/mdx | CI репозитория контента, или после письма |
| check-wikilinks | движок, scripts/check-wikilinks.mjs | граф [[вики-ссылок]] | CI репозитория контента, или после переименования заметки |
| check-dist | движок, scripts/check-dist.mjs | вывод astro build | после каждой сборки (postbuild) |
| ui_probe | этот пакет, scripts/ui_probe.mjs | страницы, отрисованные в настоящем браузере | после правок стилей и макета |
| contrast_probe | этот пакет, scripts/contrast_probe.mjs | контраст каждого текстового узла, обе темы | после любой правки токенов |
§2check-content: слой исходников
Компилирует каждый md/mdx, которому предстоит стать страницей, ровно тем же диалектом, что и сайт, — синтаксические ошибки и тихие деформации (непарные маркеры выделения, скобки, проглоченные вычислением MDX, маркеры списков, рождённые переносом строки, однострочный $$, формулы, которые KaTeX не осилит, — тот самый список, которым закрывается «Всё и сразу») окрашивают CI в красный.
Почему этот скрипт обязан приходить из движка: проверка, чей набор плагинов отличается от сайтового, хуже, чем никакой проверки — без плагина математики она читает скобки формул как выражения JSX, без GFM пропускает таблицы. Диалект написан в движке однажды; отрисовка сайта, валидация сохранений CMS и этот скрипт потребляют один и тот же — всегда.
node <engine>/scripts/check-content.mjs . --glob '**/index.{md,mdx}' --mathПомимо компиляции он ловит два класса тихих потерь во frontmatter: неэкранированный # в значении (YAML считает его комментарием и молча отрезает хвост) и YAML, который вовсе не парсится, — с указанием строки.
§3check-wikilinks: граф ссылок
Мёртвые [[вики-ссылки]] нарочно не роняют сборку: саду положено ссылаться на заметки, которые ещё не выросли. Но гнили ссылок место в CI, поэтому движок поставляет линт, который разрешает каждую вики-ссылку парсером и правилами разрешения самой библиотеки (псевдоним, брендовое имя, заголовок, локальные зеркала) и докладывает о пропавших целях, неоднозначностях и сомнительных якорях. --strict превращает мёртвые ссылки в ненулевой код выхода — именно так его запускает CI этого репозитория:
node scripts/check-links.mjs§4check-dist: слой сборки
В собранном dist/ каждая внутренняя ссылка, по которой читатель может щёлкнуть, обязана существовать. Проверка ловит тихие дыры под зелёной сборкой:
- внутренние ссылки и ассеты, указывающие на несуществующие файлы (после перекройки маршрутов такие появляются дюжинами);
- внутристраничные якоря, указывающие на несуществующие id;
- удвоенные сегменты локали в путях (
/en/en/— классический результат i18n-фолбэков, наслаивающих префикс на уже префиксованные маршруты); <a>, вложенный в<a>(HTML-парсер рано закрывает внешний, и кнопки вываливаются из карточек);- осадок ошибок KaTeX (формула стоит на странице красным текстом, а сборка остаётся зелёной).
Это демо включает его в postbuild: зелёный npm run build означает, что проверка вывода тоже пройдена.
node vendor/astro-inkbrush/scripts/check-dist.mjs dist --base ${DEMO_BASE:-/}§5Зонд слоя отрисовки
Исходники и вывод могут быть правы одновременно, а страница всё равно сломана; классика жанра — карточки главной, до которых не дотягивается ни одно правило стилей и которые отрисовываются одной сплющенной строкой голого текста: проверки ссылок и якорей остаются зелёными, ведь на отрисованную страницу они не смотрят вовсе. ui_probe прогоняет настоящий браузер по каждой странице dist на четырёх ширинах вьюпорта (1440/1024/768/430) и меряет: горизонтальное переполнение страницы, элементы шире контейнера без скролл-бокса, где им жить, текст, который SVG рисует за пределами своей рамки, подписи линий FlowDiagram, которых нет ни у линии, ни в легенде, подписи и номера поверх блока, заголовка или друг друга, линии сквозь блок, который они не соединяют, или сквозь заголовок, классы, которые не стилизует ни одно правило, пропущенные уровни заголовков, повторяющиеся id, изображения без атрибута alt, внутристраничные якоря и aria-controls, указывающие в пустоту. Он докладывает только то, что машина может доказать, — без эстетических суждений.
npm run build
node ../scripts/ui_probe.mjs dist # сам раздаёт dist; передайте baseUrl, чтобы проверить живой серверОн осматривает документ целиком — вместе с обвязкой, сайдбаром и диалогами. Зелёный итог — это когда последняя строка отчёта гласит SAMPLES WITH FINDINGS: 0 (замер — один маршрут на одной ширине).
§6Зонд контраста
Токены заявляют AA — значит, заявление измеряется, а не постулируется. contrast_probe открывает каждую страницу dist в настоящем браузере — светлая и тёмная тема, десктопная и телефонная ширина — и меряет каждый текстовый фрагмент, отрисованный в состоянии по умолчанию: текст HTML, текст SVG и генерируемый текст ::before / ::after; каждый оверлей <dialog data-probe-open> прощупывается открытым (а в найденное там поле поиска вводится запрос) на представительной странице — этот маркер и есть заявление сайта, что диалог в авторском виде полон. Фон не вычитывается из таблицы стилей: страница отрисовывается со всеми глифами, сделанными прозрачными, снимается скриншот, и пиксель под фрагментом — его фон; так градиенты, примеси color-mix(), полупрозрачные слои и ночная палитра меряются такими, какими они рендерятся. Передний план фрагмента несёт накопленную opacity элемента и его предков — приглушённый текст меряется той силой, какой его действительно видит читатель. Состояния hover и focus рецензируются глазами, не зондом. Мелкий текст держит планку 4.5:1, крупный (24px, или 18.66px жирным) — 3:1; фрагмент, чей цвет не разобрать или чей фон не снять, сам считается находкой. Отчёт называет страницу, тему, ширину, селектор, оба цвета и коэффициент каждого фрагмента, не добравшего до планки:
node ../scripts/contrast_probe.mjs dist # PROBE_THEMES / PROBE_WIDTHS сужают матрицуui_probe и contrast_probe по её выводу — всё зелёное до коммита. Демо по совместительству — испытательный стенд пакета.