
Мы разрабатываем фулстек-фреймворк Wasp — наподобие Rails или Laravel для JS, только ещё и расширенный на фронтенд. Мы с моим братом-близнецом начали этот проект в 2021 году, когда успешно прошли программу Y Combinator. За всё это время в общей сложности нам удалось привлечь $5 миллионов инвестиций.
Изначально мы хотели создать язык программирования, который бы абстрагировал типичные паттерны веб-приложений и в то же время позволял углубиться в любой стек (мы начали с React, Node.js и Prisma). Что-то вроде Terraform, но для стека веб-приложения, а не облачной инфраструктуры.
Теперь же, спустя пять лет стараний, мы поняли, что это было ошибкой. Создавать новый язык есть смысл под конкретные задачи и предметную область. В нашем же случае он людям не зашёл и создал больше проблем, чем принёс пользы.
В этой статье я расскажу, почему нам эта идея казалась перспективной, какие уроки мы вынесли и почему заменяем свой язык на TypeScript при том, что сам Wasp внутренне остаётся всё тем же.

Кратко
Статья получилась длинная, поэтому я перечислю её основные моменты, чтобы вы могли выборочно прочесть наиболее для вас интересные:
В течение многих лет разработки нам регулярно приходилось переключаться между разными программными стеками. В конечном итоге мы подумали, что было бы круто создать «универсальный фреймворк», который бы работал с любым стеком. Тогда мы решили, что для этого нужно разработать новый язык.
Разработчикам понравилось универсальное решение в виде Wasp, но желанием осваивать новый язык они не горели. Видя в названии «Lang», они начинали думать, что мы собираемся заменить JavaScript (хотя это было не так), и сомневались, что он подружится с привычными им инструментами.
Многие разработчики оценили преимущества Wasp после знакомства с ним, но изначально заинтересовать их было нелегко.
Язык набирал популярность, но по мере приближения к версии 1.0 мы поняли, что обеспокоенность «языковой» составляющей никуда не делась. Кроме того, качественно интегрировать кастомный язык в IDE оказалось сложнее, чем мы предполагали.
В итоге выяснилось, что главная ценность Wasp заключалась не в наличии собственного языка. Самым важным было сохранение единой высокоуровневой спецификации всего фулстек-приложения, что упрощало его понимание как для людей, так и для ИИ.
Мы решили заменить язык конфигурации Wasp на TypeScript. При этом изменился только интерфейс — всё остальное осталось прежним.
Почему создание нового языка казалось нам удачной идеей
У нас с братом за плечами традиционный бэкграунд в области компьютерных наук. Ранее мы преимущественно занимались алгоритмами, применяя их в сферах биоинформатики и машинного обучения. Мы никогда не считали себя веб-разработчиками и в некоторой степени даже смотрели на эту область свысока («Только погляди, чем эти ребята занимаются целыми днями — рисуют всякие кнопочки в то время как я пишу всякие крутые алгоритмы»). Как выяснилось, мы многого не понимали.
Но в нашей в группе был один предприимчивый товарищ, который всегда делал что-то на стороне и всякий раз подтягивал нас на большие проекты. Каждый из этих проектов с той или иной стороны был нам интересен (например, нужно было создать новостной портал с рекомендательным движком или аналитическую платформу для торговли опционами), но в конечном итоге чаще всего это была классическая задача веб-разработки.
Таким образом мы познакомились с PHP (создали на нём CMS с нуля, после чего узнали, что существуют целые фреймворки и даже готовые решения), Java/JBoss, а следом с Backbone.js, Angular.js и React по мере их появления, включая инструменты сборки (помните Bower, Grunt и Gulp?)
С 2013 года началась эпоха реактивных фронтенд-фреймворков, и тебе непременно нужно было использовать новейшие технологии, чтобы показать крутизну своего проекта. В результате каждый очередной проект мы начинали практически с полностью другим стеком и были вынуждены осваивать новые механизмы для реализации всего того, чему только научились — аутентификации, маршрутизации, управления состоянием и так далее.
Мы устали от смены разных стеков и их склеивания
Раньше можно было просто выбрать между Spring Boot, Django или Rails и всё спокойно наладить. Теперь же мне нужно было сначала подобрать каждую библиотеку, а потом заставить дружно работать React, Redux, Webpack, Express, Passport и Sequelize.
Не было единой системы, которая бы целостно понимала всё веб-приложение, и это чувство личной ответственности не давало нам с Мартином покоя.
Несколько раз пройдя через всю эту свистопляску с переключением между стеками (в конечном итоге мы тогда остановились на React, Node.js и Mongoose), мы задумались — а нет ли решения получше? Складывалось ощущение, что мы раз за разом изобретаем велосипед, больше времени затрачивая на управление стеком, чем на написание уникальной бизнес-логики.
Для этого даже есть отдельный термин: излишняя сложность. Он описывает выполнение такой работы, которая никак не связана с реальной задачей, но при этом необходима из-за используемых инструментов. Ровно такое у нас было ощущение.
А что, если бы мне было достаточно описать свои требования один раз?
Мы задумались: «А если бы можно было просто описать необходимые требования без всего лишнего?» Например:
«Хочу, чтобы в моём приложении была аутентификация через Google и GitHub».
«Хочу, чтобы был маршрут
/profile, доступный только аутентифицированным пользователям».«Хочу задачу cron, которая будет выполнять эту функцию каждый день в 17:00».
Понятно, что это слишком разговорные формулировки, поэтому мы представили их в более сжатом, структурированном виде:
auth: Google, GitHubpage Profile -> /profile, authRequired: true.job updateStats: выполнять функцию doSomeCalc из stats.js каждый день в 17:00.
По сути, это бы означало возможность определять требования к приложению на более высоком уровне, без привязки к конкретной реализации (что-то вроде спецификации). Мы чувствовали, что такое решение вполне реально, но с доступными нам тогда инструментами приходилось реализовывать каждое требование по всему стеку, то есть абстракция была очень слабая.
Мы никогда не ставили перед собой цель заменить существующий стек. Предполагалось, что вы по-прежнему будете реализовывать логику в конкретной среде выполнения (например, React или Node.js), только теперь появится каркас, который будет связывать всё воедино.

