csv-table: Hugo шорткод для интерактивных таблиц из CSV файлов
Положи CSV в проект, добавь одну строку в пост - получишь сортируемую, адаптивную и доступную HTML таблицу без единой строки разметки.

Если ты когда-нибудь пытался показать табличные данные на Hugo-сайте, то знаешь, как быстро всё идёт наперекосяк. Скажем, у тебя есть нормальный CSV файл с рейтингами приложений, сравнением цен или результатами бенчмарков. Чтобы превратить его в красивую таблицу на странице, придётся либо возиться с разметкой Markdown, либо писать HTML руками. Оба варианта так себе, и оба плохо масштабируются.
Я сталкивался с этим достаточно часто, чтобы в итоге сделать нормальный инструмент, шорткод csv-table. В нём ты просто указываешь CSV файл и получаешь стилизованную, сортируемую, адаптивную HTML таблицу. Одна строка в контенте, ноль ручной разметки.
Почему Markdown таблицы не работают
Каждый, кто пробовал собрать Markdown таблицу размером больше, чем 3x3 знает эту боль. Brian Wisti в своём посте про CSV и таблицы данных в Hugo сформулировал точно: читать их легко, а вот поддерживать без плагинов для редактора - мучение. И он прав. Проблемы начинаются сразу:
Во-первых, форматирование Markdown таблиц очень хрупкое. Пропустил один символ pipe или нечаянно добавил лишний столбец в строке - и вот уже вместо таблицы получаешь кашу из символов. Никаких сообщений об ошибке, просто сломанная вёрстка.
Во-вторых, они не масштабируются. Таблица 5x5 может работать нормально. А тридцать строк из Excel уже нет. Каждый раз, когда данные меняются, тебе приходится заново выравнивать все эти вертикальные черточки вручную.
В-третьих, никакой интерактивности. Тридцать строк данных, а читатель не может отсортировать их по цене, рейтингу или названию. Markdown так просто не умеет.
Ну и наконец, данные намертво вшиты в текст статьи. Они не существуют отдельно. Нельзя переиспользовать ту же таблицу в другом посте, обновить данные независимо от текста или сгенерировать их скриптом и просто подключить.
Что уже есть и почему этого мало
В Hugo есть встроенные средства для работы с данными. Функция transform.Unmarshal умеет разбирать CSV в массивы, и несколько человек в сообществе показали, как это можно использовать.
Шорткод csv-to-table от Joe Mooring - добротное решение. Он работает с ресурсами страницы, секции и глобальными ресурсами, корректно обрабатывает ошибки, поддерживает заголовки, произвольные разделители и необязательные строки заголовков. Для простой конвертации CSV в HTML - отличная отправная точка.
Brian Wisti (ссылка выше) пошёл другим путём: он вставлял CSV-данные прямо внутрь тегов шорткода, а не ссылался на внешний файл. Ещё он экспериментировал с таблицами на основе JSON и форматом list-table, ориентированным на строки. Пост интересный и хорошо показывает, как далеко можно зайти с Hugo-шорткодами при должной изобретательности.
Оба подхода дали мне идеи, но ни один не решал мою задачу целиком. Мне нужны были сортировка, адаптивность для мобильных устройств, выравнивание по столбцам, возможность скрывать столбцы и ограничивать строки, плюс правильная разметка для доступности. Причём всё это не по отдельности, а как единый инструмент, который можно использовать везде.
Как работает csv-table
Данные лежат в CSV-файле в директории assets Hugo. В тексте статьи вызов выглядит так:
{{< csv-table file="app-rankings.csv" >}}
Всё. Hugo разбирает CSV при сборке через transform.Unmarshal (без собственного парсера) и генерирует семантическую HTML-таблицу с правильными <thead>, <tbody>, атрибутами scope, ролями ARIA и индикаторами aria-sort.
На стороне клиента за интерактивность отвечает tablesort.js: клик по заголовку столбца сортирует по возрастанию, повторный клик - по убыванию. Сортировка корректно работает с форматированными числами, процентами и валютными значениями.
Просто по умолчанию, гибко по запросу
Принцип простой: соглашения важнее конфигурации. Настройки по умолчанию покрывают 80% случаев. Остальные 20% решаются параметрами, а не форком шаблона.
Вот как выглядит более развёрнутый вызов:
{{< csv-table
file="sales.csv"
caption="Q1 Sales by Region"
sortBy="Revenue"
order="desc"
limit=10
col_right="Revenue,Units"
col_hide="Internal_ID"
compact=true
>}}
Здесь берётся файл sales.csv, показываются только 10 строк с наибольшим Revenue, числовые столбцы выровнены вправо, столбец Internal ID скрыт, включён компактный режим с плотной вёрсткой. Заголовок таблицы отображается сверху.
Полный набор параметров покрывает всё, что мне понадобилось на практике:
caption- заголовок таблицыsortByиorder- начальная сортировка (по возрастанию по умолчанию)limit- максимальное число отображаемых строкcol_hide- скрытие столбцов без правки исходного файлаcol_nowrap- запрет переноса текста в столбцеcol_left,col_center,col_right- выравнивание по столбцамcol_width- явные CSS-ширины через<colgroup>header(значение “hide”) - скрыть строку заголовкаfont_monoиfont_size- управление шрифтомcompact- плотная вёрсткаresponsive- режим адаптивности: горизонтальная прокрутка или карточки на мобильныхclass- дополнительные CSS-классы
Отдельно отмечу: все параметры столбцов работают по имени, а не по номеру. Пишешь col_hide="Email,Phone", а не col_hide="3,5". Внутри шорткод строит карту «заголовок - индекс», так что поиск быстрый, а вызов остаётся читаемым. И если ты добавишь или переставишь столбцы в CSV, шорткод не сломается.
Таблицы на маленьких экранах
Таблицы и мобильные устройства - сочетание болезненное. Шорткод предлагает два режима.
По умолчанию включён scroll: таблица оборачивается в горизонтально прокручиваемый контейнер. Структура таблицы сохраняется, что важно, когда столбцы нужно сравнивать между собой (таблицы сравнения, спецификации).
Альтернатива - stack: каждая строка превращается в карточку на узком экране. Перед каждой ячейкой появляется название столбца через атрибуты data-label и CSS-псевдоэлементы ::before. Это чистый CSS, без JavaScript. Хорошо работает для данных, где каждая строка - самостоятельная запись (список приложений, каталог товаров), и читателю удобнее видеть все поля одного элемента сразу.
Что происходит под капотом
Несколько технических деталей для любопытных.
CSS и JavaScript шорткода загружаются один раз на страницу, даже если на ней пять таблиц. Механизм Scratch в Hugo отслеживает, были ли ресурсы уже подключены, и не дублирует теги <link> и <script>.
И CSS, и JS проходят через конвейер ресурсов Hugo: в production они минифицированы, а имена файлов содержат хеш для сброса кэша. Библиотеку tablesort можно загружать из CDN (настраивается в параметрах темы) или подключить локально, если сайт обходится без внешних запросов.
CSS построен на кастомных свойствах и поддерживает тёмный режим через селектор [data-theme="dark"]. Если тема уже переключает этот атрибут, таблицы подхватывают тему автоматически.
Что касается доступности: в разметке есть атрибуты role, scope на ячейках заголовков и aria-sort, которые обновляются динамически при сортировке. Скринридеры могут навигировать по структуре таблицы и понимают текущий порядок сортировки.
Если что-то пошло не так (файл не найден, неверный путь, битый CSV), шорткод покажет внятное сообщение об ошибке прямо на месте таблицы. Без молчаливых сбоев, без сломанной вёрстки.
Где это реально экономит время
Шорткод работает в моём блоге уже какое-то время. Вот сценарии, где он приносит больше всего пользы.
Когда данные часто меняются (ежемесячные рейтинги, квартальные цифры), я просто обновляю CSV и пересобираю сайт. Сам пост не трогаю.
Например, мой пост Top Raycast Extensions использует csv-table для вывода полного рейтинга расширений с сортировкой, ограничением строк и скрытыми столбцами. Все данные - один CSV файл, который я периодически перегенерирую. Обновление поста = замена файла + пересборка.
Когда данные приходят из скрипта или экспорта таблицы, CSV идёт прямо в проект без какого-либо переформатирования.
Когда нужно показать “Топ 10” из большого набора данных, параметр limit решает задачу без обрезки исходного файла.
Когда одни и те же данные нужны в нескольких постах с разным представлением (разные видимые столбцы, разная сортировка), я ссылаюсь на один CSV с разными параметрами. Один источник данных, несколько представлений.
Хочу такое же!
Код пока не в открытом доступе. Я сделал его для своей Hugo темы (“domino”) и пока не дошли руки оформить его в отдельный документированный пакет. Если будет интерес, опубликую как переиспользуемый Hugo модуль. Напиши мне, если тебе это нужно.
Все составные части хорошо задокументированы: transform.Unmarshal для разбора CSV, система параметров на основе map для операций со столбцами, tablesort.js для сортировки на клиенте, CSS custom properties для управления темой.
Сложность не в какой-то одной из этих частей, а в том, чтобы собрать их вместе: ARIA-атрибуты, адаптивные режимы, дедупликация ресурсов, обработка ошибок и разумные настройки по умолчанию, чтобы простые случаи оставались простыми.
Один емейл, когда выйдет новый пост.



