Wasp TypeScript 支持完全指南:JS 项目渐进式迁移与端到端类型安全实战
2026/9/15 19:04:51 网站建设 项目流程

Wasp TypeScript 支持完全指南:JS 项目渐进式迁移与端到端类型安全实战

【免费下载链接】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

TypeScript 在 Wasp 中是开箱即用的——无论是新建项目直接使用 TS,还是将既有 JavaScript 项目逐文件渐进式迁移,都不需要额外安装或配置。本篇指南以 Wasp 官方文档为主体,结合本仓库(wasp)中真实生成器模板与配置源码,讲解@wasp/entities@wasp/server/operations等 Wasp 自动生成的类型系统如何工作,并通过一个Task查询的完整迁移示例,带你掌握从.js.ts的每一步操作。

为什么选择 TypeScript:静态类型分析的价值

TypeScript 是一门为 JavaScript 增加静态类型分析能力的编程语言。它是 JavaScript 的超集——所有合法的 JavaScript 代码都是合法的 TypeScript 代码——并且会在运行前编译为 JavaScript。

它的类型系统带来两个核心收益:

  • 在构建期捕获错误:类型错误在编译阶段(而非运行时)被暴露,显著降低线上运行时错误率;
  • 基于类型的 IDE 自动补全:编辑器可以根据类型信息提供智能提示(IntelliSense),提升开发效率。

在 Wasp 中,每个功能模块都配套有对应的 TypeScript 文档,例如数据模型(entities)、查询(queries)与操作(actions)均有独立的类型化说明。这意味着类型安全不是 Wasp 的附加项,而是贯穿整个框架的核心能力。

新建项目:使用 TypeScript 无需任何特殊操作

如果你是从零开始一个新项目,完全不需要做任何额外的事:直接按照你感兴趣的功能文档操作即可,相关文档会告诉你需要知道的一切。官方建议新手先从 Wasp 官方教程 入手,逐步熟悉 Wasp 的声明式开发流程。

而如果你手上有一个已经用 JavaScript 写好的 Wasp 项目,想迁移到 TypeScript,则请继续阅读下面的迁移指南。

迁移原理:改扩展名 + 写类型

Wasp 对 TypeScript 的支持是"出厂自带"(out-of-the-box)的,因此迁移一个项目本质上只有两件事:

  1. 更改文件扩展名.js.ts);
  2. 编写类型(并可选地使用 Wasp 提供的 TypeScript 特性)。

这种设计允许你按文件逐个渐进式迁移整个项目,无需一次性推倒重来。下面先演示如何迁移一个文件,再把流程推广到整个项目。

迁移单个文件:一个 Task 查询的完整实战

第 1 步:确认数据模型与 Wasp 声明

假设你的schema.prisma文件中定义了Task实体:

// ... model Task { id Int @id @default(autoincrement()) description String isDone Boolean }

同时,main.wasp文件中声明了名为getTaskInfo的查询(Query),它从src/queries导入实现函数,并关联Task实体:

query getTaskInfo { fn: import { getTaskInfo } from "@src/queries", entities: [Task] }

第 2 步:迁移前的 JavaScript 实现

下面是待迁移的src/queries.js文件。它定义了一个内部辅助函数getInfoMessage,以及导出给 Wasp 使用的getTaskInfo查询实现:

import HttpError from 'wasp/server' function getInfoMessage(task) { const isDoneText = task.isDone ? 'is done' : 'is not done' return `Task '${task.description}' is ${isDoneText}.` } export const getTaskInfo = async ({ id }, context) => { const Task = context.entities.Task const task = await Task.findUnique({ where: { id } }) if (!task) { throw new HttpError(404) } return getInfoMessage(task) }

注意这里有两个明显的类型盲区:getInfoMessage的参数task没有任何类型约束,getTaskInfoargscontext也完全依赖开发者记忆——这正是我们要用 TypeScript 修复的。

第 3 步:迁移到 TypeScript

迁移只需要两步:

  1. 把文件名从queries.js改为queries.ts
  2. 编写类型(并可选地启用 Wasp 的 TypeScript 特性)。

对照如下——迁移前(Before)与迁移后(After):

