[博客翻译]使用不同Web框架构建同一应用


原文地址:https://eugeneyan.com/writing/web-frameworks/


最近,我在考虑是否应该从当前的Web应用堆栈(FastAPI、HTML、CSS和一些JavaScript)迁移到现代Web框架。我特别对FastHTML、Next.js以及Svelte产生了兴趣。

  • FastHTML:自从Jeremy Howard在一个月前发布以来,许多人已经开始使用它进行构建。它的目标是通过纯Python来实现现代Web应用。
  • Next.js:我遇到了许多使用它构建的应用程序,比如cal.comroomGPT。它拥有庞大的生态系统,并且因其能够构建生产级别的Web应用而广受欢迎。
  • SvelteKit:这个轻量级框架已受到开发者们的欢迎(Stack Overflow, TSH, State of JS),在过去几年中我的朋友Swyx也在他的文章为什么我喜欢Svelte中提到过它。

为了更深入了解这些框架,我用每个框架构建了相同的Web应用。我称之为“查看您的数据”的这款应用让使用者可以:

  • 上传一个CSV文件以初始化SQLite数据库表
  • 查看浏览器中的表格
  • 编辑表格中的个别字段
  • 删除表格中的个别行
  • 下载更新后的表格数据为新的CSV文件

通过在这几个框架中实现这些CRUD(创建、读取、更新和删除)操作,我希望能够了解每个框架的独特特点及相关的开发体验。为了简化问题,我将使用SQLite作为数据库。作为基线,我会先用我所熟悉的FastAPI构建这款应用。

Twitter和LinkedIn上的投票

我在TwitterLinkedIn上就这三个框架进行了投票。

FastAPI + Jinja + HTML + CSS + JavaScript

使用FastAPI构建这个应用相当直接(代码)。主要的组件包括:

  • main.py:用于上传/下载数据、更新字段、删除行的路由
  • index.html:定义脚本、表格和按钮的HTML文档
  • style.css:视觉样式如列宽、文本换行、滚动条等
  • script.js:客户端功能以上传CSV文件、加载显示数据、更新/删除行以及下载更新后的数据为CSV格式

FastAPI应用结构

以下是该Web应用的外观。虽然看起来并不美观,但它满足了上述要求。我有意保持了最少的视觉样式(对于当前和后续的应用程序),以便能专注于框架和功能而不是设计。

FastAPI的Web应用

FastHTML

为了学习FastHTML,我首先查阅了文档并按照此教程构建了一个简单的待办事项应用。我对不熟悉的部分提供链接至相关文档如ft componentshtmxpico.css作为上下文信息。在FastHTML的帮助下,整个应用程序可以在一个main.py和一个小的style.css中实现(代码)。

FastHTML应用结构

以下是该应用的样子。

FastHTML的Web应用

初版完成后,Hamel慷慨地提议与我一起编写这个应用的初始版本。他还邀请了FastHTML的创造者Jeremy Howard加入我们。他们向我分享了一些技巧,例如为支持LLM的文档(FastHTML的llms-ctx.txt和FastLite的html.md)提供Context帮助信息,还为使用htmx和Hyperview构建简单应用提供了很好的资源。Jeremy甚至花了时间演示如何仅用50行代码就完成了应用的构建!

from fasthtml.common import *

db = database(':memory:')
tbl = None
hdrs = (Style('''
button,input { margin: 0 1rem; }
[role="group"] { border: 1px solid #ccc; }
'''), )
app, rt = fast_app(live=True, hdrs=hdrs)

@rt("/")
async def get():
    return Titled("CSV Uploader",
        Group(
            Input(type="file", name="csv_file", accept=".csv"),
            Button("Upload", hx_post="/upload", hx_target="#results",
                   hx_encoding="multipart/form-data", hx_include='previous input'),
            A('Download', href='/download', type="button")
        ),
        Div(id="results"))

def render_row(row):
    vals = [Td(Input(value=v, name=k)) for k,v in row.items()]
    vals.append(Td(Group(Button('delete', hx_get=remove.rt(id=row['id'])),
                   Button('update', hx_post='/update', hx_include="closest tr"))))
    return Tr(*vals, hx_target='closest tr', hx_swap='outerHTML')

@rt
async def download():
    csv_data = [",".join(map(str, tbl.columns_dict))]
    csv_data += [",".join(map(str, row.values())) for row in tbl()]
    headers = {'Content-Disposition': 'attachment; filename="data.csv"'}
    return Response("\n".join(csv_data), media_type="text/csv", headers=headers)

@rt('/update')
def post(d:dict): return render_row(tbl.update(d))

@rt
def remove(id:int): tbl.delete(id)

@rt("/upload")
async def post(csv_file: UploadFile):
    global tbl
    if not csv_file.filename.endswith('.csv'): return "Please upload a CSV file"
    tbl = db.import_file('test', await csv_file.read(), pk='id')
    header = Tr(*map(Th, tbl.columns_dict))
    vals = [render_row(row) for row in tbl()]
    return Table(Thead(header), Tbody(*vals))

