# Редактор кода на Monaco для Liquid-шаблонов inSales

> Общая сборка Monaco для смешанного HTML, Liquid, CSS и JavaScript работает в пяти продуктовых интерфейсах inSales. Сделал первую интеграцию и больше трёх лет был основным мейнтейнером.

- Адрес: https://anu3ev.com/ru/work/monaco-editor/
- Период: Июль 2022 – ноябрь 2025
- Где: inSales
- Роль: Вёл разработку: языковая поддержка, браузерный API, сборка, интеграции
- Навыки: Monaco Editor, TypeScript, Liquid, JavaScript, Front-End Architecture, Vue.js, Webpack, API Integration, CI/CD, Gitlab, Ruby on Rails, SCSS, HTML, Developer Tools, Software Maintenance, Syntax Highlighting, Autocomplete
- Результаты:
  - 5: продуктовых интерфейсов inSales работают на одной сборке редактора
  - 3+ года: был основным мейнтейнером, с июля 2022 по октябрь 2025

## Задача

В шаблонах витрин inSales в одном файле живут HTML, Liquid, JavaScript и CSS, а настройки и переводы хранятся в JSON. Этот код в браузере, прямо в админке, правят партнёры-разработчики, сотрудники поддержки и технически подкованные владельцы магазинов, не скачивая шаблон в IDE.

Прежняя интеграция CodeMirror умела открыть и сохранить код, но не подсвечивала смешанный синтаксис, не подсказывала с учётом позиции курсора, не знала Emmet и сниппетов inSales. Чтобы довести её до нормальной рабочей среды, языковую часть всё равно пришлось бы проектировать заново. Поэтому выбрали Monaco — движок, на котором построен VS Code, — а кастомную сборку и её внедрение поручили мне.

## Моя роль

С июля 2022 по октябрь 2025 года вёл разработку редактора. Сделал первую интеграцию для кода виджетов, потом вынес её в общий пакет и оставался его основным мейнтейнером, пока редактор подключали к новым экранам. На мне были языковая поддержка, браузерный API, упаковка, настройка релизов и крупные интеграции. Сам движок редактирования дал Monaco. Коллеги помогали с путями к воркерам, обновлением темы и форматированием кода.

## Что я сделал

### Токенизатор для шаблонов, где HTML смешан с Liquid

Liquid стоит прямо в разметке: вывод, управляющие теги, комментарии, блоки `raw`, фильтры inSales. Он бывает и внутри значения HTML-атрибута, целиком или вперемешку с обычным текстом. Я зарегистрировал собственный язык HTML + Liquid с токенизатором Monarch и языковой конфигурацией. После закрывающего разделителя внутри атрибута токенизатор должен вернуться в состояние атрибута, а не красить остаток строки как Liquid. Внутри `script` он переключается на JavaScript или JSON, внутри `style` — на CSS, а после закрывающего тега возвращается к HTML. Пары скобок, автозакрытие и правила отступов дают файлу вести себя как один язык. Часть проблем видна только в реальном продукте: один вариант регулярного выражения полностью ломал редактор в Safari, поэтому в токенизаторе от него отказались.

### Подсказки зависят от языка под курсором

Внутри `script` нужны подсказки JavaScript, а не HTML-атрибуты и Liquid-сниппеты, внутри `style` — CSS. Первая версия определяла контекст по ближайшим открывающему и закрывающему тегам перед курсором. В 2024 году я заменил эту проверку на `getLanguageIdAtPosition` из самого Monaco: модель уже знает, какой вложенный язык стоит в этой позиции. Заодно нормализовал значения `script type` вроде `application/json` и `application/ld+json`, чтобы редкие типы не пытались загрузить несуществующий языковой сервис.

![Полный редактор шаблона: дерево файлов и подсказка querySelector внутри блока script в layouts.layout.liquid](https://anu3ev.com/images/work/monaco-editor/template-editor-script-completion.webp)

*Подсказки JavaScript внутри `script` в Liquid-файле лейаута.*

### Браузерный API из трёх функций

Чтобы редактор не принадлежал одному модальному окну, я сделал три точки входа: первая возвращает загруженный экземпляр Monaco, вторая создаёт редактор в переданном элементе с общими настройками, третья регистрирует конфигурации Liquid, JavaScript и SCSS для экранов, которые сами создают модели. Перед регистрацией языка сборка проверяет, не добавлен ли он уже, поэтому на странице с несколькими точками входа провайдеры не дублируются. Загрузка, сохранение и предпросмотр остаются за каждым продуктовым экраном. Поверх API я написал Vue-компонент для виджета с шестью файлами: разметка, SCSS, JavaScript, переводы, схема формы настроек и значения по умолчанию.

### Воркеры и упаковка тоже часть контракта

Языковой анализ в Monaco выполняется в Web Workers, поэтому одного основного бандла для работающего редактора мало. Я настроил Webpack так, чтобы он собирал бандл и пять языковых воркеров, и упаковал их в Rails Engine и внутренний gem. GitLab CI собирает бандл, собирает gem и публикует версию во внутренний реестр пакетов. Основное приложение подключает конкретную версию, а исправление токенизатора или новая команда выходят сразу во все экраны.

### Одна сборка для шаблонов и сообщений покупателям

После кода виджетов ту же сборку подключили к полному редактору шаблона с деревом файлов, затем к письмам по заказам, напоминаниям о брошенных корзинах и уведомлениям о статусе заказа в «Диалогах». В этих разделах продавец правит HTML/Liquid или текст с Liquid-переменными и проверяет результат в предпросмотре на реальном заказе или корзине.

![Редактор шаблона письма клиенту: тема, исходный код на HTML и Liquid, предпросмотр по номеру заказа](https://anu3ev.com/images/work/monaco-editor/order-email-template.webp)

## Результат

- Одна сборка редактора работает в 5 продуктовых интерфейсах inSales: код виджета, полный шаблон витрины, письма по заказам, сообщения о брошенных корзинах и уведомления «Диалогов».
- Больше 3 лет, с июля 2022 по октябрь 2025 года, был основным мейнтейнером.
- Общие доработки делаются один раз и попадают во все интерфейсы: коллеги добавили форматирование Liquid и обновили тему в пакете, не переделывая каждый экран.
- Продавцы и партнёры редактируют код в админке примерно как в VS Code: подсказки по контексту, Emmet, сниппеты inSales и комментирование Liquid по Ctrl+Shift+/ с нормальной отменой и повтором.
