Используйте highlight.js для реализации подсветки синтаксиса кода в ToolMi

В нем подробно описывается, как интегрировать highlight.js в проект Vue 3 для реализации подсветки синтаксиса кода, включая полный технический выбор, процесс реализации и обмен опытом.

Контекст проекта и анализ требований

Как разработчик ToolMi (gongjumi.com), я постоянно стремлюсь улучшить опыт работы пользователей с онлайн-инструментами. Недавно при обновлении функции отображения кода на сайте я заметил, что нативный компонент textarea имеет серьёзные UX-ограничения при вводе и выводе кода.

На Gongjumi есть множество инструментов для работы с кодом, таких как YAML-форматтер, Генератор Java-классов из SQL и Beautifier TypeScript. Все они требуют ввода кода и отображения результатов. Однако простые textarea не поддерживают подсветку синтаксиса, что затрудняет поиск ошибок в сложном коде и ухудшает общий пользовательский опыт.

Выбор технологии: почему highlight.js

Из множества библиотек для подсветки синтаксиса я выбрал highlight.js по нескольким причинам:

Широкая поддержка языков

highlight.js поддерживает более 190 языков программирования — от популярных JavaScript, Python, Java до редких функциональных языков. Это важно для сайта, обрабатывающего разнообразные форматы кода.

Разнообразие тем

Библиотека предоставляет темы в стиле GitHub, VS Code, Atom и др., что позволяет подобрать оформление, соответствующее дизайну Gongjumi.

Совместимость с экосистемой

highlight.js легко интегрируется с Vue.js, установка проста. Кроме того, встроенная автоопределение языка применяет подсветку даже без указания языка пользователем.

Преимущества производительности

По сравнению с тяжёлыми редакторами (например, Monaco Editor) highlight.js легче и загружается быстрее — идеально для быстрых онлайн-инструментов Gongjumi.

Детали реализации

Первый подход: двухслойная наложенная структура

Изначально я попробовал наложить div с подсвеченным кодом поверх прозрачного textarea:

<div class="editor-container">
  <div class="code-highlight" v-html="highlightedCode"></div>
  <textarea v-model="code" class="code-textarea"></textarea>
</div>

Этот метод позволял вводить код в прозрачный textarea, а на фоне отображался подсвеченный код. Но он имел серьёзные недостатки:

  • Слои часто смещались при длинном коде или множестве переносов строк.
  • Сложно было синхронизировать прокрутку.
  • Трудно было добиться точного совпадения шрифтов, межстрочного интервала и отступов.

Оптимизация с помощью CSS Grid

Для выравнивания я поместил оба элемента в одну сеточную область:

.editor-container {
  display: grid;
  grid-template-areas: "editor";
}
.code-highlight, .code-textarea {
  grid-area: editor;
}

Это улучшило выравнивание, но синхронизация прокрутки и единообразие стилей оставались проблемными.

Окончательное решение: contenteditable div

После множества экспериментов я пришёл к выводу, что лучше всего использовать div с contenteditable:

<div
  contenteditable="true"
  class="editable-code hljs"
  v-html="highlightedCode"
  @input="handleInput"
/>

Преимущества:

  • Используются стандартные CSS-стили highlight.js без доработок.
  • Нет сложной структуры наложения, исчезают проблемы с выравниванием и синхронизацией.
  • Редактирование и отображение максимально унифицированы, UX становится более плавным.

Основная реализация кода

Проектирование интерфейса компонента

Чтобы сделать редактор переиспользуемым во всех инструментах Gongjumi, я разработал следующий интерфейс:

interface Props {
  modelValue: string      // двунаправленная привязка кода
  language?: string       // необязательное указание языка
  readonly?: boolean      // режим только для чтения
  placeholder?: string    // текст-подсказка
  rows?: number           // число видимых строк
}

Логика подсветки синтаксиса

Ключевой момент — вызов API highlight.js и обработка ошибок:

const updateHighlight = () => {
  try {
    if (props.language && hljs.getLanguage(props.language)) {
      const result = hljs.highlight(localValue.value, { language: props.language });
      highlightedCode.value = result.value;
    } else {
      const result = hljs.highlightAuto(localValue.value);
      highlightedCode.value = result.value;
    }
  } catch (error) {
    console.error('Ошибка подсветки синтаксиса:', error);
    highlightedCode.value = escapeHtml(localValue.value);
  }
};

Режим редактирования и режим только чтения

Для ввода и отображения я реализовал два режима:

