345 - 《手撕源码 33:marz》
1、marz 是基于 Bun 的 RSC 框架。起步阶段,玩具类型的框架。非常适用于理解 Bun 和 RSC 实现原理。
2、如何快速上手?
$ bun create marz
$ cd marz-app
$ bun install
$ bun dev
注:marz 有个 bug,发布时 npm 包里少了个文件,在 https://github.com/hex2f/marz/pull/6 合完发布之前,需要手动 copy 源码仓库的 dev-worker.ts 到对应目录。
如果成功,会看到 https://img.alicdn.com/imgextra/i4/O1CN019BYysk1Ti7NbVgEym_!!6000000002415-2-tps-652-380.png 界面,上下是 server 组件,中间不断刷计数器的是 client 组件。
3、marz 有 3 个命令,dev、build 和 start。和其他框架类似,start 用于跑 build 的产物,用于生产模式或用于验证生产模式。
4、一些值得注意的点。1)打包后的产物是打包后的 esm 格式,通过 <script type=module>
引入,也没有考虑兼容性,所以说是玩具性质的框架,2)打包时没有考虑 CSS,3)react 和 react-dom 需要 18.3.0-canary-41f0e9dae-20230907 或以上,这是 RSC 的前置条件。
5、工程化方面。1)用 biome 作为 prettier 和 lint 工具,一眼还以为是新工具,想了下想起来是 rome 改名后的那个,2)npm 发包时不打包,ts 和 tsx 直接发,因为 runtime 是 bun,这些都直接支持,无需 transpile 后发包,感觉这是以后的趋势。
6、打包主要分两个部分,client 和 server。1)打包 client 是以 client entry 和 client deps 为 entry 做打包,client deps 是所有以 ‘use client’ 或 “use client” 开头的文件,2)打包 client 之后会生成 manifest.json,格式为 Record<String, { id, chunks, name }>
,key 是「文件+#+exportName」的格式,这个文件的作用是把 server 和 client 的产物串起来,3)打包 server 是以路由文件为 entry(编者注:不一定正确),target 是 bun(因为最终是用 bun 来运行),同时加了个 rsc-server 的插件,在遇到 client 组件时不打包,替换成 export default/const expr = { $$typeof, $$async, $$id, name }
的格式,其中 $$id
的值是和 manifest.json 中一一对应的。
7、打包完成后在 dev 和 start 阶段会用 Bun.serve 启一个 server,处理 /__marz
和其他。/__marz
是 rsc 路由,其他是 ssr 或静态资源路由。rsc 用 renderToPipeableStream 渲染;ssr 用 renderToReadableStream 做流式渲染。
8、从运行时的角度来看一遍 marz 的全过程。
1)当用户访问 /
,会需要有 SSR 渲染。SSR 渲染的代码如下,MarzMount 里会用 html 的输出,同时加载客户端脚本。
import { renderToReadableStream } from "react-dom/server"
const abortController = new AbortController()
const mount = (