Remix 框架是服务端渲染架构,当路由请求时生成 HTML 并返回浏览器。这种 SSR 是如何实现的呢?如果不使用 Remix 这种框架,可以在服务器段启动一个无头浏览器进行页面渲染并返回,代价就是要在服务器上启动一个 Chrome 服务,还要做一些定制,Dom 处理等等。
Remix的做法是直接调用调用 React 渲染函数, 它封装了一个 Handler 来做服务端渲染,React在浏览器中进行渲染时也是生成HTML 片段,并最终添加到 HTML DOM 树中。Remix 这个 Handler 做了同样的处理,只是上下文变成 NodeJS,NodeJS 不能提供做浏览器内置的功能,因此,并不是所有的组件都可以转换,如果要依赖 Dom 上某些状态才能渲染出来,NodeJS 上下文文中没有这些对象的,例如 document 这样的对象,那就是无法渲染的,只能在浏览器中进行渲染。
所以,Remix 并不是简单的将组件渲染成 HTML 并返回,Remix中将组件分成 Server 和 Client,Server 是服务端处理,例如数据库操作等,Client 是在浏览器中进行渲染的部分,同时 Remix 对于 Client 也做了很多优化的处理,例如并发,这是为什么 Remix 的应用运行会比较快,从开始设计的时候,它就考虑了弱网的情况。对于那些比较依赖浏览器环境的组件,服务器端不渲染而是交给客户端进行渲染。
下面我来看一下,Remix 的编译是如何工作,Remix 通过 npx remix vite:build进行编译,并在 build 目录下生成 Client 和 Server相关文件。
默认Remix 通过 Remix-Serve 启动,为了debug 方便,我们改为 Express Server。
npm i express @remix-run/express
# 创建 Server,根目录下创建 server.js
import { createRequestHandler } from "@remix-run/express";
import express from "express";
// notice that the result of `remix vite:build` is "just a module"
import * as build from "./build/server/index.js";
const app = express();
app.use(express.static("build/client"));
// and your app is "just a request handler"
app.all("*", createRequestHandler({ build }));
app.listen(3000, () => {
console.log("App listening on http://localhost:3000");
});
启动Debug,访问 local host:3000,服务器端调用了 renderToPipeableStream 方法返回Html。
通过网络可以看到HTML 返回了,但是这里并没有返回Client 的代码。
AceEditor 并没有返回,是交由客户端进行渲染的。
AceEditor 是在浏览器渲染完成HTML 之后才进行渲染的,先执行服务器端渲染,再执行客户端渲染。
Remix在 SSR 生成的过程了坐了各种优化,使得渲染在不同端进行,从而增大灵活性以适应多种场景。