Wasp 语言(.wasp)详解:用声明式领域特定语言描述全栈 Web 应用
【免费下载链接】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 官方文档(version-0.13 快照)中的《Wasp Language (.wasp)》一文,系统讲解 Wasp 语言的声明语法、完整类型系统与领域类型清单,并结合 waspc 编译器源码(Haskell 实现的 Analyzer 类型系统)逐条印证每个语言特性的真实定义,帮助你从“会写 .wasp 文件”深入到“理解 Wasp 编译器如何解析、类型检查你的声明”。
什么是 Wasp 语言
Wasp 语言(即你在.wasp文件中编写的代码)是一种声明式、静态类型的领域特定语言(DSL)。它不是通用编程语言,而更接近一种配置语言——语法上更接近 JSON、CSS 或 SQL,而非 JavaScript 或 Python。这意味着它没有控制流、没有函数定义,你只需要“声明”应用的各个组成部分,剩下的全栈行为(路由、认证、数据库、后台任务等)由 Wasp 编译器生成和接管。
Wasp 官方文档的态度很务实:这门语言足够直观,简单到你可以不读这篇语言规范、而是在学习其他功能文档时顺带掌握它;但如果你想获得更形式化的定义和对内部工作机制的更深理解,就需要理解下面两个核心:声明(declarations)与类型系统。
声明(Declarations):Wasp 代码的唯一组成单元
Wasp 语言的中心概念是声明——Wasp 代码在本质上就是一组声明的集合,每个声明描述你 Web 应用的一个部分。文档给出的典型示例:
app MyApp { title: "My app" } route RootRoute { path: "/", to: DashboardPage } page DashboardPage { component: import { DashboardPage } from "@src/Dashboard.jsx" }这个示例用三个声明描述了一个 Web 应用:app MyApp { ... }、route RootRoute { ... }和page DashboardPage { ... }。
声明的统一写法为:
<declaration_type> <declaration_name> <declaration_body>三个组成部分的含义:
<declaration_type>:Wasp 提供的声明类型之一(app、route、page等,完整清单见下文领域类型一节);<declaration_name>:你为该声明选定的标识符(如MyApp,也可以叫foobar、foo_bar或hi3Ho);<declaration_body>:声明本身的值/定义,其类型必须符合所选声明类型期望的声明体类型(declaration body type)。
以app声明为例拆解:
- 声明类型:
app - 声明名:
MyApp - 声明体:
{ title: "My app" }——一个包含字符串值字段title的字典(dict),该字典类型与app声明类型的声明体类型一致。如果你把title改成little,Wasp 编译器会给出类型错误,因为它不符合app声明体的期望类型。
每个声明都有背后明确的语义,描述你的 Web 应用应如何表现与运作。而 Wasp 语言中的其余所有类型——基本类型(string、number)、复合类型(dict、list)、枚举类型(DbSystem)等——全部用于定义声明体。
完整类型清单:Fundamental 类型与 Domain 类型
Wasp 的类型系统分为两大类:
- Fundamental 类型(基础类型):语言的基本构建块,与其他主流语言中的概念相似;
- Domain 类型(领域类型):这是 Wasp 的独特之处——它们对 Web 应用的概念建模,如
page、route等。
基本类型(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目录下的 JS/TS 模块 |
| json | {=json { a: 5, b: ["hi"] } json=} | 通过引号器(quoter)内嵌 JSON |
| psl | {=psl <psl data model syntax> psl=} | 通过引号器内嵌 Prisma Schema Language 数据模型语法 |
其中两个值得展开的要点:
ExtImport 的约束规则:
- 路径必须以
@src开头,其余部分相对于src目录解析; - 导入只允许两种形式:默认导入
import Foo或单个具名导入import { Foo }。
引号器(Quoter)语法:{=<tag> ... <tag>=}形式允许在声明体中内嵌其他语法的文本片段,目前支持json和psl两种 tag;psl用于直接嵌入 Prisma 的数据模型定义(其语法以 Prisma 官方文档为准)。
复合类型(Composite types)
| 类型 | 字面量示例 | 说明 |
|---|---|---|
| dict(字典) | { a: 5, b: "foo" } | 键值对集合,字段可必填或可选 |
| list(列表) | [1, 2, 3] | 同元素类型的有序集合 |
| tuple(元组) | (1, "bar")、(2, 4, true) | 定长混合类型,只允许大小为 2、3、4 |
领域类型(Domain types)
这是 Wasp 区别于普通配置格式的关键,全部用于描述 Web 应用概念:
声明类型(declaration types):
- action——服务端可被页面调用的操作
- api——暴露给前端的 HTTP 接口
- apiNamespace——api 的命名空间分组
- app——应用根声明(标题、认证、数据库、部署等配置都挂在这里)
- entity——数据模型实体(与
schema.prisma联动) - job——后台任务
- page——页面(绑定 React 组件与路由目标)
- query——服务端只读查询
- route——URL 路由
- crud——为实体一键生成增删改查能力
枚举类型(enum types):
- DbSystem——数据库系统(如 Postgres、Sqlite)
- HttpMethod——HTTP 方法
- JobExecutor——任务执行器
- EmailProvider——邮件发送提供方
每种领域类型的声明体类型与业务含义,在 Wasp 文档对应功能章节中逐一展开(如 认证 各声明的 body 说明)。
源码印证:类型系统如何落地在 waspc 编译器中
以上类型清单并非纸面定义,它们与 waspc(Wasp 编译器)Haskell 源码一一对应。原文档标注的“source of truth”在当前仓库中即以下两个文件。
类型 ADT:所有 Wasp 类型的形式化定义
waspc/src/Wasp/Analyzer/Type.hs 定义了全部可能类型的代数数据类型:
data Type = DeclType String -- 声明类型,如 app、page | EnumType String -- 枚举类型,如 HttpMethod | DictType (H.HashMap String DictEntryType) -- 字典,字段可必填/可选 | ListType Type | EmptyListType -- 空列表的临时类型 | TupleType (Type, Type, [Type]) -- 元组:固定前两个 + 可选剩余 | StringType | NumberType | BoolType | ExtImportType | QuoterType String -- {=tag ... tag=} 引号器几处源码细节直接印证了文档描述:
- 元组大小限制:
TupleType (Type, Type, [Type])的构造形态恰好对应文档中“元组只允许大小为 2、3、4”的规则——前两个元素固定存在,第三个之后的剩余类型通过[Type]列表表达,至多再容纳两个; - 字典字段的必填/可选区分:DictEntryType 分为
DictRequired与DictOptional两种,这解释了为何app声明体必须提供title而auth、db等字段可以省略——app声明体中不同字段的强制性正是由这一结构决定的; EmptyListType是内部机制:源码注释说明空列表在类型检查期间被赋予临时类型,待typeCheck完成后统一替换为具体ListType,属于编译器内部细节,对用户透明。
领域类型清单:stdTypes 注入机制
waspc/src/Wasp/Analyzer/StdTypeDefinitions.hs 通过stdTypes集中注册全部标准领域类型:
stdTypes :: TD.TypeDefinitions stdTypes = TD.addDeclType @App $ TD.addEnumType @DeploymentMode $ TD.addDeclType @Entity $ TD.addDeclType @Page $ TD.addDeclType @Route $ TD.addDeclType @Query $ TD.addDeclType @Action $ TD.addEnumType @JobExecutor $ TD.addDeclType @Job $ TD.addEnumType @HttpMethod $ TD.addDeclType @Api $ TD.addDeclType @ApiNamespace $ TD.addEnumType @EmailProvider $ TD.addDeclType @Crud $ TD.empty源码注释明确说明:这些领域类型是**以注入方式(而非硬编码进 Analyzer)**提供给编译器的,目的是让修改和维护 Wasp 语言本身更容易。对照文档的类型清单,两者的声明类型(action、api、apiNamespace、app、entity、job、page、query、route、crud)与枚举类型完全吻合。从源码结构看,当前主干还多出一个未在 version-0.13 文档枚举中列出的DeploymentMode枚举(用于app声明中的部署配置),这体现了文档快照与主干演进之间的正常差异——阅读本文结论时应以文档快照对应的 Wasp 版本行为为准。
声明体的真实字段:以app和page为例
文档提到app声明体是“一个包含title字段(字符串值)的字典”。而编译器侧的App记录(waspc/src/Wasp/AppSpec/App.hs)完整呈现了该声明体在类型检查通过后的形态:
data App = App { wasp :: Wasp, -- 声明自身的元数据(名字、源位置) title :: String, -- 必填,对应文档示例中的 { title: "My app" } deployment :: Maybe Deployment, head :: Maybe [String], auth :: Maybe Auth, server :: Maybe Server, client :: Maybe Client, db :: Maybe Db, emailSender :: Maybe EmailSender, webSocket :: Maybe WebSocket }title :: String是必填项,其余字段均为Maybe——与Type.hs中DictRequired/DictOptional的区分精确对应。这也解释了文档中“把title改成little会报类型错误”的原因:title是app字典类型中唯一的必填字段。
同理,waspc/src/Wasp/AppSpec/Page.hs 表明page声明体只有两个字段:
data Page = Page { component :: ExtImport, -- 必填,且必须是 ExtImport 类型 authRequired :: Maybe Bool }component字段类型是ExtImport——这从类型层面印证了 ExtImport 的独立地位:它不是普通 string,而是专门用于引用src目录下 JS/TS 模块的原始类型,编译器据此校验@src前缀与“默认导入或单个具名导入”两条规则。
声明的编译流水线:从 .wasp 文本到类型检查
Wasp 声明从文本到语义的完整处理链路,在 Analyzer 模块说明 中有一张流水线图(即本文开头的 Analyzer 图):.wasp文件先由 Parser 解析为 AST,再进入类型检查器完成声明体类型校验。README 特别指出了一个容易忽略的环节:在 Parser 与 TypeChecker 之间,还有一个基于解析后的schema.prisma文件注入entity声明的步骤——也就是说,entity类声明并非直接写在.wasp文件里,而是由编译器从 Prisma schema 派生并注入,这也是psl引号器类型存在的意义所在。
小结与延伸阅读
- 掌握
<declaration_type> <declaration_name> <declaration_body>三要素、基本类型(含 ExtImport 与 json/psl 引号器)以及领域类型清单,就覆盖了 Wasp 语言的完整语法面; - 语言规范以官方文档为准,本文引用的版本快照位于 web/versioned_docs/version-0.13/general/language.md,各类型的“source of truth”源码位于 waspc/src/Wasp/Analyzer/Type.hs 与 waspc/src/Wasp/Analyzer/StdTypeDefinitions.hs;
- 每种声明类型(action、query、api、job、crud 等)的声明体字段与行为语义,建议在 Wasp 文档对应功能章节中结合本文的类型系统视角继续阅读。
【免费下载链接】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),仅供参考