Wasp 语言(.wasp DSL)完全指南:声明式配置与类型系统深度解析
【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp
本文是 Wasp 全栈框架中Wasp 语言(.wasp 文件 DSL)的权威技术指南,讲解这门声明式、静态类型、面向 Web 应用的领域特定语言的核心语法、完整类型体系与编译器底层实现。读完本文,你将掌握
.wasp文件中声明的书写规则、app/route/page等声明类型的使用方法、全部基础类型与领域类型,并能理解类型检查、Prisma 模型注入等编译原理,为从 Wasp DSL 平滑迁移到main.wasp.ts(TypeScript 配置)打下基础。
Wasp 语言是什么
Wasp 语言(即写在.wasp文件中的语言)是一门声明式(declarative)、静态类型(statically typed)的领域特定语言(DSL)。它并非通用编程语言,而是一门更接近 JSON、CSS 或 SQL 的配置语言——你不需要描述"如何做",只需要描述"要什么",其余的全栈复杂度(认证、后台任务、RPC、邮件发送、部署等)由 Wasp 编译器负责推导和生成。
用官方文档的原话说:"这门语言非常简单直观,没有太多需要学习的内容。"你完全可以边用边学,但如果想获得更形式化的定义和对它如何工作的更深理解,那么本文接下来的内容正是为你准备的。
提示(早期预览特性):如果你愿意,也可以不用
main.wasp,而是选择用 TypeScript 来定义 Wasp 配置(main.wasp.ts)。详见 Wasp TypeScript 配置指南。本文讲解的是 Wasp 语言的 DSL 本体,而两种写法在语义上完全等价——理解 DSL 是理解 TS 配置 API 的最佳起点。
声明(Declarations):Wasp 语言的核心
Wasp 语言的中心概念是声明(declaration)。一段 Wasp 代码本质上就是一组声明,每一条声明描述你的 Web 应用的一个组成部分。
app MyApp { title: "My app" } route RootRoute { path: "/", to: DashboardPage } page DashboardPage { component: import { DashboardPage } from "@src/Dashboard.jsx" }上面的例子用三条声明描述了一个 Web 应用:
app MyApp { ... }:声明应用本身,标题为 "My app";route RootRoute { ... }:声明一条路由,路径/指向DashboardPage;page DashboardPage { ... }:声明一个页面,其 React 组件从@src/Dashboard.jsx导入。
声明的语法结构
书写一条声明的语法是:
<declaration_type> <declaration_name> <declaration_body>其中:
| 组成部分 | 含义 | 说明 |
|---|---|---|
declaration_type | 声明类型 | Wasp 提供的声明类型之一,如app、route、page、query、action、job、api、crud等 |
declaration_name | 声明名称 | 由你选择的标识符,用于命名这条声明 |
declaration_body | 声明体 | 该声明的值/定义,必须匹配对应声明类型所期望的声明体类型 |
以上面的app声明为例:
- 声明类型是
app; - 声明名称是
MyApp(你也可以用任何其他标识符,比如foobar、foo_bar、hi3Ho); - 声明体是
{ title: "My app" },即一个包含title字段、值为字符串的字典(dict),其类型与app声明类型所期望的声明体类型一致。
如果声明体类型不匹配,Wasp 编译器会直接报类型错误。例如把title改成little,因为这不匹配app声明体期望的类型,就会得到来自 Wasp 编译器的类型错误。
每一条声明背后都承载着含义,描述你的 Web 应用应当如何表现和运转。而 Wasp 语言中所有其他类型——原始类型(string、number)、复合类型(dict、list)、枚举类型(DbSystem)等——都是用来定义这些声明体的。
Wasp 类型系统全景
Wasp 的类型系统可以划分为两大类:基础类型(fundamental types)与领域类型(domain types)。
- 基础类型是语言的基本积木,与其他主流语言中的类型非常相似;
- 领域类型则是 Wasp 与众不同的地方,它们建模了 Web 应用的概念,如
page、route、query等。
这两类类型的权威定义分别位于编译器源码的 Type.hs(基础类型)与 StdTypeDefinitions.hs(领域类型)中。
基础类型
原始类型(Primitive Types)
| 类型 | 示例 | 说明 |
|---|---|---|
| string | "foo"、"they said: \"hi\"" | 字符串字面量,支持转义 |
| bool | true、false | 布尔值 |
| number | 12、14.5 | 数字,支持整数与小数 |
| declaration reference | TaskPage、updateTask | 对已存在声明的引用,直接写声明名称 |
| ExtImport(外部导入) | import Foo from "@src/bar.js"、import { Smth } from "@src/a/b.js" | 从src目录导入代码 |
| json | {=json { a: 5, b: ["hi"] } json=} | 用引号(quoter)包裹的 JSON 字面量 |
关于ExtImport有两条硬性规则:
- 路径必须以
@src开头,其余部分相对于src目录解析; - 导入必须是默认导入(
import Foo)或单个具名导入(import { Foo })。
在编译器源码 ExtImport.hs 中可以看到,ExtImport实际由三部分组成:name(导入的是什么,区分默认导入ExtImportModule与具名导入ExtImportField)、path(从哪导入)以及可选的alias(在 Wasp 配置中的本地别名)。
复合类型(Composite Types)
| 类型 | 示例 | 说明 |
|---|---|---|
| dict(字典) | { a: 5, b: "foo" } | 键值对集合 |
| list | [1, 2, 3] | 列表 |
| tuple | (1, "bar")、(2, 4, true) | 元组,只支持 2、3、4 元 |
在 Type.hs 的Type代数数据类型中,基础类型被建模为StringType、NumberType、BoolType、ExtImportType、DictType、ListType、TupleType、QuoterType等构造子。值得注意的细节是:
EmptyListType:一个特殊类型,被临时赋给空列表,在typeCheck完成后所有该类型出现处都会被替换为合适的ListType;QuoterType tag:对应{=tag ... tag=}形式的引号语法(json 只是其中一个 tag);- 字典条目的必填/可选:
DictEntryType区分DictRequired与DictOptional(对应field:与field?:),dictEntryRequired函数用于判定某条字段是否必须出现在声明体中。也就是说,声明体字典里的字段是可以声明为可选的。
领域类型(Domain Types)
领域类型定义在 StdTypeDefinitions.hs 的stdTypes集合中。从源码看,它通过TD.addDeclType/TD.addEnumType把以下类型逐一注册进类型定义表。
声明类型(Declaration Types)
| 声明类型 | 含义 |
|---|---|
| action | 写操作(修改数据) |
| api | 自定义 HTTP API |
| apiNamespace | API 命名空间 |
| app | 应用本身 |
| job | 后台任务 |
| page | 页面 |
| query | 读操作(查询数据) |
| route | 路由 |
| crud | 自动生成的 CRUD 操作 |
枚举类型(Enum Types)
| 枚举 | 合法值(依据当前仓库源码) |
|---|---|
| DbSystem | PostgreSQL、SQLite(见 Db.hs) |
| HttpMethod | ALL、GET、POST、PUT、DELETE(见 Api.hs) |
| JobExecutor | PgBoss(见 Job.hs) |
| EmailProvider | SMTP、SendGrid、Mailgun、Resend、Dummy(见 EmailSender.hs) |
说明:以上枚举的合法取值以当前仓库源码为准,例如
JobExecutor目前只有PgBoss一个构造子(其在FromJSON解析中只接受字符串"PgBoss");HttpMethod支持通配的ALL方法。
schema.prisma 中的模型(Models)
你可以在.wasp文件中直接引用schema.prisma文件中定义的模型,方式就是使用模型名,例如Task。这背后的机制值得展开:在编译器源码 Prisma.hs 中,parseEntityStatements会把 Prisma schema 中的每个模型解析为一条entity声明(以psl引号包裹模型体),并注入 Wasp 的 AST。也就是说,Prisma 模型与 Wasp 声明在编译期被统一到了同一条类型检查流水线上。
关于每种领域类型的声明体类型(body type)与语义细节,可查阅对应功能的文档页面(如认证、CRUD、Jobs、API 等)——例如api声明体包含fn、entities、httpRoute(方法 + 路径)、auth等字段,见 Api.hs;job声明体包含executor、perform、可选的schedule与entities,见 Job.hs。
声明体类型是如何被定义与检查的
理解 Wasp 类型系统最直接的方式,是去看编译器如何把它落地。Wasp 编译器(Haskell 实现)中:
类型定义表:
TypeDefinitions由declTypes(声明类型)与enumTypes(枚举类型)两个映射构成,见 TypeDefinitions.hs。每个声明类型实际是一个 Haskell 记录类型(record),其字段即声明体字段,字段类型即字段期望的 Wasp 类型——例如App记录包含wasp、title、deployment、head、auth、server、client、db、emailSender、webSocket等字段,见 App.hs;类型检查:
typeCheck :: TypeDefinitions -> AST -> Either TypeError TypedAST,见 TypeChecker.hs。它对 AST 逐条检查是否符合 Wasp 类型规则,成功则产出携带类型标注的 AST,失败则返回TypeError——这就是前面提到的"把title改成little会报类型错误"的底层来源;两种错误的区分:类型检查的错误与声明名称合法性的校验是分开的,
StdTypeDefinitions.hs的注释特别提醒:新增声明类型时,必须在Wasp.AppSpec.Valid模块的validateUniqueDeclarationNames函数中同步更新,以检查重复的声明名。
这套设计把"语言定义"与"编译器逻辑"解耦:领域类型以数据驱动的方式注入 Analyzer,而不是硬编码,这让 Wasp 编译器/语言更容易扩展和维护。
从一个完整例子看三种声明的组合
把前面文档的示例扩展成一个更具实战感的组合,可以看到app、route、page三种声明如何协作:
app MyApp { title: "My app" } route RootRoute { path: "/", to: DashboardPage } page DashboardPage { component: import { DashboardPage } from "@src/Dashboard.jsx" }逐条拆解:
app MyApp声明体是一个字典,字段title的类型是string,这是app声明体类型要求的必填字段之一;route RootRoute声明体包含path(string)与to(声明引用,指向一个page声明)两个字段;page DashboardPage声明体的component字段类型是ExtImport,导入的路径@src/Dashboard.jsx以@src开头、相对于src目录解析,且是默认导入。
这里to: DashboardPage正是声明引用(declaration reference)类型的用法——用声明名引用另一条声明,Wasp 编译器会在类型检查阶段验证被引用的声明确实存在且类型匹配。
与 Wasp TS 配置(main.wasp.ts)的对应关系
Wasp 0.15+ 提供了 Wasp TS config 作为.waspDSL 的替代方案:同样描述应用的高层结构(pages、routes、queries、actions、auth……),但用 TypeScript 书写,且是早期预览特性。从 DSL 迁移到main.wasp.ts的本质,是把每一条声明映射为App对象上的一个方法调用。
以仓库中真实的 examples/tutorials/TodoApp/main.wasp.ts 为例,前面的三条 DSL 声明在 TS 配置中等价于:
import { action, app, page, query, route } from "@wasp.sh/spec"; import { createTask, updateTask } from "./src/actions" with { type: "ref" }; import { LoginPage } from "./src/LoginPage" with { type: "ref" }; import { MainPage } from "./src/MainPage" with { type: "ref" }; import { getTasks } from "./src/queries" with { type: "ref" }; import { SignupPage } from "./src/SignupPage" with { type: "ref" }; export default app({ name: "TodoApp", wasp: { version: "0.26.0" }, title: "TodoApp", head: ["<link rel='icon' href='/favicon.ico' />"], auth: { userEntity: "User", methods: { usernameAndPassword: {}, }, onAuthFailedRedirectTo: "/login", }, spec: [ route("RootRoute", "/", page(MainPage, { authRequired: true })), route("SignupRoute", "/signup", page(SignupPage)), route("LoginRoute", "/login", page(LoginPage)), query(getTasks, { entities: ["Task"] }), action(createTask, { entities: ["Task"] }), action(updateTask, { entities: ["Task"] }), ], });对照可见:
- DSL 中的
app、route、page、query、action在 TS 配置中对应同名函数; - 页面组件、查询/动作函数在 DSL 中用 ExtImport 描述,在 TS 配置中则直接以
import ... with { type: "ref" }引入,语义一致; - 声明引用(
to: DashboardPage)在 TS 配置中体现为把page(...)的返回值直接传给route(...)。
理解 DSL 的类型系统后,你会更清楚地看到 TS 配置 API 背后的设计动机:每条声明就是一个类型化对象,字段类型与 DSL 声明体类型一一对应。完整的迁移步骤与参考文件可参阅 Wasp TypeScript 配置指南。
小结
- Wasp 语言是声明式、静态类型的配置型 DSL,全部代码就是一组描述应用各个部分的声明;
- 声明语法为
<declaration_type> <declaration_name> <declaration_body>,声明体类型必须与声明类型匹配,否则编译器报类型错误; - 类型系统分基础类型(string、bool、number、声明引用、ExtImport、json、dict、list、tuple)与领域类型(
action、api、apiNamespace、app、job、page、query、route、crud等声明类型,以及DbSystem、HttpMethod、JobExecutor、EmailProvider等枚举类型,外加schema.prisma中的模型引用); - 编译器的类型定义与检查逻辑分别位于 Type.hs、StdTypeDefinitions.hs、TypeChecker.hs,Prisma 模型通过 Prisma.hs 注入统一检查流水线;
- 你可以选择继续使用
main.waspDSL,也可以在 Wasp 0.15+ 中切换为等价的main.wasp.tsTypeScript 配置。
【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考