serve()

以下是Jeremy的应用程序:

Jeremy的应用程序

Jeremy's version of the FastHTML app

Next.JS

为了学习 Next.js,我完成了React FoundationsNext.js教程。后者通过小而具体的实践课程来教授 Next.js 的基础知识,直至建立起一个仪表板应用。Next.js 教程有 16 章,边学边编码会花费一些时间。不过,我还是建议至少坚持到第 12 章(关于数据转换),并享受其平缓的学习曲线和实际的项目。

以下是创建 Next.js app 模板的方法:

npx create-next-app@latest

在 Next.js 中构建相同的应用需要更多的代码,比 Python 版本多得多(代码)。然而,我发现其组织结构很直观:

  • api:数据表格(GET、PUT、DELETE)以及文件上传/下载的路由
  • pages.tsxlayout.tsx:页面特定和通用用户界面组件
  • components:可重用的 React 组件如表格和文件上传/下载按钮
  • lib:实用函数;在这个例子中,只有 SQLite 的一个函数

Next.js app 结构

下面是该 Web 应用的效果。内置的Tailwind CSS集成使得应用相比 FastAPI 和 FastHTML 更加精致。

Next.js Web 应用

SvelteKit

为了学习 Svelte,我部分完成了他们带在线解释器的教程。这个教程分为四个部分:(i) 基础 Svelte,(ii) 高级 Svelte,(iii) 基础 SvelteKit,(iv) 高级 SvelteKit。我完成了基础 Svelte 和基础 SvelteKit 的部分,并开始构建应用(代码)。

创建 SvelteKit app 模板时,我执行了以下命令:

npm create svelte@latest my-app

和 Next.js 类似,SvelteKit 的模板也有几个目录和组件:

  • components:可重用的 Svelte 组件如数据表格和文件上传按钮
  • api.tsdb.ts:用于获取、更新和删除数据的函数(api.ts)以及查询和更新 SQLite 数据库的操作(db.ts
  • routes:表格(GET)、行(PUT、DELETE)以及上传/下载的路由
  • +page.svelte:应用的主要页面
  • app.html:入口点及主要的 HTML 文件

SvelteKit app 结构

下面是应用的效果。一个小的变化是我尝试将“选择文件”和“上传”功能整合到一个按钮上,因此移除了“上传 CSV”按钮。

SvelteKit Web 应用

FastAPI + Svelte

我还尝试使用 FastAPI 作为后端,Svelte 作为前端构建了一个应用(代码)。所有功能和 API 都在 main.py 中实现,前端 UI 和 API 的交互分别由 +page.svelteapi.ts 处理。运行该应用程序时,我必须同时启动 FastAPI 服务器和 Svelte 开发服务器。

FastAPI + Svelte app 结构

下面是 Web 应用的效果。(我恢复了上传功能以匹配最初的 FastAPI 应用,它有一个单独的“上传 CSV”按钮。)

FastAPI + Svelte Web 应用

这里的主要挑战是在开发过程中协调两个服务器之间的通信。在生产环境中,Svelte 应用将会被编译成静态内容,并与 FastAPI 后端的 API 请求进行通信。

• • •

附录:代码助手将如何影响开发者?

这次练习让我想到基于大规模语言模型训练的代码助手将如何影响我们作为开发者的决策。例如,基于大规模语言模型的代码助手在处理像 Svelte 和 FastHTML 这样更小众或新兴的框架时是否同样有效?虽然下述推文可能有些夸张,但它确实提出了一个合理的担忧。

虽然我很不愿这么说,但 Svelte 已经死了,因为大规模语言模型更擅长写 React。 — Jess Martin

鉴于 React 和 Next 的广泛应用和更长的历史,大多数的大规模语言模型可能会基于更多的 React 和 Next 代码进行训练。对于 FastHTML 也是如此。这可能导致在处理像 FastAPI、React 和 Next.js 这样的成熟框架时,代码助手下建议代码更为有效。

作为一个轶事,在使用 FastAPI 和 Next.js 构建应用程序时,我发现使用 Cursor + Claude 更容易,而在使用 FastHTML 和 SvelteKit 时则更难。由于 FastHTML 刚刚发布几周(写作时),它的代码和文档可能尚未进入大部分大规模语言模型的训练数据,这解释了它们对 FastHTML 掌握有限的原因。

为了应对这个问题,FastHTML 的创始人 Jeremy Howard 提供了llms.txtllms-ctx.txt,这两个文档进行了上下文优化。同样地,Svelte 的 Rich Harris (在 Vercel 工作)计划 发布更多适合大规模语言模型理解的 Svelte 文档。Victor Dibia 已经撰写 关于代码助手如何影响开发人员习惯和选择,并且我们需要为人类和机器编写文档。

时间将告诉我们这些努力在解决代码助手中新框架冷启动问题的有效性。

• • •

这次练习让我熟悉了 FastHTML、Next.js 和 SvelteKit。所有代码都可以在这里找到 这里