Конфигурация

Параметры плагина и необязательные настройки Vite складывайте в sitelo.config.js. Отдельный vite.config.js не нужен.

export default {
  site: 'https://example.com',
  rss: {
    site: 'https://example.com',
    title: 'Мой блог',
    description: 'Последние записи',
    routePrefix: '/blog',
  },
  vite: {
    publicDir: 'static',
    build: {
      emptyOutDir: true,
      outDir: 'public',
    },
    server: {
      port: 8888,
    },
  },
}

Параметры плагина

Панель разработчика

Пока работает sitelo (dev), небольшая полоса внизу каждой страницы показывает файл страницы, её параметры и количество серверных островов на ней. Кнопка viewport переключает предпросмотр Desktop / Tablet / Mobile (через iframe, чтобы медиазапросы совпадали), а Copy выдаёт отладочный блок для обращений в трекер. В выводе sitelo build она не появляется никогда.

// sitelo.config.js
export default {
  devToolbar: false, // скрыть для всех в этом проекте
}

Параметры Vite

Всё, что находится под ключом vite, подмешивается в конфигурацию Vite. Флаги CLI (например, --port) важнее обоих.

Если vite.config.js уже есть

Он по-прежнему поддерживается — либо только параметры Vite, либо полный контроль над плагином:

// Только параметры Vite; sitelo всё равно подключит плагин
export default {
  publicDir: 'static',
  server: { port: 8888 },
}
// Подключить плагин самостоятельно
import htmlPages from 'sitelo'

export default {
  plugins: [htmlPages({
    site: 'https://example.com',
  })],
}

Если плагин уже подключён в вашей конфигурации Vite, задавайте параметры плагина там — и не дублируйте их как параметры плагина в sitelo.config.js (sitelo выдаст ошибку).

Sitemap и RSS

Задайте site, чтобы создавался dist/sitemap.xml.

RSS:

export default {
  rss: {
    site: 'https://example.com',
    title: 'Мой блог',
    description: 'Последние записи',
    routePrefix: '/blog',
  },
}

Создаёт dist/rss.xml с элементом для каждой страницы внутри routePrefix.

Проверка ссылок

Битые <script src> и href таблиц стилей уже прерывают сборку (см. missingAssets). linkCheck закрывает вторую половину: внутренние ссылки <a href>, ведущие на несуществующую страницу.

export default {
  linkCheck: true,   // 'warn' (по умолчанию), 'error' или объект параметров
}

После sitelo build каждая внутренняя ссылка в выводе разрешается, и всё, за чем не стоит страницы, попадает в отчёт, сгруппированный по странице, где встретилось:

[sitelo] 3 битых внутренних ссылок

  index.html
    ../escape           -> выходит за пределы каталога сборки
    /abuot              -> такой страницы нет
    /blog/missing-post  -> такой страницы нет

Как разрешаются ссылки

Проверка идёт по собранному сайту, а не по таблице маршрутов, поэтому учитывает cleanUrls, группы маршрутов, mapOutputPath, файлы, скопированные из public/, и страницы, созданные динамическими маршрутами. Она выполняется после оптимизации изображений и Pagefind, так что видит ровно то, что публикуется. Ссылка верна, когда на неё отвечает настоящий файл, и порядок проверки такой же, как у статического хостинга:

Относительные ссылки (../about) разрешаются относительно страницы, где находятся, и те, что выходят за пределы каталога сборки, попадают в отчёт. Строки запроса при разрешении игнорируются: /about?utm=x проверяет /about.

Внешние ссылки никогда не запрашиваются. https://, протокол-относительные //cdn.example.com, mailto:, tel: и прочие схемы пропускаются полностью.

Параметры