<template>
  <!-- Режим только чтения для отображения результатов -->
  <pre v-if="readonly" class="readonly-code hljs" v-html="highlightedCode"></pre>

  <!-- Режим редактирования для ввода кода -->
  <div v-else contenteditable="true" class="editable-code hljs" @input="handleInput"></div>
</template>

Ключевые технические детали

Интеграция темы

Чтобы соответствовать дизайну Gongjumi, я выбрал тему в стиле GitHub и слегка изменил цвета:

// импорт темы highlight.js
import 'highlight.js/styles/github.css';

Улучшения взаимодействия с пользователем

  • Обработка клавиши Tab: вставка пробелов вместо перехода фокуса.
  • Обработка вставки: удаление форматирования, сохранение только plain-text.
  • Дебаунс ввода: предотвращение слишком частых обновлений при быстром наборе.

Сохранение позиции курсора

Чтобы курсор не прыгал при обновлении innerHTML, я реализовал сохранение и восстановление позиции:

const handleInput = () => {
  const cursorPosition = saveCursorPosition();
  updateContent();
  updateHighlight();
  nextTick(() => {
    restoreCursorPosition(cursorPosition);
  });
};

Эффект в реальных случаях

Обновление инструмента SQL → Java Entity

  • Полная подсветка синтаксиса SQL в поле ввода.
  • Профессиональная подсветка сгенерированного Java-кода.
  • Более аккуратный интерфейс и очень положительный отзыв пользователей.

Улучшение JSON-форматтера

Подсветка помогает лучше видеть структуру JSON и быстрее находить ошибки.

Beautifier TypeScript

Переход от простых <pre> и <code> к полноценному компоненту, приближенному к IDE.

Оптимизации производительности и лучшие практики

Загрузка по требованию

import hljs from 'highlight.js/lib/core';
import javascript from 'highlight.js/lib/languages/javascript';
import python from 'highlight.js/lib/languages/python';
import java from 'highlight.js/lib/languages/java';
import sql from 'highlight.js/lib/languages/sql';

hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('python', python);
hljs.registerLanguage('java', java);
hljs.registerLanguage('sql', sql);

Управление жизненным циклом компонента

  • Инициализация highlight.js при монтировании.
  • Обновление подсветки при обновлениях компонента.
  • Очистка ресурсов при размонтировании.

Адаптивный дизайн

  • Подходящий размер шрифта и межстрочный интервал для мобильных.
  • Удобное сенсорное управление.
  • Оптимизированная прокрутка и масштабирование.

Выводы и уроки

Распространённые проблемы и решения

Смещение слоёв

Решение: отказаться от двухслойного наложения в пользу div с contenteditable. Если нужен двухслойный подход, используйте CSS Grid вместо абсолютного позиционирования.

Прыжки курсора

Решено сохранением/восстановлением позиции курсора и дебаунсом обновлений.

Конфликты стилей

Изолируйте стили highlight.js с помощью CSS Modules или scope-стилей.

Советы по отладке

  • Используйте Elements во вкладке DevTools для проверки изменений DOM.
  • Следите за ошибками highlight.js в консоли.
  • Тестируйте с разными языками и длиной кода.
  • Проверяйте в разных браузерах и на устройствах.

Планы на будущее

Новые функции

  • Номера строк: упрощают навигацию по длинным фрагментам.
  • Сворачивание кода: collapse/expand блоки.
  • Поиск и замена: быстро находить и заменять текст.
  • Интегрированный форматер: один клик с Prettier.

Дополнительная оптимизация производительности

  • Виртуальная прокрутка для больших файлов.
  • Улучшение алгоритмов подсветки для снижения задержек.
  • Кэширование для предотвращения повторного анализа.

Итоги проекта и рефлексия

Технические достижения

Интеграция highlight.js позволила глубже понять внутреннюю работу современных фронтенд-редакторов, освоить сложную обработку пользовательских взаимодействий и укрепить навыки компонентного развития.

Ценность для пользователей

Подсветка синтаксиса значительно улучшила UX при поиске ошибок и понимании сложного кода, что подтверждается отзывами пользователей.

Архитектурные выводы

Хорошо спроектированный компонент не только решает текущие задачи, но и становится надёжной базой для будущего развития и повторного использования в других проектах.

Для сайта онлайн-инструментов Gongjumi важно внимание к деталям UX. Подсветка синтаксиса — небольшая функция, но она отражает глубокое понимание потребностей пользователей и стремление к качеству.