Wasp переезжает на TypeScript: как мы убили кастомный язык и что получили взамен

#wasp#typescript#fullstack#dsl

Когда в 2021 году Wasp представили как full-stack фреймворк с собственным языком для описания логики, это казалось смелым ходом. Но через три года команда признала: кастомный DSL стал проблемой. Вот как выглядел типичный сценарий:

// Старый синтаксис Wasp DSL
app todoApp {
  title: "ToDo App",
  entities: [Task],
  pages: [Main],
  operations: {
    getAllTasks: {
      fn: import { getAll } from "@server/tasks.js"
    }
  }
}

На практике такой подход создавал больше трений, чем преимуществ. Вместо ускорения разработки приходилось постоянно переключаться между ментальными моделями — TypeScript для клиента/сервера и Wasp DSL для конфигурации. Проблема усугублялась тем, что:

  1. IDE не понимали кастомный синтаксис
  2. Сообщество не могло расширять язык
  3. Интеграция с существующими TypeScript-тулзами требовала костылей

Почему TypeScript — не очевидный выбор

Переход на TypeScript кажется логичным только постфактум. На самом деле были варианты:

Команда Wasp выбрала третий путь, но с хитрой модификацией: они не парсят TypeScript, а используют его как runtime. Вот как теперь выглядит спецификация:

// Новый синтаксис на TypeScript
import { Task } from "@wasp/entities"
import { getAllTasks } from "@server/tasks.js"

const todoApp = defineApp({
  title: "ToDo App",
  entities: [Task],
  pages: {
    Main: {
      component: import("./MainPage"),
      getInitialData: () => getAllTasks()
    }
  },
  operations: {
    getAllTasks: {
      resolver: getAllTasks
    }
  }
})

Ключевые изменения:

  1. Полная поддержка TypeScript-инструментов (автодополнение, refactoring)
  2. Импорты работают как в обычном коде
  3. Динамическая загрузка через import() вместо строковых путей

Что это даёт на практике

В процессе тестирования нового подхода я выделил три неочевидных преимущества:

1. Композиция через обычный код Раньше для reuse логики между проектами приходилось копипастить YAML-подобные блоки. Теперь можно выносить части конфига в отдельные функции:

// shared/configs/auth.ts
export const withAuth = () => ({
  auth: {
    method: 'email',
    onAuthFailedRedirectTo: '/login'
  }
})

// my-app/app.ts
import { withAuth } from '@my-lib/configs'
const app = defineApp({
  ...withAuth(),
  // остальная конфигурация
})

2. Динамическая генерация спецификации Поскольку это обычный TS, можно генерировать части конфига программно:

// Генерация CRUD-эндпоинтов для всех моделей
const crudOperations = Object.values(entities).map(entity => ({
  [`getAll${entity.name}s`]: {
    resolver: () => prisma[entity.name].findMany()
  }
}))

3. Расширяемость через декларации типов Сообщество может добавлять свои DSL-конструкты без модификации ядра:

// @types/wasp.d.ts
declare module '@wasp/spec' {
  interface AppConfig {
    featureFlags?: Record<string, boolean>
  }
}

// Использование
defineApp({
  featureFlags: { 'newDashboard': true }
})

Где подводные камни

Не всё идеально в новом подходе:

  1. Производительность сборки — парсинг динамических import() требует дополнительного шага
  2. Сложность миграции — старые проекты требуют ручного перевода
  3. Оверхеад для простых проектов — теперь нужно явно указывать типы даже для базовых конфигов

Особенно болезненным оказался пункт про производительность. На проекте с 50+ entity время сборки увеличилось с 1.2s до 2.8s — существенно для HMR-циклов.

Кому это реально полезно

Из моего опыта, новый Wasp особенно хорош для:

  1. Команд, которые уже используют TypeScript — минимальный порог входа
  2. Full-stack разработчиков — меньше контекстных переключений
  3. Библиотек поверх Wasp — теперь можно делать плагины на TS

При этом я бы не рекомендовал его для:

Что попробовать дальше, если заинтересовало:

  1. Поиграть с примером аутентификации
  2. Проверить, как работает генерация кода через wasp compile
  3. Попробовать расширить типы через @types/wasp

Лично мне больше всего нравится, как Wasp теперь использует TypeScript не как цель, а как средство. Это не “ещё один фреймворк”, а скорее набор правил, как организовывать full-stack логику. И после недели использования кажется, что они на правильном пути — даже если придётся ещё попотеть над перфомансом.


Источник: https://wasp.sh/blog/2026/06/15/wasp-typescript-spec