Grafana-плагины для источников данных обычно состоят из фронтенда (JavaScript/TypeScript, выполняется в браузере) и опционального бэкенда (как правило, на Go, запускается на сервере). Однако официальная площадка Grafana Marketplace принимает только плагины, которые могут работать без отдельного бэкенда или бинарных модулей. В нашем случае сложная бизнес-логика (~100 KB Go-кода в нескольких файлах) реализована на Go для бэкенда плагина, и ее требуется перенести во фронтенд (TypeScript) без использования WebAssembly.
Причины отказа от WebAssembly связаны с ограничениями Grafana: плагины с WASM-модулями не поддерживаются официально. В частности, попытки напрямую импортировать .wasm
в плагин приводят к ошибкам типа “Response has unsupported MIME type” при загрузке файла community.grafana.com. То есть Grafana не обслуживает файлы WASM должным образом, и наличие внешнего WASM-модуля может помешать публикации плагина в Marketplace. Таким образом, цель – встроить Go-логику во фронтенд напрямую, получив совместимый с браузером JavaScript/TypeScript код, который можно подключить в модуле (module.ts
) Grafana-плагина.
Основные требования и допущения:
-
Транспиляция Go → JavaScript без WASM: Требуется найти способы компиляции или трансляции Go-кода в чистый JavaScript (или TypeScript) для браузера.
-
Интеграция с TypeScript и Webpack: Полученный JS-код должен интегрироваться в существующий фронтенд-проект (сборка на Webpack + SWC). Размер бандла не критичен, важно, чтобы код можно было импортировать и использовать в TS.
-
Сохранение логики: Желательно по максимуму сохранить исходную реализацию на Go, избежав полной ручной переписи. Разработчики готовы внести минимальные правки в Go-код (например, убрать использование неподдерживаемых библиотек или специфичных возможностей), чтобы упростить перенос.
-
Production-сценарий: Решение должно быть приемлемым для выпуска плагина в продакшен. Необходимо оценить стабильность и ограничения каждого подхода.
Ниже рассматриваются доступные технические решения: от существующих транспиляторов Go в JS до крайних мер вроде ручной портировки кода. Также приведены возможные компромиссы в архитектуре (частичная генерация кода, упрощение функционала) и сравнение подходов по их преимуществам и недостаткам.
Самый прямой путь – воспользоваться компилятором (транспилятором), который переведет исходный Go-код в эквивалентный JavaScript. Это позволило бы продолжать поддерживать логику на Go, а сборка плагина автоматически генерировала бы JS-версию. Рассмотрим несколько инструментов, которые подходят под этот критерий.
GopherJS – наиболее известный проект для компиляции Go в JavaScript, созданный Richard Musiol и сообществом groups.google.com. Он транслирует Go-код в чистый JavaScript, позволяя исполнять практически любой Go-код в браузере groups.google.com. GopherJS поддерживает большую часть стандартной библиотеки Go и языковых возможностей (включая горутины, каналы, рефлексию и т.д.), за исключением прямого взаимодействия с ОС (файловая система, сетевые сокеты) и cgo-модулей. В браузере эти возможности все равно недоступны, поэтому обычная прикладная логика на Go, не зависящая от ОС, как правило, компилируется без изменений siongui.github.io. Например, отмечалось, что GopherJS способен «скомпилировать весь Go-код в JavaScript почти во всех случаях (если код не использует IO-операции)», и даже сложное офлайн-приложение на Go заработало в браузере без модификаций siongui.github.io.
Поддержка языка: На 2025 год GopherJS развивается сообществом и поддерживает синтаксис Go до версии 1.19, включая большинство возможностей, кроме дженериков (Generics находятся в разработке) github.com. Если исходный код Go использует дженерики Go 1.18+, возможно, потребуется их устранить (развернуть типы вручную или использовать варианты без generics), так как пока ни GopherJS, ни альтернативные транспиляторы полностью их не поддерживают. Горутины и каналы эмулируются на уровне runtime – GopherJS «полностью поддерживает горутины», выполняя планирование кооперативно в едином потоке JavaScript chromium.googlesource.com chromium.googlesource.com. Вызовы, которые бы блокировали поток, вместо этого автоматически раскладываются на асинхронные (с колбэками), чтобы не замораживать UI браузера chromium.googlesource.com. Благодаря этому семантика параллелизма Go сохранена, хотя и с некоторыми издержками по производительности.
Производительность и размер кода: Так как GopherJS стремится точно воспроизводить поведение Go, он включает в итоговый JS значительную часть рантайма Go (например, реализацию планировщика горутин, отражения типов, обработку паник, проверку границ срезов и пр.). Это приводит к увеличению размера и некоторому оверхеду в работе кода. В официальном FAQ отмечается, что «GopherJS реимплементирует большую часть рантайма Go на JavaScript…, код получается крупным из-за статической компоновки всех необходимых пакетов (аналогично статически слинкованным Go-исполняемым файлам)» community.haxe.org. Например, минимальная программа с подключением пакета fmt
при компиляции GopherJS давала ~624 KB необфусцированного JS (~12 тыс. строк) из-за подтянутых зависимостей стандартной библиотеки news.ycombinator.com. После минификации размер снизился до ~21 KB news.ycombinator.com, что говорит о высокой избыточности, устранимой сжатием. В более реальном примере сравнивали вывод GopherJS и альтернативного инструмента: GopherJS сгенерировал ~1080 KB (20k строк) JS против всего ~4 KB (158 строк) у экспериментального компилятора community.haxe.org. Разница объясняется тем, что GopherJS включил «всё подряд» для обеспечения работы Go-ков, тогда как альтернативный подход встроил лишь минимальный код (см. ниже про go2hx).
Следует понимать, что в контексте Grafana-плагина размер бандла не критичен (разработчики готовы принять большой JS-файл). Более важен вопрос скорости выполнения: GopherJS-код обычно работает медленнее чистого JS, хотя в большинстве сценариев «производительность довольно неплохая» chromium.googlesource.com. Относительные замеры показывали, что по скорости GopherJS может уступать или даже превосходить WebAssembly в зависимости от браузера и задачи dev.to. В частности, из-за дополнительной проверки границ массивов и других накладных расходов, производительность GopherJS оценивается как «посредственная (so-so)» dev.to по сравнению с «нативным» JS, но зачастую достаточная. Для задач, не требующих интенсивных вычислений в каждом кадре (например, обработка данных раз в несколько секунд по запросу), GopherJS вполне справится. Если же логика крайне требовательна к CPU, это место для компромисса – возможно, понадобится оптимизировать критичные части вручную после переноса.
Интеграция в TypeScript-плагин: GopherJS обычно применяется для написания SPA на Go, но его можно использовать и как библиотеку, вызываемую из обычного JS. Чтобы это работало, нужно в Go-коде определить API-функции и экспортировать их в JS. GopherJS предоставляет пакет github.com/gopherjs/gopherjs/js
(или актуальный syscall/js
), через который Go-код может регистрировать объекты в глобальном JS. К примеру, можно написать в Go «обертки» функций и выполнить:
js.Module.Get("exports").Set("MyFunc", myFunc)
Это вставит функцию myFunc
в объекты экспорта, если код выполняется как модуль Node/CommonJS medium.com. Таким образом, при компиляции gopherjs build
получится JS-файл, который можно подключать через require()
или import
. Практика показывает, что GopherJS умеет генерировать модуль, который импортируется из Node или webpack-бандла: «gopherjs build создаёт JS-модуль (например, simple.js
) вместе с sourcemap» medium.com. В Node.js затем можно require('simple.js')
и вызывать simple.hashit()
(если функция hashit
была экспортирована) medium.com medium.com. В браузерном окружении без CommonJS можно вместо этого привязать функции к window
или другому глобальному объекту.
Для Grafana-плагина целесообразно скомпилировать Go-код с GopherJS отдельно (например, скриптом или через go generate
), получить один JS-файл с экспортируемым API, и затем в основном module.ts
либо:
-
Импортировать этот файл как модуль (webpack позволит подтянуть CommonJS-модуль в бандл),
-
Либо просто включить файл в сборку как побочный (например, через
import './generated/go_lib.js';
чтобы он исполнился и зарегистрировал глобальные функции).
Первый вариант предпочтительнее, так как можно явно вызывать нужные функции как импортированные. Возможно, потребуется настроить в webpack конфигурацию для обработки .js
файла без транспиляции (SWC можно исключить для этого пути, если он написан на ES5). Также, если GopherJS-код ожидает окружение Node (наличие module.exports
), а плагин работает в браузере, стоит добавить проверку: в Go-обертке экспортировать функции либо через js.Global().Set(...)
(для браузера, присваивая в window
), либо через js.Module.Get("exports").Set(...)
(для Node/webpack). В простейшем случае можно всегда делать оба варианта, либо модифицировать результат компиляции, чтобы он соответствовал формату ES-модуля.
Ограничения: GopherJS не поддерживает cgo и системные вызовы. Если исходный Go-код зависел, например, от SQLite через cgo или вызывал системные функции, эти части нужно убрать. Также недоступны прямые сетевые сокеты (net.Conn
), но HTTP-клиент можно заменить на вызовы fetch
в JS или использовать XHR через специальный пакет (существуют обертки для net/http
поверх XHR для GopherJS). Файловая система (os.Open
, и т.п.) в браузере недоступна – если плагину нужно хранить данные, придётся задействовать Web Storage или Grafana API. В целом же вычислительная логика (парсинг, формирование запросов, преобразование данных и т.д.) легко переносится. GopherJS позволяет использовать практически «все возможности Go», включая комплексный runtime dev.to, поэтому большая часть оригинальной логики может сохраниться без переписывания.
Стабильность: GopherJS – проект с многолетней историей (анонсирован в 2014 groups.google.com) и сейчас поддерживается сообществом. Он применялся на практике для веб-приложений на Go, есть положительный опыт использования в реальных проектах. В нашем случае GopherJS предоставляет наибольшую совместимость и требуемый результат «из коробки». Недостатки – относительно большой выходной код и потенциально более медленное выполнение – не критичны под оговоренные условия. Таким образом, GopherJS является основным кандидатом для автоматической трансляции Go → JS.
Joy – экспериментальный компилятор Go в JavaScript, представленный в 2017 году (Matthew Mueller). Цель Joy заключалась в более «идиоматичном» переводе Go-кода в компактный JS с минимальным рантаймом, в отличие от эмуляции полной среды, как у GopherJS news.ycombinator.com. Иными словами, Joy генерировал более лаконичный JS, но ради этого поддерживал лишь подмножество Go. На момент анонса отмечалось, что «большинство существующего Go-кода пока не компилируется в JS [через Joy], так как значительная часть стандартной библиотеки еще не портирована» news.ycombinator.com. Joy планировал расширить поддержку стандартных пакетов к версии 2.0, но проект не успел этого достичь.
К сожалению, развитие Joy остановлено: официальный репозиторий помечен как “ON HOLD”, и сам автор признал, что «новые достижения Go->WASM выглядят более перспективными для фронтенд-разработки на Go» github.com. Последние обновления датируются ~2018 годом. В текущем состоянии Joy не поддерживает современный Go и не гарантирует корректную работу сложного кода. Использовать его в продакшене нецелесообразно. Тем не менее, Joy интересен как ориентир: он доказал возможность получать более компактный JS-код за счет отказа от части возможностей Go. Концепции Joy отчасти унаследовали другие проекты (например, go2hx ниже). Для нашей задачи Joy не подходит из-за отсутствия поддержки и неполной реализации.
Go2hx – относительно новый и нестандартный подход. Это компилятор Go в исходный код языка Haxe, разработка которого началась около 2019–2020 гг. Идея в том, чтобы транслировать Go в Haxe, а уже Haxe-компилятором сгенерировать JavaScript (Haxe умеет компилировать во многие target-платформы, включая JS и WASM). Проект позиционируется как «преемник TardisGo» (раннего компилятора Go в другие языки) и нацелен на поддержку как можно большей части Go STDlib на Haxe github.com github.com.
Преимущество такого двухэтапного пути – Haxe предоставляет мощные средства оптимизации (dead code elimination и пр.) и возможность тонко настроить соответствие типов. Авторы go2hx показывают значительно меньший размер итогового JS-кода по сравнению с GopherJS. В одном из сравнений: GopherJS вывел ~1080 KB / 20k строк JS, тогда как go2hx (через Haxe) – всего ~4 KB / 158 строк для аналогичного примера community.haxe.org. Это достигается за счет того, что «низкоуровневые части стандартной библиотеки переписаны вручную» на Haxe, и мощного удаления неиспользуемого кода в Haxe-компиляторе community.haxe.org community.haxe.org. Там где GopherJS тянет весь runtime, go2hx сохраняет только требуемые фрагменты. Потенциально это дает более легкий и быстрый JS-выход, ближе к рукописному коду.
Однако, статус go2hx – альфа. В README прямо указано: «Проект в ранней alpha-стадии, пока не способен корректно компилировать большинство Go-кода» github.com. Большинство языковых возможностей на данный момент работают лишь в отдельных случаях; дженерики и unsafe
не поддерживаются github.com. Также отмечается, что пока не реализованы горутины (конкурентность Go2hx не поддерживает полностью) community.haxe.org, что может быть критично, если исходный код зависит от параллелизма. Фактически go2hx в 2024 г. способен транслировать отдельные пакеты Go (например, unicode
, strings
и т.п.) и уже имеет реализацию ряда стандартных пакетов (проект go2hxlibs github.com). Но целиковое большое приложение может потребовать доработки компилятора.
Для нашей задачи go2hx представляет академический интерес: он обещает максимальную сохранность логики и компактный результат, но риски и усилия на его применение высоки. Понадобится установить Haxe, собрать go2hx, отладить его работу на нашем коде. Вероятно, не все конструкции компилируются «из коробки», придется дорабатывать либо исходник Go под ограничения go2hx, либо сам компилятор. Стабильность и покрытие кода go2hx значительно ниже, чем у GopherJS. Поэтому go2hx не рекомендуется для продакшен-сборки на текущий момент, хотя за ним стоит следить в будущем. Если проект будет активно развиваться, через некоторое время он может стать реалистичной альтернативой GopherJS, дающей более оптимальный код.
Вывод по транспиляции: GopherJS на сегодня – самый зрелый инструмент, удовлетворяющий требованиям (Go→JS, интеграция с TS, минимум изменений кода). Он позволяет перенести всю логику, требуя лишь небольших оберток для экспорта функций. Joy упоминался как интересный проект, но фактически не пригоден, а go2hx – перспективный, но сырый вариант. Ниже мы рассмотрим другие стратегии, не полагающиеся на автоматическую компиляцию, а также обобщим плюсы/минусы каждого подхода.
Хотя условие требует решить задачу без WebAssembly, стоит кратко упомянуть этот вариант – от него мы отталкиваемся, оценивая альтернативы. Начиная с Go 1.11, официальный компилятор Go умеет генерировать WASM-модуль (цель GOOS=js, GOARCH=wasm
), который затем можно вызывать из JavaScript через API syscall/js
dev.to. Существуют и оптимизированные компиляторы вроде TinyGo, которые создают меньше по размеру WASM, жертвуя частью возможностей (например, TinyGo не поддерживает рефлексию, может иметь ограничения по горутинам). В идеальном случае, компиляция Go→WASM сохраняет полностью семантику Go (включая горутины) dev.to и дает лучшее быстродействие, чем транспиляция в JS dev.to.
Однако, Grafana не предусматривает загрузку WASM-модулей в плагинах. Как видно из эксперимента разработчиков, попытка импортировать .wasm
файл привела к MIME-ошибке при загрузке community.grafana.com. То есть Grafana-сервер не отдает статические файлы с типом application/wasm
. Можно было бы попытаться обойти это, закодировав модуль в Base64 внутри JavaScript, но такой трюк скорее всего нарушит политику плагинов (да и усложнит загрузку). Официально плагины с внешними WASM не поддерживаются в Marketplace – видимо, по соображениям безопасности и совместимости. К тому же, даже если загрузить WASM, возникнет вопрос, как его исполнять: Grafana-плагин работает в изолированном iframе, но не факт, что CSP позволит создавать WebAssembly.Instance
. В общем, WASM-решение не приемлемо в рамках заданных условий.
Мы упоминаем WebAssembly только чтобы контрастировать с транспиляцией: выбранные инструменты (GopherJS и др.) фактически появились до повсеместной поддержки WASM и стали альтернативой, когда WASM был недоступен. Сейчас, если бы не ограничения Grafana, самым простым путём было бы собрать Go-код в .wasm
и вызвать его из TS (например, через генерацию оболочки с wasm-bindgen
или аналогами). Но так как это не разрешено, мы концентрируемся на чисто JS-решениях.
Классический вариант – полностью (или частично) переписать код Go на TypeScript вручную. Этот путь гарантирует, что итоговый фронтенд-код будет идиоматичным, без лишних прослоек, и оптимально интегрируется в плагин. Однако, стоимость такого решения высока: ~100 KB нелинейного Go-кода – это тысячи строк, которые нужно реализовать, проверить и отладить на другом языке. Возникают риски регрессий (если пропустить нюансы реализации) и сложности поддержки (две версии логики на разных языках).
Тем не менее, частичное ручное переписывание может оказаться необходимым, если автоматические инструменты не справятся с некоторыми частями. Рассмотрим, как можно минимизировать трудозатраты при таком подходе:
-
Выделение ядра логики: Возможно, не весь код бэкенда нужен во фронтенде. Стоит проанализировать, какие функции действительно будут использоваться плагином. Неиспользуемые части можно отложить или отбросить. Например, если в Go-коде есть модуль работы с БД, а во фронтенде эта часть не нужна (потому что данные приходят откуда-то еще), ее можно не переносить.
-
Автоматическая генерация типовых частей: Если Go-код содержит большие определения структур, констант, маппингов, можно написать утилиту на Go, которая пробежит по этим определениям и сгенерирует TypeScript-интерфейсы или объекты. Существуют инструменты для конвертации структур Go в интерфейсы TypeScript (например,
go2ts
для моделей данных) reddit.com, хотя они, как правило, не покрывают логику, а лишь типы. Тем не менее, это может сэкономить время на переписывании моделей данных и подпроектов. -
Повторное использование алгоритмов: Если логика на Go основана на известных алгоритмах или библиотеках, возможно, есть готовые реализации на JS. Стоит проверить, нет ли эквивалентной npm-библиотеки, выполняющей часть задач. Тогда вместо портирования алгоритма можно подключить библиотеку (учитывая, правда, вес бандла и лицензии).
-
Написание собственного транспилятора для подзадач: В некоторых случаях можно автоматизировать перевод однотипного кода. Например, если в Go много функций, различающихся только типами (что решалось бы дженериками, будь они), можно написать скрипт, который по шаблону генерирует аналогичные функции на TS. В пределах разумного, такие техники генерации могут помочь.
-
Тестирование: Обязателен набор юнит-тестов на исходном Go-коде (если его нет, стоит написать), и такой же на TypeScript, чтобы убедиться, что поведение совпадает. Это значительно повысит надежность портированной вручную логики.
Ручной подход гарантирует отсутствие «черного ящика» runtime, но требует времени и глубокого знания обеих платформ. Его стоит рассматривать, если автоматические транспиляторы не дали удовлетворительного результата, или если исходный код так тесно связан с серверными возможностями, что проще реализовать эту функциональность средствами браузера. Например, если Go-код открывал сокет к СУБД, во фронтенде это неосуществимо – придется перепроектировать обмен данными через HTTP API. Такие архитектурные изменения по сути тоже являются ручным перепроектированием.
Помимо крайностей «автоматически транспилировать все» и «переписать все вручную», возможны промежуточные подходы:
-
Частичная транспиляция + адаптация: Например, основную вычислительную логику (алгоритмы, парсинг) попытаться пропустить через GopherJS, а проблемные места (взаимодействие с окружением, некоторые структуры) реализовать на TS и вызывать из Go или наоборот. GopherJS позволяет вызывать из Go функции JavaScript через пакет
js
– можно написать на TS функцию доступа к Web API и вызвать ее в Go-коде какjs.Global().Call("myJsFunc", args...)
. Соответственно, смешанный код будет сложнее, но позволит оставить тяжелую логику на Go, а специфичные вещи – на JS/TS. -
Прег enerация данных: Если Go-код выполняет какие-то сложные вычисления, которые не зависят от пользовательского ввода (например, строит таблицы или шаблоны), можно выполнить их на этапе сборки и зашить результат в фронтенд. То есть, запустить Go-программу офлайн, получить, скажем, сериализованный словарь или DFA для парсера, сохранить в JSON – и пусть фронтенд просто загрузит эту структуру. Тогда и переносить весь алгоритм не придется, фронтенд будет работать с готовыми данными. Этот прием особенно полезен, если в Go есть этапы инициализации, подготовки данных.
-
Ограничение функциональности: Возможно, часть возможностей плагина не критична для MVP релиза на фронтенде. Можно временно не реализовать некоторый редко используемый функционал, что упростит портирование. В документации плагина это отразить как ограничения фронтенд-версии. В дальнейшем, по мере появления поддержки в транспиляторах или времени на реализацию, эти функции вернуть.
Каждый компромисс следует оценивать с точки зрения влияния на пользователей плагина и трудозатрат. Цель – минимизировать переписывание, но при этом добиться работоспособного решения, приемлемого в Marketplace.
Независимо от выбранного подхода (кроме, конечно, чисто ручного переписывания на TS), нужно продумать, как встроить полученный код в процесс сборки плагина. Предположим, мы используем GopherJS (как наиболее вероятный вариант):
-
Добавляем шаг сборки: например, в
package.json
скриптgo:build
который запускаетgopherjs build ./path/to/go/code.go -m -o src/generated/go_code.js
. Флаг-m
выдаст минифицированный код сразу. Этот скрипт можно делать частьюprebuild
цепочки, чтобы при каждом билдэ плагина генерировался актуальный JS из Go. -
В основном коде плагина, в
module.ts
, подключаем этот файл. Если GopherJS сгенерировал файл в формате CommonJS (сmodule.exports
), можно сделатьimport goLib = require('./generated/go_code.js')
. TypeScript потребует объявления типов для импортируемых функций (можно создать.d.ts
с описанием интерфейса goLib). Далее goLib используется как обычный объект: например,goLib.MyExportedFunc(params)
. -
Если же GopherJS-код экспортирует глобально (например,
window.MyFunc
), то можно просто импортировать модуль без присваивания:import './generated/go_code.js';
, а затем обращаться кwindow.MyFunc
. Этот вариант менее желателен, так как засоряет глобальное пространство имен и сложнее типизировать, но тоже работоспособен. -
Убедиться, что политики безопасности Grafana не блокируют наш код. Поскольку все содержится в одном JS-бандле, проблем быть не должно – это выглядит как обычный плагин. В отличие от WASM, MIME-тип JS известен и поддерживается.
-
Проверить поддержку браузеров: GopherJS генерирует ES5-код (совместимый со старыми браузерами) и использует полифилы, поэтому специфических требований нет. SWC/webpack могут дополнительно притянуть core-js для поддержки некоторых встроенных функций (например,
Map
в IE11 – хотя IE уже не актуален для Grafana). -
Sourcemap: GopherJS может генерировать source map файл, что поможет отлаживать Go-код прямо в браузере (в devtools будет виден оригинальный Go-код). Однако, для этого sourcemap надо включить в бандл. В продакшене можно от него отказаться.
Для go2hx процесс схож: сначала прогоняем haxelib run go2hx
, получаем сгенерированный Haxe-код + скомпилированный JS (Haxe компиляция), затем подключаем. Но это существенно более сложная цепочка и ее надежность пока под вопросом.
При ручном переписывании интеграция тривиальна – код сразу пишется на TypeScript, идет в ту же сборку. Нужно удостовериться, что все зависимости (например, polyfill для какого-то Node-модуля, если использован) удовлетворены Grafana. Но если писать на чистом TS/JS, проблем не возникнет.
Ниже сводная таблица по обсужденным решениям, с их преимуществами и недостатками в контексте данной задачи:
Подход / ИнструментПреимуществаНедостатки and рискиGopherJS (Go→JS)– Полностью автоматическое преобразование почти всего Go-кода в JS siongui.github.io. – Сохранение исходной логики и возможности повторного использования Go-кода (единая кодовая база). – Поддержка основных возможностей Go (горутины, каналы, reflect
и т.д.) chromium.googlesource.com. – Активно поддерживается сообществом (совместим с Go 1.19). – Проверенное решение, использовавшееся на практике; есть инструментарий для экспорта функций в JS-модуль medium.com medium.com.– Большой размер выходного кода из-за включения runtime Go (сотни KB, до 1+ MB без минификации) community.haxe.org. – Потенциально более медленная работа: эмуляция семантики Go в JS добавляет оверхед (проверки срезов, и т.п.) dev.to. – Пока нет поддержки generics (если исходный Go-код их использует, потребуется переписать эти места). – Требует настроить сборку (дополнительный шаг компиляции Go, конфигурация импорта в webpack). – Дебаг сложнее (стек-трейсы будут на JS, хотя sourcemap частично решает).Joy (Go→JS, неактивен)– Изначально задумывался для более лёгкого и понятного JS-вывода (минимальный runtime). – Меньший размер кода по сравнению с GopherJS в простых примерах за счёт выборочной поддержки фич.– Проект заморожен в развитии github.com, не совместим с современным Go. – Поддерживает далеко не весь Go (ограниченная STDlib, нет горутин и др. в полном объёме) news.ycombinator.com. – Непригоден для production: отсутствие обновлений, возможны ошибки.Go2hx (Go→Haxe→JS)– Потенциально очень компактный и эффективный итоговый JS за счёт агрессивной оптимизации Haxe (DCE) community.haxe.org community.haxe.org. – Стремление поддержать как можно больше Go-библиотек на другом уровне (в теории, сохранение логики). – Активное экспериментальное развитие, сообщество Haxe проявляет интерес (есть прототипы, REPL и т.д.).– Ранняя альфа, не компилирует большинство программ целиком github.com. – Нет поддержки горутин, дженериков, unsafe
и возможно других ключевых возможностей сейчас. – Сложность сборки: требует владения Haxe, возможна отладка генерации кода. – Непроверено в боевых условиях; высокие риски по времени и надежности. – Могут потребоваться значительные правки Go-кода под ограничения компилятора.WebAssembly (Go/TinyGo) длясравнениядля сравнениядлясравнения– Полная поддержка семантики Go (официальный компилятор) – вся логика работает как есть в WASM dev.to. – Очень высокая производительность выполнения (почти нативная, быстрее JS в вычислительных задачах) dev.to. – Стандартный подход для современных веб-приложений на разных языках.– Не поддерживается Grafana Marketplace (проблемы с загрузкой .wasm
, политика безопасности) community.grafana.com. – Требует отдельного бинарного артефакта, что нарушает требования к плагину. – Сложнее интегрировать: нужно писать JavaScript-обертки для вызовов, асинхронную инициализацию WASM и пр. – Размер WASM-модуля может быть довольно большим (сотни KB, включая рантайм Go).Ручной порт на TypeScript– Полный контроль над итоговым кодом – можно оптимизировать и сократить по максимуму. – Нативная интеграция: код сразу на TS, без внешних зависимостей и рантаймов. – Нет неизвестных багов транспиляции – разработчики сами реализуют логику под нужды фронтенда. – Проще отладка напрямую в TS.– Очень трудоемко: переписывание ~100 KB кода – большие временные затраты, риск человеческих ошибок. – Требуется детальное понимание исходной логики и тщательноe тестирование для соответствия. – Возможна потеря некоторых оптимизаций или особенностей (например, другие структуры данных в JS). – Поддержка в будущем осложняется: любые изменения нужно вносить в двух версиях (если исходный Go еще нужен отдельно). – Вероятно придется частично изменить архитектуру под ограничения браузера (не вся серверная логика переносима напрямую).
(Примечание: WebAssembly включён в таблицу для сравнения, но не рассматривается как допустимый вариант решения из-за ограничений Grafana.)
Учитывая требования и анализ вариантов, наиболее практичным решением является использование GopherJS для транспиляции Go-кода в JavaScript. Этот подход минимизирует изменения исходной логики и обеспечивает наибольшую совместимость. Разработчики смогут и дальше писать и отлаживать основную логику на Go, а сборка плагина будет генерировать JS-модуль для фронтенда. Несмотря на увеличение размера плагина и небольшой спад производительности, GopherJS-вариант удовлетворяет критерию отсутствия WebAssembly и уже применялся в подобных сценариях (Go-код в браузере) siongui.github.io. Необходимо лишь вложить некоторую работу в написание обёрток-экспорта функций (API между TS и Go-кодом) и в настройку процесса сборки. Эти затраты вполне оправданы, если сравнивать с полным ручным переписыванием.
При внедрении GopherJS следует обратить внимание на ограничения: избежать по возможности использования неподдерживаемых участков STDlib (OS, cgo), заменить их аналогами под браузер. Если в коде активно используются дженерики Go, возможно, придётся временно заменить их конкретными типами (Generics в GopherJS пока не работают github.com). Также, тестирование производительности на реальных объёмах данных не помешает – убедиться, что время выполнения в JS укладывается в рамки комфортного UX Grafana.
Альтернативные пути можно держать «в запасе». Проект go2hx потенциально интересен: если он быстро стабилизируется и начнёт поддерживать наш спектр возможностей, имеет смысл переоценить его – выигрыш в размере и скорости бандла мог бы быть существенным community.haxe.org. Пока же использовать go2hx преждевременно. Ручной портирование, как крайняя мера, стоит применять только частично для тех сегментов, которые не удалось перенести автоматически. Например, если небольшую часть функциональности проще реализовать на TS с нуля (особенно взаимодействие с внешними веб-API), лучше так и сделать, чем усложнять транспиляцию.
Вывод: Встраивание сложной Go-логики в фронтенд Grafana-виджета без WebAssembly – сложная, но осуществимая задача. На стороне плюсов – богатый инструментарий Go, который, как оказалось, можно «протащить» в браузер через транспиляцию. Решение на базе GopherJS предоставляет необходимый компромисс: плагин будет монолитным, без внешних бинарников, сохранив при этом основную бизнес-логику, написанную на Go. Это позволит публиковать плагин в Grafana Marketplace в соответствии с требованиями, не жертвуя функциональностью. При грамотной организации сборки и тестирования такое решение может быть внедрено в production-сборки плагина. При дальнейшем развитии проекта можно постепенно улучшать его – например, оптимизировать критичные участки вручную или перейти на более продвинутые инструменты трансляции, если они появятся. Сейчас же предложенная комбинация (Go + GopherJS + TS интеграция) представляется наиболее сбалансированной по трудозатратам и надежности.