ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Wasp 全栈框架 TypeScript 支持实战:从 JavaScript 项目逐文件迁移到类型安全的 TS 全栈

Wasp 全栈框架 TypeScript 支持实战:从 JavaScript 项目逐文件迁移到类型安全的 TS 全栈 Wasp 全栈框架 TypeScript 支持实战从 JavaScript 项目逐文件迁移到类型安全的 TS 全栈【免费下载链接】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当前仓库 waspc 与 examples 目录所对应的开源全栈框架v0.15 官方文档 TypeScript Support 为主线系统讲解 Wasp 框架开箱即用的 TypeScript 支持、如何将既有 JavaScript 项目按文件粒度平滑迁移到 TypeScript、Wasp 自动生成的实体类型与操作Query/Action泛型类型的工作原理并穿插仓库源码TodoAppTs 示例、tsconfig 模板、SDK 包作为佐证。读完本文你将掌握在 Wasp 项目中用 TypeScript 获得端到端客户端 ↔ 服务端类型安全的具体方法以及迁移过程中常见的编辑器类型报错排查技巧。Wasp 是一个内置电池的全栈 Web 框架使用声明式.wasp文件描述应用结构底层由 React客户端、Node.js服务端与 Prisma数据库驱动。TypeScript 作为 JavaScript 的超集为这类全栈项目带来了构建期静态类型检查与 IDE 自动补全而 Wasp 从设计之初就把 TypeScript 支持作为一等公民内置其中。TypeScript 在 Wasp 中的定位TypeScript 是一门为 JavaScript 增加静态类型分析能力的编程语言它具备两个核心特性是 JavaScript 的超集所有合法的 JavaScript 代码都是合法的 TypeScript 代码因此你可以随时在.ts文件中书写熟悉的 JS 语法编译回 JavaScript 后再运行Node.js 与浏览器最终执行的仍是 JavaScriptTypeScript 只是在开发期与构建期提供类型保障。它的价值主要体现在两个方面对应 Wasp 官方文档的表述在构建期捕获错误类型系统能在代码运行之前发现大量低级错误从而显著减少运行时错误提供基于类型的 IDE 自动补全编辑器可以依据类型信息推断出对象上可用的属性与方法提升开发效率与可维护性。在 Wasp 中每个功能模块的文档都包含对应的 TypeScript 说明——实体Entities、查询Queries、动作Actions、认证Auth、定时任务Jobs等均有各自的 TS 专属章节。这意味着 TypeScript 不是 Wasp 的附加选项而是渗透在框架每一个特性里的默认能力。新建项目什么都不用做如果你是从零开始一个新项目完全不需要任何额外配置只需按照你感兴趣的功能文档操作即可文档中的代码示例通常都提供了 JavaScript / TypeScript 双版本Tab 切换Wasp 官方建议新手从官方教程开始教程中的代码块带有 JS/TS 语言切换开关站点还会记住你的语言偏好教程明确说明Wasp 开箱即用地同时支持 JavaScript 和 TypeScript你完全可以根据需要自由选择甚至混用两者。仓库中可找到对应的真实示例examples/tutorials/TodoAppTs就是教程的纯 TypeScript 版本其src目录下同时存在queries.ts、actions.ts、MainPage.tsx等 TS/TSX 源码见 examples/tutorials/TodoAppTs/src而examples/tutorials/TodoApp则是与之对应的 JavaScript 版本——两者并存本身就说明了 Wasp 支持 JS/TS 混用与按项目选择的灵活性。迁移你的项目到 TypeScript整体思路对于已经存在的 JavaScript Wasp 项目迁移过程出奇地简单因为Wasp 本身自带开箱即用的 TypeScript 支持迁移 修改文件扩展名 使用 TypeScript 语言特性。这种设计让你可以按文件粒度渐进式迁移今天迁移一个queries.js明天迁移一个组件业务完全不受影响main.wasp文件也不需要改动。下面先演示如何迁移单个文件再推广到整个项目。迁移单个文件完整实战示例官方文档以Task实体与getTaskInfo查询为例演示了完整的迁移过程。我们先还原出迁移前的项目背景。迁移前的项目背景假设schema.prisma中定义了Task实体// ... model Task { id Int id default(autoincrement()) description String isDone Boolean }main.wasp中声明了getTaskInfo查询并指明其实现从src/queries导入、操作需要用到Task实体query getTaskInfo { fn: import { getTaskInfo } from src/queries, entities: [Task] }待迁移的src/queries.js内容如下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) }这段代码用context.entities.Task通过 Prisma Client 按id查询任务查不到时抛出 404 错误最后把任务格式化为一段人类可读的信息。迁移步骤迁移这个文件只需要两步把文件名从queries.js改为queries.ts编写一些类型并可选地使用 Wasp 的 TypeScript 专属特性。迁移前后代码对照迁移前src/queries.jsimport 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) }迁移后src/queries.tsimport HttpError from wasp/server import { type Task } from wasp/entities import { type GetTaskInfo } from wasp/server/operations function getInfoMessage(task: PickTask, isDone | description): string { const isDoneText task.isDone ? is done : is not done return Task ${task.description} is ${isDoneText}. } export const getTaskInfo: GetTaskInfoPickTask, 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) }可以看到改动非常克制辅助函数getInfoMessage补充了入参类型PickTask, isDone | description与返回类型string查询实现则用GetTaskInfoPickTask, id, string标注。除此之外函数体逻辑与迁移前一模一样。注意你不需要对.wasp文件做任何修改——查询声明、实体声明在迁移前后保持一致。Wasp 的两个 TypeScript 专属特性上述迁移后的代码用到了 Wasp 自动生成的两个类型特性它们是理解 Wasp TypeScript 支持的关键1.Task实体类型连接 Prisma 数据模型import { type Task } from wasp/entitiesTask是一个代表Task实体的类型由 Wasp 根据schema.prisma中的model Task自动生成。使用这个类型等于把你的业务代码与数据模型定义绑定在了一起修改schema.prisma中的字段会同步改变导入的Task类型如果代码中的对象结构跟不上模型变化TypeScript 会立刻抛出类型错误提醒你更新代码。这种类型耦合消除了重复定义并确保函数签名始终与实体保持一致。官方文档Entities 文档还特别说明实体类型在客户端代码中同样可用——你可以在 React 组件里import { Task } from wasp/entities并使用const task: Task {...}从而让页面组件与数据模型保持类型同步。注意v0.15 文档示例中实体类型从wasp/entities导入而当前仓库主分支的示例如 examples/tutorials/TodoAppTs/src/queries.ts使用的是无前缀的wasp/entities这是不同版本间模块命名约定上的差异迁移时以你实际安装的 Wasp 版本文档为准。2.GetTaskInfo...操作泛型全栈类型安全的基石import { type GetTaskInfo } from wasp/server/operationsGetTaskInfo...是 Wasp 根据main.wasp中的query getTaskInfo声明自动生成的泛型类型。当你用它标注查询实现时编译器自动获得三类信息context对象的类型包括context.entities中可用的实体如Task、以及当查询使用认证时context.user的类型args的类型查询接收的载荷payload类型即示例中的PickTask, id查询的返回类型即示例中的string。于是IDE 会自动为context、args提供智能提示Intellisense与类型检查函数体内误用字段会立即报错。其背后原理是Wasp 会根据.wasp文件中的查询/动作声明为每个操作生成一个以声明名命名的泛型类型getFoo→GetFoo并让context.entities的类型只包含你在entities: [...]中列出的实体——声明里没写的实体在实现中是无法访问的这从类型层面就杜绝了用到未声明实体的隐患。在 Queries 文档中还有更详细的用法泛型接受两个可选类型参数——Inputargs的类型默认never与Output返回类型默认unknown默认值选择得尽量宽松如果查询不需要输入/输出用void作为类型参数即可。另外还推荐用satisfies关键字让 TypeScript 自动推断返回类型const getFoo (async (_args, context) { const foos await context.entities.Foo.findMany() return { foos, message: Here are some foos!, queriedAt: new Date(), } }) satisfies GetFoo这样 TypeScript 既能校验context类型又能自动推断返回结构为{ foos: Foo[], message: string, queriedAt: Date }。迁移示例在真实仓库中的落地仓库中的examples/tutorials/TodoAppTs是上述迁移思路的完整落地。以 examples/tutorials/TodoAppTs/src/queries.ts 为例import type { Task } from wasp/entities; import { HttpError } from wasp/server; import type { GetTasks } from wasp/server/operations; export const getTasks: GetTasksvoid, Task[] async (args, context) { if (!context.user) { throw new HttpError(401); } return context.entities.Task.findMany({ where: { user: { id: context.user.id } }, orderBy: { id: asc }, }); };这段代码用GetTasksvoid, Task[]标注了无输入参数、返回Task数组的签名并且context.user的存在说明getTasks是一个需要认证的查询——这些信息全部由 Wasp 生成的类型自动提供。对应的客户端组件 examples/tutorials/TodoAppTs/src/MainPage.tsx 则演示了全栈类型安全的效果import { createTask, getTasks, updateTask, useQuery } from wasp/client/operations; import type { Task } from wasp/entities; export const MainPage ({ user }: { user: AuthUser }) { const { data: tasks, isLoading, error } useQuery(getTasks); // tasks 的类型由服务端 getTasks 的返回类型自动推断而来 ... };这里无需为useQuery(getTasks)的结果手动标注任何类型——客户端看到的返回类型总是与服务端实现一致这正是官方文档强调的 full-stack type safety类型在客户端和服务端永远保持同步。createTask({ description })与updateTask({ id, isDone })的载荷类型同样由服务端 Action 的类型推导而来客户端传入错误的参数结构会直接编译报错。迁移项目的其余部分三步法推广单个文件的迁移套路可以推广到整个项目。Wasp 允许你渐进式、按文件粒度地迁移JS 与 TS 文件可以长期共存main.wasp中src/queries这样的导入甚至不需要因为扩展名改变而改动Wasp 的模块解析会正确处理。当你想要迁移某个文件时遵循如下三步修改文件扩展名把.js改为.ts若涉及 JSX 语法则改为.tsx修复类型错误运行类型检查或依赖编辑器提示修复strict模式下的类型错误为args、context、辅助函数等补上类型标注查阅 Wasp 文档决定要使用哪些 TypeScript 特性例如为本文件引入Task实体类型、GetTaskInfo等操作泛型或者在前端组件中使用自动推断的类型。每迁移完一个文件你的项目就多一分类型安全且整个过程不影响其他仍为 JavaScript 的文件。迁移后的 TypeScript 工程配置从源码结构看Wasp 的 TypeScript 支持是由框架自动生成的多个tsconfig协同完成的。以 TodoAppTs 为例项目根目录的 tsconfig.json 只负责组织项目引用{ files: [], references: [ { path: ./tsconfig.src.json }, { path: ./tsconfig.wasp.json } ] }其中面向开发者源码的 tsconfig.src.json 采用相当严格的配置strict: true、jsx: preserve、moduleResolution: bundler等并把src与.wasp/out/types/appWasp 生成的类型目录一起纳入编译范围{ compilerOptions: { module: esnext, composite: true, target: esnext, moduleResolution: bundler, jsx: preserve, strict: true, esModuleInterop: true, isolatedModules: true, moduleDetection: force, lib: [dom, dom.iterable, esnext], skipLibCheck: true, allowJs: true, outDir: .wasp/out/user, types: [react, node] }, include: [src, .wasp/out/types/app], exclude: [**/*.wasp.ts] }另一方面Wasp 生成代码如服务端模板使用的 tsconfig.json 模板 则体现了框架自身的取舍它继承自tsconfig/node{version}/tsconfig.json显式关闭strict、开启allowJs注释中写道在实现更完整的 TypeScript 支持之前覆盖此项。这也从侧面印证严格类型检查主要面向开发者自己的src源码Wasp 生成的脚手架代码则保持相对宽松让迁移初期不会因为框架内部代码而报错。自动生成的类型来自哪里Wasp 的实体类型与操作泛型并不是魔法而是框架在wasp start/wasp build时根据你的schema.prisma与main.wasp动态生成的。生成物被打包为类似 SDK 的模块例如wasp/entities根据 Prisma 模型生成的实体类型wasp/server/operations根据query/action声明生成的操作泛型类型如GetTaskInfo、GetTasks及操作包装器wasp/client/operations客户端可调用的查询/动作函数含useQuery钩子。对应模板可以在仓库的waspc/data/Generator/templates/sdk/wasp/目录下找到如 sdk/wasp/package.json、server/operations/wrappers.ts等Wasp 编译时会把这些模板结合你的声明文件实例化到项目内的.wasp/out目录。也正因如此在wasp start首次运行生成类型之前编辑器可能暂时无法解析wasp/entities等模块——这是预期行为生成完成后即恢复。迁移后的常见问题LSP 与编辑器类型报错官方文档在迁移指南末尾附加了一条重要的编辑器注意事项见 web/versioned_docs/version-0.15/_TypescriptServerNote.mdLSP 问题警告使用 TypeScript 时即使wasp start正在运行你的编辑器有时仍会报告类型错误或导入错误。这是因为 Wasp 会动态重新生成类型文件.wasp/out而编辑器的 TypeScript Language Server 可能与当前代码状态失同步——类型文件更新了但语言服务器仍缓存着旧版本。解决方案是手动重启语言服务器如果你使用 VS Code打开命令面板Command Palette并选择TypeScript: Restart TS Server打开命令面板的快捷键Windows / Linux 为CtrlShiftPmacOS 为CmdShiftP。这一技巧在迁移阶段尤其实用每当你改了schema.prisma或main.wasp后实体字段、操作声明发生变化若编辑器出现莫名的报错先重启 TS Server 通常能立刻解决。小结Wasp 的 TypeScript 支持遵循约定优于配置的原则零配置起步新项目开箱即用JS/TS 可自由混用渐进式迁移逐文件改扩展名、补类型即可main.wasp无需改动全栈类型安全wasp/entities的实体类型与wasp/server/operations的操作泛型让客户端调用的载荷与返回类型永远和服务端实现同步源码级佐证TodoAppTs 示例src/queries.ts、src/MainPage.tsx展示了生产可用的写法生成模板sdk 模板、服务端 tsconfig 模板揭示了类型的生成机制。按照改扩展名 → 修类型错误 → 按需选用 Wasp TS 特性的三步流程你可以让整个 Wasp 项目在不中断开发的情况下平滑过渡到 TypeScript享受构建期错误捕获与 IDE 智能补全带来的长期收益。【免费下载链接】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),仅供参考
返回列表