От монолита к микро-плагину: пишем идеальную подсветку кода для WordPress с нуля

Экосистема WordPress страдает от раздутых плагинов. Ставишь инструмент для подсветки синтаксиса, а в довесок получаешь панель аналитики, сборщик email-адресов, баннеры с призывом купить PRO-версию и пачку лишних SQL-запросов на каждой странице. Мы хотели простую и сверхбыструю подсветку на базе Highlight.js со встроенным мигратором, который просканирует сотни старых статей и сконвертирует штатные блоки кода в современный формат. Так появился проект StoDum Code Block.

Франкенштейн и попытка распилить легаси

Сначала мы пошли по пути наименьшего сопротивления и взяли cloudscale-devtools — форк с зачатками нужной логики и интеграцией с Gutenberg. План казался надежным: отсекаем топором лишнее, меняем названия классов и выкатываем в продакшен.

Реальность ударила по рукам. Базовый плагин оказался классическим монолитом. Регистрация блока, логирование базы данных, мониторинг авторизации и отслеживание 404-ошибок лежали в одном файле на 8000 строк. Мы попытались удалить таблицы SQL и вырезать почтовые хуки, после чего плагин просто рассыпался. Скрипты на фронтенде ждали данных от удаленных модулей, пропали nonce-ключи для AJAX, а стили разъехались из-за пересечения CSS-классов.

Потратив несколько часов на попытки реанимировать этого Франкенштейна, мы выбросили старый PHP-код и написали бекенд с абсолютного нуля, забрав из форка только сырой дизайн компонентов React.

Чистая архитектура и модульный подход

Вместо монолита мы разделили ответственность по модулям. Архитектура получилась аскетичной:

  1. stodum-code-block.php — точка входа, которая делает только две вещи. Она регистрирует блок Gutenberg и подхватывает ассеты через хуки enqueue_block_editor_assets.
  2. class-stodum-settings.php — класс интерфейса в админке, использующий стандартное Settings API без самописных форм и костылей.
  3. class-stodum-migrator.php — изолированное сердце плагина, которое перезаписывает комментарии Gutenberg прямо в базе через wp_update_post.

Мы жестко закрепили единый namespace во всех ID элементов, стилях и функциях. Ассеты фронтенда теперь загружаются только в том случае, если на странице физически присутствует блок кода, что сводит нагрузку к минимуму ⚙️.

Инциденты с типизацией и темной темой

На этапе тестирования интерфейса вылезло несколько забавных багов.

Сначала отвалилась кнопка предпросмотра поста после миграции. Скрипт пытался сгенерировать ссылку, но не находил нужный ID. Проблема скрывалась в строгом равенстве JavaScript. Из PHP мы присылали идентификатор постом как число, а из DOM он считывался как строка. Один принудительный вызов parseInt вернул кнопку к жизни.

Затем темная тема попыталась захватить мир. При клике на иконку луны в конкретном блоке кода его фон становился темным, но глобальный CSS переключал переменные подсветки Highlight.js для всей страницы. В результате остальные блоки сохраняли светлый фон, и черные буквы сливались с ним в невидимую кашу. Мы изменили логику скрипта. Теперь клик по кнопке смены темы в любом блоке мгновенно переключает атрибут data-active-theme на всех сгенерированных обертках .stodum-code-wrapper в пределах страницы.

Эвристика и магия автоопределения языка

Когда парсишь старые статьи, многие базовые блоки не содержат явного указания языка. Заставлять редактора вручную проставлять теги для тысяч сниппетов — преступление. Мы написали эвристический анализатор первой строки. Если язык не задан, скрипт берет первую строчку исходника. Если это короткое слово без пробелов, анализатор воспринимает его как маркер, проставляет нужный язык и вырезает слово из текста с помощью ltrim.

Но тут мы наткнулись на серьезную проблему с автоматическим распознаванием синтаксиса.

Дело о назойливом Свифте

Из-за агрессивных алгоритмов библиотеки Highlight.js почти любой кусок кода с фигурными скобками ошибочно помечался как Apple Swift. Ситуацию усугублял встроенный перехватчик буфера обмена Gutenberg, который безжалостно вычищал наши атрибуты языка при вставке текста в редактор.

Пришлось внедрять многоуровневую систему подавления:

  1. Swift Guard — мы написали фильтр недоверия. Если автодетектор кричит, что перед ним Swift, но пользователь явно этого не просил, плагин блокирует этот ярлык и заменяет его на нейтральный.
  2. Глобальный перехватчик — плагин ловит текст из буфера обмена до того, как его обработает редактор WordPress, сам режет его на куски и принудительно вставляет готовые блоки с жестко заданными параметрами.
  3. Биометрия кода — мы внедрили систему эвристик, которая анализирует специфические паттерны.

Ядро нашей интуиции на JavaScript теперь выглядит так:

StoDum
function guessLanguage( content ) {
    var trimmed = content.trim();
    // детектор PHP: переменные, стрелочки, специфические функции
    if ( /\$[a-zA-Z_\x7f-\xff]/.test(trimmed) && (
            trimmed.indexOf('->') !== -1 || trimmed.indexOf('preg_match(') !== -1
         )
    ) return 'php';

    // bash-контроль: команды во главе строки
    if ( /^(docker|sudo|wo|git) /m.test(trimmed) || /^(export|unset) [A-Z_]+=/m.test(trimmed) ) {
        return 'bash';
    }
    return '';
}

Плагин научился узнавать код в лицо. Видит $var и -> — точно PHP. Встречает docker, sudo или специфические инструменты вроде wo — железный Bash. Мы даже научили скрипт отличать Bash-экспорт переменных от JavaScript-модулей ⚠️.

Интернационализация через консоль

Чтобы плагин работал по учебнику, мы подготовили его к мультиязычности. Все текстовые строки в скриптах обернули в функции перевода __(). Фронтенд адаптировали, вынеся системные фразы из Vanilla JS в локализованный объект через wp_localize_script.

Затем мы создали матрицу перевода и руками скомпилировали бинарный справочник через утилиту gettext/msgfmt напрямую в терминале Ubuntu. Плагин заговорил по-русски из коробки без дополнительных плагинов локализации.

То, что начиналось как попытка быстро распилить старый форк, превратилось в полноценную разработку микро-плагина. Плагин стабилен, локализован и выполняет всю грязную работу на этом сайте.