Fresh 1.0

Fresh 是 Deno 的全新全栈 Web 框架。默认情况下,用 Fresh 搭建的网页不会向客户端发送任何 JavaScript。Fresh 框架不需要搭建,极大地缩短了部署时间。近日,Fresh 发布了第一个稳定版本。

客户端渲染近年来越来越流行。React(和类似 React)的页面可以让程序通过相对简单的方法搭建复杂的 UI,所以当前 Web 框架领域主要由基于 React 的框架主导。

但是,客户端渲染成本较高,每次请求时框架通常向用户发送几百 KB (千字节) 的客户端 JavaScript。这些 JS 包通常只渲染静态内容,而原生 HTML 也可以渲染这些内容。

一些较新的框架也支持服务器端渲染,通过在服务器上进行预渲染来减少页面加载时间。但目前大多数情况下仍会将整个应用的渲染基础架构运送到每个客户端,让页面在客户端重新渲染。

这个发展其实带来很多劣势:客户端的 JavaScript 成本太高了,降低了用户体验,大幅度地增加了移动设备的功耗,而且往往不够稳定。

Fresh 采用了一种不同的模式:默认向客户端发送 0KB 的 JS;在服务器上完成大部分的渲染,客户端只负责重新渲染交互性的小模块;开发人员显式选择对特定组件进行客户端渲染。Jason Miller 早在 2020 年就描述过这种模式,详情可以查看 Islands Architecture blog post

Fresh 本质上是路由框架和模板引擎的组合 ,在服务器上按需渲染页面。除了提供服务器上的及时(JIT)渲染之外,Fresh 还提供一个接口,对一些客户端组件进行无缝渲染,从而实现最大的交互性。该框架使用 Preact 和 JSX(或TSX)在服务器和客户端上进行渲染和模板化。客户端渲染完全是在每个组件层面上选择的,因此许多应用程序根本不会向客户端发送 JavaScript。

Fresh 没有构建步骤,因此开发人员写的代码是直接在服务器和客户端上运行的代码,任何 TypeScript 或 JSX 到原生 JavaScript 的必要转换都是在需要时即时完成的,这样通过即时部署 非常快速的实现了迭代循环。

快速入门

我们创建一个新的项目,展示相关代码,来讲解 Fresh 的特别之处。

deno run -A -r https://fresh.deno.dev my-app

开发人员指定一个文件夹作为初始化脚本的最后一个参数(本示例为 my-app),脚本会在该文件夹中生成 Fresh 项目需要的最小模板。可以点击入门指南 了解所有文件的定义。

my-app/
├── README.md
├── deno.json
├── dev.ts
├── fresh.gen.ts
├── import_map.json
├── islands
│   └── Counter.tsx
├── main.ts
├── routes
│   ├── [name].tsx
│   ├── api
│   │   └── joke.ts
│   └── index.tsx
└── static
    ├── favicon.ico
    └── logo.svg

现在,找到 routes/ 文件夹,该文件夹包含了应用程序的每个路由的处理器和模板。每个文件的名称定义了路由匹配的路径(例如,api/joke.ts 文件服务于对 /api/joke 的请求)。这种文件夹结构与 Next.js 或 PHP 类似,这些系统也使用文件系统路由

以下是 routes/index.tsx 文件:

/** @jsx h */
import { h } from "preact";
import Counter from "../islands/Counter.tsx";

export default function Home() {
  return (
    <div>
      <img
        src="/logo.svg"
        height="100px"
        alt="the fresh logo: a sliced lemon dripping with juice"
      />
      <p>
        Welcome to `fresh`. Try update this message in the ./routes/index.tsx
        file, and refresh.
      </p>
      <Counter start={3} />
    </div>
  );
}

路由默认输出 JSX 模板,会在每次请求后在服务器端渲染,模板组件本身不会在客户端渲染。

这就提出了一个问题:如果开发人员想在客户端重新渲染应用程序的某些部分,例如响应一些用户的互动,怎么办?这时候就可以使用 Fresh 的 Islands。Islands 是应用程序的单独组件,在客户端再次 hydrate 来实现交互。

下面是一个 island 的例子,提供了一个带有增量和减量按钮的客户端计数器。它使用 Preact useState 钩子来跟踪计数器的值。

// islands/Counter.tsx

/** @jsx h */
import { h } from "preact";
import { useState } from "preact/hooks";

interface CounterProps {
  start: number;
}

export default function Counter(props: CounterProps) {
  const [count, setCount] = useState(props.start);
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count - 1)}>-1</button>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

必须把 Island 放置在 islands/ 文件夹,如果路由的模板中使用了 island,Fresh 会自动在客户端上对其再次 hydrate。

Fresh 不仅是一个前端框架,还是一个编写网站的完全集成系统。开发人员可以处理任何类型的请求、返回自定义响应、进行数据库请求等等。这个路由返回的是纯文本的 HTTP 响应,不是 HTML 页面,例如:

// routes/api/joke.ts

const JOKES = [/** jokes here */];

export const handler = (_req: Request): Response => {
  const randomIndex = Math.floor(Math.random() * JOKES.length);
  const body = JOKES[randomIndex];
  return new Response(body);
};

开发人员也可以用 Fresh 为路由获取异步数据。下面是一个从磁盘文件加载博客文章的路由:

// routes/blog/[id].tsx

import { HandlerContext, PageProps } from "$fresh/server.ts";

export const handler = async (_req: Request, ctx: HandlerContext): Response => {
  const body = await Deno.readTextFile(`./posts/${ctx.params.id}.md`);
  return ctx.render({ body });
};

export default function BlogPostPage(props: PageProps) {
  const { body } = props.data;
  // ...
}

因为 Fresh 非常依赖动态的服务器端渲染,必须保证其速度,所以 Fresh 非常适合在 Deno Deploy、Netlify Edge Functions 或 Supabase Edge Functions 等边缘 runtime 场景运行。渲染过程在物理上非常靠近用户,所以可以最大限度地减少网络延迟。

将一个 Fresh 应用部署到 Deno Deploy 只需几秒钟:先将代码推送到 GitHub 仓库,然后将该仓库链接到 Deno Deploy 仪表板中的一个项目。然后,你的项目就会从全球 34个地区提供服务,每天免费提供 10 万次请求。

生产准备

Fresh 1.0 是一个稳定版本,可用于生产环境,Deno 的许多公共网络服务都使用 Fresh。Fresh 的工作才刚刚开始,我们会进一步改善用户和开发人员的体验。

大家可以试一下 Deno Deploy,它用起来非常快,而且操作简单。

最后,感谢 Sylvain Cau, @hashrock,以及 Christian Norrman 在 Fresh 项目上的帮助。还要感谢 Preact team 搭建 Preact 以及 Jason Miller 为 Islands Architecture 写的文章。

原文作者:Luca Casonato
原文链接:Fresh 1.0

推荐阅读
相关专栏
前端与跨平台
90 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。