От favicon до WebAssembly: как два студента создали сервис, который заменил 167 инструментов и работает без единого запроса к серверу

Четыре года разработки, 8 изолированных песочниц, ноль зависимостей от бэкенда и архитектура, в которой приватность данных пользователя возведена в абсолют. История создания Halfcoder.

Представьте: вам нужно быстро проверить регулярку, декодировать Base64 или сгенерировать UUID. Вы открываете пять вкладок, на одной всё тормозит, на второй — баннер на пол-экрана, третья просит зарегистрироваться. Знакомая боль? Нам с Алексеем — более чем. Поэтому четыре года назад мы начали делать Halfcoder.

Кто мы и почему это не «очередной пет-проект»

Мы познакомились на первом курсе колледжа. Общая проблема — программа отставала от индустрии на годы. Пока мы изучали технологии пятилетней давности, мир уже жил в экосистеме React, Next.js и WebAssembly. Формула выживания была простой: вечера после пар, документация, пет-проекты, споры об архитектуре и только зарождавшееся IT-сообщество для таких же энтузиастов.

Лёша носил идею давно: сделать единый набор инструментов, который заменит десятки разрозненных сервисов. Не "проект для портфолио", а реально полезный продукт для ежедневной работы. На втором курсе он подключил меня — и мы начали строить.

Спустя четыре года, несколько примкнувших и отвалившихся дизайнеров, десятки переписанных компонентов и бесчисленное количество выпитого кофе мы запустились. На halfcoder.ru сейчас живёт 167 утилит и 8 интерактивных песочниц. Дальше я расскажу про стек, архитектуру, изоляцию и инженерные решения, которые мы вынесли из этого марафона.

Что под капотом

Если кратко — это "швейцарский нож" для разработчика, который целиком работает в браузере. Никакой серверной обработки данных. Вообще.

Вот полный список того, что уже есть:

  • Форматтеры: JSON, XML, CSS, HTML, SQL, YAML, TypeScript, PHP, Python
  • Конвертеры: цветов, Markdown → HTML, CSV, изображений (PNG/JPEG/WebP), favicon-генератор
  • Кодировщики: Base64, Base32, Unicode, Hex, Punycode
  • Генераторы: паролей (crypto.getRandomValues), UUID v1/v3/v4/v5/v7, robots.txt, multipart/form-data, OAuth 2.0 handshake
  • Криптография: кодирование и раскодирование в разные форматы
  • Изображения: получение цвета и метаданных, конвертация форматов
  • DevOps: готовые шаблоны Dockerfile, docker-compose, nginx.conf, манифесты Kubernetes
  • Шпаргалки: от бэкенда до Kubernetes
  • Песочницы: JavaScript, Python, SQL, Regex, Bash (эмулятор), HTML/CSS, Markdown, Cron

И всё это бесплатно, без рекламы и регистрации. Почему? Потому что мы делали сервис для себя и сообщества, а не для монетизации на данных пользователей.

Архитектурный принцип: клиент — это всё

Главное решение, к которому мы пришли: все утилиты работают без отправки данных на сервер. Это не маркетинговая фича, а осознанный архитектурный выбор с тремя последствиями:

  1. Мгновенный отклик. Нет сетевых задержек, данные обрабатываются локально.
  2. Нулевая серверная инфраструктура.

Исключений нет. Даже конвертация изображений работает через Canvas API — файл загружается в скрытый <canvas>, обрабатывается и отдаётся пользователю через canvas.toDataURL() без единого запроса к серверу. Вот код favicon-генератора:

const generateFavicons = (imageFile: File) => { const reader = new FileReader(); reader.onload = (event) => { const img = new Image(); img.onload = () => { sizes.forEach(size => { const [width, height] = size.split("x").map(Number); const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, width, height); results[size] = canvas.toDataURL("image/png"); }); }; img.src = event.target?.result as string; }; reader.readAsDataURL(imageFile); };

Песочницы: как изолировать выполнение кода без Docker