import HttpError from '@wasp/core/HttpError.js' function getInfoMessage(task) { const isDoneText = task.isDone ? 'is done' : 'is not done' return `Task '${task.description}' is ${isDoneText}.` } export const getTaskInfo = async ({ id }, context) => { const Task = context.entities.Task const task = await Task.findUnique({ where: { id } }) if (!task) { throw new HttpError(404) } return getInfoMessage(task) }
import HttpError from 'wasp/server' import { type Task } from '@wasp/entities' import { type GetTaskInfo } from '@wasp/server/operations' function getInfoMessage(task: Pick<Task, 'isDone' | 'description'>): string { const isDoneText = task.isDone ? 'is done' : 'is not done' return `Task '${task.description}' is ${isDoneText}.` } export const getTaskInfo: GetTaskInfo<Pick<Task, 'id'>, string> = async ( { id }, context ) => { const Task = context.entities.Task const task = await Task.findUnique({ where: { id } }) if (!task) { throw new HttpError(404) } return getInfoMessage(task) }

迁移后,你的代码由 TypeScript 接管,并使用了两个 Wasp 特有的类型特性:

Task类型:连接 Prisma 数据模型

import { type Task } from '@wasp/entities'

Task是表示Task实体的类型,它的来源是schema.prisma中的模型定义。使用这一类型,你的业务代码就与数据库模型建立了强类型连接:当 Prisma 模型字段变化时,所有引用处的类型检查会立刻提示你。

在示例中,我们通过Pick<Task, 'isDone' | 'description'>只挑选辅助函数真正用到的两个字段,既精确又避免了与整个实体类型的过度耦合。

关于实体类型的更多用法,可参考 数据模型与实体文档。

GetTaskInfo泛型:Wasp 自动生成的查询类型

import { type GetTaskInfo } from '@wasp/server/operations'

GetTaskInfo<...>是 Wasp 为每个在main.wasp中声明的查询自动生成的泛型类型。给实现函数标注上它之后,编译器会自动获知:

  • context对象的类型(包括context.entities.Task对应的 Prisma delegate 类型);
  • args参数的类型;
  • 查询的返回类型

于是编辑器会为你提供 IntelliSense 与完整的类型检查。在示例中GetTaskInfo<Pick<Task, 'id'>, string>表示:该查询接收一个包含id字段的参数对象,返回一个string

关于查询实现类型的完整说明,见 查询(Queries)文档。

注意:整个迁移过程不需要改动.wasp文件(在 version-0.16 中为main.wasp),Wasp 的声明层对文件扩展名是透明的。

迁移整个项目:逐文件渐进式三步流程

你可以按"文件粒度"渐进式迁移整个项目,每迁移一个文件都遵循上面演示的流程:

  1. 更改文件扩展名.js.ts);
  2. 修复类型错误(让tsc编译通过);
  3. 阅读对应功能的 Wasp 文档,决定要启用哪些 TypeScript 特性(如@wasp/entities实体类型、@wasp/server/operations的操作泛型等)。

这种渐进式策略允许 JavaScript 与 TypeScript 文件在同一个 Wasp 项目中长期共存,团队可以按模块优先级分批推进,无需"大爆炸式"重写。

深入原理:这些类型从哪来?

上面用到的@wasp/entities@wasp/server/operations并非手写代码,而是 Wasp 生成器在每次wasp start/wasp build时自动生成的 SDK 的一部分。在本仓库中可以找到它们的生成模板:

实体类型:从 Prisma Client 直接复用

模板 waspc/data/Generator/templates/sdk/wasp/entities/index.ts 展示了@wasp/entities的生成逻辑:Wasp 直接把@prisma/client中生成的实体类型重新导出,并同时导出Entity(实体联合类型)与EntityName(实体名字符串联合类型):