ПараметрПо умолчаниюОписание
mode'warn''warn' пишет в лог и продолжает; 'error' прерывает сборку — полезно в CI
exclude[]Glob-шаблоны или регулярные выражения href, которые надо пропустить
checkFragmentsfalseДополнительно проверяет, что цели #fragment есть на связанной странице
export default {
  linkCheck: {
    mode: 'error',                    // прервать сборку при битой ссылке
    checkFragments: true,             // проверять также цели #fragment
    exclude: ['/api/**', /^\/legacy\//],
  },
}

checkFragments выключен по умолчанию, потому что идентификаторы, добавляемые клиентским JavaScript, отсутствуют в собранном HTML и попадали бы в отчёт как отсутствующие. Целями фрагментов считаются и id, и устаревший атрибут якоря name.

Сайты на базовом пути

Если сайт развёрнут по base (скажем, проектный сайт GitHub Pages), любая корневая ссылка без этого базового пути попадёт в отчёт. Ссылка /about на сайте, отдаваемом из /repo/, уводит браузер в корень хоста, а не внутрь вашего сайта — разрешив её по выводу, мы скрыли бы ровно ту ошибку, которую и стоит ловить. Если это сделано намеренно, используйте exclude.

Аудит Lighthouse

sitelo lighthouse проверяет готовую сборку с помощью Lighthouse. Это необязательная peer-зависимость:

npm install -D lighthouse

sitelo отдаёт dist/ так же, как sitelo preview, направляет headless-Chrome на каждую страницу и печатает оценки:

[sitelo] lighthouse mobile - 3 pages

  page           perf  a11y  best   seo
  /                98   100   100   100
  /docs            95   100   100   100
  /docs/routing    97   100   100   100

[sitelo] lighthouse audited 3 pages in 31.4s

Каждая страница проверяется по тому URL, который сайт указывает в ссылках, — /docs, а не dist/docs.html, — поэтому cleanUrls, группы маршрутов и base учтены. Задайте пороги, и отчёт станет проверкой: всё, что ниже порога, завершает команду с ошибкой.

export default {
  lighthouse: {
    exclude: ['404.html'],   // страницу 404 проверять обычно незачем
    thresholds: {
      performance: 90,
      accessibility: 100,
      'best-practices': 95,
      seo: 100,
    },
  },
}

Оценки записываются так, как их показывает Lighthouse (0100); его собственные доли от 0 до 1 тоже работают. С mode: 'warn' будет только запись в лог.

export default {
  lighthouse: {
    formFactor: 'desktop',   // десктопный пресет Lighthouse
    runs: 3,                 // три прогона на страницу, медианная оценка
    output: true,            // полные отчёты в .sitelo/lighthouse/
    onBuild: true,           // проверять и в конце sitelo build
    flags: { throttlingMethod: 'provided' },
  },
}

Lighthouse управляет настоящим Chrome: установите его там, где идёт аудит, или укажите путь в CHROME_PATH. Оценки производительности колеблются между прогонами, поэтому перед порогом на них поставьте runs: 3.

Поиск Pagefind

Необязательный статический поиск на Pagefind — это опциональная peer-зависимость. Установите её, когда нужен поиск, затем включите индексацию, разметьте содержимое, подключите интерфейс и запустите sitelo build.

npm install -D pagefind

1. Включите индексацию

Поставьте pagefind: true. После sitelo build появится dist/pagefind/ и, по умолчанию, копия в public/pagefind/, чтобы следующий sitelo (dev) или sitelo preview могли отдавать /pagefind/ без пересборки.

export default {
  pagefind: true,
}

2. Разметьте содержимое и добавьте точку монтирования

Поставьте data-pagefind-body на основное содержимое, чтобы навигация и подвал не индексировались. Оставьте пустой элемент под интерфейс поиска:

import {
  html, head, title, link, script, body, header, a, div, main, h1, p,
} from 'javascript-to-html'

export default () =>
  html({ lang: 'ru' },
    head(
      title('Мой сайт'),
      link({ rel: 'stylesheet', href: '/styles.css' }),
      script({ type: 'module', src: '/main.js' }),
    ),
    body(
      header(
        a({ href: '/' }, 'Главная'),
        div({ id: 'search' }),
      ),
      main({ 'data-pagefind-body': '' },
        h1('Привет'),
        p('Индексируется только эта область.'),
      ),
    ),
  )

3. Подключите интерфейс Pagefind

Загрузите /pagefind/pagefind-ui.js и pagefind-ui.css из своего клиентского скрипта (только после того, как сборка создала индекс):

async function initSearch() {
  const mount = document.querySelector('#search')
  if (!mount) return

  // Индекс появляется только после `sitelo build` (по умолчанию синхронизируется в public/pagefind)
  try {
    const probe = await fetch('/pagefind/pagefind-ui.js', { method: 'HEAD' })
    if (!probe.ok) return
  } catch {
    return
  }

  const style = document.createElement('link')
  style.rel = 'stylesheet'
  style.href = '/pagefind/pagefind-ui.css'
  document.head.appendChild(style)

  await new Promise((resolve, reject) => {
    const script = document.createElement('script')
    script.src = '/pagefind/pagefind-ui.js'
    script.onload = resolve
    script.onerror = reject
    document.body.appendChild(script)
  })

  new window.PagefindUI({
    element: '#search',
    showImages: false,
  })
}

initSearch()

4. Соберите и исключите синхронизированную копию из Git

sitelo build
# затем: sitelo preview — или sitelo (dev) с public/pagefind
public/pagefind/

Расширенные параметры (syncPublic, glob, язык, селекторы, …) задаются объектом: pagefind: { syncPublic: false, glob: '**/*.html' }. Все параметры интерфейса: pagefind.app.

404

Создайте src/404.ht.js, чтобы получить dist/404.html. Иначе будет создана аккуратная страница по умолчанию.