大家好,很高兴又见面了,我是"高级前端?进阶?",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
前言
Fresh 是面向 JavaScript 和 TypeScript 开发人员的全栈现代 Web 框架,旨在提供创建高质量、高性能、个性化 Web 应用程序的新方式。 开发者可以使用 Fresh 来创建主页、博客、大型 Web 应用程序(如 GitHub 或 Twitter)等等。
Fresh 的核心是路由框架和模板引擎的组合,可在服务器上按需渲染页面。 除了服务器上的即时 (JIT) 渲染之外,Fresh 还提供了一个接口,用于在客户端无缝渲染某些组件,以实现最大程度的可交互性。 Fresh 框架使用 Preact 和 JSX 在服务器和客户端上进行渲染和模板化。Fresh 底层基于 Preact、并使用基于孤岛架构的客户端水合作用。
同时,Fresh 没有任何构建步骤,开发者的代码可直接在服务端、客户端运行。 TypeScript 或 JSX 到纯 JavaScript 的任何必要转换都是在需要时即时完成的。从而极大的加快迭代、部署效率。开发者可以借助于 deno 手动将新项目部署到任何平台,但是为了最佳体验建议部署在类似 Deno Deploy 等边缘运行时。
目前 Fresh 在 Github 上有 10.2k 的 star,400+的 fork、超过 120+的开发者贡献代码。因此,这篇文章将详细介绍 Fresh 提供的功能。
1.什么是 Deno
Deno(/di?no?/,发音为 dee-no)是一个安全的 JavaScript、TypeScript 和 WebAssembly 运行时,同时具有出色的开发人员体验。 它建立在 V8(谷歌的 JavaScript 运行时引擎)、Rust 和 Tokio 之上。Deno 构建于以下几个核心能力:
- Rust(Deno 的核心是用 Rust 编写的,Node 的核心是用 C++ 编写的)
- Tokio(用 Rust 编写的事件循环)
- TypeScript(Deno 开箱即用地支持 JavaScript 和 TypeScript)
- V8(Google 的 JavaScript 运行时用于 Chrome 和 Node 等)
下表从语言支持、包管理、安全&权限、代码集成、机器执行等诸多维度展示了 Node.js 和 Deno 的主要区别:
Deno 提供了以下核心特性:
- 提供网络平台功能,采用网络平台标准。 如使用 ES 模块、web worker、支持 fetch
- 默认安全。 除非明确启用,否则不得访问文件、网络或环境。
- 开箱即用地支持 TypeScript
- 发布单个可执行文件 (deno)
- 提供内置开发工具,如代码格式化程序 (deno fmt)、linter (deno lint)、测试运行器 (deno test) 和用于编辑器的语言服务器
- 有一组经过审查(审核)的标准模块,可以与 Deno 一起使用
- 可以将脚本打包到单个 JavaScript 文件或可执行文件中。
- 支持使用现有的 npm 模块
2.什么是 Fresh?
Fresh 建立在 Deno 之上,开发者使用 TypeScript 编写应用程序。 Fresh 自称是用于服务器端渲染的下一代全栈 Web 框架,它类似于其他全栈 Web 框架,如: Django、Ruby on Rails、Symfony 或 Larvel 等等。
从本质上讲,Fresh 是一个路由框架,它使用 Preact(一种轻量级的 React 替代方案)作为模板引擎。 模板引擎使开发者能够在应用程序中使用静态模板文件。 Fresh 负责将模板中的变量替换为实际值,并将模板转换为发送给客户端的 HTML 文件。
深入了解 Fresh 旨在解决的问题之前,大家一起先看看 Deno 博客中的几段话:
1. 客户端渲染很昂贵, React等框架通常会在每次请求时向用户发送数百 KB 的客户端 JavaScript。 这些 JS 包通常只是渲染静态内容,但这些静态内容也可以作为纯 HTML 提供。
2. 一些框架也支持服务器端渲染, 这有助于通过在服务器上预渲染来减少页面加载时间。 但是大多数当前实现仍然将完整的应用程序渲染结构发送给客户端,以便页面可以在客户端上完全重新渲染。
3. 这是一个糟糕的体验,客户端 JavaScript 非常昂贵,它会显著降低用户体验,大大增加移动设备的功耗,而且通常不是很健壮。
那么 Fresh 与 Next.js 或 Remix 等其他 JavaScript 框架的工作方式究竟有何不同?以 Next.js 为例, 它预渲染每个页面,本质上意味着它提前为页面生成 HTML,而不是让客户端去做。 这会带来更好的性能和 SEO,因为机器人能够更有效地“抓取”站点内容。 当一个页面被浏览器加载时, JavaScript 代码就会运行,并通过一个称为 补水(Hydration )的过程使页面完全交互。
Fresh 与 Next.js 类似,无需将 JavaScript 代码发送到浏览器,而是在服务器上将所有内容渲染为静态 HTML。但是,网站需要交互性,而仅静态 HTML 并不能解决交互问题, 这就是孤岛架构的用武之地。
Fresh 有一个 islands 目录,其中包含所有交互式组件,这些组件需要将 JavaScript 发送到浏览器, 而非 islands 目录的所有其他组件将渲染为静态 HTML。 这就是 Fresh 采用的部分水化(Partial Hydration)机制。
3.Fresh 特点
与所有其他 JavaScript 框架一样,Fresh 为开发者提供了很多优秀的功能。接下来一起看看这些功能是什么,以及它们的作用。
3.1 边缘即时渲染
Fresh 在服务器上将所有内容渲染为静态 HTML。 最重要的是,当开发者在 Fresh 中创建 API 路由时,它基于 Fetch API 接口,允许将其部署到无服务器边缘函数(Serverless Edge Functions ),例如 Deno Deploy。
3.2 基于孤岛架构的客户水合
本质上,基于孤岛架构的客户端水合适用于应用程序的一小部分内容,该部分需要 JavaScript 才能进行交互。 例如,在 Fresh 主页底部有一个计数器,它可以被水合以提供更好的交互性。
下图比较了孤岛架构、 SSR 、渐进式水合的不同。 其中 Marko、Astro 、Qwik 、Fresh 等框架都是采用这种孤岛架构进行服务器端渲染。
- SSR: 一次性渲染所有组件,然后客户端水合
- 渐进式水合:一次性渲染所有组件,只对关键组件水合、然后渐进式水合其他组件 s
- 孤岛架构:静态组件由服务端渲染为静态 HTML,交互式组件动态加载 JavaScript 脚本
3.3 零运行时开销
这个功能源自之前谈到的基于岛屿的客户端水合作用。 默认情况下,在应用程序中,Fresh 不会向浏览器发送 JavaScript, 只是发送一个静态的 HTML 文件。
3.4 零构建
虽然 Fresh 旨在部署到 Deno Deploy,但实际上它可以部署到任何运行基于 Deno 的 Web 服务器的系统或平台。 下面示例表示将 Fresh 部署到运行 Docker 容器的平台。
为 Docker 打包 Fresh 应用程序时,务必在容器中设置 DENO_DEPLOYMENT_ID 环境变量。 此变量需要设置为一个不透明的字符串 ID,代表当前正在运行的应用程序的版本。 版本可以是 Git 提交的哈希,或项目中所有文件的哈希。比如下面的 Dockerfile 文件内容:
在 Git 存储库中构建 Docker 映像:
$ docker build --build-arg GIT_REVISION=$(git rev-parse HEAD) -t my-fresh-app .
然后运行 Docker 容器:
$ docker run -t -i -p 80:8000 my-fresh-app
3.5 无需配置
开发者无需真正配置任何内容即可开始使用 Fresh 开发应用程序, 只需使用 CLI 即可立即开始!
3.6 开箱即用的 TypeScript 支持
开发者不需要像在 Node.js 中那样在 Fresh 中单独配置 TypeScript, 这与 Deno 本身开箱即用的支持 TypeScript 类似 。
4.Fresh 示例
4.1 实例化项目
Fresh 需要 Deno CLI 1.25.0 或更高版本,可以通过下面命令安装或更新。
deno run -A -r https://fresh.deno.dev my-project
进入新建的项目目录,运行如下命令启动开发服务器:
deno task start
现在可以在浏览器中打开 http://localhost:8000 来查看该页面。
4.2 Fresh 组件拆分
Fresh 框架将构成页面的各种组件,分为 route 和 island 两类,约定存放在 routes/ 和 islands/ 两个目录,Fresh 处理这两类组件的方式完全不同:
- Route Component:仅在服务端执行,直接响应 SSR 渲染出的 HTML 给客户端,在客户端不会加载和执行任何 JS 代码,无需 hydrate。
- Island Component:不仅在服务端执行, JS 也会在客户端加载,同时需要 hydrate,所以 Island 可以响应用户的交互。
这两类组件便是 Fresh 框架对孤岛架构(Islands Architecture) 的实现。在这个架构中,Routes 负责静态内容,无需交互,而 Islands 通过 JS 来提升页面交互性。Islands 之间相互独立,一个崩溃不会影响另一个,同时 hydrate 也保存独立,hydrate 完成后即可立即响应用户的交互(无任务阻塞情况下)。
Route 和 Island 这两种组件本质上都是 Preact 组件。Island 比 Route 更正常一些,和常规的参与 SSR 的组件没太大区别,任何需要在客户端执行 JS 的区块,都必须抽成一个 Island Component 独立出去。而 Route 更像是被 Fresh 当做模板引擎使用使用。
下面是官方 Fresh 的一个简单示例:
// routes/index.tsx
// Route组件
import Counter from '../islands/Counter.tsx';
export default function Home() {
return (
<div>
<p>
Welcome to Fresh. Try to update this message in the ./routes/index.tsx
file, and refresh.
</p>
<Counter start={3} />
</div>
);
}
// islands/Counter.tsx
// islands组件
import { useState } from 'preact/hooks';
import { IS_BROWSER } from '$fresh/runtime.ts';
interface CounterProps {
start: number;
}
export default function Counter(props: CounterProps) {
const [count, setCount] = useState(props.start);
return (
<div>
<p>{count}</p>
{/* disabled 具有交互性 */}
<button onClick={() => setCount(count - 1)} disabled={!IS_BROWSER}>
-1
</button>
<button onClick={() => setCount(count + 1)} disabled={!IS_BROWSER}>
+1
</button>
</div>
);
}大家好,很高兴又见面了,我是"高级前端?进阶?",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
运行效果如下:
可以看到,客户端加载的 JS 代码很少:
- main.js 和 chunk-TDJO6WAF.js 主要是 Fresh 的 runtime 代码和 preact
- island-counter.js 就是的 Island 组件
如果开发者希望添加更多的可交互组件,与 island-counter.js 一样,需要独立为一个个互不影响的 island 组件。
需要注意的是:Fresh 内部强依赖 Preact,通过 Preact 将所有组件渲染为 HTML,给 Islands 打好标记。同时 JS 的依赖收集根据约定的目录控制好范围。在客户端,使用少量运行时和 Preact,完成 hydrate。
5.本文总结
除了官方宣传的几个特点之外,Fresh 框架还有几个特点值得关注:
- 孤岛架构:它占了几乎全部的优点,客户端只需加载少量的 JS 代码、纯静态组件(页面)无需 hydrate、良好的性能、支持 SEO、基于组件的现代化开发模式等等。
- Fresh 深度集成 Preact:Fresh 无需构建流程,可以直接在服务端渲染 Preact 组件。相比 React,Preact 在大多数情况下确实好用也够用。不像传统 React SSR 要构建两份代码分别用于 server 和 client,Fresh 拥有框架级的支持,虽然没有在生产环境大规模验证过,但这个思路确实优秀。
- handler route 和 component route: 非常像是把 koa、express 简化处理,同时用 JSX 替换了传统的模板引擎,让开发者可以像写前端代码一样写后端 deno 应用。
当然了,Fresh 目前还不是很成熟,如静态文件处理、Debug、Hot Reload、第三方生态等方面都还很单薄,但这并不影响学习了解它的设计思路和架构。
因为篇幅有限,文章并没有过多展开,如果有兴趣,可以在我的主页继续阅读,同时文末的参考资料提供了大量优秀文档以供学习。最后,欢迎大家点赞、评论、转发、收藏!
参考资料
https://dev.to/harshhhdev/fresh-the-next-gen-javascript-web-framework-b39
https://keenwon.com/fresh-introduction/
https://keenwon.com/better-react-ssr/
https://morioh.com/p/a1eebaad1760
https://fresh.deno.dev/
https://fresh.deno.dev/docs/concepts/deployment
https://blog.openreplay.com/an-introduction-to-fresh/
https://morioh.com/p/a1eebaad1760