[{"data":1,"prerenderedAt":266},["ShallowReactive",2],{"project-ru-monaco-editor":3,"recommendation-mention-sources":204},{"id":4,"title":5,"body":6,"category":160,"context":161,"cover":162,"coverAlt":163,"demo":164,"description":165,"extension":166,"featured":167,"locale":168,"meta":169,"navigation":170,"order":171,"path":172,"published":170,"repository":164,"results":173,"role":180,"seo":181,"slug":182,"stack":183,"stem":201,"year":202,"__hash__":203},"projects\u002Fru\u002Fprojects\u002Fmonaco-editor.md","Редактор кода на Monaco для Liquid-шаблонов inSales",{"type":7,"value":8,"toc":145},"minimark",[9,14,18,21,25,28,32,37,53,57,82,89,98,102,105,109,112,116,119,125,129],[10,11,13],"h2",{"id":12},"задача","Задача",[15,16,17],"p",{},"В шаблонах витрин inSales в одном файле живут HTML, Liquid, JavaScript и CSS, а настройки и переводы хранятся в JSON. Этот код в браузере, прямо в админке, правят партнёры-разработчики, сотрудники поддержки и технически подкованные владельцы магазинов, не скачивая шаблон в IDE.",[15,19,20],{},"Прежняя интеграция CodeMirror умела открыть и сохранить код, но не подсвечивала смешанный синтаксис, не подсказывала с учётом позиции курсора, не знала Emmet и сниппетов inSales. Чтобы довести её до нормальной рабочей среды, языковую часть всё равно пришлось бы проектировать заново. Поэтому выбрали Monaco — движок, на котором построен VS Code, — а кастомную сборку и её внедрение поручили мне.",[10,22,24],{"id":23},"моя-роль","Моя роль",[15,26,27],{},"С июля 2022 по октябрь 2025 года вёл разработку редактора. Сделал первую интеграцию для кода виджетов, потом вынес её в общий пакет и оставался его основным мейнтейнером, пока редактор подключали к новым экранам. На мне были языковая поддержка, браузерный API, упаковка, настройка релизов и крупные интеграции. Сам движок редактирования дал Monaco. Коллеги помогали с путями к воркерам, обновлением темы и форматированием кода.",[10,29,31],{"id":30},"что-я-сделал","Что я сделал",[33,34,36],"h3",{"id":35},"токенизатор-для-шаблонов-где-html-смешан-с-liquid","Токенизатор для шаблонов, где HTML смешан с Liquid",[15,38,39,40,44,45,48,49,52],{},"Liquid стоит прямо в разметке: вывод, управляющие теги, комментарии, блоки ",[41,42,43],"code",{},"raw",", фильтры inSales. Он бывает и внутри значения HTML-атрибута, целиком или вперемешку с обычным текстом. Я зарегистрировал собственный язык HTML + Liquid с токенизатором Monarch и языковой конфигурацией. После закрывающего разделителя внутри атрибута токенизатор должен вернуться в состояние атрибута, а не красить остаток строки как Liquid. Внутри ",[41,46,47],{},"script"," он переключается на JavaScript или JSON, внутри ",[41,50,51],{},"style"," — на CSS, а после закрывающего тега возвращается к HTML. Пары скобок, автозакрытие и правила отступов дают файлу вести себя как один язык. Часть проблем видна только в реальном продукте: один вариант регулярного выражения полностью ломал редактор в Safari, поэтому в токенизаторе от него отказались.",[33,54,56],{"id":55},"подсказки-зависят-от-языка-под-курсором","Подсказки зависят от языка под курсором",[15,58,59,60,62,63,65,66,69,70,73,74,77,78,81],{},"Внутри ",[41,61,47],{}," нужны подсказки JavaScript, а не HTML-атрибуты и Liquid-сниппеты, внутри ",[41,64,51],{}," — CSS. Первая версия определяла контекст по ближайшим открывающему и закрывающему тегам перед курсором. В 2024 году я заменил эту проверку на ",[41,67,68],{},"getLanguageIdAtPosition"," из самого Monaco: модель уже знает, какой вложенный язык стоит в этой позиции. Заодно нормализовал значения ",[41,71,72],{},"script type"," вроде ",[41,75,76],{},"application\u002Fjson"," и ",[41,79,80],{},"application\u002Fld+json",", чтобы редкие типы не пытались загрузить несуществующий языковой сервис.",[15,83,84],{},[85,86],"img",{"alt":87,"src":88},"Полный редактор шаблона: дерево файлов и подсказка querySelector внутри блока script в layouts.layout.liquid","\u002Fimages\u002Fwork\u002Fmonaco-editor\u002Ftemplate-editor-script-completion.webp",[15,90,91],{},[92,93,94,95,97],"em",{},"Подсказки JavaScript внутри ",[41,96,47],{}," в Liquid-файле лейаута.",[33,99,101],{"id":100},"браузерный-api-из-трёх-функций","Браузерный API из трёх функций",[15,103,104],{},"Чтобы редактор не принадлежал одному модальному окну, я сделал три точки входа: первая возвращает загруженный экземпляр Monaco, вторая создаёт редактор в переданном элементе с общими настройками, третья регистрирует конфигурации Liquid, JavaScript и SCSS для экранов, которые сами создают модели. Перед регистрацией языка сборка проверяет, не добавлен ли он уже, поэтому на странице с несколькими точками входа провайдеры не дублируются. Загрузка, сохранение и предпросмотр остаются за каждым продуктовым экраном. Поверх API я написал Vue-компонент для виджета с шестью файлами: разметка, SCSS, JavaScript, переводы, схема формы настроек и значения по умолчанию.",[33,106,108],{"id":107},"воркеры-и-упаковка-тоже-часть-контракта","Воркеры и упаковка тоже часть контракта",[15,110,111],{},"Языковой анализ в Monaco выполняется в Web Workers, поэтому одного основного бандла для работающего редактора мало. Я настроил Webpack так, чтобы он собирал бандл и пять языковых воркеров, и упаковал их в Rails Engine и внутренний gem. GitLab CI собирает бандл, собирает gem и публикует версию во внутренний реестр пакетов. Основное приложение подключает конкретную версию, а исправление токенизатора или новая команда выходят сразу во все экраны.",[33,113,115],{"id":114},"одна-сборка-для-шаблонов-и-сообщений-покупателям","Одна сборка для шаблонов и сообщений покупателям",[15,117,118],{},"После кода виджетов ту же сборку подключили к полному редактору шаблона с деревом файлов, затем к письмам по заказам, напоминаниям о брошенных корзинах и уведомлениям о статусе заказа в «Диалогах». В этих разделах продавец правит HTML\u002FLiquid или текст с Liquid-переменными и проверяет результат в предпросмотре на реальном заказе или корзине.",[15,120,121],{},[85,122],{"alt":123,"src":124},"Редактор шаблона письма клиенту: тема, исходный код на HTML и Liquid, предпросмотр по номеру заказа","\u002Fimages\u002Fwork\u002Fmonaco-editor\u002Forder-email-template.webp",[10,126,128],{"id":127},"результат","Результат",[130,131,132,136,139,142],"ul",{},[133,134,135],"li",{},"Одна сборка редактора работает в 5 продуктовых интерфейсах inSales: код виджета, полный шаблон витрины, письма по заказам, сообщения о брошенных корзинах и уведомления «Диалогов».",[133,137,138],{},"Больше 3 лет, с июля 2022 по октябрь 2025 года, был основным мейнтейнером.",[133,140,141],{},"Общие доработки делаются один раз и попадают во все интерфейсы: коллеги добавили форматирование Liquid и обновили тему в пакете, не переделывая каждый экран.",[133,143,144],{},"Продавцы и партнёры редактируют код в админке примерно как в VS Code: подсказки по контексту, Emmet, сниппеты inSales и комментирование Liquid по Ctrl+Shift+\u002F с нормальной отменой и повтором.",{"title":146,"searchDepth":147,"depth":147,"links":148},"",2,[149,150,151,159],{"id":12,"depth":147,"text":13},{"id":23,"depth":147,"text":24},{"id":30,"depth":147,"text":31,"children":152},[153,155,156,157,158],{"id":35,"depth":154,"text":36},3,{"id":55,"depth":154,"text":56},{"id":100,"depth":154,"text":101},{"id":107,"depth":154,"text":108},{"id":114,"depth":154,"text":115},{"id":127,"depth":147,"text":128},"Браузерные редакторы","inSales","\u002Fimages\u002Fwork\u002Fmonaco-editor\u002Fwidget-code-liquid-autocomplete.webp","Редактор кода виджета с шестью вкладками файлов и подсказкой Liquid-тега for внутри HTML-разметки",null,"Общая сборка Monaco для смешанного HTML, Liquid, CSS и JavaScript работает в пяти продуктовых интерфейсах inSales. Сделал первую интеграцию и больше трёх лет был основным мейнтейнером.","md",false,"ru",{},true,8,"\u002Fru\u002Fprojects\u002Fmonaco-editor",[174,177],{"value":175,"label":176},"5","продуктовых интерфейсов inSales работают на одной сборке редактора",{"value":178,"label":179},"3+ года","был основным мейнтейнером, с июля 2022 по октябрь 2025","Вёл разработку: языковая поддержка, браузерный API, сборка, интеграции",{"title":5,"description":165},"monaco-editor",[184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200],"Monaco Editor","TypeScript","Liquid","JavaScript","Front-End Architecture","Vue.js","Webpack","API Integration","CI\u002FCD","Gitlab","Ruby on Rails","SCSS","HTML","Developer Tools","Software Maintenance","Syntax Highlighting","Autocomplete","ru\u002Fprojects\u002Fmonaco-editor","Июль 2022 – ноябрь 2025","p0cH9Gf1sF4TLceHOqVtxo-ULfzPH0skcVf3M6Xw3qQ",[205,216,221,236,246,251,256,261],{"stem":206,"name":207,"date":208,"featured":147,"published":170,"mentions":209},"recommendations\u002Fandrey-stikheev","Andrey Stikheev","2026-09-08",[210,213],{"project":211,"quote":212},"product-image-editor","Among his most notable projects was a sophisticated browser-based image editor built with TypeScript and Fabric.js, now used by thousands of merchants.",{"project":214,"quote":215},"abandoned-carts","He also developed the frontend for a paid abandoned-cart product and contributed to the production migration from Vue 2 to Vue 3.",{"stem":217,"name":218,"date":219,"featured":164,"published":170,"mentions":220},"recommendations\u002Fdaniil-churakov","Daniil Churakov","2026-08-27",[],{"stem":222,"name":223,"date":224,"featured":154,"published":170,"mentions":225},"recommendations\u002Fdmitry-sadovsky","Dmitry Sadovsky","2026-08-22",[226,229,231,234],{"project":227,"quote":228},"visual-storefront","For years he was the driving force behind our visual storefront editor — a large, complex application where merchants build their online stores.",{"project":211,"quote":230},"It became the tool merchants use to design product images right inside the admin panel — the kind of component companies usually buy rather than build.",{"project":232,"quote":233},"image-editor","What impressed me most was the infographic editor he built essentially from scratch: a full canvas-based graphic editor with artboards, shapes, text styling, templates, and undo\u002Fredo history, powered by an editor core he developed himself.",{"project":214,"quote":235},"Our abandoned carts functionality — with multi-channel customer messaging, communication history, and analytics — was largely his work, from UI down to the details of how requests are cancelled and states stay in sync.",{"stem":237,"name":238,"date":239,"featured":240,"published":170,"mentions":241},"recommendations\u002Fegor-fedkin","Egor Fedkin","2026-08-31",1,[242,244],{"project":211,"quote":243},"Today, the tool is actively used by around 3,000 users.",{"project":214,"quote":245},"It has already generated approximately twice the revenue needed to cover its development costs.",{"stem":247,"name":248,"date":249,"featured":164,"published":170,"mentions":250},"recommendations\u002Femmanuel-stephen","Emmanuel Stephen","2026-10-11",[],{"stem":252,"name":253,"date":254,"featured":164,"published":170,"mentions":255},"recommendations\u002Fevgeny-lonshakov","Евгений Лоншаков","2026-09-21",[],{"stem":257,"name":258,"date":259,"featured":164,"published":170,"mentions":260},"recommendations\u002Froman-gorbachev","Роман Горбачев","2026-09-17",[],{"stem":262,"name":263,"date":264,"featured":164,"published":170,"mentions":265},"recommendations\u002Fvladimir-bedarev","Bedarev Vladimir","2026-09-16",[],1793195987387]