Кому интересно, вот выдержка кода, демонстрирующая, как наш DSL (Domain-Specific Language) выглядел в своём изначальном виде:
app todoApp { title: "ToDo App", // Отображается во вкладке браузера. auth: { // Фулстек-аутентификация из коробки. userEntity: User, methods: { google: {}, gitHub: {}, email: {...} } } } route RootRoute { path: "/", to: MainPage } page MainPage { authRequired: true, // Доступ только для авторизованных пользователей. component: import Main from "@client/Main" // <-- Ваш код на React. } query getTasks { fn: import { getTasks } from "@server/tasks", // <-- Ваш код на Node.js. entities: [Task] // Автоматическая инвалидация кэша. }
Ключевым открытием для нас стало то, что фундаментальные компоненты веб-разработки меняются очень медленно в сравнении со скоростью развития сопутствующих техник реализации. Что десять лет назад, что сегодня — мы всё так же работаем со страницами, маршрутами, эндпоинтами и моделями данных. В то же время реализация UI прошла целый круг: от серверных шаблонов через толстого клиента и обратно к серверным экшенам.
Создавая Wasp, мы хотели предоставить разработчикам возможность максимально широко описывать свои требования на предметном уровне, а системе поручить реализацию деталей с использованием последних трендов. В таком случае на выходе будут получаться более понятные продукты, которые прослужат намного дольше.
Зачем создавать язык с нуля? Почему бы не использовать существующие?
Благодаря своему личному опыту, мы достаточно чутко нащупали проблему, но затем свернули не туда.
Было две главных причины, по которым мы решили создавать новый язык с нуля с собственным компилятором, а не использовать тот же TS или Python:
Мы хотели полностью контролировать синтаксис, а также сделать его максимально чистым и избавить от лишнего бойлерплейта.
Мы представляли Wasp как инструмент, который в конечном итоге будет дружить с любой средой выполнения. К примеру, вы пишете часть типичной логики CRUD на Node.js, а потом реализуете обширную обработку данных на Python, так как у него для этого есть более подходящие библиотеки.
И хотя мы рассматривали TypeScript (поначалу пользователи нам так и советовали — сделать встроенный DSL на его основе, то есть просто библиотеку), тогда это казалось «неправильным». Создавалось ощущение, что так мы предадим свои принципы и отклонимся от общей концепции Wasp.
В некотором смысле мы даже горели желанием запустить Wasp со своим языком. Это должно было подкрепить нашу позицию и отчётливо показать, что мы работаем не над очередным стандартным фреймворком с привязкой к языку (типа Rails или Django), а над чем-то более основательным.

Честно говоря, мы были в восторге от того, что работаем над чем-то столь «крутым» и фундаментальным. Мы с Мартином оба увлечённые поклонники Haskell, и для этого гвоздя наш функциональный молоток подходил идеально.
Сообщество по достоинству оценило идею, а с языком просто мирилось
Спустя где-то год работы над Wasp, когда мы выпустили альфа-версию и представили её разработчикам, вокруг нас образовалось небольшая группа ранних пользователей, и мы были приняты в программу Y Combinator. Вскоре после этого мы подняли pre-seed раунд, который позволил нам собрать команду и приступить к созданию первой «реальной» версии.
Поначалу всё двигалось медленно, так как мы только запускали проект. Но разработчики уже сильно устали от бойлерплейта и постоянного склеивания стеков, поэтому Wasp начал активно развиваться, особенно после запуска беты.
Вы также можете помнить, что в тот же 2021 год на сцене нарисовалась ещё парочка фреймворков «Rails для JS» — RedwoodJS (от создателей GitHub) и BlitzJS. Они сразу привлекли к себе внимание сообщества, и стало понятно, что мы заняты решением очень важной проблемы.
Иногда странность проекта идёт ему на пользу
В определённом смысле именно «странность» Wasp упасла его от участи Redwood и Blitz. Отказавшись от тесной привязки к конкретной технологии (у Redwood это была GraphQL, а у Blitz — Next.js), он остался достаточно универсальным для быстрой адаптации и избежал морального устаревания.

Тем не менее в диалогах с разработчиками в разных контекстах продолжал всплывать вопрос: «Но зачем вы создали новый язык?» Мы регулярно слышали несколько основных возражений.
Я вижу «wasp-lang» — он что, заменит JavaScript?
И хотя мы не планировали замещать существующий стек веб-разработки, а лишь хотели его расширить (в случае Wasp вы всё так же пишете 90% кода на React и Node.js), упоминание «lang» было не самым удачным для передачи этого посыла.
Когда разработчик видел «wasp-lang.dev», он автоматически думал: «Мм, это что-то вроде Rust или Java». И после формирования такого видения, поменять его было уже трудно. Человек тут же определял Wasp в категорию «выглядит круто, но пока слишком сыро».
Мы были реально в восторге от того, что создаём новый язык, и делали на этом акцент, вот только недооценили степень предвзятости разработчиков к этому термину.
А он будет работать с моей IDE и существующими инструментами?
Даже если вам удаётся вызвать у человека кредит доверия к языку, следующий вопрос звучит как «А у него есть своя экосистема?» Разработчики знают, сколько усилий стоит создание нового стандарта, и сколько времени необходимо на формирование вокруг него экосистемы.
Это не для меня. Я не хочу учить Haskell

Для написания компилятора внутренне мы используем Haskell, но конечные пользователи никогда этого не увидят и будут работать только с TS. Можно сравнить это с тем, как ядро Prisma изначально разрабатывалось на Rust, а Terraform HCL на Go.
Тем не менее наши изначальные заявления об использовании Haskell оказали нам медвежью услугу. У этого языка небольшое, но очень живое и полное энтузиазма сообщество, изголодавшееся по реальным проектам, особенно в сфере инструментов разработки.
Его участники весьма положительно реагировали на наши публикации о достижениях проекта, вот только эти публикации всё больше позиционировали Wasp как «язык на базе Haskell», особенно если судить только по заголовкам.

Прибавьте сюда тот факт, что в разделе «Languages» репозитория GitHub было указано «Haskell: 90%» (позже мы нашли способ этот нюанс обыграть), и у вас точно сформируется уверенное ошибочное восприятие.
Проблема крылась в обёртке
Многим разработчикам, которые всерьёз попробовали Wasp, этот фреймворк нравился, и они не сильно возражали против языка. Он значительно ускорял их работу, избавляя от многонедельного изучения нового стека и позволяя продолжать использовать React/Node.js. Но вот заставить разработчиков совершить решительный скачок от «Что это такое?» к «Пожалуй, попробую» было очень трудно.

Но мы не отступали. После выхода беты популярность фреймворка резко возросла, и мы начали замечать, как крупные игроки поглощают стартапы, построенные на Wasp, а корпорации начинают создавать внутренние инструменты и развёртывать их в локальной инфраструктуре (в конце концов приложение на Wasp — это просто статические файлы фронтенда и один Docker-образ для бэкенда).
Чтобы преодолеть психологическую преграду в стиле «Это какой-то странный новый фреймворк… Зачем мне его пробовать?», мы создавали на основе Wasp продукты (опенсорсную систему шаблонов для SaaS и наш собственный «ранний» Lovable), которые смещали принятие решений на более высокий уровень. Эта стратегия отлично сработала и привлекла к Wasp множество людей.
Но всё же фундаментальная проблема оставалась. Разработчики, которые начинали работать с Wasp, не могли понять, что именно мы создаём, поэтому и восторга у них наш проект не вызывал.
Последняя капля — попытка реализовать поддержку в IDE
Мы всегда считали так — если наш язык окажется ошибкой, об этом мы узнаем от его пользователей. Но по факту оказалось, наоборот: в то время, как разработчики, которые перешли на Wasp, с удовольствием продолжали им пользоваться, мы сами начали сталкиваться со всё большим числом внутренних проблем.
Одним из нюансов, который мы недооценили, стал объём работы, необходимый для создания сопутствующих инструментов, особенно реализации поддержки в IDE и редакторах кода. Сегодня планка ожиданий разработчиков, особенно в среде JS, очень высока, а граница между IDE и компилятором постепенно размывается.
Углубившись в создание инструментов для кастомного языка, мы тут же поняли, что вся экосистема построена под «стандартные» фреймворки JS и TS. Стоит лишь немного отклониться, и вы остаётесь сами по себе, быстро упираясь в глухую стену.

В конечном итоге мы разработали собственный языковой сервер и расширение VS Code для него. Но поскольку Wasp использовал в качестве встраиваемого языка DSL Prisma и содержал множество ссылок на файлы React/Node.js, мы достигли лишь 80% от желаемого результата.
Прощай Wasp. Здравствуй TypeScript!
В свете всей накопившейся боли по обслуживанию кастомного языка и постоянных хлопот с завлечением разработчиков, мы поняли, что истинная причина лежит глубже простого опасения людей пробовать новый фреймворк.
Охват рос, и всё больше разработчиков выпускали готовые приложения на Wasp в продакшен. Но что бы мы ни пробовали, всё сводилось к отзывам вроде: «Мне реально нравится ваша идея с Wasp, но зачем собственный язык?» Складывалось ощущение, что мы движемся с поднятым ручником.
В конечном итоге та эргономичность, которой мы стремились достичь за счёт собственного языка, оказалась не так важна, как мы считали. Разработчики вполне довольны использованием знакомого им TypeScript, даже если это порой вынуждает писать несколько лишних строк или ставить лишние скобки.
Язык никогда не был основным козырем
Когда мы запустили Wasp, термины «язык» и «спецификация» были для нас практически синонимами. Мы просто не могли представить одно без другого.
Но понаблюдав за тем, как разработчики используют Wasp, и какие его аспекты им действительно по душе, мы поняли, что это определённо не язык. Их радовало то, что Wasp полноценно понимает всё приложение на основе высокоуровневой спецификации (например, main.wasp, а теперь main.wasp.ts), позволяя им с ходу оценивать его работу и чувствовать контроль.
Одним из аспектов, через который мы попытались сделать это более явным, стала команда wasp studio. Выполнив её в терминале, вы сможете глазами Wasp «увидеть» своё приложение на этапе компиляции и проанализировать его до генерации целевого кода:

Наличие такой «системы поддержки» сделало процесс сборки приложения более управляемым. В условиях, когда всё чаще используется ИИ, а разработчики всё реже перепроверяют код после сборки, эта возможность стала ещё ценнее — особенно для нового поколения так называемых вайб-кодеров, которые не имеют глубокого технического бэкграунда.
И после перехода с кастомного языка Wasp на TS это целостное понимание приложения нисколько не изменилось. Мы лишь заменили «интерфейс» компилятора, то есть способ определения высокоуровневой спецификации в Wasp.
TypeScript SDK — от экспериментов к продакшену
// Определение page и route. const loginPage = app.page('LoginPage', { component: { importDefault: 'Login', from: '@src/pages/auth/Login' } }); app.route('LoginRoute', { path: '/login', to: loginPage }); // Запрос. app.query('getTasks', { fn: { import: 'getTasks', from: '@src/queries' }, entities: ['Task'] }); // Асинхронная задача. app.job('mySpecialJob', { executor: 'PgBoss', perform: { fn: { import: 'foo', from: '@src/jobs/bar' }, executorOptions: { pgBoss: { retryLimit: 1 } } }, entities: ['Task'] }); export default app;
Мы внедрили TypeScript SDK в качестве экспериментальной функциональности для сбора обратной связи. И отзывы оказались весьма положительными — некоторые из наших новых пользователей предпочли именно его и язык Wasp даже не пробовали.
После внутреннего тестирования такой связки и её проверки сообществом Wasp стало ясно, что мы на верном пути. Это не только исключило извечный вопрос: «Зачем новый язык?», но и решило все проблемы, с которыми мы боролись в течение нескольких лет.
Любой редактор теперь работает по умолчанию. Разработчики могут использовать условные выражения, циклы и импорты (например, реализовать собственную маршрутизацию на основе файлов). Разделение спецификации между несколькими файлами стало тривиальной задачей. Плюс у нас появилась прочная основа для реализации ранее запланированных продвинутых функций, таких как Full Stack Modules.
Спасибо тебе DSL за всё — мы бы не прошли этот путь без тебя
В ретроспективе возникает чувство, что Wasp не достиг бы успеха, если бы мы начали как-то по-другому. В тот момент это был лучший способ выразить то, что мы хотели создать, подкреплённый личными предпочтениями и тем, что нам просто казалось интересным. Именно горящее любопытство в стиле: «Мы понимаем, что эта идея звучит безумно, но нам очень интересно попробовать». заставляло нас упорно двигаться вперёд, даже когда никто не обращал на это внимания.
Подход с собственным DSL позволил нам оставаться верными идее, что спецификация должна быть отделена от реализации. Мы по-прежнему смотрим в сторону поддержки других языков и сред выполнения (например, Python или Rust), а также взвешиваем реальную пользу от наличия полной картины приложения на этапе компиляции и возможности анализировать его перед генерацией целевого кода (что открывает широкие перспективы для внедрения поддержки разных архитектур, оптимизаций и так далее).
Ваш ИИ-агент тоже оценит Wasp
Сегодня всё больше кода пишется с помощью ИИ-агентов. И в этом свете мы заметили, что многие разработчики ищут инструменты, которые изначально обеспечивают чёткую структуру и предлагают готовые архитектурные решения. В свою очередь, это делает добавление новых функций более предсказуемым, а также упрощает понимание и анализ генерируемого кода.
И Wasp идеально вписывается в эту парадигму, так как охватывает весь стек вашего приложения, и обеспечивает непрерывную согласованную работу всех компонентов. Именно поэтому многие разработчики также воспряли былой любовью к «олдскульным» монолитным фреймворкам вроде Django, Rails и Laravel. Wasp стремится предоставить вам такой же опыт и ощущение, что «всё просто работает», только для экосистемы JS.
Мы постоянно слышим от разработчиков, которые используют Wasp, что его стек лучше всего сочетается с ИИ, и некоторые из них даже попробовали 10 различных решений, прежде чем остановиться на Wasp.
Скоро запускаем Wasp с TypeScript
Через пару недель мы проведём Launch Week, в течение которой будем предоставлять TypeScript SDK как основной способ использования Wasp. Наше сообщество уже выразило воодушевление этими переменами, и мы с нетерпением ждём отзывов от вас.
Если вы раньше проявляли интерес к Wasp, но не решались попробовать, то сейчас самое время. Теперь не нужно учить новый язык, достаточно TypeScript — обо всём остальном позаботиться Wasp.