import type { {=# entities =} {= name =}, {=/ entities =} } from "@prisma/client" export type { {=# entities =} {= name =}, {=/ entities =} } from "@prisma/client" export type Entity = {=# entities =}| {= name =}{=/ entities =}| never export type EntityName = {=# entities =}| "{= name =}"{=/ entities =}| never

这就是为什么import { type Task } from '@wasp/entities'能与你schema.prisma中的model Task保持完全同步——它本质上是 Prisma Client 类型的一份"转发"。

操作泛型:查询/操作类型的生成骨架

模板 waspc/data/Generator/templates/sdk/wasp/server/operations/queries/types.ts 定义了每个查询类型(如GetTaskInfo)的生成骨架:它是一个接收<Input, Output>两个泛型参数的别名,内部根据该查询是否启用认证,解析为AuthenticatedQueryDefinitionUnauthenticatedQueryDefinition

export type {= typeName =}<Input extends Payload = never, Output extends Payload = Payload> = {=# usesAuth =} AuthenticatedQueryDefinition< {=/ usesAuth =} {=^ usesAuth =} UnauthenticatedQueryDefinition< {=/ usesAuth =} [ {=# entities =}{= internalTypeName =},{=/ entities =} ], Input, Output >

而上下文(context)的类型由模板 waspc/data/Generator/templates/sdk/wasp/server/_types/index.ts 定义:Context<Entities>会展开为包含entities字段的对象,entities的类型是EntityMap——由"实体名 → Prisma delegate"的映射推导而来;启用认证时则会扩展出带user字段的ContextWithUser

查询实现如何被注入 entities

你写的查询函数并不会被直接调用。生成器模板 waspc/data/Generator/templates/server/src/queries/_query.ts 显示,Wasp 会为每个查询生成一个包装函数,在调用你的实现前注入entities(将prisma.Task等 delegate 挂到context.entities上):

export default async function (args, context) { return ({= jsFn.importIdentifier =} as any)(args, { ...context, entities: { {=# entities =} {= name =}: prisma.{= prismaIdentifier =}, {=/ entities =} }, }) }

这解释了为何你可以在getTaskInfo中直接使用context.entities.Task.findUnique(...),也解释了为什么GetTaskInfo泛型能够精确推断context的类型——两者同源于你在 Wasp 声明中列出的entities

构建期的强制类型检查

Wasp 不仅在编辑器层面提供类型,还在构建期强制检查。生成器中的 Vite 插件模板 waspc/data/Generator/templates/sdk/wasp/client/vite/plugins/typescriptCheck.ts 会在buildStart阶段调用tsc --project <srcTsConfig> --noEmit,任何类型错误都会导致构建失败(TypeScript check failed):

const child = spawn( 'tsc', ['--project', srcTsConfigPath, '--noEmit'], { stdio: 'inherit', shell: process.platform === 'win32' } )

换句话说,类型安全从"编辑器的建议"升级为"构建的硬性门槛"。

项目中的 TypeScript 配置参考

仓库内的真实 Wasp 项目展示了标准的 TS 配置。例如 examples/kitchen-sink/tsconfig.wasp.json 开启了strict模式、moduleResolution: "bundler"allowJs(允许 JS/TS 共存,正是渐进式迁移的配置基础),并将**/*.wasp.ts.wasp/out/types/spec纳入检查范围;各示例项目根目录下的tsconfig.jsontsconfig.src.json则分别面向应用源码与构建产物,可直接作为自己项目配置的参照。

常见坑:编辑器 LSP 报错与解决方案

在开发过程中你可能会遇到一个典型现象:wasp start正常运行,但编辑器却报告类型错误或导入错误

这通常是因为 TypeScript Language Server(TS 语言服务器)与当前代码状态不同步——Wasp 在启动时会动态生成@wasp/entities@wasp/server/operations等 SDK 类型,如果语言服务器缓存了旧版本的生成文件,就会产生"幽灵报错"。

如果你使用 VS Code,可以通过命令面板手动重启 TS 语言服务器来恢复同步:

  • Windows / Linux:按Ctrl+Shift+P打开命令面板;
  • macOS:按Cmd+Shift+P打开命令面板;

然后选择"TypeScript: Restart TS Server"即可。

小结

Wasp 的 TypeScript 支持贯穿"编写—检查—构建"全流程:新建项目零配置即可使用;存量 JS 项目可通过"改扩展名 + 写类型"的方式逐文件渐进迁移;@wasp/entities@wasp/server/operations等自动生成的类型让实体数据与查询实现获得端到端的类型安全;构建期的tsc检查则把类型错误拦截在发布之前。掌握本文的迁移流程与类型机制,你就可以放心地把任意 Wasp 项目平滑升级到全量 TypeScript。

进一步阅读:

  • Wasp 官方教程(从创建项目开始)
  • 数据模型与实体(entities)
  • 查询(queries)实现与类型

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询