У нас восемь интерактивных сред, и каждая изолирована по-своему. Ни одного контейнера на сервере — всё живёт на клиенте. Вот как мы решили задачу безопасности для каждой среды:

  • JavaScript исполняется в отдельном Web Worker. У него нет доступа к DOM, window и document. Весь вывод console.log перехватывается и передаётся в панель вывода через postMessage. Если пользователь напишет бесконечный цикл, это не заблокирует основной поток — воркер просто прибивается по таймауту, и песочница остаётся доступной для нового запуска.JavaScript исполняется в отдельном Web Worker. У него нет доступа к DOM, window и document. Весь вывод console.log перехватывается и передаётся в панель вывода через postMessage. Если пользователь напишет бесконечный цикл, это не заблокирует основной поток — воркер просто прибивается по таймауту, и песочница остаётся доступной для нового запуска.
  • Python работает через Pyodide — CPython, скомпилированный в WebAssembly. Пользователю доступна вся стандартная библиотека: json, re, math и другие модули. А вот import os или любой другой вызов, пытающийся обратиться к файловой системе хоста, невозможен. Никакого pip, никаких сторонних пакетов — только то, что уже вкомпилировано в рантайм.
  • SQL построен на SQL.js — это SQLite, портированный в WebAssembly. Выполнение происходит в Web Worker, а Wasm-файл подгружается с того же origin, без обращения к внешним CDN. Пользователь может создавать таблицы, писать SELECT с JOIN и GROUP BY, накатывать дампы — всё, что умеет SQLite, но в полностью изолированной среде браузера.
  • HTML/CSS рендерится в <iframe sandbox="allow-scripts">. Атрибут sandbox запрещает доступ к cookie и DOM родительской страницы. Даже если пользователь напишет внутри превью <script>document.cookie</script>, он не получит сессионные cookie Halfcoder — контекст изолирован на уровне браузерного движка.<iframe sandbox="allow-scripts">. Атрибут sandbox запрещает доступ к cookie и DOM родительской страницы. Даже если пользователь напишет внутри превью <script>document.cookie</script>, он не получит сессионные cookie Halfcoder — контекст изолирован на уровне браузерного движка.
  • Markdown после рендеринга в HTML прогоняется через DOMPurify. Этот санитайзер вырезает все <script>, on*-обработчики и произвольные теги. На выходе — чистый и безопасный HTML, который можно вставить в DOM без риска XSS. Никакой пользовательский код не выполнится, даже если он был хитро завёрнут в Markdown-разметку.
  • Bash — это полностью самописный эмулятор командной строки. Он поддерживает базовые команды: echo, export, pwd, wc, grep, head, tail, cat и пайпы между ними. Никаких системных вызовов — только парсинг команд и симуляция вывода. Работает на клиенте, без единой строки серверного кода.
  • Regex — нативный JavaScript без каких-либо дополнительных слоёв. Обычный new RegExp() с визуализацией совпадений. CodeMirror при этом даёт приятную подсветку синтаксиса регулярки, но сама логика — чистый браузерный движок.
  • Cron использует связку из двух библиотек: cron-parser v5 для разбора расписания и cronstrue для генерации человекочитаемого описания. Даты форматируются через Luxon. Все три библиотеки работают на клиенте, не требуя серверной валидации cron-выражений.

Почему мы не взяли Monaco и Prettier

На этапе проектирования мы осознанно отказались от нескольких популярных решений в пользу лёгких альтернатив или собственных реализаций.

  1. Редактор: CodeMirror 6 вместо Monaco. Monaco мощнее, но тяжелее. Для нашего сценария с динамическими импортами и сотней страниц CodeMirror оказался лучшим выбором — он легче и лучше дружит с ленивой загрузкой.
  2. Форматтеры: ручная реализация вместо Prettier. Здесь мы пошли против течения. Prettier тянет за собой много зависимостей, а для некоторых языков требует дополнительные плагины. Мы хотели полный контроль и минимальный размер бандла. Пример SQL-форматтера на чистых регулярках:
const formatSql = () => { let formatted = input .replace(/\s+/g, " ") .replace(/\s*,\s*/g, ", ") .replace(/\s*\(\s*/g, " (") .replace(/\s*\)\s*/g, ") ") .trim(); const keywords = ["SELECT", "FROM", "WHERE", "JOIN", /* ... */]; keywords.forEach((keyword) => { const regex = new RegExp(`\\b${keyword}\\b`, "gi"); formatted = formatted.replace(regex, keyword); }); formatted = formatted .replace(/\bSELECT\b/gi, "\nSELECT") .replace(/\bFROM\b/gi, "\nFROM") .replace(/\bWHERE\b/gi, "\nWHERE"); };

Да, это не полноценный парсер. Но для 90% повседневных запросов разработчиков такого форматирования хватает с головой. Аналогичный подход мы применили к YAML (нормализация отступов), HEX/Unicode (charCodeAt + toString(16)) и Base64 (нативный atob).

Для JSON хватило родного JSON.parse + JSON.stringify со space = 2.

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

Отдельно остановлюсь на генераторе паролей, потому что это вопрос безопасности. Мы используем только crypto.getRandomValues — криптографически стойкий API, а не Math.random(), который даёт предсказуемые значения:

const array = new Uint32Array(length); crypto.getRandomValues(array); for (let i = 0; i < length; i++) { password += chars[array[i] % chars.length]; }

Пользователь может выбрать длину пароля от 4 до 128 символов, количество паролей (до 20 за раз) и наборы символов: заглавные, строчные, цифры, спецсимволы. Интерфейс — кастомные чекбоксы с анимацией в едином дизайне системы.

Стек и инфраструктура

  • Next.js 14 с App Router. Роутинг на [slug]/page.tsx позволил разложить 167 утилит по отдельным директориям и загружать их лениво.
  • Zustand. Локальные сторы ровно там, где нужны, без переусложнения Redux.
  • Tailwind CSS. С сотнями страниц классический CSS был бы адом поддержки.
  • Lucide React. Современный и лёгкий набор иконок.
  • Динамические импорты. CodeMirror и его расширения грузятся только для страниц с редакторами, что позволяет держать первоначальный бандл минимальным.

UX, дизайн и реальность стартапа

За четыре года через проект прошло несколько дизайнеров — такие же студенты, как и мы. К моменту запуска все они уже покинули команду, и финальный дизайн мы доводили своими силами. Текущая версия в разы отличается от изначальной, но мы заложили несколько принципов, которые соблюли до конца:

  • Тёмная тема по умолчанию с возможностью переключения
  • Минимум отвлекающих элементов
  • Кнопка «Копировать» всегда под рукой
  • Поле ввода и вывода на одном экране для форматтеров

Что дальше

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

В ближайших планах:

  • Календарь с IT-событиями (хакатоны, конференции и т. д.)
  • Коллекция часто используемых шаблонов кода

Будем рады, если вы заглянете на halfcoder.ru, потестируете утилиты и, возможно, найдёте баги или предложите идеи для новых инструментов. Фидбек можно писать мне или Алексею Овчинникову (@allelleo).

Ссылки: halfcoder.ru